# 一、修改商品其他信息

# 1. 商品列表展示其他信息

app/controller/admin/goods.js

    // 商品列表页面
    async index() {
        const { ctx, app } = this;
        //分页:可以提炼成一个公共方法page(模型名称,where条件,其他参数options)
        let keyword = ctx.query.keyword || '';
        let data = await ctx.page('Goods',{
            name:{
                [this.app.Sequelize.Op.like]: '%' + keyword + '%',
            }
        },{
            include:[{
                model:app.model.GoodsClass,
                attributes:['id','name'],
            }],
        });
        // let data = await ctx.service.goodsClass.datalist({ limit: 10000 });
        // console.log('数据', data);
        // ctx.body = data;
        // return;
        // data = data.rules;
        //渲染公共模版
        await ctx.renderTemplate({
            title: '商品列表',//现在网页title,面包屑导航title,页面标题
            data,
            tempType: 'table', //模板类型:table表格模板 ,form表单模板
            table: {
                //表格上方按钮,没有不要填buttons
                buttons: [
                    {
                        url: '/shop/admin/goods-/create',//新增路径
                        desc: '创建商品',//新增 //按钮名称
                        // icon: 'fa fa-plus fa-lg',//按钮图标
                    },
                    // {
                    //     url: '/shop/admin/goods/create',//新增路径
                    //     desc: '上传商品',//新增 //按钮名称
                    //     // icon: 'fa fa-plus fa-lg',//按钮图标
                    // }
                ],
                //表头
                columns: [
                    {
                        title: '商品分类和商品名称等',
                        // key: 'name',
                        class: 'text-left',//可选
                        width:150,
                        render(item) { 
                            // console.log('每个item',item);
                            return `<div style="font-size:12px;">
                                 <p style="color:#999000;"><span>商品分类:</span><span>${item.goods_class.name}</span></p>
                                 <div style="display:flex;">
                                     <img src="${item.cover}" style="width:50px;height:50px;margin-right:10px;">
                                     <p style="width:90px;display:flex;
                                     flex-wrap:wrap;white-space:wrap;"><span>${item.name}</span></p>
                                 </div>
                            </div>`;
                        }
                    },
                    {
                        title: '价格相关',
                        // key: 'name',
                        class: 'text-left',//可选
                        render(item) { 
                            // console.log('每个item',item);
                            return `<div style="font-size:12px;">
                                 <p style="color:#999000;"><span>起售价(最低售价,多少元起):</span>
                                 <span>${item.min_price?item.min_price:'暂无'}</span></p>
                                 <p style="color:#999000;"><span>历史最低价:</span>
                                 <span>${item.history_min_price?item.history_min_price:'暂无'}</span></p>
                                 <p style="color:#999000;"><span>券后价:</span>
                                 <span>${item.coupon_price?item.coupon_price:'暂无'}</span></p>
                                 <p style="color:#999000;"><span>折后价:</span>
                                 <span>${item.discount_price?item.discount_price:'暂无'}</span></p>
                                 <p style="color:#999000;"><span>秒杀价:</span>
                                 <span>${item.spike_price?item.spike_price:'暂无'}</span></p>
                                 <p style="color:#999000;"><span>其它设置价(如:首件价,新客价,30天低价等等):</span>
                                 <span>${item.other_price?item.other_price:'暂无'}</span></p>
                                 <p><a href="/shop/admin/goods-/${item.id}/editPrice"
                                 style="color:green;">修改商品价格相关信息</a></p>
                            </div>`;
                        }
                    },
                    {
                        title: '库存相关',
                        // key: 'name',
                        class: 'text-left',//可选
                        render(item) { 
                            // console.log('每个item',item);
                            return `<div style="font-size:12px;">
                                 <p style="color:#999000;"><span>单位(默认:件):</span>
                                 <span>${item.unit?item.unit:'件'}</span></p>
                                 <p style="color:#999000;"><span>库存:</span>
                                 <span>${item.stock?item.stock:'暂无'}</span></p>
                                 <p style="color:#999000;"><span>库存预警:</span>
                                 <span>${item.min_stock?item.min_stock:'暂无'}</span></p>
                                 <p><a href="/shop/admin/goods-/${item.id}/editStock"
                                 style="color:green;">修改商品库存相关信息</a></p>
                            </div>`;
                        }
                    },
                    {
                        title: '库存是否显示',
                        key: 'stock_display',
                        // width: 200,//可选
                        class: 'text-left',//可选
                        hidekeyData: true,//是否隐藏key对应的数据
                        render(item) {
                            // console.log('可用状态里面每个item', item);
                            let arr = [
                                { value: 1, name: '显示' },
                                { value: 0, name: '隐藏' },
                            ];
                            let str = `<div class="btn-group btn-group-${item.id}">`;
                            for (let i = 0; i < arr.length; i++) {
                                str += `<button type="button" class="btn btn-light" data="${item.stock_display}"
                                value="${arr[i].value}"
                                @click="changeBtnStatus('stock_display','btn-group-${item.id}',${arr[i].value},${i},${item.id},'/shop/admin/goods-','Goods')">${arr[i].name}</button>`;
                            }
                            str += `</div>`;
                            return str;
                        }
                    },
                    {
                        title: '统计相关',
                        // key: 'name',
                        class: 'text-left',//可选
                        render(item) { 
                            // console.log('每个item',item);
                            return `<div style="font-size:12px;">
                                 <p style="color:#999000;"><span>商品评分:</span>
                                 <span>${item.rating?item.rating:5.0}</span></p>
                                 <p style="color:#999000;"><span>总销量:</span>
                                 <span>${item.sale_count?item.sale_count:'暂无'}</span></p>
                                 <p style="color:#999000;"><span>商品评论数量:</span>
                                 <span>${item.review_count?item.review_count:'暂无'}</span></p>
                                 <p style="color:#999000;"><span>商品收藏量:</span>
                                 <span>${item.love_count?item.love_count:'暂无'}</span></p>
                                 <p style="color:#999000;"><span>商品推荐量:</span>
                                 <span>${item.recommend_count?item.recommend_count:'暂无'}</span></p>
                                 <p><a href="/shop/admin/goods-/${item.id}/editTotalinfo"
                                 style="color:green;">修改商品统计相关信息</a></p>
                            </div>`;
                        }
                    },
                    {
                        title: '商品标签',
                        // key: 'goods_tags',
                        class: 'text-left',//可选
                        render(item) { 
                            // console.log('每个item',item);
                            let str = ``;
                            if(item.goods_tags){
                                let arr = item.goods_tags.split(/[,,]/);
                                for(let i=0;i<arr.length;i++){
                                    str += `<span style="border:1px solid #909090;padding:2px;margin:2px;">${arr[i]}</span>`;
                                }
                            }
                            return `<div style="font-size:12px;width:100px;display:flex;flex-wrap:wrap;">
                                 ${str}
                                 <p style="margin-top:10px;"><a href="/shop/admin/goods-/${item.id}/editTags"
                                 style="color:green;">修改商品标签</a></p>
                            </div>`;
                        }
                    },
                    {
                        title: '审核状态',
                        key: 'ischeck',
                        // width: 200,//可选
                        class: 'text-left',//可选
                        hidekeyData: true,//是否隐藏key对应的数据
                        render(item) {
                            // console.log('可用状态里面每个item', item);
                            let arr = [
                                { value: 1, name: '通过' },
                                { value: 0, name: '审核中' },
                                { value: 2, name: '拒绝' },
                            ];
                            let str = `<div class="btn-group btn-group-${item.id}">`;
                            for (let i = 0; i < arr.length; i++) {
                                str += `<button type="button" class="btn btn-light" data="${item.ischeck}"
                                value="${arr[i].value}"
                                @click="changeBtnStatus('ischeck','btn-group-${item.id}',${arr[i].value},${i},${item.id},'/shop/admin/goods-','Goods')">${arr[i].name}</button>`;
                            }
                            str += `</div>`;
                            return str;
                        }
                    },
                    {
                        title: '商品状态',
                        key: 'goods_status',
                        // width: 200,//可选
                        class: 'text-left',//可选
                        hidekeyData: true,//是否隐藏key对应的数据
                        render(item) {
                            // console.log('可用状态里面每个item', item);
                            let arr = [
                                { value: 1, name: '上架' },
                                { value: 0, name: '仓库' },
                                { value: 2, name: '下架' },
                                { value: 3, name: '违规下架' },
                                { value: 4, name: '回收站' },
                            ];
                            let str = `<div class="btn-group btn-group-${item.id}">`;
                            for (let i = 0; i < arr.length; i++) {
                                str += `<button type="button" class="btn btn-light" data="${item.goods_status}"
                                value="${arr[i].value}"
                                @click="changeBtnStatus('goods_status','btn-group-${item.id}',${arr[i].value},${i},${item.id},'/shop/admin/goods-','Goods')">${arr[i].name}</button>`;
                            }
                            str += `</div>`;
                            return str;
                        }
                    },
                    // {
                    //     title: '分类下的商品',
                    //     // key: 'name',
                    //     class: 'text-left',//可选
                    //     render(item) { //树形数据
                    //         // console.log('每个item',item);
                    //         // if (item.level) {
                    //         //     let w = item.level * 40;
                    //         //     return `<span style="display:inline-block;width:${w}px"></span>`;
                    //         // }
                    //         return `<a href="/shop/admin/imageclass/${item.id}/imgList">${item.images.length}张</a>`;
                    //     }
                    // },
                    // {
                    //     title: '是否是导航栏栏目',
                    //     key: 'isnav',
                    //     width: 200,//可选
                    //     class: 'text-center',//可选
                    //     hidekeyData: true,//是否隐藏key对应的数据
                    //     render(item) {
                    //         console.log('可用状态里面每个item', item);
                    //         let arr = [
                    //             { value: 1, name: '是' },
                    //             { value: 0, name: '否' },
                    //         ];
                    //         let str = `<div class="btn-group btn-group-${item.id}">`;
                    //         for (let i = 0; i < arr.length; i++) {
                    //             str += `<button type="button" class="btn btn-light" data="${item.isnav}"
                    //             value="${arr[i].value}"
                    //             @click="changeBtnStatus('isnav','btn-group-${item.id}',${arr[i].value},${i},${item.id},'category','Category')">${arr[i].name}</button>`;
                    //         }
                    //         str += `</div>`;
                    //         return str;
                    //     }
                    // },
                    {
                        title: '排序',
                        key: 'order',
                        class: 'text-center',//可选
                    },
                    {
                        title: '状态',
                        key: 'status',
                        width: 200,//可选
                        class: 'text-center',//可选
                        hidekeyData: true,//是否隐藏key对应的数据
                        render(item) {
                            // console.log('可用状态里面每个item', item);
                            let arr = [
                                { value: 1, name: '可用' },
                                { value: 0, name: '不可用' },
                            ];
                            let str = `<div class="btn-group btn-group-${item.id}">`;
                            for (let i = 0; i < arr.length; i++) {
                                str += `<button type="button" class="btn btn-light" data="${item.status}"
                                value="${arr[i].value}"
                                @click="changeBtnStatus('status','btn-group-${item.id}',${arr[i].value},${i},${item.id},'/shop/admin/goods-','Goods')">${arr[i].name}</button>`;
                            }
                            str += `</div>`;
                            return str;
                        }
                    },
                    {
                        title: '操作',
                        class: 'text-right',//可选
                        action: {
                            //修改
                            edit: function (id) {
                                return `/shop/admin/goods-/edit/${id}`;
                            },
                            //删除
                            delete: function (id) {
                                return `/shop/admin/goods-/${id}/delete`;
                            }
                        }
                    },
                ],
            },
        });
    }

