本章节主要讲食品列表,具体内容包括:根据分类获取列表、默认显示全部数据、添加食品信息、修改食品信息、删除食品信息、查询食品信息、获取食品总数、食品数据分页等等,可以说它把大部分内容都包揽进去了,读者通过这个模块,可以更加清晰的知道nodejs+es6/7的用法。
创建食品数据模型
我们在web->models文件夹下创建food.js,具体内容如下:
'use strict';
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
const foodSchema = new Schema({
rating: {type: Number,default: 0},
is_featured: {type: Number, default: 0},
restaurant_id: {type: Number,isRequired: true}, //isRequired,说明该字段不能为空
category_id: {type: Number, isRequired: true},
pinyin_name: {type: String, default: ''},
display_times: {type: Array, default: []},
attrs: {type: Array, default:[]},
description: {type: String, default: ""},
month_sales: {type: Number, default: 0},
rating_count: {type: Number, default: 0},
tips: String,
image_path: String,
specifications: [Schema.Types.Mixed],
server_utc: {type: Date, default: Date.now()},
is_essential: {type: Boolean, default: false},
attributes: {type: Array, default: []},
item_id: {type: Number, isRequired: true},
limitation: Schema.Types.Mixed,
name: {type: String, isRequired: true},
satisfy_count: {type: Number, default: 0},
activity: Schema.Types.Mixed,
satisfy_rate: {type: Number, default: 0},
specfoods: [{
original_price: {type: Number, default: 0},
sku_id: {type: Number, isRequired: true},
name: {type: String, isRequired: true},
pinyin_name: {type: String, default: ""},
restaurant_id: {type: Number, isRequired: true},
food_id: {type: Number, isRequired: true},
packing_fee: {type: Number, default: 0},
recent_rating: {type: Number, default: 0},
promotion_stock: {type: Number, default: -1},
price: {type: Number, default: 0},
sold_out: {type: Boolean, default: false},
recent_popularity: {type: Number, default: 0},
is_essential: {type: Boolean, default: false},
item_id: {type: Number, isRequired: true},
checkout_mode: {type: Number, default: 1},
stock: {type: Number, default: 1000},
specs_name: String,
specs: [
{
name: String,
value: String
}
]
}]
});
foodSchema.index({item_id: 1});
const menuSchema = new Schema({
description: String,
is_selected: {type:Boolean, default: true},
icon_url: {type: String, default: ''},
name: {type: String, isRequired: true},
id: {type: Number, isRequired: true},
restaurant_id: {type: Number, isRequired: true},
type: {type: Number, default: 1},
foods: [foodSchema]
});
menuSchema.index({id: 1});
const Food = mongoose.model('Food',foodSchema);
const Menu = mongoose.model('Menu',menuSchema);
export {Food, Menu}
创建商铺模型
因为商铺需要与食品关联,所以我们需要创建这个模型,我们在web->models文件夹下创建shop.js文件,具体代码如下:
'use strict';
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
const shopSchema = new Schema({
activities: [{
description: String,
icon_color: String,
icon_name: String,
id: Number,
name: String
}],
address: String,
delivery_mode: {
color: String,
id: Number,
is_solid: Boolean,
text: String,
},
description: {type: String, default:''},
order_lead_time: {type: String, default: ''},
distance: {type: String, default: ''},
location: {type:[Number],index: '2d'},
float_delivery_fee: {type: Number, default: 0},
float_minimum_order_amount: {type: Number, default: 0},
id: Number,
category: String,
identification: {
company_name: {type: String, default: ''},
identificate_agency: { type: String, default: "" },
identificate_date: { type: Date, default: Date.now },
legal_person: { type: String, default: "" },
licenses_date: { type: String, default: "" },
licenses_number: { type: String, default: "" },
licenses_scope: { type: String, default: "" },
operation_period: { type: String, default: "" },
registered_address: { type: String, default: "" },
registered_number: { type: String, default: "" },
},
image_path: {type: String,default: ''},
is_premium: {type: Boolean, default: false},
is_new: {type: Boolean, default: false},
latitude: Number,
longitude: Number,
license: {
business_license_image: { type: String, default: "" },
catering_service_license_image: { type: String, default: "" },
},
name: {type: String, required: true},
opening_hours: {type: Array, default:["08:30/20:30"]},
phone: {type: String, required: true},
piecewise_agent_fee: {tips: String},
promotion_info: { type: String, default: "欢迎光临,用餐高峰请提前下单,谢谢" },
rating: { type: Number, default: 0 },
rating_count: { type: Number, default: 0 },
recent_order_num: { type: Number, default: 0 },
status: { type: Number, default: 0 },
supports: [{
description: String,
icon_color: String,
icon_name: String,
id: Number,
name: String
}],
});
shopSchema.index({id: 1});
const Shop = mongoose.model('Shop',shopSchema);
export default Shop;
编写食品控制器
这是本章的重点,代码稍微长了点,希望读者好好研究一下。我们在web->controller->shop文件夹下创建food.js文件,具体代码如下:
import {Food as FoodModel, Menu as MenuModel} from '../../models/shop/food';
import ShopModel from '../../models/shop/shop';
import BaseComponent from '../../prototype/baseComponent';
import formidable from 'formidable';
class Food extends BaseComponent {
constructor(){
super();
this.defaultData = [{
name: '热销榜',
description: '大家喜欢吃,才叫真好吃。',
icon_url: "5da3872d782f707b4c82ce4607c73d1ajpeg",
is_selected: true,
type: 1,
foods: [],
}, {
name: '优惠',
description: '美味又实惠, 大家快来抢!',
icon_url: "4735c4342691749b8e1a531149a46117jpeg",
type: 1,
foods: [],
}]
this.initData = this.initData.bind(this);
this.addFood = this.addFood.bind(this);
this.getCategory = this.getCategory.bind(this);
this.addCategory = this.addCategory.bind(this);
this.getSpecfoods = this.getSpecfoods.bind(this);
this.updateFood = this.updateFood.bind(this);
}
//数据初始化
async initData(restaurant_id){
for (let i = 0; i < this.defaultData.length; i++) {
let category_id;
try{
category_id = await this.getId('category_id');
}catch(err){
console.log('获取category_id失败');
throw new Error(err);
}
const defaultData = this.defaultData[i];
const Category = {...defaultData, id: category_id, restaurant_id};
const newFood = new MenuModel(Category);
try{
await newFood.save();
console.log('初始化食品数据成功');
}catch(err){
console.log('初始化食品数据失败');
throw new Error(err);
}
}
}
//获取食品种类
async getCategory(req, res, next){
const restaurant_id = req.params.restaurant_id; //restaurant_id为需要传递的参数
try{
const category_list = await MenuModel.find({restaurant_id});
res.send({
status: 1,
category_list,
})
}catch(err){
console.log('获取餐馆食品种类失败');
res.send({
status: 0,
type: 'ERROR_GET_DATA',
message: '获取数据失败'
})
}
}
//添加食品种类
async addCategory(req, res, next){
const form = new formidable.IncomingForm();
form.parse(req, async (err, fields, files) => {
try{
if (!fields.name) {
throw new Error('必须填写食品类型名称');
}else if(!fields.restaurant_id){
throw new Error('餐馆ID错误');
}
}catch(err){
console.log(err.message, err);
res.send({
status: 0,
type: 'ERROR_PARAMS',
message: err.message
})
return
}
let category_id;
try{
category_id = await this.getId('category_id');
}catch(err){
console.log('获取category_id失败');
res.send({
type: 'ERROR_DATA',
message: '获取数据失败'
})
return
}
const foodObj = {
name: fields.name,
description: fields.description,
restaurant_id: fields.restaurant_id,
id: category_id,
foods: [],
}
const newFood = new MenuModel(foodObj);
try{
await newFood.save();
res.send({
status: 1,
success: '添加食品种类成功',
})
}catch(err){
console.log('保存数据失败');
res.send({
status: 0,
type: 'ERROR_IN_SAVE_DATA',
message: '保存数据失败',
})
}
})
}
//添加食品
async addFood(req, res, next){
const form = new formidable.IncomingForm();
form.parse(req, async (err, fields, files) => {
try{
if (!fields.name) {
throw new Error('必须填写食品名称');
}else if(!fields.image_path){
throw new Error('必须上传食品图片');
}else if(!fields.specs.length){
throw new Error('至少填写一种规格');
}else if(!fields.category_id){
throw new Error('食品类型ID错误');
}else if(!fields.restaurant_id){
throw new Error('餐馆ID错误');
}
}catch(err){
console.log('前台参数错误', err.message);
res.send({
status: 0,
type: 'ERROR_PARAMS',
message: err.message
})
return
}
let category;
let restaurant;
try{
category = await MenuModel.findOne({id: fields.category_id});
restaurant = await ShopModel.findOne({id: fields.restaurant_id});
}catch(err){
console.log('获取食品类型和餐馆信息失败');
res.send({
status: 0,
type: 'ERROR_DATA',
message: '添加食品失败'
})
return
}
let item_id;
try{
item_id = await this.getId('item_id');
}catch(err){
console.log('获取item_id失败');
res.send({
status: 0,
type: 'ERROR_DATA',
message: '添加食品失败'
})
return
}
const rating_count = Math.ceil(Math.random()*1000);
const month_sales = Math.ceil(Math.random()*1000);
const tips = rating_count + "评价 月售" + month_sales + "份";
const newFood = {
name: fields.name,
description: fields.description,
image_path: fields.image_path,
activity: null,
attributes: [],
restaurant_id: fields.restaurant_id,
category_id: fields.category_id,
satisfy_rate: Math.ceil(Math.random()*100),
satisfy_count: Math.ceil(Math.random()*1000),
item_id,
rating: (4 + Math.random()).toFixed(1),
rating_count,
month_sales,
tips,
specfoods: [],
specifications: [],
}
if (fields.activity) {
newFood.activity = {
image_text_color: 'f1884f',
icon_color: 'f07373',
image_text: fields.activity,
}
}
if (fields.attributes.length) {
fields.attributes.forEach(item => {
let attr;
switch(item){
case '新':
attr = {
icon_color: '5ec452',
icon_name: '新'
}
break;
case '招牌':
attr = {
icon_color: 'f07373',
icon_name: '招牌'
}
break;
}
newFood.attributes.push(attr);
})
}
try{
const [specfoods, specifications] = await this.getSpecfoods(fields, item_id);
newFood.specfoods = specfoods;
newFood.specifications = specifications;
}catch(err){
console.log('添加specs失败', err);
res.send({
status: 0,
type: 'ERROR_DATA',
message: '添加食品失败'
})
return
}
try{
const foodEntity = await FoodModel.create(newFood);
category.foods.push(foodEntity);
category.markModified('foods');
await category.save();
res.send({
status: 1,
success: '添加食品成功',
});
}catch(err){
console.log('保存食品到数据库失败', err);
res.send({
status: 0,
type: 'ERROR_DATA',
message: '添加食品失败'
})
}
})
}
//获取符合要求的食品
async getSpecfoods(fields, item_id){
let specfoods = [], specifications = [];
if (fields.specs.length < 2) {
let food_id, sku_id;
try{
sku_id = await this.getId('sku_id');
food_id = await this.getId('food_id');
}catch(err){
throw new Error('获取sku_id、food_id失败')
}
specfoods.push({
packing_fee: fields.specs[0].packing_fee,
price: fields.specs[0].price,
specs: [],
specs_name: fields.specs[0].specs,
name: fields.name,
item_id,
sku_id,
food_id,
restaurant_id: fields.restaurant_id,
recent_rating: (Math.random()*5).toFixed(1),
recent_popularity: Math.ceil(Math.random()*1000),
})
}else{
specifications.push({
values: [],
name: "规格"
})
for (let i = 0; i < fields.specs.length; i++) {
let food_id, sku_id;
try{
sku_id = await this.getId('sku_id');
food_id = await this.getId('food_id');
}catch(err){
throw new Error('获取sku_id、food_id失败')
}
specfoods.push({
packing_fee: fields.specs[i].packing_fee,
price: fields.specs[i].price,
specs: [{
name: "规格",
value: fields.specs[i].specs
}],
specs_name: fields.specs[i].specs,
name: fields.name,
item_id,
sku_id,
food_id,
restaurant_id: fields.restaurant_id,
recent_rating: (Math.random()*5).toFixed(1),
recent_popularity: Math.ceil(Math.random()*1000),
})
specifications[0].values.push(fields.specs[i].specs);
}
}
return [specfoods, specifications]
}
//获取菜单
async getMenu(req, res, next){
const restaurant_id = req.query.restaurant_id;
const allMenu = req.query.allMenu;
if (!restaurant_id || !Number(restaurant_id)) {
console.log('获取餐馆参数ID错误');
res.send({
status: 0,
type: 'ERROR_PARAMS',
message: '餐馆ID参数错误',
})
return
}
let filter;
if (allMenu) {
filter = {restaurant_id}
}else{
filter = {restaurant_id, $where: function(){return this.foods.length}};
}
try{
const menu = await MenuModel.find(filter, '-_id');
res.send(menu);
}catch(err){
console.log('获取食品数据失败', err);
res.send({
status: 0,
type: 'GET_DATA_ERROR',
message: '获取食品数据失败'
})
}
}
//获取菜单详细页
async getMenuDetail(req, res, next){
const category_id = req.params.category_id;
if (!category_id || !Number(category_id)) {
console.log('获取Menu详情参数ID错误');
res.send({
status: 0,
type: 'ERROR_PARAMS',
message: 'Menu ID参数错误',
})
return
}
try{
const menu = await MenuModel.findOne({id: category_id}, '-_id');
res.send(menu)
}catch(err){
console.log('获取Menu详情失败', err);
res.send({
status: 0,
type: 'GET_DATA_ERROR',
message: '获取Menu详情失败'
})
}
}
//获取食品列表
async getFoods(req, res, next){
const {restaurant_id, limit = 20, offset = 0} = req.query; //分页设置
try{
let filter = {};
if (restaurant_id && Number(restaurant_id)) {
filter = {restaurant_id}
}
const foods = await FoodModel.find(filter, '-_id').sort({item_id: -1}).limit(Number(limit)).skip(Number(offset));
res.send(foods);
}catch(err){
console.log('获取食品数据失败', err);
res.send({
status: 0,
type: 'GET_DATA_ERROR',
message: '获取食品数据失败'
})
}
}
//获取食品总数
async getFoodsCount(req, res, next){
const restaurant_id = req.query.restaurant_id;
try{
let filter = {};
if (restaurant_id && Number(restaurant_id)) {
filter = {restaurant_id}
}
const count = await FoodModel.find(filter).count();
res.send({
status: 1,
count,
})
}catch(err){
console.log('获取食品数量失败', err);
res.send({
status: 0,
type: 'ERROR_TO_GET_COUNT',
message: '获取食品数量失败'
})
}
}
//修改食品信息
async updateFood(req, res, next){
const form = new formidable.IncomingForm();
form.parse(req, async (err, fields, files) => {
if (err) {
console.log('获取食品信息form出错', err);
res.send({
status: 0,
type: 'ERROR_FORM',
message: '表单信息错误',
})
return
}
const {name, item_id, description = "", image_path, category_id, new_category_id} = fields;
try{
if (!name) {
throw new Error('食品名称错误');
}else if(!item_id || !Number(item_id)){
throw new Error('食品ID错误');
}else if(!category_id || !Number(category_id)){
throw new Error('食品分类ID错误');
}else if(!image_path){
throw new Error('食品图片地址错误');
}
const [specfoods, specifications] = await this.getSpecfoods(fields, item_id);
let newData;
if (new_category_id !== category_id) {
newData = {name, description, image_path, category_id: new_category_id, specfoods, specifications};
const food = await FoodModel.findOneAndUpdate({item_id}, {$set: newData});
const menu = await MenuModel.findOne({id: category_id})
const targetmenu = await MenuModel.findOne({id: new_category_id})
let subFood = menu.foods.id(food._id);
subFood.set(newData)
targetmenu.foods.push(subFood)
targetmenu.markModified('foods');
await targetmenu.save()
await subFood.remove()
await menu.save()
}else{
newData = {name, description, image_path, specfoods, specifications};
const food = await FoodModel.findOneAndUpdate({item_id}, {$set: newData});
const menu = await MenuModel.findOne({id: category_id})
let subFood = menu.foods.id(food._id);
subFood.set(newData)
await menu.save()
}
res.send({
status: 1,
success: '修改食品信息成功',
})
}catch(err){
console.log(err.message, err);
res.send({
status: 0,
type: 'ERROR_UPDATE_FOOD',
message: '更新食品信息失败',
})
}
})
}
//删除食品
async deleteFood(req, res, next){
const food_id = req.params.food_id;
if (!food_id || !Number(food_id)) {
console.log('food_id参数错误');
res.send({
status: 0,
type: 'ERROR_PARAMS',
message: 'food_id参数错误',
})
return
}
try{
const food = await FoodModel.findOne({item_id: food_id});
const menu = await MenuModel.findOne({id: food.category_id})
let subFood = menu.foods.id(food._id);
await subFood.remove()
await menu.save()
await food.remove()
res.send({
status: 1,
success: '删除食品成功',
})
}catch(err){
console.log('删除食品失败', err);
res.send({
status: 0,
type: 'DELETE_FOOD_FAILED',
message: '删除食品失败',
})
}
}
}
export default new Food();
编写路由
下面我们编写路由信息。我们在routes文件夹下创建shop.js文件,然后把我们的食品控制器加进去,具体代码如下:
import express from 'express';
import Shop from '../web/controller/shop/shop';
import Food from '../web/controller/shop/food';
import Check from '../web/middlewares/check'; //这是判断用户是否登录的文件,在前面的章节中我们已经写好了
const router = express.Router();
router.post('/addshop', Check.checkAdmin, Shop.addShop);
router.get('/restaurants', Shop.getRestaurants);
router.get('/restaurants/count', Shop.getShopCount);
router.post('/updateshop', Check.checkAdmin, Shop.updateshop);
router.delete('/restaurant/:restaurant_id', Check.checkSuperAdmin, Shop.deleteResturant);
router.get('/restaurant/:restaurant_id', Shop.getRestaurantDetail);
router.post('/addfood', Check.checkAdmin, Food.addFood);
router.get('/getcategory/:restaurant_id', Food.getCategory);
router.post('/addcategory', Check.checkAdmin, Food.addCategory);
router.get('/v2/menu', Food.getMenu);
router.get('/v2/menu/:category_id', Food.getMenuDetail);
router.get('/v2/foods', Food.getFoods);
router.get('/v2/foods/count', Food.getFoodsCount);
router.post('/v2/updatefood', Check.checkAdmin, Food.updateFood);
router.delete('/v2/food/:food_id', Check.checkSuperAdmin, Food.deleteFood);
export default router
当路由写好后,我们就在控制台启动项目,启动命令:npm run dev。然后在浏览器中输入:http://127.0.0.1:3000/shop/v2/menu?restaurant_id=1,就能看到我们想要的效果了。截图如下:
相关章节
nodeJS开发一套完整的项目(1、基础配置)
nodeJS开发一套完整的项目(2、相关模块介绍)
nodeJS开发一套完整的项目(3、数据库链接和项目启动)
nodeJS开发一套完整的项目(4、编写底层功能模块)
nodeJS开发一套完整的项目(5、开发用户模块)
nodeJS开发一套完整的项目(6、省市县模块)
为了更好的服务大家,请加入我们的技术交流群:( 511387930),同时您也可以扫描下方的二维码关注我们的公众号,每天我们都会分享经验,谢谢大家。