Bläddra i källkod

feat(quantityDiscount): 实现阶梯优惠和上不封顶活动类型

ylong 1 vecka sedan
förälder
incheckning
a253a19ba3

+ 2 - 2
src/views/marketing/quantityDiscount/components/PageSearch.vue

@@ -22,8 +22,8 @@
             class="!w-[150px]"
             clearable
         >
-             <el-option label="满元减钱" value="price_reduction" />
-             <!-- Add more types if needed -->
+             <el-option label="阶梯优惠" value="1" />
+             <el-option label="上不封顶" value="2" />
         </el-select>
         <el-button type="primary" @click="handleSearch">查询</el-button>
         <el-button @click="handleReset">重置</el-button>

+ 103 - 52
src/views/marketing/quantityDiscount/components/QuantityDiscountDialog.vue

@@ -1,18 +1,18 @@
 <template>
     <ele-modal v-model="visible" :title="title" width="800px" :append-to-body="true" :close-on-click-modal="false">
         <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
-            <el-form-item label="活动名称" prop="name">
-                <el-input v-model="form.name" placeholder="请输入活动名称" maxlength="20" show-word-limit />
+            <el-form-item label="活动名称" prop="activityName">
+                <el-input v-model="form.activityName" placeholder="请输入活动名称" maxlength="20" show-word-limit />
             </el-form-item>
 
             <el-form-item label="使用商品" required>
-                <el-radio-group v-model="form.productType">
-                    <el-radio label="all">所有商品</el-radio>
-                    <el-radio label="specific">指定商品</el-radio>
+                <el-radio-group v-model="form.scope">
+                    <el-radio label="1">所有商品</el-radio>
+                    <el-radio label="2">指定商品</el-radio>
                 </el-radio-group>
-                <div v-if="form.productType === 'specific'" class="mt-2">
-                    <el-button type="primary" plain size="small">选择商品</el-button>
-                    <span class="text-gray-400 text-xs ml-2">已选 {{ form.products.length }} 个商品</span>
+                <div v-if="form.scope === '2'" class="mt-2 w-full">
+                    <el-input v-model="form.isbnText" type="textarea" :rows="4" placeholder="请输入商品ISBN,多个用逗号或换行分隔" />
+                    <span class="text-gray-400 text-xs ml-2">已输入 {{ isbnCount }} 个ISBN</span>
                 </div>
             </el-form-item>
 
@@ -23,40 +23,40 @@
             </el-form-item>
 
             <el-form-item label="设置优惠" required>
-                <el-radio-group v-model="form.discountType" class="mb-4">
-                    <el-radio label="tiered">阶梯优惠</el-radio>
-                    <el-radio label="nocap">上不封顶</el-radio>
+                <el-radio-group v-model="form.type" class="mb-4">
+                    <el-radio label="1">阶梯优惠</el-radio>
+                    <el-radio label="2">上不封顶</el-radio>
                 </el-radio-group>
 
-                <!-- Tiered Discount -->
-                <div v-if="form.discountType === 'tiered'" class="bg-gray-50 p-4 rounded">
-                    <div v-for="(tier, index) in form.tiers" :key="index" class="flex items-center mb-2 last:mb-0">
+                <!-- Tiered Discount (Type 1) -->
+                <div v-if="form.type === '1'" class="bg-gray-50 p-4 rounded">
+                    <div v-for="(tier, index) in form.stepList" :key="index" class="flex items-center mb-2 last:mb-0">
                         <span class="mr-2">满</span>
-                        <el-input-number v-model="tier.threshold" :min="0" :precision="2" :controls="false"
+                        <el-input-number v-model="tier.thresholdMoney" :min="0" :precision="2" :controls="false"
                             class="!w-[100px]" />
                         <span class="mx-2">元</span>
                         <span class="mr-2">减</span>
-                        <el-input-number v-model="tier.discount" :min="0" :precision="2" :controls="false"
+                        <el-input-number v-model="tier.discountMoney" :min="0" :precision="2" :controls="false"
                             class="!w-[100px]" />
                         <span class="mx-2">元</span>