# 2. 解决商品列表数据过宽,不能滑动查看数据的问题

# 1. 主模版隐藏了滚动条,调整设置

app/view/admin/layout/main_app.html 改成电脑端出现滚动条,手机端隐藏滚动条

<style type="text/css">
    @media (max-width: 768px) {
        ::-webkit-scrollbar {
            display: none;  
            width: 2px !important;  
            height: 2px !important;  
            -webkit-appearance: none;  
            background: transparent;  
        }
    }
</style>

# 2. 如果不想滚动条查看,想通过鼠标查看列表数据,代码如下

app/view/admin/layout/main_app.html


<style>
	/* 隐藏默认滚动条(可选) */
	.table-responsive {
		overflow-x: auto;
		-webkit-overflow-scrolling: touch; /* 启用平滑滚动 */
		scrollbar-width: none; /* Firefox */
	}
	.table-responsive::-webkit-scrollbar {
		display: none; /* Chrome/Safari */
	}
	
	/* 添加拖动光标提示 */
	.table-responsive.dragging {
		cursor: grab;
		user-select: none;
	}
</style>
	
<script>
	document.addEventListener('DOMContentLoaded', function() {
		const slider = document.querySelector('.table-responsive');
		let isDown = false;
		let startX;
		let scrollLeft;
	
		slider.addEventListener('mousedown', (e) => {
			isDown = true;
			slider.classList.add('dragging');
			startX = e.pageX - slider.offsetLeft;
			scrollLeft = slider.scrollLeft;
		});
	
		slider.addEventListener('mouseleave', () => {
			isDown = false;
			slider.classList.remove('dragging');
		});
	
		slider.addEventListener('mouseup', () => {
			isDown = false;
			slider.classList.remove('dragging');
		});
	
		slider.addEventListener('mousemove', (e) => {
			if (!isDown) return;
			e.preventDefault();
			const x = e.pageX - slider.offsetLeft;
			const walk = (x - startX) * 2; // 调整滚动速度
			slider.scrollLeft = scrollLeft - walk;
		});
		// 添加触摸事件支持
		// let touchStartX;
		// let touchScrollLeft;

		// slider.addEventListener('touchstart', (e) => {
		// 	touchStartX = e.touches[0].pageX;
		// 	touchScrollLeft = slider.scrollLeft;
		// });

		// slider.addEventListener('touchmove', (e) => {
		// 	e.preventDefault();
		// 	const x = e.touches[0].pageX;
		// 	const walk = (x - touchStartX) * 2;
		// 	slider.scrollLeft = touchScrollLeft - walk;
		// });
	});
