modelapp/model/goods_cate.jsmodule.exports = app => { const mongoose = app.mongoose; const Schema = mongoose.Schema; var d=new Date(); const GoodsCateSchema = new Schema({ title:{ type:String }, cate_img: { type: String }, filter_attr: { type: String }, //筛选id link:{ type: String }, template:{ /指定当前分类的模板/ type:String }, pid:{ type:Schema.Types.Mixed //混合类型 }, sub_title:{ /seo相关的标题 关键词 描述/ type:String }, keywords:{ type:String }, description:{ type:String }, status: { type: Number,default:1 }, add_time: { type:Number, default: d.getTime() } }); return mongoose.model(‘GoodsCate’, GoodsCateSchema,‘goods_cate’); }router.js router.get(’/admin/goodsCate’, controller.admin.goodsCate.index); router.get(’/admin/goodsCate/add’, controller.admin.goodsCate.add); router.post(’/admin/goodsCate/doAdd’, controller.admin.goodsCate.doAdd);增加controllerapp/controller/admin/goodsCate.js async add(){ var result = await this.ctx.model.GoodsCate.find({‘pid’:‘0’}) await this.ctx.render(‘admin/goodsCate/add’,{ cateList:result }); } async doAdd(){ let parts = this.ctx.multipart({ autoFields: true }); let files = {}; let stream; while ((stream = await parts()) != null) { if (!stream.filename) { break; } let fieldname = stream.fieldname; //file表单的名字 //上传图片的目录 let dir=await this.service.tools.getUploadFile(stream.filename); let target = dir.uploadDir; let writeStream = fs.createWriteStream(target); await pump(stream, writeStream); files=Object.assign(files,{ [fieldname]:dir.saveDir }) } if(parts.field.pid!=0){ parts.field.pid=this.app.mongoose.Types.ObjectId(parts.field.pid); //调用mongoose里面的方法把字符串转换成ObjectId } let goodsCate =new this.ctx.model.GoodsCate(Object.assign(files,parts.field)); await goodsCate.save(); await this.success(’/admin/goodsCate’,‘增加分类成功’); }viewapp/view/admin/goodsCate/add.html一级分类的pid是0,顶级分类二级分类的pid是_id,一级分类(如下图的手机/电话卡)<%- include ../public/page_header.html %><div class=“panel panel-default”> <div class=“panel-heading”> 增加商品分类 </div> <div class=“panel-body”> <div class=“table-responsive input-form”> <form action="/admin/goodsCate/doAdd?_csrf=<%=csrf%>" method=“post” enctype=“multipart/form-data”> <ul class=“form_input”> <li> <span>分类名称:</span> <input type=“text” name=“title” class=“input”/></li> <li> <span>上级分类:</span> <select name=“pid” id=“pid”> <option value=“0”>顶级分类</option> <%for(var i=0;i<cateList.length;i++){%> <option value="<%=cateList[i]._id%>"><%=cateList[i].title%></option> <%}%> </select> </li> <li> <span>分类图片:</span> <input type=“file” name=“cate_img”/></li> <li> <span>筛选属性:</span> <input type=“text” name=“filter_attr” class=“input”/></li> <li> <span>跳转地址:</span> <input type=“text” name=“link” class=“input”/></li> <li> <span>分类模板:</span> <input type=“text” name=“template” class=“input”/><span>空表示默认模板</span></li> <li> <span>Seo标题:</span> <input type=“text” name=“sub_title” class=“input”/></li> <li> <span>Seo关键词: </span><input type=“text” name=“keywords” class=“input”/></li> <li> <span>Seo描述:</span> <textarea name=“description” id=“description” cols=“84” rows=“4”></textarea></li> <li> <span>排 序:</span> <input type=“text” name=“sort”/></li> <li> <span>状 态:</span> <input type=“radio” name=“status” checked value=“1” id=“a”/> <label for=“a”>显示</label> <input type=“radio” name=“status” value=“0” id=“b”/><label for=“b”>隐藏</label> </li> <li> <br/> <button type=“submit” class=“btn btn-primary”>提交</button> </li> </ul> </form> </div> </div></div></body></html>效果查找controllerapp/controller/admin/goodsCate.js通过pid关联自己查询match筛选pid为0的数据 async index() { var result=await this.ctx.model.GoodsCate.aggregate([ { $lookup:{ from:‘goods_cate’, localField:’_id’, foreignField:‘pid’, as:‘items’ } }, { $match:{ “pid”:‘0’ } } ]) console.log(JSON.stringify(result)); await this.ctx.render(‘admin/goodsCate/index’,{ list:result }); }viewapp/view/admin/goodsCate/index.html<%- include ../public/page_header.html %> <div class=“panel panel-default”> <div class=“panel-heading clear”> <span>商品分类列表</span> <a href="/admin/goodsCate/add" class=“btn btn-primary fr”>增加商品分类</a> </div> <div class=“panel-body”> <div class=“table-responsive”> <table class=“table table-bordered”> <thead> <tr class=“th”> <th>分类名称</th> <th>分类图片</th> <th class=“text-center”>排序</th> <th class=“text-center”>状态</th> <th class=“text-center”>操作</th> </tr> </thead> <tbody> <%for(var i=0;i<list.length;i++){%> <tr> <td><%=list[i].title%></td> <td><img class=“pic” src="<%=list[i].cate_img%>" /></td> <td class=“text-center”><span onclick=“app.editNum(this,‘GoodsCate’,‘sort’,’<%=list[i]._id%>’)"><%=list[i].sort%></span></td> <td class=“text-center”> <%if(list[i].status==1){%> <img src="/public/admin/images/yes.gif” onclick=“app.changeStatus(this,‘GoodsCate’,‘status’,’<%=list[i]._id%>’)” /> <%}else{%> <img src="/public/admin/images/no.gif" onclick=“app.changeStatus(this,‘GoodsCate’,‘status’,’<%=list[i]._id%>’)” /> <%}%> </td> <td class=“text-center”> <a href="/admin/goodsCate/edit?id=<%=list[i]._id%>">修改</a> <a class=“delete” href="/admin/delete?model=GoodsCate&id=<%=list[i]._id%>">删除</a></td> </tr> <%for(var j=0;j<list[i].items.length;j++){%> <tr> <td>—–<%=list[i].items[j].title%></td> <td><img class=“pic” src="<%=list[i].items[j].cate_img%>" /></td> <td class=“text-center”><span onclick=“app.editNum(this,‘GoodsCate’,‘sort’,’<%=list[i].items[j]._id%>’)"><%=list[i].items[j].sort%></span></td> <td class=“text-center”> <%if(list[i].status==1){%> <img src="/public/admin/images/yes.gif” onclick=“app.changeStatus(this,‘GoodsCate’,‘status’,’<%=list[i].items[j]._id%>’)” /> <%}else{%> <img src="/public/admin/images/no.gif" onclick=“app.changeStatus(this,‘GoodsCate’,‘status’,’<%=list[i].items[j]._id%>’)” /> <%}%> </td> <td class=“text-center”> <a href="/admin/goodsCate/edit?id=<%=list[i].items[j]._id%>">修改</a> <a class=“delete” href="/admin/delete?model=GoodsCate&id=<%=list[i].items[j]._id%>">删除</a></td> </tr> <%}%> <%}%> </tbody> </table> </div> </div> </div></body></html>效果红色为一级分类蓝色为二级分类上传图片时,生成多种分辨率的图片安装依赖cnpm install jimp –saveserviceapp/service/tools.jsresize 尺寸quality 质量write 名字const Jimp = require(“jimp”); //生成缩略图的模块 async jimpImg(target){ //上传图片成功以后生成缩略图 Jimp.read(target, (err, lenna) => { if (err) throw err; lenna.resize(200, 200) // 尺寸 .quality(90) // 质量 .write(target+’_200x200’+path.extname(target)); // save lenna.resize(400, 400) // 尺寸 .quality(90) // 质量 .write(target+’_400x400’+path.extname(target)); // save }); }controllerapp/controller/admin/goodsCate.js增加一行this.service.tools.jimpImg(target); async doAdd(){ let parts = this.ctx.multipart({ autoFields: true }); let files = {}; let stream; while ((stream = await parts()) != null) { if (!stream.filename) { break; } let fieldname = stream.fieldname; //file表单的名字 //上传图片的目录 let dir=await this.service.tools.getUploadFile(stream.filename); let target = dir.uploadDir; let writeStream = fs.createWriteStream(target); await pump(stream, writeStream); files=Object.assign(files,{ [fieldname]:dir.saveDir }) this.service.tools.jimpImg(target); } if(parts.field.pid!=0){ parts.field.pid=this.app.mongoose.Types.ObjectId(parts.field.pid); //调用mongoose里面的方法把字符串转换成ObjectId } let goodsCate =new this.ctx.model.GoodsCate(Object.assign(files,parts.field)); await goodsCate.save(); await this.success(’/admin/goodsCate’,‘增加分类成功’); }效果编辑controllerapp/controller/admin/goodsCate.jsviewapp/view/admin/goodsCate/edit.html<%- include ../public/page_header.html %> <div class=“panel panel-default”> <div class=“panel-heading”> 编辑商品分类 </div> <div class=“panel-body”> <div class=“table-responsive input-form”> <form action="/admin/goodsCate/doEdit?_csrf=<%=csrf%>" method=“post” enctype=“multipart/form-data”> <ul class=“form_input”> <input type=“hidden” name=“id” value="<%=list._id%>" /> <li> <span>分类名称:</span> <input type=“text” name=“title” class=“input” value="<%=list.title%>"/></li> <li> <span>上级分类:</span> <select name=“pid” id=“pid”> <option value=“0”>顶级分类</option> <%for(var i=0;i<cateList.length;i++){%> <option value="<%=cateList[i]._id%>" <%if(cateList[i]._id.toString()==list.pid){%> selected <%}%>><%=cateList[i].title%></option> <%}%> </select> </li> <li> <span>分类图片:</span> <input type=“file” name=“cate_img”/> <br /> <span> </span> <img class=“pic” src="<%=list.cate_img%>" /> </li> <li> <span>筛选属性:</span> <input type=“text” name=“filter_attr” class=“input” value="<%=list.filter_attr%>"/></li> <li> <span>跳转地址:</span> <input type=“text” name=“link” class=“input” value="<%=list.link%>" /></li> <li> <span>分类模板:</span> <input type=“text” name=“template” class=“input” value="<%=list.template%>" /><span>空表示默认模板</span></li> <li> <span>Seo标题:</span> <input type=“text” name=“sub_title” class=“input” value="<%=list.sub_title%>"/></li> <li> <span>Seo关键词: </span><input type=“text” name=“keywords” class=“input” value="<%=list.keywords%>"/></li> <li> <span>Seo描述:</span> <textarea name=“description” id=“description” cols=“84” rows=“4”><%=list.description%></textarea></li> <li> <span>排 序:</span> <input type=“text” name=“sort” value="<%=list.sort%>"/></li> <li> <span>状 态:</span> <input type=“radio” name=“status” <%if(list.status==1){%> checked <%}%> value=“1” id=“a”/> <label for=“a”>显示</label> <input type=“radio” <%if(list.status==0){%> checked <%}%> name=“status” value=“0” id=“b”/><label for=“b”>隐藏</label> </li> <li> <br/> <button type=“submit” class=“btn btn-primary”>提交</button> </li> </ul> </form> </div> </div> </div></body></html>