-                        <el-button v-if="form.tiers.length > 1" type="danger" link @click="removeTier(index)">
+                        <el-button v-if="form.stepList.length > 1" type="danger" link @click="removeTier(index)">
                             删除
                         </el-button>
                     </div>
-                    <el-button v-if="form.tiers.length < 5" type="success" class="mt-2 w-full" @click="addTier">
+                    <el-button v-if="form.stepList.length < 5" type="success" class="mt-2 w-full" @click="addTier">
                         增加一级优惠
                     </el-button>
                     <div class="text-orange-400 text-xs mt-2">最多可设置5个阶梯</div>
                 </div>
 
-                <!-- No Cap Discount -->
+                <!-- No Cap Discount (Type 2) -->
                 <div v-else class="bg-gray-50 p-4 rounded flex items-center">
                     <span class="mr-2">每满</span>
-                    <el-input-number v-model="form.nocap.threshold" :min="0" :precision="2" :controls="false"
+                    <el-input-number v-model="form.thresholdMoney" :min="0" :precision="2" :controls="false"
                         class="!w-[100px]" />
                     <span class="mx-2">元</span>
                     <span class="mr-2">减</span>
-                    <el-input-number v-model="form.nocap.discount" :min="0" :precision="2" :controls="false"
+                    <el-input-number v-model="form.discountMoney" :min="0" :precision="2" :controls="false"
                         class="!w-[100px]" />
                     <span class="mx-2">元</span>
                 </div>
@@ -72,6 +72,7 @@
 <script setup>
 import { ref, reactive, computed, watch } from 'vue';
 import { EleMessage } from 'ele-admin-plus/es';
