Procházet zdrojové kódy

fix 激活码管理增加排序*优化 bug

ylong před 3 měsíci
rodič
revize
5e78403b3a

+ 8 - 2
src/components/CommonPage/CommonTable.vue

@@ -33,6 +33,7 @@ let props = defineProps({
     columns: { type: Array, default: () => [] },
     bodyStyle: { type: Object, default: () => ({}) },
     flexTable: { type: Boolean, default: true },
+    sortFunction: { type: Function },
 });
 let { proxy } = getCurrentInstance();
 
@@ -62,16 +63,21 @@ const datasource = (table) => {
     whereCache.value = where;
 
     let tempOrders = {};
+    console.log('orders', orders);
     if (orders && orders.orderByColumn) {
         tempOrders.orderType = sorter.column.columnKey;
         tempOrders.orderWay = orders.isAsc === "ascending" ? "asc" : "desc";
     }
-
+    // 兼容激活码排序
+    if (props.sortFunction) {
+        tempOrders = props.sortFunction(orders);
+    }
+    
     return queryPage({ ...initKeys, ...where, ...tempOrders, ...pages });
 };
 
 /** 搜索 */
-const reload = (where={}) => {
+const reload = (where = {}) => {
     let data = where && where.search ? { page: 1, where } : { where: { ...whereCache.value, ...where } };
     delete data.where?.search;
     delete data.where?.isReset;

+ 22 - 1
src/views/code/list/components/in-detail-modal.vue

@@ -7,7 +7,21 @@
             :columns="columns"
             :tools="false"
             :bodyStyle="{ padding: 0 }"
-        />
+        >
+            <!-- 激活码图片插槽 -->
+            <template #urlLink="{ row }">
+                <el-image
+                    v-if="row.urlLink"
+                    :src="row.urlLink"
+                    :preview-src-list="[row.urlLink]"
+                    fit="cover"
+                    style="width: 80px; height: 80px; border-radius: 4px; cursor: pointer;"
+                    :preview-teleported="true"
+                    @error="handleImageError"
+                />
+                <span v-else class="text-gray-400">暂无图片</span>
+            </template>
+        </common-table>
 
         <template #footer>
             <el-button @click="handleCancel">关闭</el-button>
@@ -37,6 +51,8 @@
 
     /** 表格列配置 */
     const columns = ref([
+        //激活码图片
+        { label: '激活码图片', prop: 'urlLink', width: 120, align: 'center', slot: 'urlLink' },
         { label: 'ISBN', prop: 'isbn', width: 140, align: 'center' },
         { label: '书名', prop: 'bookName', minWidth: 200, align: 'left' },
         { label: '数量', prop: 'num', width: 80, align: 'center', formatter: (row) => 1 },
@@ -63,6 +79,11 @@
         visible.value = false;
     };
 
+    /** 图片加载错误处理 */
+    const handleImageError = (e) => {
+        console.warn('图片加载失败:', e);
+    };
+
     /** 弹窗打开事件 */
     const handleOpen = (isbn) => {
         if (isbn) {

+ 23 - 4
src/views/code/list/components/out-stock-modal.vue

@@ -1,6 +1,6 @@
 <!-- 出库操作弹窗 -->
 <template>
-    <ele-modal form :width="600" v-model="visible" title="出库" @closed="handleClosed">
+    <ele-modal form :width="500" v-model="visible" title="出库" @closed="handleClosed">
         <el-form ref="formRef" :model="formData" :rules="rules" label-width="80px">
             <el-form-item label="ISBN" prop="isbn">
                 <el-input v-model="formData.isbn" placeholder="请输入ISBN" readonly />
@@ -11,12 +11,12 @@
             </el-form-item>
 
             <el-form-item label="数量" prop="num">
-                <el-input-number v-model="formData.num" :min="1" :max="9999" placeholder="请输入数量" style="width: 100%" @change="handleNumChange" />
+                <el-input-number v-model="formData.num" :min="1" :max="currentBook?.stockNum || 9999" :precision="0" placeholder="请输入数量" style="width: 200px" @change="handleNumChange" />
             </el-form-item>
 
             <el-form-item label="单价" prop="price">
                 <el-input-number v-model="formData.price" :min="0" :precision="2" placeholder="请输入单价"
-                    style="width: 100%" />
+                    style="width: 200px" />
             </el-form-item>
 
             <el-form-item label="备注" prop="remark">
@@ -60,7 +60,19 @@ const formData = reactive({
 const rules = reactive({
     num: [
         { required: true, message: '请输入数量', trigger: 'blur' },
-        { type: 'number', min: 1, message: '数量必须大于0', trigger: 'blur' }
+        { type: 'number', min: 1, message: '数量必须大于0', trigger: 'blur' },
+        { 
+            validator: (rule, value, callback) => {
+                if (!Number.isInteger(value)) {
+                    callback(new Error('数量必须是整数'));
+                } else if (currentBook.value && value > currentBook.value.stockNum) {
+                    callback(new Error(`数量不能大于库存数量${currentBook.value.stockNum}`));
+                } else {
+                    callback();
+                }
+            }, 
+            trigger: 'blur' 
+        }
     ],
     price: [
         { required: true, message: '请输入单价', trigger: 'blur' },
@@ -97,6 +109,13 @@ const handleClosed = () => {
 
 /** 数量变化处理 */
 const handleNumChange = (value) => {
+    // 验证数量不能超过库存
+    if (currentBook.value && value > currentBook.value.stockNum) {
+        EleMessage.warning(`出库数量不能大于库存数量${currentBook.value.stockNum}`);
+        formData.num = currentBook.value.stockNum;
+        return;
+    }
+    
     if (currentBook.value) {
         // 数量大于1时使用批发价,否则使用零售价
         if (value > 1) {

+ 224 - 0
src/views/code/list/components/stock-image-modal.vue

@@ -0,0 +1,224 @@
+<!-- 库存图片查看弹窗 -->
+<template>
+    <ele-modal form :width="900" v-model="visible" title="库存图片">
+        <div class="stock-image-container">
+            <!-- 图片网格 -->
+            <div v-if="imageList.length > 0" class="image-grid">
+                <div 
+                    v-for="(imageUrl, index) in imageList" 
+                    :key="index" 
+                    class="image-item"
+                    @click="handleImageClick(imageUrl, index)"
+                >
+                    <el-image
+                        :src="imageUrl"
+                        fit="cover"
+                        class="stock-image"
+                        :preview-src-list="imageList"
+                        :initial-index="index"
+                        :preview-teleported="true"
+                        @error="handleImageError"
+                    />
+                </div>
+            </div>
+
+            <!-- 空状态 -->
+            <div v-else-if="!loading" class="empty-state">
+                <el-empty description="暂无库存图片" />
+            </div>
+
+            <!-- 加载状态 -->
+            <div v-if="loading" class="loading-state">
+                <el-skeleton :rows="3" animated />
+            </div>
+
+            <!-- 分页 -->
+            <div v-if="total > 10" class="pagination-container">
+                <el-pagination
+                    v-model:current-page="currentPage"
+                    v-model:page-size="pageSize"
+                    :page-sizes="[12, 24, 48, 96]"
+                    :total="total"
+                    layout="total, sizes, prev, pager, next, jumper"
+                    @size-change="handleSizeChange"
+                    @current-change="handleCurrentChange"
+                />
+            </div>
+        </div>
+
+        <template #footer>
+            <el-button @click="handleCancel">关闭</el-button>
+        </template>
+    </ele-modal>
+</template>
+
+<script setup>
+import { ref, reactive, watch, nextTick } from 'vue';
+import { EleMessage } from 'ele-admin-plus/es';
+import request from '@/utils/request';
+
+/** 弹窗是否打开 */
+const visible = defineModel({ type: Boolean });
+
+/** 当前ISBN */
+const currentIsbn = ref('');
+
+/** 加载状态 */
+const loading = ref(false);
+
+/** 图片列表 */
+const imageList = ref([]);
+
+/** 分页信息 */
+const currentPage = ref(1);
+const pageSize = ref(10);
+const total = ref(0);
+
+/** 关闭弹窗 */
+const handleCancel = () => {
+    visible.value = false;
+};
+
+/** 图片点击处理 */
+const handleImageClick = (imageUrl, index) => {
+    // Element Plus 的 el-image 组件会自动处理预览
+    console.log('点击图片:', imageUrl, '索引:', index);
+};
+
+/** 图片加载错误处理 */
+const handleImageError = (e) => {
+    console.warn('图片加载失败:', e);
+};
+
+/** 分页大小改变 */
+const handleSizeChange = (newSize) => {
+    pageSize.value = newSize;
+    currentPage.value = 1;
+    fetchStockImages();
+};
+
+/** 当前页改变 */
+const handleCurrentChange = (newPage) => {
+    currentPage.value = newPage;
+    fetchStockImages();
+};
+
+/** 获取库存图片 */
+const fetchStockImages = async () => {
+    if (!currentIsbn.value) return;
+
+    loading.value = true;
+    try {
+        const response = await request.get(`/activation/bookActivationInfo/stockImgPage/${currentIsbn.value}`, {
+            params: {
+                pageNum: currentPage.value,
+                pageSize: pageSize.value
+            }
+        });
+
+        if (response.data.code === 200) {
+            const data = response.data;
+            imageList.value = data.rows || [];
+            total.value = data.total || 0;
+        } else {
+            EleMessage.error(response.data.msg || '获取库存图片失败');
+            imageList.value = [];
+            total.value = 0;
+        }
+    } catch (error) {
+        console.error('获取库存图片失败:', error);
+        EleMessage.error('获取库存图片失败');
+        imageList.value = [];
+        total.value = 0;
+    } finally {
+        loading.value = false;
+    }
+};
+
+/** 弹窗打开事件 */
+const handleOpen = (isbn) => {
+    if (isbn) {
+        currentIsbn.value = isbn;
+        visible.value = true;
+        currentPage.value = 1;
+        pageSize.value = 24;
+        total.value = 0;
+        imageList.value = [];
+        
+        nextTick(() => {
+            fetchStockImages();
+        });
+    }
+};
+
+/** 监听弹窗关闭,清理数据 */
+watch(visible, (newVal) => {
+    if (!newVal) {
+        currentIsbn.value = '';
+        imageList.value = [];
+        total.value = 0;
+        currentPage.value = 1;
+        loading.value = false;
+    }
+});
+
+defineExpose({
+    handleOpen
+});
+</script>
+
+<style scoped>
+.stock-image-container {
+    min-height: 400px;
+}
+
+.image-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
+    height: 150px;
+    gap: 16px;
+    margin-bottom: 20px;
+}
+
+.image-item {
+    position: relative;
+    cursor: pointer;
+    border-radius: 8px;
+    overflow: hidden;
+    transition: transform 0.2s ease;
+    height: 150px;
+}
+
+.image-item:hover {
+    transform: scale(1.05);
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.stock-image {
+    width: 100%;
+    height: 150px;
+    border-radius: 8px;
+}
+
+.empty-state {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    min-height: 300px;
+}
+
+.loading-state {
+    padding: 20px;
+}
+
+.pagination-container {
+    position: sticky;
+    bottom: 0;
+    background: white;
+    display: flex;
+    justify-content: center;
+    margin-top: 20px;
+    padding: 20px 0;
+    z-index: 10;
+}
+</style>

+ 43 - 4
src/views/code/list/index.vue

@@ -2,7 +2,7 @@
     <ele-page flex-table>
         <code-search @search="reload" />
 
-        <common-table ref="pageRef" :pageConfig="pageConfig" :columns="columns">
+        <common-table ref="pageRef" :pageConfig="pageConfig" :columns="columns" :sortFunction="sortFunction">
             <template #bookInfo="{ row }">
                 <div class="book-info">
                     <div class="book-title" @click="handleClick(row)">{{ row.bookName }}</div>
@@ -47,12 +47,19 @@
                 </div>
             </template>
 
+            <template #stockNum="{ row }">
+                <el-button type="primary" link @click="handleViewStockImages(row)">
+                    {{ row.stockNum || 0 }}
+                </el-button>
+            </template>
+
             <template #action="{ row }">
                 <div>
                     <el-button type="warning" v-permission="'code:activation:out'" link @click="handleViewDetails(row)">
                         出库
                     </el-button>
-                    <el-button type="danger" v-permission="'code:activation:inDetails'" link @click="handleInDetails(row)">
+                    <el-button type="danger" v-permission="'code:activation:inDetails'" link
+                        @click="handleInDetails(row)">
                         入库明细
                     </el-button>
                     <el-button type="success" v-permission="'code:activation:outDetails'" link
@@ -79,6 +86,9 @@
         <!-- 价格编辑弹窗 -->
         <price-edit-modal ref="priceEditModalRef" v-model="priceEditVisible" @success="handlePriceEditSuccess" />
 
+        <!-- 库存图片查看弹窗 -->
+        <stock-image-modal ref="stockImageModalRef" v-model="stockImageVisible" />
+
         <!-- 商品编辑弹窗 -->
         <booksEdit ref="bookEditRef" />
     </ele-page>
@@ -96,6 +106,7 @@ import InDetailModal from './components/in-detail-modal.vue';
 import OutStockModal from './components/out-stock-modal.vue';
 import ActivationOutStockModal from './components/activation-out-stock-modal.vue';
 import PriceEditModal from './components/price-edit-modal.vue';
+import StockImageModal from './components/stock-image-modal.vue';
 import booksEdit from '@/views/data/books/components/books-edit.vue';
 
 defineOptions({ name: 'CodeList' });
@@ -106,6 +117,7 @@ const inDetailVisible = ref(false);
 const outStockVisible = ref(false);
 const activationOutStockVisible = ref(false);
 const priceEditVisible = ref(false);
+const stockImageVisible = ref(false);
 
 /** 弹窗组件引用 */
 const outDetailModalRef = ref(null);
@@ -113,6 +125,7 @@ const inDetailModalRef = ref(null);
 const outStockModalRef = ref(null);
 const activationOutStockModalRef = ref(null);
 const priceEditModalRef = ref(null);
+const stockImageModalRef = ref(null);
 
 //编辑图书
 const bookEditRef = ref(null);
@@ -121,6 +134,21 @@ function handleClick(row) {
     bookEditRef.value?.handleOpen(row);
 }
 
+/** 排序函数 */
+const sortFunction = (orders) => {
+    if (orders && orders.orderByColumn) {
+        // 1-按照库存降序 2-按照库存升序3-按照销量降序4-按照销量升序
+        if (orders.orderByColumn === 'stockNum') {
+            orders.orderField = orders.isAsc === "ascending" ? 2 : 1;
+        } else if (orders.orderByColumn === 'salesNum') {
+            orders.orderField = orders.isAsc === "ascending" ? 4 : 3;
+        }
+    }
+    return orders;
+}
+
+
+
 /** 表格列配置 */
 const columns = reactive([
     {
@@ -171,13 +199,16 @@ const columns = reactive([
         label: '库存',
         prop: 'stockNum',
         align: 'center',
-        width: 120
+        width: 120,
+        slot: 'stockNum',
+        sortable: true,
     },
     {
         label: '销量',
         prop: 'salesNum',
         align: 'center',
-        width: 120
+        width: 120,
+        sortable: true,
     },
     {
         columnKey: 'action',
@@ -239,6 +270,14 @@ function handleOpenActivationModal(data) {
     activationOutStockModalRef.value?.handleOpen(data);
 }
 
+/** 查看库存图片 */
+const handleViewStockImages = (row) => {
+    stockImageVisible.value = true;
+    nextTick(() => {
+        stockImageModalRef.value?.handleOpen(row.isbn);
+    });
+};
+
 /** 编辑价格 */
 function handleEditPrice(row, type) {
     if (row && row.isbn) {