</script>


# 3. 总路由

app/router/admin/shop.js

module.exports = app => {
    ...
    //修改商品标签相关界面和数据
    router.get('/shop/admin/goods-/:id/editTags', controller.admin.goods.editTags);
    router.post('/shop/admin/goods-/:id/saveTags', controller.admin.goods.saveTags);
    //修改商品统计相关界面和数据
    router.get('/shop/admin/goods-/:id/editTotalinfo', controller.admin.goods.editTotalinfo);
    router.post('/shop/admin/goods-/:id/saveTotalinfo', controller.admin.goods.saveTotalinfo);
    //修改商品库存相关界面和数据
    router.get('/shop/admin/goods-/:id/editStock', controller.admin.goods.editStock);
    router.post('/shop/admin/goods-/:id/saveStock', controller.admin.goods.saveStock);
    //修改商品价格相关
    router.get('/shop/admin/goods-/:id/editPrice', controller.admin.goods.editPrice);
    router.post('/shop/admin/goods-/:id/savePrice', controller.admin.goods.savePrice);
    //删除商品功能
    router.post('/shop/admin/goods-/:id/delete', controller.admin.goods.deleteAPI);
    router.get('/shop/admin/goods-/:id/delete', controller.admin.goods.delete);
    //修改商品状态功能
    router.post('/shop/admin/goods-/:id/update_status',controller.admin.goods.updateStatus);
    //修改商品界面
    router.get('/shop/admin/goods-/edit/:id', controller.admin.goods.edit);
    //批量删除商品
    router.post('/shop/admin/goods-/deleteAll', controller.admin.goods.deleteAll);
    //修改商品数据功能
    router.post('/shop/admin/goods-/:id', controller.admin.goods.update);
    // 创建商品界面
    router.get('/shop/admin/goods-/create', controller.admin.goods.create);
    //创建商品提交数据
    router.post('/shop/admin/goods-', controller.admin.goods.save);
    //商品列表页面
    router.get('/shop/admin/goods-/:page', controller.admin.goods.indexAPI);
    router.get('/shop/admin/goods-', controller.admin.goods.index);
    
};

# 4. 修改商品价格相关

