# 一、调整商品规格表 skus 字段及 商品规格后台功能

说明:

我们在商品表设计的时候,有一个字段 sku_value, 它这个字段和表 skus 有关,而我们的商品是划分在商品分类下面,因此,我们这个地方可以做一个调整: 给商品规格表 skus 增加一个字段 goods_class_id即商品分类id,这样在我们新增商品的时候,当我们选择了商品类别,关于商品规格的部分,就会自动对应到商品分类的这个商品规格里面,便于我们操作。

由此,我们可以从侧面看出,当我们的项目在开发的过程中,随着你需求的增加,或者产品经理或者客户,随时提出了他新的需求,我们要不断调整我们已经开发的部分,包括已经开发好了的表,比如我们现在的商品规格表 skus

那么如何调整表呢?

在我们第二学期第三季开发我们网站前后台的时候,我说过,大家可以直接去数据库对表 skus 新增一个字段goods_class_id,然后另外一种方式,就是如果你用的迁移文件,那么回退几个数据表,对要修改的表 skus 修改迁移文件,然后重新创建表就可以了。

# 1. 修改迁移文件

await queryInterface.createTable('skus', {
    id: { 
        type: INTEGER(20).UNSIGNED, 
        primaryKey: true, 
        autoIncrement: true,
        comment: '主键id'
    },
    goods_class_id: {
        type: INTEGER(20).UNSIGNED,
        allowNull: false,
        comment: '商品分类id',
        references: { //关联关系
          model: 'goods_class', //关联的表
          key: 'id' //关联表的主键
        },
        onDelete: 'CASCADE', //删除时操作
        onUpdate: 'RESTRICT', // 更新时操作
    },
    ...
});

# 2. 回退迁移文件,重新创建表

// 创建数据库
npx sequelize db:migrate
// 如果有问题需要回滚,可以通过 `db:migrate:undo` 回退一个变更
npx sequelize db:migrate:undo
// 可以通过 `db:migrate:undo:all` 回退到初始状态
npx sequelize db:migrate:undo:all

# 3. 修改模型文件

app/model/skus.js

'use strict';

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

    const Skus = app.model.define('skus', {
        id: {
            type: INTEGER(20).UNSIGNED,
            primaryKey: true,
            autoIncrement: true,
            comment: '主键id'
        },
        goods_class_id: {
            type: INTEGER(20).UNSIGNED,
            allowNull: false,
            comment: '商品分类id',
            references: { //关联关系
              model: 'goods_class', //关联的表
              key: 'id' //关联表的主键
            },
            onDelete: 'CASCADE', //删除时操作
            onUpdate: 'RESTRICT', // 更新时操作
        },
        ...
    });


    // 模型关联关系
    Skus.associate = function (models) {
        // 关联到自己 (通过pid关联)
        // this.hasMany(app.model.GoodsClass, {
        //     foreignKey: 'pid',
        //     as: 'ChildGoodsClass' // 别名(可选)
        // });

        // 关联商品模型
        // this.hasMany(app.model.Image, {
        //     foreignKey: 'image_class_id',
        //     // as: 'images' // 别名(可选)
        // });

        // 关联商品分类:
        // 一个商品分类可以有多个规格, 反过来,反向一对多
        this.belongsTo(app.model.GoodsClass, {
            foreignKey: 'goods_class_id',
            // as: 'GoodsClass' // 别名(可选)
        });
        
    }

    return Skus;
}

# 4. 修改控制器文件

