소스 검색

feat(商品选择对话框): 重构商品选择对话框并添加描述编辑功能

- 将商品选择对话框从CommonTable改为原生el-table实现
- 添加"全部"和"已选择"标签页切换功能
- 支持商品描述编辑功能,可通过show-description属性控制
- 优化分页逻辑,支持跨页选择保持
- 更新绑定逻辑以包含商品描述信息
ylong 1 주 전
부모
커밋
1d9d11013e

+ 29 - 4
src/views/salesOps/booklist/components/booklist-bind.vue

@@ -3,6 +3,7 @@
         ref="goodsSelectRef"
         v-model="visible"
         :default-selected="selectedBooks"
+        :show-description="true"
         title="绑定图书"
         @confirm="handleConfirm"
     />
@@ -19,6 +20,7 @@
     const visible = ref(false);
     const selectedBooks = ref([]);
     const originalIsbns = ref([]);
+    const originalBooksMap = ref({});
     const currentId = ref(null);
     const goodsSelectRef = ref(null);
 
@@ -26,6 +28,7 @@
         currentId.value = row.id;
         selectedBooks.value = [];
         originalIsbns.value = [];
+        originalBooksMap.value = {};
 
         // Reset dialog state
         if (goodsSelectRef.value?.reset) {
@@ -47,12 +50,16 @@
             const mappedList = (Array.isArray(list) ? list : []).map(
                 (item) => ({
                     ...item,
-                    isbn: item.bookIsbn || item.isbn
+                    isbn: item.bookIsbn || item.isbn,
+                    bookDesc: item.bookDesc || ''
                 })
             );
 
             selectedBooks.value = mappedList;
             originalIsbns.value = mappedList.map((b) => b.isbn);
+            mappedList.forEach(b => {
+                originalBooksMap.value[b.isbn] = b;
+            });
         } catch (e) {}
     };
 