app/controller/admin/goods.js

    //修改商品价格相关界面
    async editPrice() {
        const { ctx, app } = this;
        const id = ctx.params.id;
        let currentdata = await app.model.Goods.findOne({
            where: {
                id
            }
        });
        if (!currentdata) {
            return ctx.apiFail('该商品不存在');
        }
        currentdata = JSON.parse(JSON.stringify(currentdata));
        // console.log('当前商品分类数据', currentdata);
        // return;

        // 渲染模版前先拿到所有分类
        // let data = await ctx.service.goodsClass.dropdown_goodsclass_list();
        // console.log('下拉框显示的所有分类', JSON.stringify(data));
        // return;

        //渲染公共模版
        await ctx.renderTemplate({
            id,
            title: '修改商品:' + currentdata.name,//现在网页title,面包屑导航title,页面标题
            tempType: 'form', //模板类型:table表格模板 ,form表单模板
            form: {
                //修改提交地址
                action: '/shop/admin/goods-/' + id + '/savePrice',
                //  字段
                fields: [
                    // {
                    //     label: '放在哪个商品分类里面',
                    //     type: 'dropdown', //下拉框
                    //     name: 'goods_class_id',
                    //     default: JSON.stringify(data),
                    //     placeholder: '不调整(如需调整请选择)',
                    // },
                    {
                        label: '起售价(最低售价,多少元起)',
                        type: 'number',
                        name: 'min_price',
                        placeholder: '请输入起售价(最低售价,多少元起),选填',
                        // default:'默认值测试', //新增时候默认值,可选
                    },
                    {
                        label: '历史最低价',
                        type: 'number',
                        name: 'history_min_price',
                        placeholder: '请输入历史最低价,选填',
                        // default:'默认值测试', //新增时候默认值,可选
                    },
                    {
                        label: '券后价',
                        type: 'number',
                        name: 'coupon_price',
                        placeholder: '请输入券后价,选填',
                        // default:'默认值测试', //新增时候默认值,可选
                    },
                    {
                        label: '折后价',
                        type: 'number',
                        name: 'discount_price',
                        placeholder: '请输入折后价,选填',
                        // default:'默认值测试', //新增时候默认值,可选
                    },
                    {
                        label: '秒杀价',
                        type: 'number',
                        name: 'spike_price',
                        placeholder: '请输入秒杀价,选填',
                        // default:'默认值测试', //新增时候默认值,可选
                    },
                    {
                        label: '其它设置价(如:首件价,新客价,30天低价等等)',
                        type: 'number',
                        name: 'other_price',
                        placeholder: '请输入其它设置价(如:首件价,新客价,30天低价等等),选填',
                        // default:'默认值测试', //新增时候默认值,可选
                    },
                ],
                //修改内容默认值
                data:currentdata,
            },
            //修改成功之后跳转到哪个页面
            successUrl: '/shop/admin/goods-',
        });
    }
    //修改商品价格相关功能
    async savePrice() {
        const { ctx, app } = this;
        //1.参数验证
        this.ctx.validate({
            id: {
                type: 'int',
                required: true,
                desc: '商品id'
            },
            min_price: {
                type: 'float',
                required: false,
                // defValue: '',
                desc: '起售价(最低售价,多少元起)'
            },
            history_min_price: {
                type: 'float',
                required: false,
                // defValue: '',
                desc: '历史最低价'
            },
            coupon_price: {
                type: 'float',
                required: false,
                // defValue: '',
                desc: '券后价'
            },
            discount_price: {
                type: 'float',
                required: false,
                // defValue: '',
                desc: '折后价'
            },
            spike_price: {
                type: 'float',
                required: false,
                // defValue: '',
                desc: '秒杀价'
            },
            other_price: {
                type: 'float',
                required: false,
                // defValue: '',
                desc: '其它设置价(如:首件价,新客价,30天低价等等)'
            }
            
        });

        // 参数
        const id = ctx.params.id;
        const {  min_price, history_min_price, coupon_price, discount_price,spike_price, other_price } = ctx.request.body;
        // 先看一下是否存在
        // let dataclass = await app.model.GoodsClass.findOne({ where: { id:goods_class_id } });
        // if (!dataclass) {
        //     return ctx.apiFail('该商品分类记录不存在');
        // }
        let data = await app.model.Goods.findOne({ where: { id } });
        if (!data) {
            return ctx.apiFail('该商品不存在');
        }
        //存在
        // const Op = this.app.Sequelize.Op;//拿Op,固定写法
        // //只要不放在同一分类级别下还是可以的
        // const hasdata = await app.model.GoodsClass.findOne({
        //     where: {
        //         name,
        //         id: {
        //             [Op.ne]: id
        //         }
        //     }
        // });
        // if (hasdata && hasdata.pid == pid) {
        //     return ctx.apiFail('同一个分类下不能有相同的商品分类名称');
        // }
        // 修改数据
        data.min_price = min_price;
        data.history_min_price = history_min_price;
        data.coupon_price = coupon_price;
        data.discount_price = discount_price;
        data.spike_price = spike_price;
        data.other_price = other_price;
        await data.save();
        // 给一个反馈
        ctx.apiSuccess('修改成功');
    }

# 5. 修改商品库存、统计、标签相关信息

