Просмотр исходного кода

feat(营销降价): 实现书单管理及规则配置功能对接真实后端接口

ylong 3 недель назад
Родитель
Сommit
2f3af7f849

+ 23 - 13
src/views/marketing/shareDiscount/booklist/components/BooklistAdd.vue

@@ -9,13 +9,17 @@
         <el-form :model="form" label-width="80px">
             <el-form-item label="ISBN">
                 <el-input
-                    v-model="form.isbns"
+                    v-model="form.isbn"
                     type="textarea"
                     :rows="4"
                     placeholder="请输入ISBN,多个ISBN请换行"
                 />
             </el-form-item>
         </el-form>
+        <template #footer>
+            <el-button @click="handleCancel">取消</el-button>
+            <el-button type="primary" :loading="loading" @click="handleConfirm">保存</el-button>
+        </template>
     </ele-modal>
 </template>
 
@@ -27,8 +31,9 @@
     const emit = defineEmits(['success']);
 
     const visible = ref(false);
+    const loading = ref(false);
     const form = ref({
-        isbns: '',
+        isbn: '',
     });
 
     const updateVisible = (val) => {
@@ -37,26 +42,31 @@
 
     const handleOpen = () => {
         visible.value = true;
-        form.value.isbns = '';
+        form.value.isbn = '';
     };
 
     const handleConfirm = async () => {
-        if (!form.value.isbns) {
+        if (!form.value.isbn) {
             ElMessage.warning('请输入ISBN');
             return;
         }
+        loading.value = true;
         try {
-            await request.post('/marketing/shareDiscount/book/add', {
-                isbns: form.value.isbns.split('\n').filter(item => item.trim())
+            const res = await request.post('/activity/reduce/book/add', {
+                isbn: form.value.isbn
             });
-            ElMessage.success('添加成功');
-            emit('success');
-            updateVisible(false);
+            if (res.data.code === 200) {
+                ElMessage.success(res.data.msg || '添加成功');
+                emit('success');
+                updateVisible(false);
+            } else {
+                ElMessage.error(res.data.msg || '添加失败');
+            }
         } catch (error) {
-            // Mock success
-            ElMessage.success('添加成功 (Mock)');
-            emit('success');
-            updateVisible(false);
+            console.error(error);
+            ElMessage.error('添加失败');
+        } finally {
+            loading.value = false;
         }
     };
 

+ 24 - 7
src/views/marketing/shareDiscount/booklist/components/BooklistImport.vue

@@ -33,11 +33,13 @@
     import { ref, defineEmits } from 'vue';
     import { ElMessage } from 'element-plus';
     import { UploadFilled } from '@element-plus/icons-vue';
+    import request from '@/utils/request';
 
     const emit = defineEmits(['success']);
 
     const visible = ref(false);
     const fileList = ref([]);
+    const uploadRef = ref(null);
 
     const updateVisible = (val) => {
         visible.value = val;
@@ -52,17 +54,32 @@
         fileList.value = fileListRef;
     };
 
-    const handleConfirm = () => {
+    const handleConfirm = async () => {
         if (fileList.value.length === 0) {
             ElMessage.warning('请选择文件');
             return;
         }
-        // Simulate API call
-        setTimeout(() => {
-            ElMessage.success('导入成功');
-            emit('success');
-            updateVisible(false);
-        }, 500);
+        
+        const formData = new FormData();
+        formData.append('file', fileList.value[0].raw);
+
+        try {
+            const res = await request.post('/activity/reduce/book/import', formData, {
+                headers: {
+                    'Content-Type': 'multipart/form-data'
+                }
+            });
+            if (res.data.code === 0) {
+                ElMessage.success(res.data.msg || '导入成功');
+                emit('success');
+                updateVisible(false);
+            } else {
+                ElMessage.error(res.data.msg || '导入失败');
+            }
+        } catch (error) {
+            console.error(error);
+            ElMessage.error('导入失败');
+        }
     };
 
     const handleCancel = () => {

+ 52 - 75
src/views/marketing/shareDiscount/booklist/index.vue

@@ -1,43 +1,32 @@
 <template>
-    <div class="h-full">
+    <ele-page flex-table :bodyStyle="{ padding: 0 }">
         <page-search @search="handleSearch" />
-        <common-table
-            ref="tableRef"
-            :columns="columns"
-            :page-config="pageConfig"
-            @selection-change="handleSelectionChange"
-        >
+        <common-table ref="tableRef" :columns="columns" :page-config="pageConfig"
+            @selection-change="handleSelectionChange">
             <template #toolbar>
                 <el-button type="primary" :icon="Plus" @click="handleAdd">新建</el-button>
-                <el-button type="danger" :icon="Delete" @click="handleBatchDelete">批量删除</el-button>
+                <!-- <el-button type="danger" :icon="Delete" @click="handleBatchDelete">批量删除</el-button> -->
                 <el-button type="success" :icon="Upload" @click="handleImport">导入</el-button>
                 <el-button type="warning" :icon="Download" @click="handleExport">导出</el-button>
             </template>
 
             <template #cover="{ row }">
-                <el-image
-                    style="width: 50px; height: 70px"
-                    :src="row.cover"
-                    :preview-src-list="[row.cover]"
-                    fit="cover"
-                />
+                <el-image style="width: 50px; height: 70px" :src="row.cover" :preview-src-list="[row.cover]"
+                    fit="cover" />
+            </template>
+            <template #sellStatus="{ row }">
+                <dict-data code="sell_status" v-model="row.sellStatus" type="tag"></dict-data>
             </template>
 
-            <template #action="{ row }">
+            <!-- <template #action="{ row }">
                 <el-button type="danger" link @click="handleDelete(row)">删除</el-button>
-            </template>
+            </template> -->
         </common-table>
 
-        <booklist-add
-            ref="addRef"
-            @success="refreshData"
-        />
+        <booklist-add ref="addRef" @success="refreshData" />
 
-        <booklist-import
-            ref="importRef"
-            @success="refreshData"
-        />
-    </div>
+        <booklist-import ref="importRef" @success="refreshData" />
+    </ele-page>
 </template>
 
 <script setup>
@@ -45,7 +34,6 @@
     import { Plus, Delete, Upload, Download } from '@element-plus/icons-vue';
     import { ElMessage, ElMessageBox } from 'element-plus';
     import CommonTable from '@/components/CommonPage/CommonTable.vue';
-    import request from '@/utils/request';
     import PageSearch from './components/PageSearch.vue';
     import BooklistAdd from './components/BooklistAdd.vue';
     import BooklistImport from './components/BooklistImport.vue';
@@ -60,7 +48,8 @@
     const selection = ref([]);
 
     const pageConfig = reactive({
-        pageUrl: '/marketing/shareDiscount/book/list',
+        pageUrl: '/activity/reduce/book/pagelist',
+        exportUrl: '/activity/reduce/book/export',
         fileName: '降价营销书单',
         cacheKey: 'shareDiscountBookListTable',
         rowKey: 'id',
@@ -71,19 +60,34 @@
         { type: 'selection', width: 50 },
         { prop: 'cover', label: '封面', slot: 'cover', width: 80 },
         { prop: 'isbn', label: 'ISBN', width: 140 },
-        { prop: 'title', label: '书名', minWidth: 150 },
+        { prop: 'bookName', label: '书名', minWidth: 150 },
         { prop: 'author', label: '作者', width: 120 },
-        { prop: 'publisher', label: '出版社', width: 150 },
-        { prop: 'publishTime', label: '出版时间', width: 120 },
-        { prop: 'price', label: '定价', width: 100 },
+        { prop: 'publish', label: '出版社', width: 150 },
+        { prop: 'pubDate', label: '出版时间', width: 120 },
+        { prop: 'price', label: '定价', width: 80 },
+        { prop: 'productPrice', label: '售价', width: 80 },
+        {
+            prop: 'sellStatus',
+            label: '销售状态',
+            width: 100,
+            slot: 'sellStatus',
+            formatter: (row) => ['待上架', '出售中', '手动下架', '售罄', '黑名单'][row.sellStatus] || row.sellStatus
+        },
+        { prop: 'recycleDiscount', label: '回收折扣', width: 100 },
+        { prop: 'recyclePrice', label: '回收价格', width: 100 },
+        { prop: 'upsellPrice', label: '加价金额', width: 100 },
         { prop: 'createTime', label: '添加时间', width: 160 },
-        { prop: 'action', label: '操作', slot: 'action', width: 100, fixed: 'right' }
+        // { prop: 'action', label: '操作', slot: 'action', width: 100, fixed: 'right' }
     ];
 
     const handleSearch = (params) => {
         tableRef.value?.reload(params);
     };
 
+    const refreshData = () => {
+        tableRef.value?.reload();
+    };
+
     const handleSelectionChange = (val) => {
         selection.value = val;
     };
@@ -97,49 +101,22 @@
     };
 
     const handleExport = () => {
-        // In a real scenario, we might use window.open or a download utility
-        // using pageConfig.exportUrl if supported, or just a mock message
-        ElMessage.success('导出任务已创建');
+        tableRef.value?.exportData();
     };
 
-    const handleBatchDelete = () => {
-        if (selection.value.length === 0) {
-            ElMessage.warning('请选择要删除的记录');
-            return;
-        }
-        ElMessageBox.confirm('确认删除选中的记录吗?', '提示', {
-            type: 'warning'
-        }).then(async () => {
-            try {
-                const ids = selection.value.map(item => item.id);
-                await request.post('/marketing/shareDiscount/book/delete', { ids });
-                ElMessage.success('删除成功');
-                refreshData();
-            } catch (error) {
-                // Mock success if API fails (since it's not real backend)
-                ElMessage.success('删除成功 (Mock)');
-                refreshData();
-            }
-        });
-    };
-
-    const handleDelete = (row) => {
-        ElMessageBox.confirm('确认删除该记录吗?', '提示', {
-            type: 'warning'
-        }).then(async () => {
-            try {
-                await request.post('/marketing/shareDiscount/book/delete', { ids: [row.id] });
-                ElMessage.success('删除成功');
-                refreshData();
-            } catch (error) {
-                 // Mock success
-                ElMessage.success('删除成功 (Mock)');
-                refreshData();
-            }
-        });
-    };
-
-    const refreshData = () => {
-        tableRef.value?.reload();
-    };
+    // const handleBatchDelete = () => {
+    //     if (selection.value.length === 0) {
+    //         ElMessage.warning('请选择要删除的记录');
+    //         return;
+    //     }
+    //     ElMessageBox.confirm('确认删除选中的记录吗?', '提示', {
+    //         type: 'warning'
+    //     }).then(async () => {
+    //         // No delete API available
+    //     });
+    // };
+
+    // const handleDelete = (row) => {
+    //     // No delete API available
+    // };
 </script>

+ 123 - 90
src/views/marketing/shareDiscount/rules/index.vue

@@ -4,136 +4,88 @@
             <div class="text-lg font-bold mb-6">降价营销规则</div>
             <div class="flex">
                 <div class="flex-1 max-w-3xl">
-                    <el-form
-                        :model="form"
-                        label-width="200px"
-                        class="rules-form"
-                        v-loading="loading"
-                    >
+                    <el-form :model="form" label-width="200px" class="rules-form" v-loading="loading">
                         <el-form-item label="分享一人降价比例:">
                             <div class="flex items-center">
-                                <el-input-number
-                                    v-model="form.shareRatio"
-                                    :min="0"
-                                    :max="100"
-                                />
+                                <el-input-number v-model="form.reduceRate" :min="0" :max="100" />
                                 <span class="ml-2">(%)</span>
                             </div>
                         </el-form-item>
 
                         <el-form-item label="降价限制时间:">
                             <div class="flex items-center">
-                                <el-input-number
-                                    v-model="form.limitTime"
-                                    :min="0"
-                                />
+                                <el-input-number v-model="form.inviteExpireHour" :min="0" />
                                 <span class="ml-2">(小时)</span>
-                                <span class="ml-4 text-gray-400"
-                                    >以助力成功计算</span
-                                >
+                                <span class="ml-4 text-gray-400">以助力成功计算</span>
                             </div>
                         </el-form-item>
 
                         <el-form-item label="每单每本书可降价次数:">
                             <div class="flex items-center">
-                                <el-input-number
-                                    v-model="form.discountTimesPerBook"
-                                    :min="0"
-                                />
+                                <el-input-number v-model="form.reduceTimesPerIsbn" :min="0" />
                                 <span class="ml-2">(次)</span>
                             </div>
                         </el-form-item>
 
                         <el-form-item label="每个ID每天助力限制:">
                             <div class="flex items-center">
-                                <el-input-number
-                                    v-model="form.assistLimitPerDay"
-                                    :min="0"
-                                />
+                                <el-input-number v-model="form.assistTimesEveryDay" :min="0" />
                                 <span class="ml-2">(次)</span>
                             </div>
                         </el-form-item>
 
                         <el-form-item label="分享一人最高降价:">
                             <div class="flex items-center">
-                                <el-input-number
-                                    v-model="form.maxDiscountPerShare"
-                                    :min="0"
-                                    :precision="2"
-                                />
+                                <el-input-number v-model="form.maxPerHelp" :min="0" :precision="2" />
                                 <span class="ml-2">(元)</span>
                             </div>
                         </el-form-item>
 
                         <el-form-item label="分享一人最低降价:">
                             <div class="flex items-center">
-                                <el-input-number
-                                    v-model="form.minDiscountPerShare"
-                                    :min="0"
-                                    :precision="2"
-                                />
+                                <el-input-number v-model="form.minPerHelp" :min="0" :precision="2" />
                                 <span class="ml-2">(元)</span>
                             </div>
                         </el-form-item>
 
                         <el-form-item label="降价取消场景:">
                             <div class="flex items-center w-full">
-                                <el-select
-                                    v-model="form.cancelScenarios"
-                                    multiple
-                                    placeholder="请选择"
-                                    style="width: 300px"
-                                >
-                                    <el-option
-                                        label="订单退款"
-                                        value="refund"
-                                    />
-                                    <el-option
-                                        label="订单取消"
-                                        value="cancel"
-                                    />
-                                </el-select>
+                                <dict-data v-model="form.upsellValidScenario" code="upsell_valid_scenario"
+                                    type="multipleSelect" style="width:300px" />
                             </div>
                         </el-form-item>
 
                         <el-form-item label="预留手机号:">
                             <div class="flex items-center">
-                                <el-input
-                                    v-model="form.mobile"
-                                    placeholder="请输入手机号"
-                                    style="width: 200px"
-                                />
+                                <el-input v-model="form.mobile" placeholder="请输入手机号" style="width: 300px" />
+                            </div>
+                        </el-form-item>
+
+                        <el-form-item label="验证码:" v-if="needVerification">
+                            <div class="flex items-center">
+                                <el-input v-model="form.code" placeholder="请输入验证码" style="width: 200px" />
+                                <el-button type="primary" link class="ml-4" @click="handleResendCode"
+                                    :disabled="cooldown > 0">
+                                    {{ cooldown > 0 ? `${cooldown}秒后重发` : '获取验证码' }}
+                                </el-button>
                             </div>
                         </el-form-item>
 
                         <el-form-item label="营销降价书单:">
                             <div class="flex items-center">
-                                <el-select
-                                    v-model="form.booklistId"
-                                    placeholder="请选择书单"
-                                    style="width: 300px"
-                                >
-                                    <el-option
-                                        label="营销书单降价"
-                                        value="1"
-                                    />
-                                </el-select>
+                                <dict-data v-model="form.reduceScope" code="reduce_scope" style="width:300px" />
                             </div>
                         </el-form-item>
 
                         <el-form-item>
-                            <el-button type="primary" :loading="saving" @click="handleSave"
-                                >保存</el-button
-                            >
+                            <el-button type="primary" :loading="saving" @click="handleSave">保存</el-button>
                         </el-form-item>
                     </el-form>
                 </div>
 
                 <!-- Right Side Note -->
                 <div class="w-64 ml-8">
-                    <div
-                        class="bg-yellow-50 border border-yellow-200 p-4 rounded text-sm text-gray-600"
-                    >
+                    <div class="bg-yellow-50 border border-yellow-200 p-4 rounded text-sm text-gray-600">
                         <p class="mb-2 font-bold">注意事项:</p>
                         <p class="mb-2">单本金额不能为负,最低为0</p>
                         <p>
@@ -147,7 +99,7 @@
 </template>
 
 <script setup>
-    import { reactive, ref, onMounted } from 'vue';
+    import { reactive, ref, onMounted, onUnmounted } from 'vue';
     import { EleMessage } from 'ele-admin-plus/es';
     import request from '@/utils/request';
 
@@ -155,40 +107,114 @@
 
     const loading = ref(false);
     const saving = ref(false);
+    const needVerification = ref(false);
+    const cooldown = ref(0);
+    let timer = null;
 
     const form = reactive({
-        shareRatio: 10,
-        limitTime: 24,
-        discountTimesPerBook: 3,
-        assistLimitPerDay: 1,
-        maxDiscountPerShare: 3,
-        minDiscountPerShare: 0.5,
-        cancelScenarios: ['refund', 'cancel'],
-        mobile: '18888888888',
-        booklistId: '1'
+        reduceRate: void 0,
+        inviteExpireHour: void 0,
+        reduceTimesPerIsbn: void 0,
+        assistTimesEveryDay: void 0,
+        maxPerHelp: void 0,
+        minPerHelp: void 0,
+        upsellValidScenario: [],
+        mobile: '',
+        reduceScope: void 0,
+        code: ''
     });
 
+    const startCooldown = () => {
+        cooldown.value = 60;
+        if (timer) clearInterval(timer);
+        timer = setInterval(() => {
+            if (cooldown.value > 0) {
+                cooldown.value--;
+            } else {
+                clearInterval(timer);
+                timer = null;
+            }
+        }, 1000);
+    };
+
     const fetchRules = async () => {
         loading.value = true;
         try {
-            const res = await request.get('/marketing/shareDiscount/rules/detail');
-            Object.assign(form, res.data);
+            const res = await request.get('/activity/reduce/manage/rule/get');
+            if (res.data.code === 0 || res.data.code === 200) {
+                const data = res.data.data;
+                Object.assign(form, data);
+                form.upsellValidScenario = data.upsellValidScenario.map(v => v.toString());
+                form.reduceScope = data.reduceScope?.toString();
+                if(form.mobile){
+                    needVerification.value = true;
+                }
+            }
         } catch (error) {
-            // Mock data if API fails
-            console.log('Using mock data for rules');
+            console.error(error);
         } finally {
             loading.value = false;
         }
     };
 
+    const updateRule = async () => {
+        try {
+            const res = await request.post('/activity/reduce/manage/rule/update', form);
+            if (res.data.code === 0 || res.data.code === 200) {
+                EleMessage.success('保存成功');
+                form.code = '';
+            } else {
+                EleMessage.error(res.data.msg || '保存失败');
+            }
+        } catch (error) {
+            console.error(error);
+            EleMessage.error('保存失败');
+        }
+    };
+
+    const handleResendCode = async () => {
+        try {
+            const res = await request.post('/activity/reduce/manage/rule/getCode');
+            if (res.data.code === 0 || res.data.code === 200) {
+                if (res.data.data === 1) {
+                    EleMessage.success('验证码已发送');
+                    startCooldown();
+                } else {
+                    EleMessage.info('无需验证码');
+                    needVerification.value = false;
+                }
+            } else {
+                EleMessage.error(res.data.msg || '获取验证码失败');
+            }
+        } catch (error) {
+            console.error(error);
+            EleMessage.error('获取验证码失败');
+        }
+    };
+
     const handleSave = async () => {
         saving.value = true;
         try {
-            await request.post('/marketing/shareDiscount/rules/update', form);
-            EleMessage.success('保存成功');
+            if (form.code) {
+                await updateRule();
+            } else {
+                // Check if verification is needed
+                const res = await request.post('/activity/reduce/manage/rule/getCode');
+                if (res.data.code === 0 || res.data.code === 200) {
+                    if (res.data.data === 1) {
+                        needVerification.value = true;
+                        EleMessage.success('验证码已发送,请输入验证码');
+                        startCooldown();
+                    } else {
+                        await updateRule();
+                    }
+                } else {
+                    EleMessage.error(res.data.msg || '获取验证码失败');
+                }
+            }
         } catch (error) {
-             // Mock success
-            EleMessage.success('保存成功 (Mock)');
+            console.error(error);
+            EleMessage.error('操作失败');
         } finally {
             saving.value = false;
         }
@@ -197,6 +223,13 @@
     onMounted(() => {
         fetchRules();
     });
+
+    onUnmounted(() => {
+        if (timer) {
+            clearInterval(timer);
+            timer = null;
+        }
+    });
 </script>
 
 <style scoped>