@@ -63,9 +70,20 @@
         try {
             const currentIsbns = rows.map((b) => b.isbn);
 
-            const toAdd = currentIsbns.filter(
-                (isbn) => !originalIsbns.value.includes(isbn)
+            const toAdd = rows.filter(
+                (b) => {
+                    if (!originalIsbns.value.includes(b.isbn)) {
+                        return true;
+                    }
+                    // Check if description changed
+                    const orig = originalBooksMap.value[b.isbn];
+                    if (orig && (orig.bookDesc || '') !== (b.bookDesc || '')) {
+                        return true;
+                    }
+                    return false;
+                }
             );
+
             const toRemove = originalIsbns.value.filter(
                 (isbn) => !currentIsbns.includes(isbn)
             );
@@ -73,10 +91,17 @@
             const promises = [];
 
             if (toAdd.length > 0) {
+                const bindBookList = toAdd.map(b => ({
+                    bookIsbn: b.isbn,
+                    bookDesc: b.bookDesc || ''
+                }));
+                const bookIsbnList = toAdd.map(b => b.isbn);
+
                 promises.push(
                     request.post('/book/showIndex/bindBook', {
                         showCateId: currentId.value,
-                        bookIsbnList: toAdd
+                        bookIsbnList: bookIsbnList,
+                        bindBookList: bindBookList
                     })
                 );
             }

+ 288 - 81
src/views/salesOps/components/GoodsSelectDialog.vue

@@ -7,7 +7,7 @@
         :body-style="{ padding: '0 20px 20px' }"
     >
         <!-- Search -->
-        <div class="p-0 flex justify-between items-start">
+        <div class="p-0">
             <el-form :inline="true" :model="searchForm" label-width="0px">
                 <el-form-item>
                     <el-input
@@ -30,45 +30,163 @@
                     <el-button @click="handleReset">重置</el-button>
                 </el-form-item>
             </el-form>
-            <div class="pt-1">
-                <span class="text-gray-600"
-                    >已选择
-                    <span class="text-primary font-bold">{{
-                        selectedCount
-                    }}</span>
-                    项</span
-                >
+        </div>
+
+        <div class="flex justify-between items-end mb-4">
+            <!-- Tabs -->
+            <div class="flex space-x-6 border-b border-gray-200 flex-1 mr-4">
+                <div 
+                    class="cursor-pointer pb-2 px-2 whitespace-nowrap"
+                    :class="activeTab === 'all' ? 'text-primary border-b-2 border-primary font-medium' : 'text-gray-600'"
+                    @click="activeTab = 'all'"
+                >全部</div>
+                <div 
+                    class="cursor-pointer pb-2 px-2 whitespace-nowrap"
+                    :class="activeTab === 'selected' ? 'text-primary border-b-2 border-primary font-medium' : 'text-gray-600'"
+                    @click="activeTab = 'selected'"
+                >已选择 ({{ selectedCount }})</div>
             </div>
+            
+            <!-- Pagination -->
+            <el-pagination
+                v-if="activeTab === 'all'"
+                v-model:current-page="currentPage"
+                v-model:page-size="pageSize"
+                :page-sizes="[10, 20, 50, 100]"
+                layout="total, sizes, prev, pager, next, jumper"
+                :total="total"
+                @size-change="handleSizeChange"
+                @current-change="handleCurrentChange"
+                class="shrink-0"
+            />
+            <el-pagination
+                v-else
+                v-model:current-page="selectedCurrentPage"
+                v-model:page-size="selectedPageSize"
+                :page-sizes="[10, 20, 50, 100]"
+                layout="total, sizes, prev, pager, next, jumper"
+                :total="selectedCount"
+                class="shrink-0"
+            />
         </div>
 
-        <!-- Table -->
-        <common-table
+        <!-- Table for All -->
+        <el-table
+            v-show="activeTab === 'all'"
             ref="tableRef"
-            :columns="columns"
-            :page-config="pageConfig"
-            :bodyStyle="{ padding: '0' }"
+            :data="tableData"
+            row-key="isbn"
+            v-loading="loading"
             @selection-change="handleSelectionChange"
-            :toolbar="false"
+            border
+            height="400px"
         >
-            <template #cover="{ row }">
-                <el-image
-                    :src="row.cover"
-                    class="w-10 h-10 object-cover"
-                    :preview-src-list="[row.cover]"
-                    preview-teleported
-                >
-                    <template #error>
-                        <div
-                            class="w-10 h-10 bg-gray-100 flex items-center justify-center text-gray-400"
-                        >
-                            <el-icon>
-                                <Picture />
-                            </el-icon>
+            <el-table-column type="selection" width="55" align="center" :reserve-selection="true" />
+            <el-table-column label="图示" width="80" align="center">
+                <template #default="{ row }">
+                    <el-image
+                        :src="row.cover"
+                        class="w-10 h-10 object-cover"
+                        :preview-src-list="[row.cover]"
+                        preview-teleported
+                    >
+                        <template #error>
+                            <div class="w-10 h-10 bg-gray-100 flex items-center justify-center text-gray-400">
+                                <el-icon><Picture /></el-icon>
+                            </div>
+                        </template>
+                    </el-image>
+                </template>
+            </el-table-column>
+            <el-table-column prop="isbn" label="ISBN" width="140" align="center" />
+            <el-table-column prop="bookName" label="书名" min-width="200" show-overflow-tooltip />
+            
+            <template v-if="showDescription">
+                <el-table-column label="商品描述" min-width="200">
+                    <template #default="{ row }">
+                        <div v-if="!row._editingDesc" class="cursor-pointer flex items-center" @click="row._editingDesc = true">
+                            <span class="truncate flex-1" :title="row.bookDesc || '点击输入描述'">{{ row.bookDesc || '点击输入描述' }}</span>
+                            <el-icon class="ml-1 text-gray-400"><Edit /></el-icon>
                         </div>
+                        <el-input
+                            v-else
+                            v-model="row.bookDesc"
+                            placeholder="请输入商品描述"
+                            size="small"
+                            @blur="row._editingDesc = false"
+                            @keyup.enter="row._editingDesc = false"
+                            @input="val => updateSelectionDesc(row, val)"
+                            autofocus
+                        />
                     </template>
-                </el-image>
+                </el-table-column>
+            </template>
+            <template v-else>
+                <el-table-column prop="author" label="作者" width="120" show-overflow-tooltip />
+                <el-table-column prop="price" label="价格" width="100" align="center" />
             </template>
-        </common-table>
+        </el-table>
+
+        <!-- Table for Selected -->
+        <el-table
+            v-show="activeTab === 'selected'"
+            :data="selectedTableData"
+            row-key="isbn"
+            border
+            height="400px"
+        >
+            <el-table-column width="55" align="center">
+                <template #header>
+                    <el-checkbox :model-value="true" disabled />
+                </template>
+                <template #default="{ row }">
+                    <el-checkbox :model-value="true" @change="() => handleRemoveSelection(row)" />
+                </template>
+            </el-table-column>
+            <el-table-column label="图示" width="80" align="center">
+                <template #default="{ row }">
+                    <el-image
+                        :src="row.cover"
+                        class="w-10 h-10 object-cover"
+                        :preview-src-list="[row.cover]"
+                        preview-teleported
+                    >
+                        <template #error>
+                            <div class="w-10 h-10 bg-gray-100 flex items-center justify-center text-gray-400">
+                                <el-icon><Picture /></el-icon>
+                            </div>
+                        </template>
+                    </el-image>
+                </template>
+            </el-table-column>
+            <el-table-column prop="isbn" label="ISBN" width="140" align="center" />
+            <el-table-column prop="bookName" label="书名" min-width="200" show-overflow-tooltip />
+            
+            <template v-if="showDescription">
+                <el-table-column label="商品描述" min-width="200">
+                    <template #default="{ row }">
+                        <div v-if="!row._editingDesc" class="cursor-pointer flex items-center" @click="row._editingDesc = true">
+                            <span class="truncate flex-1" :title="row.bookDesc || '点击输入描述'">{{ row.bookDesc || '点击输入描述' }}</span>
+                            <el-icon class="ml-1 text-gray-400"><Edit /></el-icon>
+                        </div>
+                        <el-input
+                            v-else
+                            v-model="row.bookDesc"
+                            placeholder="请输入商品描述"
+                            size="small"
+                            @blur="row._editingDesc = false"
+                            @keyup.enter="row._editingDesc = false"
+                            @input="val => updateSelectionDesc(row, val)"
+                            autofocus
+                        />
+                    </template>
+                </el-table-column>
+            </template>
+            <template v-else>
+                <el-table-column prop="author" label="作者" width="120" show-overflow-tooltip />
+                <el-table-column prop="price" label="价格" width="100" align="center" />
+            </template>
+        </el-table>
 
         <template #footer>
             <div class="flex justify-end items-center">
@@ -83,13 +201,14 @@
 
 <script setup>
     import { ref, reactive, watch, computed, nextTick } from 'vue';
-    import { Picture } from '@element-plus/icons-vue';
-    import CommonTable from '@/components/CommonPage/CommonTable.vue';
+    import { Picture, Edit } from '@element-plus/icons-vue';
+    import request from '@/utils/request';
 
     const props = defineProps({
         title: { type: String, default: '添加商品' },
         width: { type: String, default: '900px' },
-        defaultSelected: { type: Array, default: () => [] }
+        defaultSelected: { type: Array, default: () => [] },
+        showDescription: { type: Boolean, default: false }
     });
 
     const emit = defineEmits(['update:modelValue', 'confirm']);
@@ -101,69 +220,157 @@
         isbn: ''
     });
 