app/controller/admin/goods.js

    //修改商品库存相关界面
    async editStock() {
        const { ctx, app } = this;
        const id = ctx.params.id;
        let currentdata = await app.model.Goods.findOne({
            where: {
                id
            }
        });
        if (!currentdata) {
            return ctx.apiFail('该商品不存在');
        }
        currentdata = JSON.parse(JSON.stringify(currentdata));
        // console.log('当前商品分类数据', currentdata);
        // return;

        // 渲染模版前先拿到所有分类
        // let data = await ctx.service.goodsClass.dropdown_goodsclass_list();
        // console.log('下拉框显示的所有分类', JSON.stringify(data));
        // return;

        //渲染公共模版
        await ctx.renderTemplate({
            id,
            title: '修改商品:' + currentdata.name,//现在网页title,面包屑导航title,页面标题
            tempType: 'form', //模板类型:table表格模板 ,form表单模板
            form: {
                //修改提交地址
                action: '/shop/admin/goods-/' + id + '/saveStock',
                //  字段
                fields: [
                    // {
                    //     label: '放在哪个商品分类里面',
                    //     type: 'dropdown', //下拉框
                    //     name: 'goods_class_id',
                    //     default: JSON.stringify(data),
                    //     placeholder: '不调整(如需调整请选择)',
                    // },
                    {
                        label: '单位(默认:件)',
                        type: 'text',
                        name: 'unit',
                        placeholder: '请输入单位,选填',
                        default:'件', //新增时候默认值,可选
                    },
                    {
                        label: '库存',
                        type: 'number',
                        name: 'stock',
                        placeholder: '请输入库存,选填',
                        // default:'默认值测试', //新增时候默认值,可选
                    },
                    {
                        label: '库存预警',
                        type: 'number',
                        name: 'min_stock',
                        placeholder: '请输入库存预警[低于多少库存通知商家],选填',
                        // default:'默认值测试', //新增时候默认值,可选
                    },
                    
                ],
                //修改内容默认值
                data:currentdata,
            },
            //修改成功之后跳转到哪个页面
            successUrl: '/shop/admin/goods-',
        });
    }
    //修改商品库存相关数据
    async saveStock() {
        const { ctx, app } = this;
        //1.参数验证
        this.ctx.validate({
            id: {
                type: 'int',
                required: true,
                desc: '商品id'
            },
            unit: {
                type: 'string',
                required: false,
                defValue: '件',
                desc: '单位'
            },
            stock: {
                type: 'int',
                required: false,
                defValue: 0,
                desc: '库存'
            },
            min_stock: {
                type: 'int',
                required: false,
                defValue: 0,
                desc: '库存预警'
            },
            
        });

        // 参数
        const id = ctx.params.id;
        const {  unit, stock, min_stock} = ctx.request.body;
        // 先看一下是否存在
        // let dataclass = await app.model.GoodsClass.findOne({ where: { id:goods_class_id } });
        // if (!dataclass) {
        //     return ctx.apiFail('该商品分类记录不存在');
        // }
        let data = await app.model.Goods.findOne({ where: { id } });
        if (!data) {
            return ctx.apiFail('该商品不存在');
        }
        //存在
        // const Op = this.app.Sequelize.Op;//拿Op,固定写法
        // //只要不放在同一分类级别下还是可以的
        // const hasdata = await app.model.GoodsClass.findOne({
        //     where: {
        //         name,
        //         id: {
        //             [Op.ne]: id
        //         }
        //     }
        // });
        // if (hasdata && hasdata.pid == pid) {
        //     return ctx.apiFail('同一个分类下不能有相同的商品分类名称');
        // }
        // 修改数据
        data.unit = unit;
        data.stock = stock;
        data.min_stock = min_stock;
        
        await data.save();
        // 给一个反馈
        ctx.apiSuccess('修改成功');
    }

    //修改商品统计相关界面
    async editTotalinfo() {
        const { ctx, app } = this;
        const id = ctx.params.id;
        let currentdata = await app.model.Goods.findOne({
            where: {
                id
            }
        });
        if (!currentdata) {
            return ctx.apiFail('该商品不存在');
        }
        currentdata = JSON.parse(JSON.stringify(currentdata));
        // console.log('当前商品分类数据', currentdata);
        // return;

        // 渲染模版前先拿到所有分类
        // let data = await ctx.service.goodsClass.dropdown_goodsclass_list();
        // console.log('下拉框显示的所有分类', JSON.stringify(data));
        // return;

        //渲染公共模版
        await ctx.renderTemplate({
            id,
            title: '修改商品:' + currentdata.name,//现在网页title,面包屑导航title,页面标题
            tempType: 'form', //模板类型:table表格模板 ,form表单模板
            form: {
                //修改提交地址
                action: '/shop/admin/goods-/' + id + '/saveTotalinfo',
                //  字段
                fields: [
                    // {
                    //     label: '放在哪个商品分类里面',
                    //     type: 'dropdown', //下拉框
                    //     name: 'goods_class_id',
                    //     default: JSON.stringify(data),
                    //     placeholder: '不调整(如需调整请选择)',
                    // },
                    {
                        label: '商品评分',
                        type: 'number',
                        name: 'rating',
                        placeholder: '请输入商品评分,选填',
                        default:'5.0', //新增时候默认值,可选
                    },
                    {
                        label: '总销量',
                        type: 'number',
                        name: 'sale_count',
                        placeholder: '请输入总销量,选填',
                        // default:'默认值测试', //新增时候默认值,可选
                    },
                    {
                        label: '商品评论数量',
                        type: 'number',
                        name: 'review_count',
                        placeholder: '请输入商品评论数量,选填',
                        // default:'默认值测试', //新增时候默认值,可选
                    },
                    {
                        label: '商品收藏量',
                        type: 'number',
                        name: 'love_count',
                        placeholder: '请输入商品收藏量,选填',
                        // default:'默认值测试', //新增时候默认值,可选
                    },
                    {
                        label: '商品推荐量',
                        type: 'number',
                        name: 'recommend_count',
                        placeholder: '请输入商品推荐量,选填',
                        // default:'默认值测试', //新增时候默认值,可选
                    },
                    
                ],
                //修改内容默认值
                data:currentdata,
            },
            //修改成功之后跳转到哪个页面
            successUrl: '/shop/admin/goods-',
        });
    }
    //修改商品统计相关数据
    async saveTotalinfo() {
        const { ctx, app } = this;
        //1.参数验证
        this.ctx.validate({
            id: {
                type: 'int',
                required: true,
                desc: '商品id'
            },
            rating: {
                type: 'float',
                required: false,
                defValue: '5.0',
                desc: '商品评分'
            },
            sale_count: {
                type: 'int',
                required: false,
                defValue: 0,
                desc: '总销量'
            },
            review_count: {
                type: 'int',
                required: false,
                defValue: 0,
                desc: '商品评论数量'
            },
            love_count: {
                type: 'int',
                required: false,
                defValue: 0,
                desc: '商品收藏量'
            },
            recommend_count: {
                type: 'int',
                required: false,
                defValue: 0,
                desc: '商品推荐量'
            },
        });

        // 参数
        const id = ctx.params.id;
        const {  rating, sale_count, review_count, love_count, recommend_count } = ctx.request.body;
        // 先看一下是否存在
        // let dataclass = await app.model.GoodsClass.findOne({ where: { id:goods_class_id } });
        // if (!dataclass) {
        //     return ctx.apiFail('该商品分类记录不存在');
        // }
        let data = await app.model.Goods.findOne({ where: { id } });
        if (!data) {
            return ctx.apiFail('该商品不存在');
        }
        //存在
        // const Op = this.app.Sequelize.Op;//拿Op,固定写法
        // //只要不放在同一分类级别下还是可以的
        // const hasdata = await app.model.GoodsClass.findOne({
        //     where: {
        //         name,
        //         id: {
        //             [Op.ne]: id
        //         }
        //     }
        // });
        // if (hasdata && hasdata.pid == pid) {
        //     return ctx.apiFail('同一个分类下不能有相同的商品分类名称');
        // }
        // 修改数据
        data.rating = rating;
        data.sale_count = sale_count;
        data.review_count = review_count;
        data.love_count = love_count;
        data.recommend_count = recommend_count;
        
        await data.save();
        // 给一个反馈
        ctx.apiSuccess('修改成功');
    }

    //修改商品标签相关界面
    async editTags() {
        const { ctx, app } = this;
        const id = ctx.params.id;
        let currentdata = await app.model.Goods.findOne({
            where: {
                id
            }
        });
        if (!currentdata) {
            return ctx.apiFail('该商品不存在');
        }
        currentdata = JSON.parse(JSON.stringify(currentdata));
        // console.log('当前商品分类数据', currentdata);
        // return;

        // 渲染模版前先拿到所有分类
        // let data = await ctx.service.goodsClass.dropdown_goodsclass_list();
        // console.log('下拉框显示的所有分类', JSON.stringify(data));
        // return;

        //渲染公共模版
        await ctx.renderTemplate({
            id,
            title: '修改商品:' + currentdata.name,//现在网页title,面包屑导航title,页面标题
            tempType: 'form', //模板类型:table表格模板 ,form表单模板
            form: {
                //修改提交地址
                action: '/shop/admin/goods-/' + id + '/saveTags',
                //  字段
                fields: [
                    // {
                    //     label: '放在哪个商品分类里面',
                    //     type: 'dropdown', //下拉框
                    //     name: 'goods_class_id',
                    //     default: JSON.stringify(data),
                    //     placeholder: '不调整(如需调整请选择)',
                    // },
                    {
                        label: '商品标签',
                        type: 'textarea',
                        name: 'goods_tags',
                        placeholder: '请输入商品标签,选填,如:近期销量飙升,30天上新,近7天同类热卖,24小时内发货',
                        default:'', //新增时候默认值,可选
                    },
                ],
                //修改内容默认值
                data:currentdata,
            },
            //修改成功之后跳转到哪个页面
            successUrl: '/shop/admin/goods-',
        });
    }
    //修改商品标签相关数据
    async saveTags() {
        const { ctx, app } = this;
        //1.参数验证
        this.ctx.validate({
            id: {
                type: 'int',
                required: true,
                desc: '商品id'
            },
            goods_tags: {
                type: 'string',
                required: false,
                defValue: '',
                desc: '商品标签',
                range:{
                    min:2,
                    max:2000
                }
            },
        });

        // 参数
        const id = ctx.params.id;
        const {  goods_tags } = ctx.request.body;
        // 先看一下是否存在
        // let dataclass = await app.model.GoodsClass.findOne({ where: { id:goods_class_id } });
        // if (!dataclass) {
        //     return ctx.apiFail('该商品分类记录不存在');
        // }
        let data = await app.model.Goods.findOne({ where: { id } });
        if (!data) {
            return ctx.apiFail('该商品不存在');
        }
        //存在
        // const Op = this.app.Sequelize.Op;//拿Op,固定写法
        // //只要不放在同一分类级别下还是可以的
        // const hasdata = await app.model.GoodsClass.findOne({
        //     where: {
        //         name,
        //         id: {
        //             [Op.ne]: id
        //         }
        //     }
        // });
        // if (hasdata && hasdata.pid == pid) {
        //     return ctx.apiFail('同一个分类下不能有相同的商品分类名称');
        // }
        // 修改数据
        data.goods_tags = goods_tags;

        await data.save();
        // 给一个反馈
        ctx.apiSuccess('修改成功');
    }