app/controller/admin/skus.js

    // 创建商品规格界面
    async create() {
        const { ctx, app } = this;

        //先看一下有没有商品分类
        let data = await ctx.service.goodsClass.dropdown_goodsclass_list();
        data.shift();
        //console.log('先看一下有没有商品分类', data);
        if (data.length == 0) {
            //提示
            ctx.toast('请先创建商品分类,在创建商品规格', 'danger');
            //跳转
            return  ctx.redirect('/shop/admin/goodsclass/create');
        }

        //渲染公共模版
        await ctx.renderTemplate({
            title: '创建商品规格',//现在网页title,面包屑导航title,页面标题
            tempType: 'form', //模板类型:table表格模板 ,form表单模板
            form: {
                //提交地址
                action: "/shop/admin/skus",
                //  字段
                fields: [
                    {
                        label: '属于哪个商品分类',
                        type: 'dropdown', //下拉框
                        name: 'goods_class_id',
                        default: JSON.stringify(data),
                        placeholder: '请选择一个商品分类',
                    },
                    {
                        label: '商品规格名称',
                        type: 'text',
                        name: 'name',
                        placeholder: '请输入商品规格名称',
                        // default:'默认值测试', //新增时候默认值,可选
                    },
                    {
                        label: '商品规格值',
                        type: 'textarea',
                        name: 'default',
                        placeholder: '请输入商品规格值,多个值用逗号隔开',
                    },
                    {
                        label: '规格类型',
                        type: 'number',
                        name: 'type',
                        placeholder: '请输入商品规格类型:0无限制,1颜色,2图片,3尺寸等等,选填,默认0',
                        default:0,
                    },
                    {
                        label: '排序',
                        type: 'number',
                        name: 'order',
                        placeholder: '请输入排序',
                        default:50,
                    },
                    {
                        label: '可用状态',
                        type: 'btncheck', //按钮组选择
                        name: 'status',
                        default: JSON.stringify([
                            { value: 1, name: '可用', checked: true },
                            { value: 0, name: '不可用' },
                        ]),
                        placeholder: '状态 0不可用 1可用 等等状态',
                    },
                ],
            },
            //新增成功之后跳转到哪个页面
            successUrl: '/shop/admin/skus',
        });
    }
    //创建商品规格提交数据
    async save() {
        const { ctx, app } = this;
        //一般处理流程
        //1.参数验证
        this.ctx.validate({
            name: {
                type: 'string',  //参数类型
                required: true, //是否必须
                // defValue: '', 
                desc: '商品规格名称', //字段含义
                range:{
                    min:2,
                    max:30
                }
            },
            default: {
                type: 'string',
                required: true,
                // defValue: '',
                desc: '商品规格值',
                range:{
                    min:0,
                    max:2000
                }
            },
            status: {
                type: 'int',
                required: false,
                defValue: 1,
                desc: '状态 0不可用 1可用 等等状态',
                range:{
                    in:[0,1]
                }
            },
            type: {
                type: 'int',
                required: false,
                defValue: 0,
                desc: '规格类型:0无限制,1颜色,2图片,3尺寸等等'
            },
            order: {
                type: 'int',
                required: false,
                defValue: 50,
                desc: '排序'
            },
            goods_class_id: {
                type: 'int',
                required: true,
                // defValue: 0,
                desc: '商品分类id'
            },
        });
        //先判断一下直播功能中的礼物账号是否存在,不存在在写入数据库
        //2.写入数据库
        //3.成功之后给页面反馈
        let { name, default:defaultValue, status, type,order,goods_class_id } = this.ctx.request.body;
        // if (await this.app.model.Skus.findOne({ where: { name } })) {
        //     return this.ctx.apiFail('分类名称已存在');
        // } 
        //写入数据库
        const res = await this.app.model.Skus.create({
            name, 
            default:defaultValue, 
            status, 
            type,
            order,
            goods_class_id
        });
        this.ctx.apiSuccess('创建商品规格成功');
    }
    //商品规格列表页面
    async index() {
        const { ctx, app } = this;
        //分页:可以提炼成一个公共方法page(模型名称,where条件,其他参数options)
        let keyword = ctx.query.keyword || '';
        let data = await ctx.page('Skus',{
            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/skus/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',//可选
                        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 `<span>${item.goods_class.name}</span>`;
                        }
                    },
                    {
                        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>`;
                        //     }
                        // }
                    },
                    // {
                    //     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: 'default',
                        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>`;
                            // }
                            let str = '';
                            let array = item.default.split(',');
                            for (let i = 0;   i < array.length; i++) {
                                const element = array[i];
                                str += `<span style="margin-right:5px;margin-bottom:5px">${element}</span>`;
                            }
                            return `<div style="width:200px;display:flex;
                            flex-wrap:wrap;font-size:12px;">
                                ${str}
                            </div>`;
                        }
                    },
                    {
                        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/skus','Skus')">${arr[i].name}</button>`;
                            }
                            str += `</div>`;
                            return str;
                        }
                    },
                    {
                        title: '操作',
                        class: 'text-right',//可选
                        action: {
                            //修改
                            edit: function (id) {
                                return `/shop/admin/skus/edit/${id}`;
                            },
                            //删除
                            delete: function (id) {
                                return `/shop/admin/skus/${id}/delete`;
                            }
                        }
                    },
                ],
            },
        });
    }