+    const activeTab = ref('all');
     const currentSelections = ref([]);
     const selectedCount = computed(() => currentSelections.value.length);
 
-    // Columns Configuration
-    const columns = [
-        {
-            type: 'selection',
-            width: 55,
-            align: 'center',
-            reserveSelection: true
-        },
-        {
-            label: '封面',
-            slot: 'cover',
-            prop: 'cover',
-            width: 80,
-            align: 'center'
-        },
-        { prop: 'isbn', label: 'ISBN', width: 140, align: 'center' },
-        {
-            prop: 'bookName',
-            label: '书名',
-            minWidth: 200,
-            showOverflowTooltip: true
-        },
-        {
-            prop: 'author',
-            label: '作者',
-            width: 120,
-            showOverflowTooltip: true
-        },
-        { prop: 'price', label: '价格', width: 100, align: 'center' }
-    ];
-
-    const pageConfig = reactive({
-        pageUrl: '/book/bookInfo/list',
-        rowKey: 'isbn',
-        tool: false,
-        params: {} // Initial params
+    // Pagination for 'all'
+    const tableData = ref([]);
+    const total = ref(0);
+    const currentPage = ref(1);
+    const pageSize = ref(10);
+    const loading = ref(false);
+
+    // Pagination for 'selected'
+    const selectedCurrentPage = ref(1);
+    const selectedPageSize = ref(10);
+    const selectedTableData = computed(() => {
+        const start = (selectedCurrentPage.value - 1) * selectedPageSize.value;
+        const end = start + selectedPageSize.value;
+        return currentSelections.value.slice(start, end);
     });
 
-    const handleSearch = () => {
-        tableRef.value?.reload({ ...searchForm });
+    const handleSearch = async () => {
+        if (activeTab.value === 'selected') {
+            selectedCurrentPage.value = 1;
+            return;
+        }
+        
+        loading.value = true;
+        try {
+            const params = {
+                ...searchForm,
+                page: currentPage.value,
+                limit: pageSize.value
+            };
+            const res = await request.get('/book/bookInfo/list', { params });
+            if (res.data.code === 200) {
+                const data = res.data.data || res.data;
+                const rows = data.rows || data;
+                
+                const updateRowDesc = (row) => {
+                    const selectedItem = currentSelections.value.find(s => s.isbn === row.isbn);
+                    if (selectedItem && selectedItem.bookDesc !== undefined) {
+                        row.bookDesc = selectedItem.bookDesc;
+                        return;
+                    }
+                    const defaultItem = props.defaultSelected.find(d => d.isbn === row.isbn);
+                    if (defaultItem) {
+                        row.bookDesc = defaultItem.bookDesc;
+                    }
+                };
+
+                rows.forEach(updateRowDesc);
+                tableData.value = rows;
+                total.value = data.count || rows.length;
+
+                // Sync selections to table
+                nextTick(() => {
+                    rows.forEach(row => {
+                        const isSelected = currentSelections.value.some(s => s.isbn === row.isbn);
+                        if (isSelected && tableRef.value) {
+                            tableRef.value.toggleRowSelection(row, true);
+                        }
+                    });
+                });
+            }
+        } catch (e) {
+            console.error(e);
+        } finally {
+            loading.value = false;
+        }
+    };
+
+    const handleSizeChange = (val) => {
+        pageSize.value = val;
+        handleSearch();
+    };
+
+    const handleCurrentChange = (val) => {
+        currentPage.value = val;
+        handleSearch();
     };
 
     const handleReset = () => {
         searchForm.bookName = '';
         searchForm.isbn = '';
+        currentPage.value = 1;
         handleSearch();
     };
 
     const reset = () => {
         searchForm.bookName = '';
         searchForm.isbn = '';
+        activeTab.value = 'all';
         currentSelections.value = [];
+        currentPage.value = 1;
+        selectedCurrentPage.value = 1;
         if (tableRef.value) {
             tableRef.value.clearSelection();
-            tableRef.value.reload({ pageNum: 1 });
         }
+        handleSearch();
     };
 
     const handleSelectionChange = (rows) => {
-        currentSelections.value = rows;
+        // Need to merge cross-page selections
+        // Add new rows
+        rows.forEach(row => {
+            if (!currentSelections.value.some(s => s.isbn === row.isbn)) {
+                currentSelections.value.push({ ...row });
+            }
+        });
+        
+        // Remove unselected rows from current page
+        tableData.value.forEach(row => {
+            const isSelectedNow = rows.some(r => r.isbn === row.isbn);
+            if (!isSelectedNow) {
+                const idx = currentSelections.value.findIndex(s => s.isbn === row.isbn);
+                if (idx !== -1) {
+                    currentSelections.value.splice(idx, 1);
+                }
+            }
+        });
+    };
+
+    const handleRemoveSelection = (row) => {
+        const idx = currentSelections.value.findIndex(s => s.isbn === row.isbn);
+        if (idx !== -1) {
+            currentSelections.value.splice(idx, 1);
+        }
+        // Also untoggle in table if it's currently loaded
+        if (tableRef.value) {
+            const tableRow = tableData.value.find(r => r.isbn === row.isbn);
+            if (tableRow) {
+                tableRef.value.toggleRowSelection(tableRow, false);
+            }
+        }
+        // Adjust pagination if needed
+        const maxPage = Math.ceil(currentSelections.value.length / selectedPageSize.value) || 1;
+        if (selectedCurrentPage.value > maxPage) {
+            selectedCurrentPage.value = maxPage;
+        }
+    };
+
+    const updateSelectionDesc = (row, val) => {
+        const selected = currentSelections.value.find(s => s.isbn === row.isbn);
+        if (selected) {
+            selected.bookDesc = val;
+        }
+        // Also update the row in tableData if it exists
+        const tableRow = tableData.value.find(r => r.isbn === row.isbn);
+        if (tableRow) {
+            tableRow.bookDesc = val;
+        }
     };
 
     const handleCancel = () => {
@@ -171,24 +378,24 @@
     };
 
     const handleConfirm = () => {
-        const selections =
-            tableRef.value?.getSelections() || currentSelections.value;
-        emit('confirm', selections);
+        emit('confirm', currentSelections.value);
         visible.value = false;
     };
 
     watch(visible, (val) => {
         if (val) {
             nextTick(() => {
-                handleSearch();
                 if (props.defaultSelected && props.defaultSelected.length > 0) {
-                    if (tableRef.value?.setSelectedRows) {
-                        tableRef.value.setSelectedRows(props.defaultSelected);
-                    }
+                    currentSelections.value = JSON.parse(JSON.stringify(props.defaultSelected));
+                } else {
+                    currentSelections.value = [];
                 }
+                currentPage.value = 1;
+                handleSearch();
             });
         }
     });
+    
     defineExpose({
         reset
     });

+ 29 - 4
src/views/salesOps/topics/components/topics-bind.vue

@@ -3,6 +3,7 @@
         ref="goodsSelectRef"
         v-model="visible"
         :default-selected="selectedBooks"
+        :show-description="true"
         title="绑定图书"
         @confirm="handleConfirm"
     />
@@ -19,6 +20,7 @@
     const visible = ref(false);
     const selectedBooks = ref([]);
     const originalIsbns = ref([]);
+    const originalBooksMap = ref({});
     const currentId = ref(null);
     const goodsSelectRef = ref(null);
 
@@ -26,6 +28,7 @@
         currentId.value = row.id;
         selectedBooks.value = [];
         originalIsbns.value = [];
+        originalBooksMap.value = {};
 
         // Reset dialog state
         if (goodsSelectRef.value?.reset) {
@@ -47,12 +50,16 @@
             const mappedList = (Array.isArray(list) ? list : []).map(
                 (item) => ({
                     ...item,
-                    isbn: item.bookIsbn || item.isbn
+                    isbn: item.bookIsbn || item.isbn,
+                    bookDesc: item.bookDesc || ''
                 })
             );
 
             selectedBooks.value = mappedList;
             originalIsbns.value = mappedList.map((b) => b.isbn);
+            mappedList.forEach(b => {
+                originalBooksMap.value[b.isbn] = b;
+            });
         } catch (e) {}
     };
 
@@ -63,9 +70,20 @@
         try {
             const currentIsbns = rows.map((b) => b.isbn);
 
-            const toAdd = currentIsbns.filter(
-                (isbn) => !originalIsbns.value.includes(isbn)
+            const toAdd = rows.filter(
+                (b) => {
+                    if (!originalIsbns.value.includes(b.isbn)) {
+                        return true;
+                    }
+                    // Check if description changed
+                    const orig = originalBooksMap.value[b.isbn];
+                    if (orig && (orig.bookDesc || '') !== (b.bookDesc || '')) {
+                        return true;
+                    }
+                    return false;
+                }
             );
+
             const toRemove = originalIsbns.value.filter(
                 (isbn) => !currentIsbns.includes(isbn)
             );
@@ -73,10 +91,17 @@
             const promises = [];
 
             if (toAdd.length > 0) {
+                const bindBookList = toAdd.map(b => ({
+                    bookIsbn: b.isbn,
+                    bookDesc: b.bookDesc || ''
+                }));
+                const bookIsbnList = toAdd.map(b => b.isbn);
+
                 promises.push(
                     request.post('/book/showIndex/bindBook', {
                         showCateId: currentId.value,
-                        bookIsbnList: toAdd
+                        bookIsbnList: bookIsbnList,
+                        bindBookList: bindBookList
                     })
                 );
             }