Node + MongoDB 建站 2期 -3

实现分类功能

1 设计分类数据模型

创建category schema及model

var mongoose = require('mongoose')
var Schema = mongoose.Schema
var ObjectId = Schema.Types.ObjectId

var CategorySchema = new Schema({
  name: String,
  movies: [{type: ObjectId, ref: 'Movie'}],
  meta: {
    createAt: {
      type: Date,
      default: Date.now()
    },
    updateAt: {
      type: Date,
      default: Date.now()
    }
  }
})

// var ObjectId = mongoose.Schema.Types.ObjectId
CategorySchema.pre('save', function(next) {
  if (this.isNew) {
    this.meta.createAt = this.meta.updateAt = Date.now()
  }
  else {
    this.meta.updateAt = Date.now()
  }

  next()
})

CategorySchema.statics = {
  fetch: function(cb) {
    return this
      .find({})
      .sort('meta.updateAt')
      .exec(cb)
  },
  findById: function(id, cb) {
    return this
      .findOne({_id: id})
      .exec(cb)
  }
}

module.exports = CategorySchema

2 分类后台录入及分类存储

修改index jade如下,添加分类panel

extends ../layout
block content
  .container
    .row
      each cat in categories
        .panel.panel-default
          .panel-heading
            h3
              a(href='/results?cat=#{cat._id}&p=0') #{cat.name}
          .panel-body
            if cat.movies && cat.movies.length > 0
              each item in movies
                .col-md-2
                    .thumbnail
                        a(href="/movie/#{item._id}")
                          if item.poster.indexOf('http:') > -1
                            img(src="#{item.poster}", alt="#{item.title}")
                          else
                            img(src="/upload/#{item.poster}", alt="#{item.title}")
                        .caption
                              h4 #{item.title}
                              p: a.btn.btn-primary(href="/movie/#{item._id}", role="button") 观看预告片

修改index controller如下,查询categories

var mongoose = require('mongoose')
var Movie = require('../models/movie')
var Category = require('../models/Category')

exports.index = function(req, res) {
    Category
    .find({})
    .populate({
      path: 'movies',
      select: 'title poster',
      options: { limit: 6 }
    })
    .exec(function(err, categories) {
      if (err) {
        console.log(err)
      }
      res.render('index', {
        title: 'imooc 首页',
        categories: categories
      })
    })
}

admin jade中添加分类label

        .form-group
          label.col-sm-2.control-label(for="inputCategory") 电影分类
          .col-sm-10
            input#inputCategory.form-control(type="text", name="movie[category]", value=movie.category)

movie schema中添加category字段

var ObjectId = Schema.Types.ObjectId
......
  category: {
    type: ObjectId,
    ref: 'Category'
  },

创建分类录入页category_admin jade

extends ../layout

block content
  .container
    .row
      form.form-horizontal(method="post", action="/admin/category")
        .form-group
          label.col-sm-2.control-label(for="inputCategory") 电影分类
          .col-sm-10
            input#inputCategory.form-control(type="text", name="category[name]", value=category.name)
        .form-group
          .col-sm-offset-2.col-sm-10
          button.btn.btn-default(type="submit") 录入

添加category controller,保存后到category list页面

var mongoose = require('mongoose')
var Category = mongoose.model('Category')

// admin new page
exports.new = function(req, res) {
  res.render('category_admin', {
    title: 'imooc 后台分类录入页',
    category: {}
  })
}

// admin post movie
exports.save = function(req, res) {
  var _category = req.body.category
  var category = new Category(_category)

  category.save(function(err, category) {
    if (err) {
      console.log(err)
    }

    res.redirect('/admin/category/list')
  })
}
// catelist page
exports.list = function(req, res) {
  Category.fetch(function(err, catetories) {
    if (err) {
      console.log(err)
    }

    res.render('categorylist', {
      title: 'imooc 分类列表页',
      catetories: catetories
    })
  })
}

创建category list页面

extends ../layout

block content
  .container
    .row
      table.table.table-hover.table-bordered
        thead
          tr
            th 名字
            th 时间
            th 查看
            th 修改
            th 删除
        tbody
          each item in catetories
            tr(class="item-id-#{item._id}")
              td #{item.name}
              td #{moment(item.meta.updateAt).format('MM/DD/YYYY')}
              td: a(target="_blank", href="../movie/#{item._id}") 查看
              td: a(target="_blank", href="../admin/update/#{item._id}") 修改
              td
                button.btn.btn-danger.del(type="button", data-id="#{item._id}") 删除