# 二、创建商品

# 1. 路由

app/router/admin/shop.js

module.exports = app => {
    ...
    //删除商品功能
    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);
    
};

# 2. 新建模型

app/model/goods.js

'use strict';


module.exports = app => {
    const { 
        INTEGER, 
        STRING, 
        DATE, 
        TEXT, 
        FLOAT, 
        DECIMAL,
        BOOLEAN 
      } = app.Sequelize;

    const Goods = app.model.define('goods', {
        id: { 
            type: INTEGER(20).UNSIGNED, 
            primaryKey: true, 
            autoIncrement: true,
            comment: '主键id'
          },
          goods_class_id: {
            type: INTEGER(20).UNSIGNED,
            allowNull: false,
            comment: '商品分类id',
            references: { //关联关系
              model: 'goods_class', //关联的表
              key: 'id' //关联表的主键
            },
            onDelete: 'CASCADE', //删除时操作
            onUpdate: 'RESTRICT', // 更新时操作
          },
          name: { 
            type: STRING(255), 
            allowNull: false, 
            defaultValue: '', 
            comment: '商品名称'
          },
          desc: { 
            type: STRING(2000), 
            allowNull: true, 
            defaultValue: '', 
            comment: '商品描述'
          },
          content: { 
            type: TEXT, 
            allowNull: true, 
            comment: '商品详情'
          },
          cover: { 
            type: STRING(1000), 
            allowNull: true, 
            comment: '商品封面图代表(更多图片关联至goods_banner表)'
          },
          rating: {
            type: FLOAT,
            allowNull: true,
            defaultValue: 5.0,
            comment: '商品评分'
          },
          sale_count: {
            type: INTEGER(11),
            allowNull: true,
            defaultValue: 0,
            comment: '总销量'
          },
          review_count: {
            type: INTEGER(11),
            allowNull: true,
            defaultValue: 0,
            comment: '评论数量'
          },
          love_count: {
            type: INTEGER(11),
            allowNull: true,
            defaultValue: 0,
            comment: '收藏量'
          },
          recommend_count: {
            type: INTEGER(11),
            allowNull: true,
            defaultValue: 0,
            comment: '推荐量'
          },
          min_price: {
            type: DECIMAL(10, 2),
            allowNull: true,
            comment: '起售价(最低售价,多少元起)'
          },
          history_min_price: {
            type: DECIMAL(10, 2),
            allowNull: true,
            comment: '历史最低价'
          },
          coupon_price: {
            type: DECIMAL(10, 2),
            allowNull: true,
            comment: '券后价'
          },
          discount_price: {
            type: DECIMAL(10, 2),
            allowNull: true,
            comment: '折后价'
          },
          spike_price: {
            type: DECIMAL(10, 2),
            allowNull: true,
            comment: '秒杀价'
          },
          other_price: {
            type: DECIMAL(10, 2),
            allowNull: true,
            comment: '其它设置价(如:首件价,新客价,30天低价等等)'
          },
          unit: {
            type: STRING(10),
            allowNull: true,
            defaultValue: '件',
            comment: '商品单位(默认:件)'
          },
          stock: {
            type: INTEGER(11),
            allowNull: true,
            defaultValue: 0,
            comment: '库存'
          },
          min_stock: {
            type: INTEGER(11),
            allowNull: true,
            defaultValue: 0,
            comment: '库存预警'
          },
          stock_display: {
            type: INTEGER(1),
            allowNull: true,
            defaultValue: 0,
            comment: '库存显示:0隐藏 1显示'
          },
          ischeck: {
            type: INTEGER(1),
            allowNull: true,
            defaultValue: 0,
            comment: '审核状态:0审核中 1通过 2拒绝(不通过)'
          },
          goods_status: {
            type: INTEGER(1),
            allowNull: true,
            defaultValue: 1,
            comment: '商品状态:0仓库(未上架)、1上架、2下架、3违规下架、4回收站等'
          },
          sku_value: {
            type: TEXT,
            allowNull: true,
            comment: '商品规格属性(商品参数)'
          },
          goods_tags: {
            type: STRING(2000),
            allowNull: true,
            comment: '商品标签(如:近期销量飙升,30天上新,近7天同类热卖,24小时内发货,等等,用逗号隔开)'
          },
          order: {
            type: INTEGER,
            allowNull: true,
            defaultValue: 50,
            comment: '排序,默认50'
          },
          status: {
            type: INTEGER(1),
            allowNull: false,
            defaultValue: 1,
            comment: '可用状态:0禁用 1启用'
          },
        // 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') },
        delete_time: {
          type: DATE,
          allowNull: true,
          comment: '删除时间'
        }
    });


    // 模型关联关系
    Goods.associate = function (models) {
        // 关联分类 反向一对多
        Goods.belongsTo(app.model.GoodsClass);
    }

    return Goods;
}