# 二、给商品添加图片到 goods_banner

# 1. 总路由

app/router/admin/shop.js

//商品图片处理
router.get('/shop/admin/goods-/:goods_id/createGoodsBanner', controller.admin.goods.createGoodsBanner);
router.post('/shop/admin/goods-/:goods_id/saveGoodsBanner', controller.admin.goods.saveGoodsBanner);
router.get('/shop/admin/goods-/:goods_id/indexGoodsBanner', controller.admin.goods.indexGoodsBanner);
router.get('/shop/admin/goods-/:id/editGoodsBanner', controller.admin.goods.editGoodsBanner);
router.post('/shop/admin/goods-/:id/updateGoodsBanner', controller.admin.goods.updateGoodsBanner);
router.get('/shop/admin/goods-/:id/deleteGoodsBanner', controller.admin.goods.deleteGoodsBanner);
//修改商品标签相关界面和数据
...

# 2. 新建商品图片模型

app/model/goods_banner.js

'use strict';

module.exports = app => {
    const { INTEGER, STRING, DATE, ENUM, TEXT, BIGINT } = app.Sequelize;

    const GoodsBanner = app.model.define('goods_banner', {
        id: {
            type: INTEGER(20).UNSIGNED,
            primaryKey: true,
            autoIncrement: true,
            comment: '主键id'
        },
        goods_id: {
            type: INTEGER(20).UNSIGNED,
            allowNull: false,
            // defaultValue:0,
            comment: '商品id',
            references: { //关联关系
                model: 'goods', //关联的表
                key: 'id' //关联表的主键
            },
            onDelete: 'cascade', //删除时操作
            onUpdate: 'restrict', // 更新时操作
        },
        url: {
            type: STRING(1000),
            allowNull: false,
            defaultValue: '',
            comment: '图片地址'
        },
        order: {
            type: INTEGER,//不限定长度.默认int(11)
            allowNull: true,
            defaultValue: 50,
            comment: '排序,默认50'
        },
        status: {
            type: INTEGER(1),
            allowNull: false,
            defaultValue: 1,
            comment: '状态:1:启用,0:禁用'
        },
        // sex: { type: ENUM, values: ['男','女','保密'], allowNull: true, defaultValue: '保密', comment: '留言用户性别'},
        create_time: {
            type: DATE,
            allowNull: false,
            defaultValue: app.Sequelize.fn('NOW'),
            get() {
                return app.formatTime(this.getDataValue('create_time'));
            }
        },
        update_time: { type: DATE, allowNull: false, defaultValue: app.Sequelize.fn('NOW') }
    });


    // 模型关联关系
    GoodsBanner.associate = function (models) {
        // 关联商品 反向一对多
        GoodsBanner.belongsTo(app.model.Goods, {
            foreignKey: 'goods_id' // 明确指定外键字段
        });
    }

    return GoodsBanner;
}

# 3. 控制器代码

