xuhaifeng há 3 meses atrás
pai
commit
78759a536e
2 ficheiros alterados com 503 adições e 30 exclusões
  1. 365 0
      src/components/GoodsSkuDialog/index.vue
  2. 138 30
      src/views/goods/commonGoods/index.vue

+ 365 - 0
src/components/GoodsSkuDialog/index.vue

@@ -0,0 +1,365 @@
+<template>
+    <el-dialog :title="title" :visible.sync="dialogVisible" width="1000px" append-to-body>
+        <div class="app-container">
+            <el-row :gutter="10" class="mb8">
+                <!-- 移除新增按钮 -->
+            </el-row>
+
+            <!-- 列表 -->
+            <el-table v-loading="loading" :data="skuList">
+                <el-table-column type="selection" width="55" align="center" />
+                <el-table-column label="规格编号" align="center" prop="id" />
+                <el-table-column label="规格详情" align="center" width="200" :show-overflow-tooltip="true">
+                    <template slot-scope="scope">
+                        <span v-for="spec in scope.row.specVoList" :key="spec.id">
+                            <p>{{ spec.specName }}:{{ spec.specValue }}</p>
+                        </span>
+                    </template>
+                </el-table-column>
+                <el-table-column label="零售价(元)" align="center" width="150">
+                    <template slot-scope="scope">
+                        <el-input-number 
+                            v-model="scope.row.salePrice" 
+                            :precision="2" 
+                            :step="0.1"
+                            :min="0"
+                            controls-position="right"
+                            size="mini"
+                            @change="handlePriceStockChange(scope.row)" />
+                    </template>
+                </el-table-column>
+                <el-table-column label="批发价(元)" align="center" width="150" v-if="userInfoVO.userType != '02'">
+                    <template slot-scope="scope">
+                        <el-input-number 
+                            v-model="scope.row.wholesalePrice" 
+                            :precision="2" 
+                            :step="0.1"
+                            :min="0"
+                            controls-position="right"
+                            size="mini"
+                            @change="handlePriceStockChange(scope.row)" />
+                    </template>
+                </el-table-column>
+                <el-table-column label="进货价(元)" align="center" width="150">
+                    <template slot-scope="scope">
+                        <el-input-number 
+                            v-model="scope.row.purchasePrice" 
+                            :precision="2" 
+                            :step="0.1"
+                            :min="0"
+                            controls-position="right"
+                            size="mini"
+                            :disabled="userInfoVO.userType != '00'"
+                            @change="handlePriceStockChange(scope.row)" />
+                    </template>
+                </el-table-column>
+                <el-table-column label="库存" align="center" width="150">
+                    <template slot-scope="scope">
+                        <el-input-number 
+                            v-model="scope.row.stock" 
+                            :min="0"
+                            :step="1"
+                            controls-position="right"
+                            size="mini"
+                            :disabled="userInfoVO.userType != '00'"
+                            @change="handlePriceStockChange(scope.row)" />
+                    </template>
+                </el-table-column>
+                <el-table-column label="启用状态" align="center" width="100">
+                    <template slot-scope="scope">
+                        <el-switch
+                            v-model="scope.row.status"
+                            active-value="0"
+                            inactive-value="1"
+                            @change="handleStatusChange(scope.row)">
+                        </el-switch>
+                    </template>
+                </el-table-column>
+                <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+                    <template slot-scope="scope">
+                        <el-button type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['goods:commonGoods:edit']">修改</el-button>
+                    </template>
+                </el-table-column>
+            </el-table>
+
+            <!-- 分页 -->
+            <pagination
+                v-show="total > 0"
+                :total="total"
+                :page.sync="queryParams.pageNum"
+                :limit.sync="queryParams.pageSize"
+                @pagination="getList"
+            />
+        </div>
+
+        <!-- SKU编辑弹窗 -->
+        <el-dialog
+            :title="editTitle"
+            :visible.sync="editDialogVisible"
+            width="50%"
+            append-to-body
+        >
+            <el-form ref="form" :model="form" :rules="rules" label-width="120px" v-if="Object.keys(form).length > 0">
+                <div class="specvo_content">
+                    <div class="list_specvo">
+                        <div class="title">物料规格</div>
+                        <div v-for="(item, index) in specVoList" :key="index">
+                            <el-row :gutter="15">
+                                <el-col :span="10">
+                                    <el-form-item label="规格名称">
+                                        <el-input v-model="item.specName" disabled />
+                                    </el-form-item>
+                                </el-col>
+                                <el-col :span="10">
+                                    <el-form-item label="规格内容">
+                                        <el-input v-model="item.specValue" disabled />
+                                    </el-form-item>
+                                </el-col>
+                            </el-row>
+                        </div>
+                    </div>
+                    <el-row :gutter="20">
+                        <el-col :span="12">
+                            <el-form-item label="零售价(元)" prop="salePrice">
+                                <el-input-number v-model="form.salePrice" placeholder="请输入零售价" :precision="2" :step="0.1" :min="0" :max="9999999" style="width: 100%" />
+                            </el-form-item>
+                        </el-col>
+                        <el-col :span="12">
+                            <el-form-item label="批发价(元)" prop="wholesalePrice" v-if="userInfoVO.userType != '02'">
+                                <el-input-number v-model="form.wholesalePrice" placeholder="请输入批发价" :precision="2" :step="0.1" :min="0" :max="9999999" style="width: 100%" />
+                            </el-form-item>
+                        </el-col>
+                        <el-col :span="12">
+                            <el-form-item label="进货价(元)" prop="purchasePrice">
+                                <el-input-number v-model="form.purchasePrice" placeholder="请输入进货价" :precision="2" :step="0.1" :min="0" :max="9999999" :disabled="userInfoVO.userType != '00'" style="width: 100%" />
+                            </el-form-item>
+                        </el-col>
+                        <el-col :span="12">
+                            <el-form-item label="库存" prop="stock">
+                                <el-input-number v-model="form.stock" placeholder="请输入库存" :precision="0" :step="1" :min="0" :max="9999999" :disabled="userInfoVO.userType != '00'" style="width: 100%" />
+                            </el-form-item>
+                        </el-col>
+                    </el-row>
+                </div>
+            </el-form>
+            <div slot="footer" class="dialog-footer">
+                <el-button type="primary" @click="submitForm">确 定</el-button>
+                <el-button @click="cancelEdit">取 消</el-button>
+            </div>
+        </el-dialog>
+    </el-dialog>
+</template>
+
+<script>
+import { listSku, getSku, delSku, addSku, updateSku, updateGoodsStatus } from '@/api/core/sku'
+
+export default {
+    name: 'GoodsSkuDialog',
+    props: {
+        title: {
+            type: String,
+            default: '规格管理'
+        }
+    },
+    data() {
+        return {
+            dialogVisible: false,
+            editDialogVisible: false,
+            editTitle: '',
+            loading: false,
+            userInfoVO: null,
+            goodsId: null,
+            // 查询参数
+            queryParams: {
+                pageNum: 1,
+                pageSize: 10,
+                goodsId: null
+            },
+            // 总条数
+            total: 0,
+            // SKU列表数据
+            skuList: [],
+            // 表单参数
+            form: {
+                salePrice: 0,
+                wholesalePrice: 0,
+                purchasePrice: 0
+            },
+            // 表单校验
+            rules: {
+                salePrice: [{ required: true, message: '零售价不能为空', trigger: 'blur' }],
+                wholesalePrice: [{ required: true, message: '批发价不能为空', trigger: 'blur' }],
+                purchasePrice: [{ required: true, message: '进货价不能为空', trigger: 'blur' }]
+            },
+            specVoList: [
+                {
+                    specName: '',
+                    specValue: ''
+                }
+            ]
+        }
+    },
+    created() {
+        this.userInfoVO = this.getUserInfo()
+    },
+    methods: {
+        // 显示弹窗
+        show(goodsId) {
+            this.goodsId = goodsId
+            this.queryParams.goodsId = goodsId
+            this.dialogVisible = true
+            this.getList()
+        },
+        // 获取SKU列表
+        async getList() {
+            this.loading = true
+            try {
+                const response = await listSku(this.queryParams)
+                this.skuList = response.rows
+                this.total = response.total
+            } catch (error) {
+                console.error('获取SKU列表失败:', error)
+            } finally {
+                this.loading = false
+            }
+        },
+        // 新增SKU
+        handleAdd() {
+            this.reset()
+            this.editDialogVisible = true
+            this.editTitle = '添加规格'
+        },
+        // 修改SKU
+        handleUpdate(row) {
+            this.reset()
+            getSku(row.id).then(response => {
+                this.form = response.data
+                this.specVoList = this.form.specVoList
+                this.editDialogVisible = true
+                this.editTitle = '修改价格和库存'
+            })
+        },
+        // 删除SKU
+        handleDelete(row) {
+            this.$modal.confirm('是否确认删除该规格?').then(() => {
+                return delSku(row.id)
+            }).then(() => {
+                this.getList()
+                this.$modal.msgSuccess('删除成功')
+            })
+        },
+        // 处理价格或库存变更
+        handlePriceStockChange(row) {
+            updateSku(this.handleUpdateParams(row)).then(() => {
+                this.$modal.msgSuccess('更新成功')
+            }).catch(() => {
+                this.getList() // 更新失败时刷新数据
+            })
+        },
+        // 状态变更
+        handleStatusChange(row) {
+            let text = row.status === '0' ? '启用' : '停用'
+            this.$confirm('确认要' + text + '吗?').then(() => {
+                return updateGoodsStatus(row.id, row.status)
+            }).then(() => {
+                this.$modal.msgSuccess(text + '成功')
+            }).catch(() => {
+                row.status = row.status === '0' ? '1' : '0'
+            })
+        },
+        // 提交表单
+        submitForm() {
+            this.$refs.form.validate(valid => {
+                if (valid) {
+                    updateSku(this.handleUpdateParams(this.form)).then(() => {
+                        this.$modal.msgSuccess('修改成功')
+                        this.editDialogVisible = false
+                        this.getList()
+                    })
+                }
+            })
+        },
+        // 重置表单
+        reset() {
+            this.form = {
+                id: null,
+                sort: null,
+                status: null,
+                salePrice: null,
+                wholesalePrice: null,
+                purchasePrice: null,
+                stock: null,
+                specVoList: []
+            }
+            this.specVoList = []
+            if (this.$refs.form) {
+                this.$refs.form.resetFields()
+            }
+        },
+        // 取消编辑
+        cancelEdit() {
+            this.editDialogVisible = false
+            this.reset()
+        },
+        // 添加规格
+        addSpecList() {
+            if (this.specVoList.length < 2) {
+                this.specVoList.push({
+                    specName: '',
+                    specValue: ''
+                })
+            } else {
+                this.$modal.msgSuccess('最多添加2种规格')
+            }
+        },
+        // 删除规格
+        deleteSpecList(index) {
+            this.specVoList.splice(index, 1)
+        },
+        // 处理更新参数
+        handleUpdateParams(data) {
+            if (this.userInfoVO.userType == '00') {
+                return data
+            }
+            if (this.userInfoVO.userType == '01') {
+                data.purchasePrice = null
+                return data
+            }
+            if (this.userInfoVO.userType == '02') {
+                data.wholesalePrice = null
+                data.purchasePrice = null
+                return data
+            }
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+.specvo_content {
+    padding: 5px 20px 20px;
+    .list_specvo {
+        padding: 0px 0px 10px;
+        margin-bottom: 40px;
+        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
+        border-radius: 5px;
+        .title {
+            color: #606266;
+            padding: 10px 20px;
+            border-bottom: 1px #f4f4f4 solid;
+            font-size: 14px;
+            margin-bottom: 20px;
+        }
+    }
+    .remark {
+        margin-top: 30px;
+        background-color: rgba(255, 228, 74, 0.1);
+        border: 1px dashed #ffe44a;
+        font-size: 14px;
+        color: #e6a23c;
+        width: 100%;
+        padding: 20px;
+        border-radius: 5px;
+    }
+}
+</style> 

+ 138 - 30
src/views/goods/commonGoods/index.vue

@@ -31,48 +31,84 @@
         </el-row>
         <!-- 列表 -->
         <Page uri="/mapi/core/goods/list" :request-params="queryParams" ref="pagination">
-            <el-table-column label="ID" align="center" prop="id" />
-            <el-table-column label="商品编号" align="center" prop="goodsCode" />
-            <el-table-column label="商品名称" align="center" prop="goodsName" />
-            <el-table-column label="图片" align="center" prop="goodsImg" width="100">
+            <el-table-column label="商品信息" align="left" min-width="400">
                 <template slot-scope="scope">
-                    <el-image style="width: 80px; height: 80px" :src="scope.row.goodsImg" :preview-src-list="[scope.row.goodsImg]"> </el-image>
-                </template>
-            </el-table-column>
-            <el-table-column label="品牌" align="center" prop="brandName" />
-            <el-table-column label="商品分类" align="center" prop="goodsCategoryId">
-                <template slot-scope="scope">
-                    <span v-if="scope.row.goodsCategoryId == item.id" v-for="item in categoryList" :key="item.id"> {{ item.categoryName }}</span>
+                    <div class="goods-info">
+                        <el-image 
+                            style="width: 80px; height: 80px; margin-right: 10px;" 
+                            :src="scope.row.goodsImg" 
+                            :preview-src-list="[scope.row.goodsImg]">
+                        </el-image>
+                        <div class="goods-detail">
+                            <div class="goods-name">{{scope.row.goodsName}}</div>
+                            <div class="goods-code">商品编号:{{scope.row.goodsCode}}</div>
+                            <div class="goods-category">
+                                <el-tag size="mini" type="info">{{scope.row.brandName}}</el-tag>
+                                <el-tag size="mini" type="info" v-if="scope.row.goodsCategoryId" style="margin-left: 5px;">
+                                    <span v-if="scope.row.goodsCategoryId == item.id" v-for="item in categoryList" :key="item.id">
+                                        {{item.categoryName}}
+                                    </span>
+                                </el-tag>
+                            </div>
+                        </div>
+                    </div>
                 </template>
             </el-table-column>
-            <el-table-column label="促销标识" align="center" prop="isSell">
+
+            <el-table-column label="价格/库存" align="center" width="200">
                 <template slot-scope="scope">
-                    <el-tag v-if="scope.row.isSell == dict.value" v-for="dict in dict.type.sys_yes_no" :label="dict.value" :key="dict.code"> {{ dict.label }}</el-tag>
+                    <div v-if="scope.row.skuList && scope.row.skuList.length > 0">
+                        <div>销售价:¥{{getMinMaxPrice(scope.row.skuList, 'salePrice')}}</div>
+                        <div>批发价:¥{{getMinMaxPrice(scope.row.skuList, 'wholesalePrice')}}</div>
+                        <div>总库存:{{getTotalStock(scope.row.skuList)}}</div>
+                    </div>
+                    <div v-else>
+                        <div>暂无价格信息</div>
+                    </div>
                 </template>
             </el-table-column>
-            <el-table-column label="规格数量" align="center" prop="goodsItemNum">
+
+            <el-table-column label="规格" align="center" width="120">
                 <template slot-scope="scope">
-                    <el-button type="text" @click="handleSkuUpdate(scope.row)">{{ scope.row.goodsItemNum }}种规格</el-button>
+                    <el-button type="text" @click="openSkuDialog(scope.row)">
+                        <span>{{scope.row.goodsItemNum}}种规格</span>
+                        <i class="el-icon-arrow-right"></i>
+                    </el-button>
                 </template>
             </el-table-column>
-            <el-table-column label="余额支付" align="center" prop="isBalancePay">
+
+            <el-table-column label="支付方式" align="center" width="150">
                 <template slot-scope="scope">
-                    <el-tag v-if="scope.row.isBalancePay == dict.value" v-for="dict in dict.type.sys_yes_no" :label="dict.value" :key="dict.code"> {{ dict.label }}</el-tag>
+                    <div>
+                        <el-tag v-if="scope.row.isBalancePay === 'Y'" size="mini" type="success">余额支付</el-tag>
+                        <el-tag v-else size="mini" type="info">不支持余额</el-tag>
+                    </div>
+                    <div style="margin-top: 5px;">
+                        <el-tag v-if="scope.row.isSell === 'Y'" size="mini" type="warning">促销商品</el-tag>
+                    </div>
                 </template>
             </el-table-column>
-            <!--            <el-table-column label="金额(元)" align="center" prop="cashMoney" />-->
-            <el-table-column label="排序值" align="center" prop="sort" />
-            <el-table-column label="启用状态" align="center" prop="status">
+
+            <el-table-column label="排序/状态" align="center" width="120">
                 <template slot-scope="scope">
-                    <el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
+                    <div>排序:{{scope.row.sort}}</div>
+                    <div style="margin-top: 5px;">
+                        <el-switch
+                            v-model="scope.row.status"
+                            active-value="0"
+                            inactive-value="1"
+                            @change="handleStatusChange(scope.row)">
+                        </el-switch>
+                    </div>
                 </template>
             </el-table-column>
-            <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+
+            <el-table-column label="操作" align="center" width="200" fixed="right">
                 <template slot-scope="scope">
                     <el-button type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['goods:commonGoods:edit']" v-if="userInfoVO.userType == '00'">修改</el-button>
                     <el-button type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['goods:commonGoods:remove']" v-if="userInfoVO.userType == '00'">删除</el-button>
-                    <el-button type="text" icon="el-icon-s-shop" @click="handleSkuUpdate(scope.row)" v-hasPermi="['goods:commonGoods:list']">查看规格</el-button>
-                    <el-button type="text" icon="el-icon-view" v-if="goodsType != 1" @click="gotoComment(scope.row)">查看评论</el-button>
+                    <el-button type="text" icon="el-icon-s-shop" @click="openSkuDialog(scope.row)" v-hasPermi="['goods:commonGoods:list']">规格</el-button>
+                    <el-button type="text" icon="el-icon-view" v-if="goodsType != 1" @click="gotoComment(scope.row)">评论</el-button>
                 </template>
             </el-table-column>
         </Page>
@@ -175,7 +211,7 @@
                                                 class="input-new-tag"
                                                 v-if="spec.inputVisible"
                                                 v-model="spec.inputValue"
-                                                :ref="'saveTagInput' + index"
+                                                ref="tagInput"
                                                 size="small"
                                                 @keyup.enter.native="handleInputConfirm(spec, index)"
                                                 @blur="handleInputConfirm(spec, index)">
@@ -281,6 +317,8 @@
                 <el-button @click="cancel">取 消</el-button>
             </div>
         </el-dialog>
+        <!-- 添加规格弹窗组件 -->
+        <goods-sku-dialog ref="skuDialog"></goods-sku-dialog>
     </div>
 </template>
 
@@ -289,9 +327,13 @@ import { listGoods, getGoods, delGoods, addGoods, updateGoods, updateGoodsStatus
 import { listCategory } from '@/api/core/category'
 import { listBrand } from '@/api/core/brand'
 import { status } from 'nprogress'
+import GoodsSkuDialog from '@/components/GoodsSkuDialog'
 
 export default {
     name: 'appGoods',
+    components: {
+        GoodsSkuDialog
+    },
     dicts: ['goods_type', 'sys_yes_no'],
     data() {
         return {
@@ -643,7 +685,10 @@ export default {
         },
         /** 设置物料 */
         handleSkuUpdate(row) {
-            this.$tab.openPage(row.goodsName + '的物料', '/goods/setSku/' + row.id)
+            this.openSkuDialog(row)
+        },
+        openSkuDialog(row) {
+            this.$refs.skuDialog.show(row.id)
         },
         handleRemove(file, fileList) {
             console.log(fileList)
@@ -741,10 +786,28 @@ export default {
             })
         },
         showInput(spec, index) {
-            spec.inputVisible = true
-            this.$nextTick(_ => {
-                this.$refs['saveTagInput' + index][0].$refs.input.focus()
-            })
+            // 先重置其他所有输入框的状态
+            this.form.specList.forEach(item => {
+                this.$set(item, 'inputVisible', false);
+            });
+            
+            // 设置当前输入框可见
+            this.$set(spec, 'inputVisible', true);
+            
+            // 强制更新视图
+            this.$forceUpdate();
+            
+            // 等待 DOM 更新后设置焦点
+            this.$nextTick(() => {
+                if (this.$refs.tagInput && this.$refs.tagInput.length > 0) {
+                    const inputs = this.$refs.tagInput;
+                    // 找到最后一个输入框(应该是刚刚显示的那个)
+                    const input = Array.isArray(inputs) ? inputs[inputs.length - 1] : inputs;
+                    if (input && input.$el.querySelector('input')) {
+                        input.$el.querySelector('input').focus();
+                    }
+                }
+            });
         },
         handleInputConfirm(spec, index) {
             let inputValue = spec.inputValue
@@ -813,6 +876,18 @@ export default {
                 specValue: '',
                 image: ''
             })
+        },
+        getMinMaxPrice(skuList, priceType) {
+            if (!skuList || skuList.length === 0) return '0.00';
+            const prices = skuList.map(sku => sku[priceType] || 0);
+            const min = Math.min(...prices);
+            const max = Math.max(...prices);
+            return min === max ? min.toFixed(2) : `${min.toFixed(2)} ~ ${max.toFixed(2)}`;
+        },
+        
+        getTotalStock(skuList) {
+            if (!skuList || skuList.length === 0) return 0;
+            return skuList.reduce((total, sku) => total + (sku.stock || 0), 0);
         }
     }
 }
@@ -842,4 +917,37 @@ export default {
     margin-left: 10px;
     vertical-align: bottom;
 }
+
+.goods-info {
+    display: flex;
+    align-items: flex-start;
+    padding: 10px 0;
+}
+
+.goods-detail {
+    flex: 1;
+    overflow: hidden;
+}
+
+.goods-name {
+    font-size: 14px;
+    font-weight: bold;
+    color: #303133;
+    margin-bottom: 5px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    display: -webkit-box;
+    -webkit-line-clamp: 2;
+    -webkit-box-orient: vertical;
+}
+
+.goods-code {
+    font-size: 12px;
+    color: #909399;
+    margin-bottom: 5px;
+}
+
+.goods-category {
+    margin-top: 5px;
+}
 </style>