# 3. 新建控制器

app/controller/admin/goods.js

'use strict';

const Controller = require('egg').Controller;

class GoodsController extends Controller {
    // 创建商品界面
    async create() {
        const { ctx, app } = this;

        // 渲染模版前先拿到所有分类
        let data = await ctx.service.goodsClass.dropdown_goodsclass_list();
        //渲染公共模版
        await ctx.renderTemplate({
            title: '创建商品',//现在网页title,面包屑导航title,页面标题
            tempType: 'form', //模板类型:table表格模板 ,form表单模板
            form: {
                //提交地址
                action: "/shop/admin/goods",
                //  字段
                fields: [
                    {
                        label: '放在哪个商品分类里面',
                        type: 'dropdown', //下拉框
                        name: 'goods_class_id',
                        default: JSON.stringify(data),
                        placeholder: '请选择一个商品分类',
                    },
                    {
                        label: '商品名称',
                        type: 'text',
                        name: 'name',
                        placeholder: '请输入商品名称',
                        // default:'默认值测试', //新增时候默认值,可选
                    },
                    {
                        label: '商品描述',
                        type: 'textarea',
                        name: 'desc',
                        placeholder: '请输入商品描述,选填',
                    },
                    {
                        label: '商品封面图代表',
                        type: 'file', //'fileoss'-上传到oss云存储里面
                        name: 'cover',
                    },
                    {
                        label: '商品详情',
                        type: 'editor', //富文本编辑器
                        name: 'content',
                        placeholder: '请输入商品详情,选填',
                    },
                    {
                        label: '排序',
                        type: 'number',
                        name: 'order',
                        placeholder: '请输入排序',
                        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',
        });
    }
    //创建商品提交数据
    async save() {
        const { ctx, app } = this;
        //一般处理流程
        //1.参数验证
        this.ctx.validate({
            name: {
                type: 'string',  //参数类型
                required: true, //是否必须
                // defValue: '', 
                desc: '商品名称', //字段含义
                range:{
                    min:2,
                    max:255
                }
            },
            desc: {
                type: 'string',
                required: false,
                defValue: '',
                desc: '商品描述',
                range:{
                    min:0,
                    max:2000
                }
            },
            status: {
                type: 'int',
                required: false,
                defValue: 1,
                desc: '分类状态 0不可用 1可用 等等状态',
                range:{
                    in:[0,1]
                }
            },
            goods_class_id: {
                type: 'int',
                required: true,
                // defValue: 0,
                desc: '商品分类id',
                range:{
                    min:1,
                }
            },
            order: {
                type: 'int',
                required: false,
                defValue: 50,
                desc: '排序'
            },
            content: {
                type: 'string',
                required: false,
                defValue: '',
                desc: '商品详情'
            },
            cover: {
                type: 'string',
                required: true,
                // defValue: '',
                desc: '商品封面图代表'
            }
        });
        //2.业务处理
        let { name, desc, status, goods_class_id,order,content,cover } = this.ctx.request.body;
        if (!await this.app.model.GoodsClass.findOne({ where: { id: goods_class_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.Goods.create({
            name,
            desc,
            status,
            goods_class_id,
            order,
            content,
            cover 
        });
        this.ctx.apiSuccess('创建商品成功');
    }
    //商品列表页面
    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',//可选
                        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>
                                 <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;"></img>
                                     <p><span>商品名称:</span><span>${item.name}</span></p>
                                 </div>
                            </div>`;
                        }
                    },
                    // {
                    //     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`;
                            }
                        }
                    },
                ],
            },
        });
    }
    //商品列表api
    async indexAPI() {}
    //修改商品界面
    async edit() {}
    //修改商品数据功能
    async update() {}
    //修改商品状态功能
    async updateStatus() {}
    //删除商品功能
    async delete() {}
    //删除商品功能api
    async deleteAPI() {}
    //批量删除商品
    async deleteAll() {}
}