app/controller/admin/goods.js

    //商品图片部分
    //新增商品图片界面
    async createGoodsBanner(){
        const { ctx, app } = this;
        //1.参数验证
        this.ctx.validate({
            goods_id: {
                type: 'int',
                required: true,
                // defValue: 0,
                desc: '商品id',
                range:{
                    min:1
                }
            },
        });
        // 参数
        const goods_id = ctx.params.goods_id;
        let data = await app.model.Goods.findOne({ where: { id:goods_id } });
        if (!data) {
            return ctx.apiFail('该商品不存在');
        }
        // 渲染模版前先拿到所有分类
        // let data = await ctx.service.goodsClass.dropdown_goodsclass_list();
        //渲染公共模版
        await ctx.renderTemplate({
            title: '添加商品:' + data.name + '的图片',//现在网页title,面包屑导航title,页面标题
            tempType: 'form', //模板类型:table表格模板 ,form表单模板
            form: {
                //提交地址
                action: "/shop/admin/goods-/" + goods_id + "/saveGoodsBanner",
                //  字段
                fields: [
                    // {
                    //     label: '放在哪个商品分类里面',
                    //     type: 'dropdown', //下拉框
                    //     name: 'goods_class_id',
                    //     default: JSON.stringify(data),
                    //     placeholder: '请选择一个商品分类',
                    // },
                    {
                        label: '商品图片地址',
                        type: 'file', //fileoss - 上传到oss云存储里面, file-上传到自己的服务器上
                        name: 'url',
                        placeholder: '请选择商品图片',
                        // default:'默认值测试', //新增时候默认值,可选
                    },
                    {
                        label: '排序',
                        type: 'number',
                        name: 'order',
                        placeholder: '请输入排序,默认50,越小越靠前',
                        default:'50', //新增时候默认值,可选
                    },
                    {
                        label: '状态',
                        type: 'btncheck', //按钮组选择
                        name: 'status',
                        default: JSON.stringify([
                            { value: 1, name: '可用', checked: true },
                            { value: 0, name: '不可用' },
                        ]),
                        placeholder: '状态 0不可用 1可用 等等状态',
                    },
                ],
            },
            //新增成功之后跳转到哪个页面
            successUrl: '/shop/admin/goods-/' + goods_id + '/indexGoodsBanner',
        });
    }
    //新增商品图片数据
    async saveGoodsBanner(){
        const { ctx, app } = this;
        //一般处理流程
        //1.参数验证
        this.ctx.validate({
            goods_id: {
                type: 'int',
                required: true,
                // defValue: 0,
                desc: '商品id',
                range:{
                    min:1
                }
            },
            status: {
                type: 'int',
                required: false,
                defValue: 1,
                desc: '状态 0不可用 1可用 等等状态',
                range:{
                    in:[0,1]
                }
            },
            order: {
                type: 'int',
                required: false,
                defValue: 50,
                desc: '排序'
            },
            url: {
                type: 'string',
                required: true,
                // defValue: '',
                desc: '商品图片地址'
            }
        });
        // 看一下商品是否存在
        let goods_id = ctx.params.goods_id;
        let { status, order,url} = this.ctx.request.body;
        if (!await this.app.model.Goods.findOne({ where: { id: goods_id } })) {
            return this.ctx.apiFail('商品不存在,不能上传商品图片');
        }
        // const Op = this.app.Sequelize.Op;//拿Op,固定写法
        // //分类名称,是否已经存在,如果存在,但是只要不放在同一分类下还是可以的
        // const hasdata = await app.model.GoodsClass.findOne({
        //     where: {
        //         name,
        //         pid:pid
        //     }
        // });
        // if (hasdata) {
        //     return ctx.apiFail('同一个分类下不能有相同的分类名称');
        // }
        //写入数据库
        const res = await this.app.model.GoodsBanner.create({
            goods_id,status, order,url
        });
        this.ctx.apiSuccess('上传商品图片成功');
    }
    //商品图片列表
    async indexGoodsBanner(){}
    //修改商品图片界面
    async editGoodsBanner(){}
    //修改商品图片界面数据
    async updateGoodsBanner(){}
    //删除商品图片
    async deleteGoodsBanner(){}

# 三、goods_banner表列表展示,修改,删除

# 1. 控制器代码