+import request from '@/utils/request';
 
 const props = defineProps({
     modelValue: Boolean,
@@ -89,74 +90,124 @@ const title = computed(() => props.data ? '编辑活动' : '新建活动');
 
 const formRef = ref(null);
 const form = reactive({
-    name: '',
-    productType: 'all',
-    products: [],
+    id: undefined,
+    activityName: '',
+    scope: '1',
+    isbnText: '',
     dateRange: [],
-    discountType: 'tiered',
-    tiers: [{ threshold: undefined, discount: undefined }],
-    nocap: { threshold: undefined, discount: undefined }
+    type: '1',
+    stepList: [{ thresholdMoney: undefined, discountMoney: undefined }],
+    thresholdMoney: undefined,
+    discountMoney: undefined
+});
+
+const isbnCount = computed(() => {
+    if (!form.isbnText) return 0;
+    return form.isbnText.split(/[\n,]/).filter(item => item.trim()).length;
 });
 
 const rules = {
-    name: [{ required: true, message: '请输入活动名称', trigger: 'blur' }],
+    activityName: [{ required: true, message: '请输入活动名称', trigger: 'blur' }],
     dateRange: [{ required: true, message: '请选择活动时间', trigger: 'change', type: 'array' }]
 };
 
 watch(() => props.data, (val) => {
     if (val) {
-        // Populate form with val
-        Object.assign(form, JSON.parse(JSON.stringify(val)));
-        // Handle specific data transformations if needed
+        form.id = val.id;
+        form.activityName = val.activityName;
+        form.scope = String(val.scope || '1');
+        form.type = String(val.type || '1');
+        form.thresholdMoney = val.thresholdMoney;
+        form.discountMoney = val.discountMoney;
+        form.stepList = val.stepList && val.stepList.length ? JSON.parse(JSON.stringify(val.stepList)) : [{ thresholdMoney: undefined, discountMoney: undefined }];
+        
         if (val.startTime && val.endTime) {
             form.dateRange = [val.startTime, val.endTime];
+        } else {
+            form.dateRange = [];
+        }
+
+        // Handle bookIsbnList if available (assuming API might return it or we handle it)
+        // Since we are using textarea for input, we join array to string
+        // Note: The list API doesn't show bookIsbnList. If edit needs it, it might be missing.
+        // We will assume if it's there, we use it.
+        if (val.bookIsbnList && Array.isArray(val.bookIsbnList)) {
+            form.isbnText = val.bookIsbnList.join('\n');
+        } else {
+            form.isbnText = '';
         }
     } else {
-        // Reset form
-        form.name = '';
-        form.productType = 'all';
-        form.products = [];
+        form.id = undefined;
+        form.activityName = '';
+        form.scope = '1';
+        form.isbnText = '';
         form.dateRange = [];
-        form.discountType = 'tiered';
-        form.tiers = [{ threshold: undefined, discount: undefined }];
-        form.nocap = { threshold: undefined, discount: undefined };
+        form.type = '1';
+        form.stepList = [{ thresholdMoney: undefined, discountMoney: undefined }];
+        form.thresholdMoney = undefined;
+        form.discountMoney = undefined;
     }
 }, { immediate: true });
 
 const addTier = () => {
-    if (form.tiers.length >= 5) return;
-    form.tiers.push({ threshold: undefined, discount: undefined });
+    if (form.stepList.length >= 5) return;
+    form.stepList.push({ thresholdMoney: undefined, discountMoney: undefined });
 };
 
 const removeTier = (index) => {
-    form.tiers.splice(index, 1);
+    form.stepList.splice(index, 1);
 };
 
 const handleSubmit = async () => {
     if (!formRef.value) return;
     await formRef.value.validate(async (valid) => {
         if (valid) {
-            // Validation for tiers/nocap
-            if (form.discountType === 'tiered') {
-                for (const tier of form.tiers) {
-                    if (!tier.threshold || !tier.discount) {
+            // Validation
+            if (form.type === '1') {
+                for (const tier of form.stepList) {
+                    if (!tier.thresholdMoney || !tier.discountMoney) {
                         EleMessage.error('请完善阶梯优惠信息');
                         return;
                     }
                 }
             } else {
-                if (!form.nocap.threshold || !form.nocap.discount) {
+                if (!form.thresholdMoney || !form.discountMoney) {
                     EleMessage.error('请完善上不封顶优惠信息');
                     return;
                 }
             }
 
-            // Simulate API call
-            setTimeout(() => {
-                EleMessage.success(props.data ? '修改成功' : '添加成功');
-                emit('success');
-                visible.value = false;
-            }, 500);
+            if (form.scope === '2' && !form.isbnText) {
+                EleMessage.error('请输入指定商品ISBN');
+                return;
+            }
+
+            const payload = {
+                id: form.id,
+                activityName: form.activityName,
+                scope: form.scope,
+                type: form.type,
+                startTime: form.dateRange[0],
+                endTime: form.dateRange[1],
+                thresholdMoney: form.type === '2' ? form.thresholdMoney : undefined,
+                discountMoney: form.type === '2' ? form.discountMoney : undefined,
+                stepList: form.type === '1' ? form.stepList : undefined,
+                bookIsbnList: form.scope === '2' ? form.isbnText.split(/[\n,]/).map(s => s.trim()).filter(s => s) : undefined
+            };
+
+            try {
+                const res = await request.post('/shop/shopDiscountInfo/setDiscount', payload);
+                if (res.data.code === 200) {
+                    EleMessage.success(form.id ? '修改成功' : '添加成功');
+                    emit('success');
+                    visible.value = false;
+                } else {
+                    EleMessage.error(res.data.msg || '操作失败');
+                }
+            } catch (error) {
+                console.error(error);
+                EleMessage.error('操作失败');
+            }
         }
     });
 };

+ 91 - 90
src/views/marketing/quantityDiscount/index.vue

@@ -5,48 +5,50 @@
             <PageSearch @search="handleSearch" @reset="handleReset" />
 
             <!-- Table -->
-            <common-table ref="tableRef" :columns="columns" :datasource="datasource"
-                :page-config="{ rowKey: 'id', tool: false }" :bodyStyle="{ padding: 0 }">
+            <common-table ref="tableRef" :columns="columns" :pageConfig="pageConfig" :bodyStyle="{ padding: 0 }">
                 <template #toolbar>
                     <el-button type="success" plain @click="handleCreate">新建活动</el-button>
-
                 </template>
 
-                <template #type>
-                    <el-tag effect="dark" color="#7c3aed" style="border-color: #7c3aed">全店商品活动</el-tag>
+                <template #type="{ row }">
+                    <el-tag v-if="row.type === '1'" type="primary">阶梯优惠</el-tag>
+                    <el-tag v-else-if="row.type === '2'" type="warning">上不封顶</el-tag>
+                    <el-tag v-else type="info">未知</el-tag>
                 </template>
 
-                <template #status="{ row }">
-                    <div v-if="row.status === 'active'" class="flex items-center">
-                        <el-tag type="success" effect="dark" class="mr-4 !px-6">进行中</el-tag>
-                        <el-switch v-model="row.enabled" active-color="#13ce66" inactive-color="#ff4949" />
-                    </div>
-                    <span v-else-if="row.status === 'ended'" class="text-gray-400">已结束</span>
-                    <span v-else class="text-orange-400">未开始</span>
+                <template #scope="{ row }">
+                    <el-tag v-if="row.scope === '1'" type="success">所有商品</el-tag>
+                    <el-tag v-else-if="row.scope === '2'" type="info">指定商品</el-tag>
                 </template>
 
                 <template #condition="{ row }">
-                    {{ row.condition }}
+                    <div v-if="row.type === '1'">
+                        <div v-for="(step, index) in row.stepList" :key="index">
+                            满{{ step.thresholdMoney }}减{{ step.discountMoney }}
+                        </div>
+                    </div>
+                    <div v-else-if="row.type === '2'">
+                        每满{{ row.thresholdMoney }}减{{ row.discountMoney }}
+                    </div>
                 </template>
 
-                <template #detail="{ row }">
-                    <div v-for="(line, index) in row.detail" :key="index">
-                        {{ line }}
-                    </div>
+                <template #status="{ row }">
+                    <el-tag v-if="getStatus(row) === 'active'" type="success" effect="dark">进行中</el-tag>
+                    <el-tag v-else-if="getStatus(row) === 'pending'" type="warning">未开始</el-tag>
+                    <el-tag v-else type="info">已结束</el-tag>
                 </template>
 
                 <template #time="{ row }">
                     <div class="text-xs text-gray-500">
-                        <div>创建 {{ row.createTime }}</div>
                         <div>开始 {{ row.startTime }}</div>
                         <div>结束 {{ row.endTime }}</div>
                     </div>
                 </template>
 
                 <template #action="{ row }">
-                    <el-button type="primary" link @click="handleEdit(row)">修改活动</el-button>
-                    <!-- Add Data Button just for demo of DataDialog -->
-                    <el-button type="primary" link @click="handleShowData(row)">数据</el-button>
+                    <el-button type="primary" link @click="handleEdit(row)">修改</el-button>
+                    <!-- <el-button type="danger" link @click="handleDelete(row)">删除</el-button> -->
+                    <!-- <el-button type="primary" link @click="handleShowData(row)">数据</el-button> -->
                 </template>
             </common-table>
         </div>
@@ -58,73 +60,72 @@
 </template>
 
 <script setup>
-import { ref, reactive } from 'vue';
-import CommonTable from '@/components/CommonPage/CommonTable.vue';
-import PageSearch from './components/PageSearch.vue';
-import QuantityDiscountDialog from './components/QuantityDiscountDialog.vue';
-import QuantityDiscountDataDialog from './components/QuantityDiscountDataDialog.vue';
-
-defineOptions({ name: 'QuantityDiscount' });
-
-const tableRef = ref(null);
-const dialogVisible = ref(false);
-const dialogData = ref(null);
-const dataDialogVisible = ref(false);
-const currentActivity = ref(null);
-
-const columns = ref([
-    { label: '活动名称', prop: 'nameInfo', minWidth: 150, formatter: (row) => `${row.name}\nID:${row.id}` },
-    { label: '活动类型', prop: 'type', slot: 'type', minWidth: 120 },
-    { label: '优惠条件', prop: 'condition', slot: 'condition', minWidth: 100 },
-    { label: '优惠详情', prop: 'detail', slot: 'detail', minWidth: 150 },
-    { label: '活动状态', prop: 'status', slot: 'status', minWidth: 150 },
-    { label: '活动时间', prop: 'time', slot: 'time', minWidth: 220 },
-    { label: '操作', prop: 'action', slot: 'action', width: 150, fixed: 'right' }
-]);
-
-const datasource = async ({ page, limit }) => {
-    // Mock data based on Figure 1
-    await new Promise(resolve => setTimeout(resolve, 300));
-    return [
-        {
-            id: '65635655',
-            name: '店铺优惠0801',
-            type: 'all',
-            condition: '满元减钱',
-            detail: ['满10元,减2元', '满20元,减5元'],
-            status: 'active',
-            enabled: true,
-            createTime: '2025-08-06 11:45:55',
-            startTime: '2025-10-06 00:00:00',
-            endTime: '2025-11-30 23:59:59'
-        }
-    ];
-};
-
-const reload = () => {
-    tableRef.value?.reload();
-};
-
-const handleSearch = (params) => {
-    console.log('Search:', params);
-    reload();
-};
-
-const handleReset = () => {
-    reload();
-};
-
-const handleCreate = () => {
-    dialogData.value = null;
-    dialogVisible.value = true;
-};
-
-const handleEdit = (row) => {
-    dialogData.value = { ...row }; // Simplistic clone
-    dialogVisible.value = true;
-};
-
-const handleShowData = (row) => {
-    dataDialogVisible.value = true;
-};
+    import { ref, reactive } from 'vue';
+    import { EleMessage } from 'ele-admin-plus/es';
+    import CommonTable from '@/components/CommonPage/CommonTable.vue';
+    import PageSearch from './components/PageSearch.vue';
+    import QuantityDiscountDialog from './components/QuantityDiscountDialog.vue';
+    import QuantityDiscountDataDialog from './components/QuantityDiscountDataDialog.vue';
+
+    defineOptions({ name: 'QuantityDiscount' });
+
+    const tableRef = ref(null);
+    const dialogVisible = ref(false);
+    const dialogData = ref(null);
+    const dataDialogVisible = ref(false);
+
+    const pageConfig = reactive({
+        pageUrl: '/shop/shopDiscountInfo/pagelist',
+        rowKey: 'id',
+        tool: false
+    });
+
+    const columns = ref([
+        { label: '活动名称', prop: 'activityName', minWidth: 150 },
+        { label: '活动类型', prop: 'type', slot: 'type', minWidth: 100 },
+        { label: '适用范围', prop: 'scope', slot: 'scope', minWidth: 100 },
+        { label: '优惠规则', prop: 'condition', slot: 'condition', minWidth: 200 },
+        { label: '活动状态', prop: 'status', slot: 'status', minWidth: 100 },
+        { label: '活动时间', prop: 'time', slot: 'time', minWidth: 220 },
+        { label: '操作', prop: 'action', slot: 'action', width: 150, fixed: 'right' }
+    ]);
+
+    const getStatus = (row) => {
+        const now = new Date().getTime();
+        const start = new Date(row.startTime).getTime();
+        const end = new Date(row.endTime).getTime();
+        if (now < start) return 'pending';
+        if (now > end) return 'ended';
+        return 'active';
+    };
+
+    const reload = () => {
+        tableRef.value?.reload();
+    };
+
+    const handleSearch = (params) => {
+        tableRef.value?.reload(params);
+    };
+
+    const handleReset = () => {
+        tableRef.value?.reload();
+    };
+
+    const handleCreate = () => {
+        dialogData.value = null;
+        dialogVisible.value = true;
+    };
+
+    const handleEdit = (row) => {
+        dialogData.value = { ...row };
+        dialogVisible.value = true;
+    };
+
+    const handleDelete = (row) => {
+        // Implement delete if API available
+    };
+
+    const handleShowData = (row) => {
+        dataDialogVisible.value = true;
+    };
 </script>