module.exports = GoodsController;

# 三、商品列表数据、修改、删除

# 1. 字体图标查看 https://fontawesome.dashgame.com/ (opens new window)

# 2. 后台左侧菜单

data/root.json

[
    ...
   {"id":24,"pid":17, "name": "商品管理", "icon": "fa fa-inbox", "url": "/shop/admin/goods-" }
]

# 3. 路由

app/router/admin/shop.js

module.exports = app => {
    ...
    //删除商品功能
    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

'use strict';

const Controller = require('egg').Controller;

class GoodsController extends Controller {
    // 创建商品界面
    async create() {
        const { ctx, app } = this;

        // 渲染模版前先拿到所有分类
        let data = await ctx.service.goodsClass.dropdown_goodsclass_list();
        //渲染公共模版
        await ctx.renderTemplate({
            title: '创建商品',//现在网页title,面包屑导航title,页面标题
            tempType: 'form', //模板类型:table表格模板 ,form表单模板
            form: {
                //提交地址
                action: "/shop/admin/goods-",
                //  字段
                fields: [
                    {
                        label: '放在哪个商品分类里面',
                        type: 'dropdown', //下拉框
                        name: 'goods_class_id',
                        default: JSON.stringify(data),
                        placeholder: '请选择一个商品分类',
                    },
                    {
                        label: '商品名称',
                        type: 'text',
                        name: 'name',
                        placeholder: '请输入商品名称',
                        // default:'默认值测试', //新增时候默认值,可选
                    },
                    {
                        label: '商品描述',
                        type: 'textarea',
                        name: 'desc',
                        placeholder: '请输入商品描述,选填',
                    },
                    {
                        label: '商品封面图代表',
                        type: 'file',//fileoss - 上传到oss云存储里面, file-上传到自己的服务器上
                        name: 'cover',
                        placeholder: '请选择图片',
                        // default:'默认值测试', //新增时候默认值,可选
                    },
                    {
                        label: '商品详情',
                        type: 'editor', //富文本编辑器
                        name: 'content',
                        placeholder: '请输入商品详情,选填',
                    },
                    {
                        label: '排序',
                        type: 'number',
                        name: 'order',
                        placeholder: '请输入排序',
                        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-',
        });
    }
    // 创建商品提交数据
    async save() {
        const { ctx, app } = this;
        //一般处理流程
        //1.参数验证
        this.ctx.validate({
            name: {
                type: 'string',  //参数类型
                required: true, //是否必须
                // defValue: '', 
                desc: '商品名称', //字段含义
                range:{
                    min:2,
                    max:255
                }
            },
            desc: {
                type: 'string',
                required: false,
                defValue: '',
                desc: '商品描述',
                range:{
                    min:0,
                    max:2000
                }
            },
            status: {
                type: 'int',
                required: false,
                defValue: 1,
                desc: '状态 0不可用 1可用 等等状态',
                range:{
                    in:[0,1]
                }
            },
            goods_class_id: {
                type: 'int',
                required: true,
                // defValue: 0,
                desc: '商品分类id',
                range:{
                    min:1
                }
            },
            order: {
                type: 'int',
                required: false,
                defValue: 50,
                desc: '排序'
            },
            content: {
                type: 'string',
                required: false,
                defValue: '',
                desc: '商品详情'
            },
            cover: {
                type: 'string',
                required: true,
                // defValue: '',
                desc: '商品封面图代表'
            }
        });
        //先判断一下直播功能中的礼物账号是否存在,不存在在写入数据库
        //2.写入数据库
        //3.成功之后给页面反馈
        let { name, desc, status, goods_class_id,order,content,cover } = this.ctx.request.body;
        if (!await this.app.model.GoodsClass.findOne({ where: { id: goods_class_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.Goods.create({
            name,
            desc,
            status,
            goods_class_id,
            order,
            content,
            cover
        });
        this.ctx.apiSuccess('创建商品成功');
    }
    // 商品列表页面
    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',//可选
                        render(item) { 
                            // console.log('每个item',item);
                            return `<div>
                                 <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><span>商品名称:</span><span>${item.name}</span></p>
                                 </div>
                            </div>`;
                        }
                    },
                    // {
                    //     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`;
                            }
                        }
                    },
                ],
            },
        });
    }
    // 商品列表api
    async indexAPI() {}
    // 修改商品界面
    async edit() {
        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,
                //  字段
                fields: [
                    {
                        label: '放在哪个商品分类里面',
                        type: 'dropdown', //下拉框
                        name: 'goods_class_id',
                        default: JSON.stringify(data),
                        placeholder: '不调整(如需调整请选择)',
                    },
                    {
                        label: '商品名称',
                        type: 'text',
                        name: 'name',
                        placeholder: '请输入商品名称',
                        // default:'默认值测试', //新增时候默认值,可选
                    },
                    {
                        label: '商品描述',
                        type: 'textarea',
                        name: 'desc',
                        placeholder: '请输入商品描述,选填',
                    },
                    {
                        label: '商品封面图代表',
                        type: 'file',//fileoss - 上传到oss云存储里面, file-上传到自己的服务器上
                        name: 'cover',
                        placeholder: '请选择图片',
                        // default:'默认值测试', //新增时候默认值,可选
                    },
                    {
                        label: '商品详情',
                        type: 'editor', //富文本编辑器
                        name: 'content',
                        placeholder: '请输入商品详情,选填',
                    },
                    {
                        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-',
        });

    }
    // 修改商品数据功能
    async update() {
        const { ctx, app } = this;
        //1.参数验证
        this.ctx.validate({
            id: {
                type: 'int',
                required: true,
                desc: '商品分类id'
            },
            name: {
                type: 'string',  //参数类型
                required: true, //是否必须
                // defValue: '', 
                desc: '商品名称', //字段含义
                range:{
                    min:2,
                    max:255
                }
            },
            desc: {
                type: 'string',
                required: false,
                defValue: '',
                desc: '商品描述',
                range:{
                    min:0,
                    max:2000
                }
            },
            status: {
                type: 'int',
                required: false,
                defValue: 1,
                desc: '状态 0不可用 1可用 等等状态',
                range:{
                    in:[0,1]
                }
            },
            goods_class_id: {
                type: 'int',
                required: true,
                // defValue: 0,
                desc: '商品分类id',
                range:{
                    min:1
                }
            },
            order: {
                type: 'int',
                required: false,
                defValue: 50,
                desc: '排序'
            },
            content: {
                type: 'string',
                required: false,
                defValue: '',
                desc: '商品详情'
            },
            cover: {
                type: 'string',
                required: true,
                // defValue: '',
                desc: '商品封面图代表'
            }
        });

        // 参数
        const id = ctx.params.id;
        const {  name, desc, status, goods_class_id,order,content,cover } = 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.name = name;
        data.desc = desc;
        data.status = status;
        data.goods_class_id = goods_class_id;
        data.order = order;
        data.content = content;
        data.cover = cover;
        await data.save();
        // 给一个反馈
        ctx.apiSuccess('修改商品成功');
    }
    // 修改商品状态功能
    async updateStatus() {}
    // 删除商品功能
    async delete() {
        const { ctx, app } = this;
        const id = ctx.params.id;

        let data = await app.model.Goods.findOne({
            where: {
                id
            }
        });
        if (!data) {
            return ctx.apiFail('该记录不存在');
        }
        await app.model.Goods.destroy({
            where: {
                id
            }
        });
        //提示
        ctx.toast('商品删除成功', 'success');
        //跳转
        ctx.redirect('/shop/admin/goods-');
    }
    // 删除商品功能api
    async deleteAPI() {}
    // 批量删除商品
    async deleteAll() {}
}

module.exports = GoodsController;

# 四、修改商品其他信息

具体查看,修改商品其他信息

更新时间: 2025年4月27日星期日下午3点49分