app/controller/admin/goods.js

    //商品图片列表
    async indexGoodsBanner(){
        const { ctx, app } = this;
        //参数
        const classId = parseInt(ctx.params.goods_id);
        let page = parseInt(ctx.params.page) || 1;

        let limit = parseInt(ctx.query.limit) || 10;
        let order = ctx.query.order || 'asc';
        let keyword = ctx.query.keyword || '';
        let where = {};
        if(keyword){
            where.name = {
                [this.app.Sequelize.Op.like]:'%'+keyword+'%',
            };
        }


        //查看是否存在
        let classdata = await app.model.Goods.findOne({
            where:{
                id:classId,
                status:1
            }
        });
        if(!classdata){
            return ctx.apiFail('该商品不存在或已禁用');
        }
        //存在,则查图片列表
        /*
        let list = await app.model.GoodsBanner.findAll({
            where:{
                goods_id:classId,
                status:1,
                // name:{
                //     [this.app.Sequelize.Op.like]:'%'+keyword+'%',
                // }
                ...where,
            },
            offset:(page-1)*limit,
            limit,
            order:[
                ['order',order]
            ],
            // attributes:['id','name','url'],
            attributes:{
                exclude:['create_time','update_time']
            },
            include:[
                {
                    model:app.model.Goods,
                    // as:'imageClass',
                    // attributes:{
                    //     exclude:['create_time','update_time']
                    // },
                    attributes:['name','id'],
                }
            ]
        });
        let totalCount = await app.model.GoodsBanner.count({
            where:{
                goods_id:classId,
                status:1,
                ...where,
            }
        });
        ctx.apiSuccess({
            list,
            totalCount
        });
        */
        //分页:可以提炼成一个公共方法page(模型名称,where条件,其他参数options)
        let data = await ctx.page('GoodsBanner',{
            goods_id:classId
        },{
            include:[{
                model:app.model.Goods,
                attributes:['id','name']
            }]
        });
        // let data = await ctx.service.imageClass.datalist({ limit: 10000 });
        // console.log('分类数据', data);
        // ctx.body = data;
        // return;
        // data = data.rules;
        //渲染公共模版
        await ctx.renderTemplate({
            title: '商品:' + classdata.name + '下的全部图片',//现在网页title,面包屑导航title,页面标题
            data,
            tempType: 'table', //模板类型:table表格模板 ,form表单模板
            table: {
                //表格上方按钮,没有不要填buttons
                buttons: [
                    {
                        url: '/shop/admin/goods-/'+classId+'/createGoodsBanner',//新增路径
                        desc: '上传图片',//新增 //按钮名称
                        // icon: 'fa fa-plus fa-lg',//按钮图标
                    }
                ],
                //表头
                columns: [
                    {
                        title: '图片',
                        // key: 'name',
                        class: 'text-left',//可选
                        render(item) { //树形数据
                            // console.log('每个item',item);
                            // if (item.level) {
                            //     let w = item.level * 40;
                            //     return `<span style="display:inline-block;width:${w}px"></span>`;
                            // }
                            return `
                              <div style="display:flex;">
                                 <img src="${item.url}" style="width:100px;height:100px;" />
                              </div>
                            `;
                        }
                    },
                    // {
                    //     title: '是否是导航栏栏目',
                    //     key: 'isnav',
                    //     width: 200,//可选
                    //     class: 'text-center',//可选
                    //     hidekeyData: true,//是否隐藏key对应的数据
                    //     render(item) {
                    //         console.log('可用状态里面每个item', item);
                    //         let arr = [
                    //             { value: 1, name: '是' },
                    //             { value: 0, name: '否' },
                    //         ];
                    //         let str = `<div class="btn-group btn-group-${item.id}">`;
                    //         for (let i = 0; i < arr.length; i++) {
                    //             str += `<button type="button" class="btn btn-light" data="${item.isnav}"
                    //             value="${arr[i].value}"
                    //             @click="changeBtnStatus('isnav','btn-group-${item.id}',${arr[i].value},${i},${item.id},'category','Category')">${arr[i].name}</button>`;
                    //         }
                    //         str += `</div>`;
                    //         return str;
                    //     }
                    // },
                    {
                        title: '排序',
                        key: 'order',
                        class: 'text-center',//可选
                    },
                    {
                        title: '可用状态',
                        key: 'status',
                        width: 200,//可选
                        class: 'text-center',//可选
                        hidekeyData: true,//是否隐藏key对应的数据
                        render(item) {
                            // console.log('可用状态里面每个item', item);
                            let arr = [
                                { value: 1, name: '可用' },
                                { value: 0, name: '不可用' },
                            ];
                            let str = `<div class="btn-group btn-group-${item.id}">`;
                            for (let i = 0; i < arr.length; i++) {
                                str += `<button type="button" class="btn btn-light" data="${item.status}"
                                value="${arr[i].value}"
                                @click="changeBtnStatus('status','btn-group-${item.id}',${arr[i].value},${i},${item.id},'/shop/admin/goods-/${classId}/indexGoodsBanner','GoodsBanner')">${arr[i].name}</button>`;
                            }
                            str += `</div>`;
                            return str;
                        }
                    },
                    {
                        title: '操作',
                        class: 'text-right',//可选
                        action: {
                            //修改
                            edit: function (id) {
                                return `/shop/admin/goods-/${id}/editGoodsBanner`;
                            },
                            //删除
                            delete: function (id) {
                                return `/shop/admin/goods-/${id}/deleteGoodsBanner`;
                            }
                        }
                    },
                ],
            },
        });
    }
    //修改商品图片界面
    async editGoodsBanner(){
        const { ctx, app } = this;
        const id = ctx.params.id;
        let currentdata = await app.model.GoodsBanner.findOne({
            where: {
                id,
                // status:1
            }
        });
        if (!currentdata) {
            return ctx.apiFail('该商品图片不存在');
        }
        currentdata = JSON.parse(JSON.stringify(currentdata));
        // console.log('当前商品分类数据', currentdata);
        // return;

        // 渲染模版前先拿到所有分类
        // let data = await ctx.service.goodsClass.dropdown_goodsclass_list();
        // console.log('下拉框显示的所有分类', JSON.stringify(data));
        // return;

        //渲染公共模版
        await ctx.renderTemplate({
            id,
            title: '修改图片',//现在网页title,面包屑导航title,页面标题
            tempType: 'form', //模板类型:table表格模板 ,form表单模板
            form: {
                //修改提交地址
                action: '/shop/admin/goods-/' + id + '/updateGoodsBanner',
                //  字段
                fields: [
                    // {
                    //     label: '放在哪个商品分类里面',
                    //     type: 'dropdown', //下拉框
                    //     name: 'goods_class_id',
                    //     default: JSON.stringify(data),
                    //     placeholder: '不调整(如需调整请选择)',
                    // },
                    {
                        label: '商品图片地址',
                        type: 'file', // fileoss - 上传到oss云存储里面, file-上传到自己的服务器上
                        name: 'url',
                        placeholder: '请选择商品图片',
                        // default:'默认值测试', //新增时候默认值,可选
                    },
                    {
                        label: '排序',
                        type: 'number',
                        name: 'order',
                        placeholder: '请输入排序',
                        // default:50,
                    },
                    {
                        label: '可用状态',
                        type: 'btncheck', //按钮组选择
                        name: 'status',
                        default: JSON.stringify([
                            { value: 1, name: '可用', checked: currentdata.status === 1 },
                            { value: 0, name: '不可用', checked: currentdata.status === 0 },
                        ]),
                        placeholder: '状态 0不可用 1可用 等等状态',
                    },
                ],
                //修改内容默认值
                data:currentdata,
            },
            //修改成功之后跳转到哪个页面
            successUrl: '/shop/admin/goods-/'+currentdata.goods_id+'/indexGoodsBanner',
        });
    }
    //修改商品图片数据
    async updateGoodsBanner(){
        const { ctx, app } = this;
        //1.参数验证
        this.ctx.validate({
            id: {
                type: 'int',
                required: true,
                desc: '图片id'
            },
            status: {
                type: 'int',
                required: false,
                defValue: 1,
                desc: '状态 0不可用 1可用 等等状态',
                range:{
                    in:[0,1]
                }
            },
            order: {
                type: 'int',
                required: false,
                defValue: 50,
                desc: '排序'
            },
            url: {
                type: 'string',
                required: true,
                // defValue: '',
                desc: '商品图片地址'
            }
        });

        // 参数
        const id = ctx.params.id;
        // 先看一下是否存在
        let data = await app.model.GoodsBanner.findOne({ where: { id } });
        if (!data) {
            return ctx.apiFail('该图片不存在');
        }
        const {  status, order, url } = ctx.request.body;
        let goods_id = data.goods_id;
        //存在
        // const Op = this.app.Sequelize.Op;//拿Op,固定写法
        // //只要不放在同一分类级别下还是可以的
        // const hasdata = await app.model.GoodsClass.findOne({
        //     where: {
        //         name,
        //         id: {
        //             [Op.ne]: id
        //         }
        //     }
        // });
        // if (hasdata && hasdata.pid == pid) {
        //     return ctx.apiFail('同一个分类下不能有相同的商品分类名称');
        // }
        // 修改数据
        data.url = url;
        data.status = status;
        data.goods_id = goods_id;
        data.order = order;
        await data.save();
        // 给一个反馈
        ctx.apiSuccess('修改图片成功');
    }
    //删除商品图片
    async deleteGoodsBanner(){
        const { ctx, app } = this;
        const id = ctx.params.id;

        let data = await app.model.GoodsBanner.findOne({ where: { id } });
        if (!data) {
            return ctx.apiFail('该商品图片不存在');
        }
        await app.model.GoodsBanner.destroy({
            where: {
                id
            }
        });
        //提示
        ctx.toast('商品图片删除成功', 'success');
        //跳转
        ctx.redirect('/shop/admin/goods-/'+data.goods_id+'/indexGoodsBanner');
    }

# 四、修改商品参数信息 goods_param

# 1. 商品参数表 goods_param

具体查看,3. 商品表goods的外联表goods_param[商品参数表]字段设计

# 2. 修改商品参数信息说明

具体查看,修改商品参数信息

更新时间: 2025年4月27日星期日晚上8点44分