在routes中添加路由

var Category = require('../app/controllers/category')
  // Category
app.get('/admin/category/new', User.signinRequired, User.adminRequired, Category.new)
app.post('/admin/category', User.signinRequired, User.adminRequired, Category.save)
app.get('/admin/category/list', User.signinRequired, User.adminRequired, Category.list)

3 电影录入增加分类

添加分类选择

        .form-group
          label.col-sm-2.control-label(for="inputCategory") 电影分类
          .col-sm-10
            input#inputCategory.form-control(type="text", name="movie[categoryName]", value=movie.categoryName)
        .form-group
          label.col-sm-2.control-label 分类选择
          each cat in categories
            label.radio-inline
              if movie.category
                input(type="radio", name="movie[category]", value=cat._id, checked=cat._id.toString()==movie.category.toString())
              else
                input(type="radio", name="movie[category]", value=cat._id)
              | #{cat.name}

修改movie controller

var Category = require('../models/category')
......
exports.new =  function(req, res) {
    Category.find({},function(err,categories){
        res.render('admin', {
            title: 'imooc 后台录入页',
            categories: categories,
            movie: {}
        })
    })
}

通过/admin/category/new新建分类,查看后台录入页


修改movie controller的save方法,同时保存category

    } else {
        _movie = new Movie(movieObj)
        var categoryId = movieObj.category
        _movie.save(function(err,movie) {
            if (err){
                console.log(err)
            }
            if (categoryId) {
                Category.findById(categoryId, function(err, category) {
                category.movies.push(movie._id)

                category.save(function(err, category) {
                    res.redirect('/movie/' + movie._id)
                })
                })
            }         
        })

修改update方法,获取categories

    if (id) {
        Movie.findById(id,function(err,movie){
            Category.find({}, function(err, categories) {
                res.render('admin', {
                title: 'imooc 后台更新页',
                movie: movie,
                categories: categories
                })
            })
        })
    }

4 获取豆瓣API数据

豆瓣电影的API地址格式为 https://api.douban.com/v2/movie/subject/+id
在admin.js中添加

    $('#douban').blur(function() {
      var douban = $(this)
      var id = douban.val()
  
      if (id) {
        $.ajax({
          url: 'https://api.douban.com/v2/movie/subject/' + id,
          cache: true,
          type: 'get',
          dataType: 'jsonp',
          crossDomain: true,
          jsonp: 'callback',
          success: function(data) {
            $('#inputTitle').val(data.title)
            $('#inputDoctor').val(data.directors[0].name)
            $('#inputCountry').val(data.countries[0])
            $('#inputPoster').val(data.images.large)
            $('#inputYear').val(data.year)
            $('#inputSummary').val(data.summary)
          }
        })
      }
    })

在admin jade中引入js,添加输入框

        .form-group
          label.col-sm-2.control-label 豆瓣同步
          .col-sm-10
            input#douban.form-control(type="text")
......
  script(src='/js/admin.js')

启动服务,输入豆瓣id,点击空白处,将调用ajax请求自动填写数据


5 电影录入增加分类自定义

修改movie controller的save方法,当输入分类名称时,新建category并save,save成功同时更新movie对象的category

    } else {
        _movie = new Movie(movieObj)
        var categoryId = movieObj.category
        var categoryName = movieObj.categoryName
        _movie.save(function(err,movie) {
            if (err){
                console.log(err)
            }
            if (categoryId) {
                Category.findById(categoryId, function(err, category) {
                category.movies.push(movie._id)

                category.save(function(err, category) {
                    res.redirect('/movie/' + movie._id)
                })
                })
            }
            else if (categoryName) {
                var category = new Category({
                name: categoryName,
                movies: [movie._id]
                })

                category.save(function(err, category) {
                movie.category = category._id
                movie.save(function(err, movie) {
                    res.redirect('/movie/' + movie._id)
                })
                })
            }

6 添加分类列表及分页

添加results.jade

extends ../layout

block content
  .container
    .row
      .panel.panel-default
        .panel-heading
          h3 #{keyword}
        .panel-body
          if movies && movies.length > 0
            each item in movies
              .col-md-2
                .thumbnail
                  a(href="/movie/#{item._id}")
                    img(src="#{item.poster}", alt="#{item.title}")
                  .caption
                    h4 #{item.title}
                    p: a.btn.btn-primary(href="/movie/#{item._id}", role="button") 观看预告片
      ul.pagination
        - for (var i = 0; i < totalPage; i++) {
          - if (currentPage == (i + 1)) {
              li.active
                span #{currentPage}
          - }
          - else {
              li
                a(href='/results?#{query}&p=#{i}') #{i + 1}
          - }
        - }

在route中添加路由app.get('/results', Index.search)
在index controller中添加search方法

// search page
exports.search = function(req, res) {
  var catId = req.query.cat
  var q = req.query.q
  var page = req.query.p
  var count = 2
  var index = page * count

  if (catId) {
    Category
      .find({_id: catId})
      .populate({
        path: 'movies',
        select: 'title poster'
      })
      .exec(function(err, categories) {
        if (err) {
          console.log(err)
        }
        var category = categories[0] || {}
        var movies = category.movies || []
        var results = movies.slice(index, index + count)

        res.render('results', {
          title: 'imooc 结果列表页面',
          keyword: category.name,
          currentPage: (page + 1),
          query: 'cat=' + catId,
          totalPage: Math.ceil(movies.length / count),
          movies: results
        })
      })
  }

查看结果


7 添加搜索功能

修改header jade,添加搜索框

  .row
    .page-header.clearfix
      h1= title
      .col-md-4
        small 重度科幻迷
      .col-md-8
        form(method='GET', action='/results')
          .input-group.col-sm-4.pull-right
            input.form-control(type='text', name='q')
            span.input-group-btn
              button.btn.btn-default(type='submit') 搜索

在search方法中根据catId判断,通过搜索框是通过Movie来找,通过new RegExp(q + '.*', 'i')来模糊匹配

// search page
exports.search = function(req, res) {
  var catId = req.query.cat
  var q = req.query.q
  // var page = parseInt(req.query.p, 10) || 0
  var page = req.query.p
  var count = 3
  var index = page * count

  if (catId) {
    Category
      .find({_id: catId})
      .populate({
        path: 'movies',
        select: 'title poster'
      })
      .exec(function(err, categories) {
        if (err) {
          console.log(err)
        }
        var category = categories[0] || {}
        var movies = category.movies || []
        var results = movies.slice(index, index + count)

        res.render('results', {
          title: 'imooc 结果列表页面',
          keyword: category.name,
          currentPage: (page + 1),
          query: 'cat=' + catId,
          totalPage: Math.ceil(movies.length / count),
          movies: results
        })
      })
  }
  else {
    Movie
      .find({title: new RegExp(q + '.*', 'i')})
      .exec(function(err, movies) {
        if (err) {
          console.log(err)
        }
        var results = movies.slice(index, index + count)

        res.render('results', {
          title: 'imooc 结果列表页面',
          keyword: q,
          currentPage: (page + 1),
          query: 'q=' + q,
          totalPage: Math.ceil(movies.length / count),
          movies: results
        })
      })
  }
}

8 海报上传功能

添加文件上传组件,form添加enctype属性

form.form-horizontal(method="post", action="/admin/movie/new"  enctype="multipart/form-data")
        .form-group
          label.col-sm-2.control-label(for="uploadPoster") 海报上传
          .col-sm-10
            input#uploadPoster(type="file", name="uploadPoster")

movie controller中添加savePoster,save方法中更新poster

// admin poster
exports.savePoster = function(req, res, next) {
  var posterData = req.files.uploadPoster
  var filePath = posterData.path
  var originalFilename = posterData.originalFilename

  if (originalFilename) {
    fs.readFile(filePath, function(err, data) {
      var timestamp = Date.now()
      var type = posterData.type.split('/')[1]
      var poster = timestamp + '.' + type
      var newPath = path.join(__dirname, '../../', '/public/upload/' + poster)

      fs.writeFile(newPath, data, function(err) {
        req.poster = poster
        next()
      })
    })
  }
  else {
    next()
  }
}
exports.save =  function(req,res){
    var id = req.body.movie._id
    var movieObj = req.body.movie
    var _movie

    if (req.poster) {
        movieObj.poster = req.poster
      }

安装connect-multiparty npm i connect-multiparty --save-dev,并在app js中添加

var multipart = require('connect-multiparty');
app.use(multipart());

8 添加PV统计

在list jade中添加PV列,movie schema中添加PV

  pv: {
    type: Number,
    default: 0
  },

在movie controller中,detail中更新movie,每次进入detail页面PV将增加1

  Movie.update({_id: id}, {$inc: {pv: 1}}, function(err) {
    if (err) {
      console.log(err)
    }
  })
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容