Node.js 切近实战(四) 之图书管理系统(图书查询)

最近又当上了Master,负责带项目,有时候,遇到的问题我很郁闷。比如一个Story,需求中说的是将单个修改改为批量修改,举个例子,商品信息修改,之前是用一个商品id修改,但是现在改成多个商品id修改。我的意思是直接将文本框宽度高度加大,支持回车换行就行了,然后再将API修改为支持批量查询。这个界面上上面是一个Grid,下面是一个表单,选择Grid的数据后,会加载到下面表单。只能加载一条下去,就因为这个,有人提出如果加载一个下去,那么大个文本框只显示一个选中的商品id,视觉上无法接受。说是要将选择的单个和用于输入的多个文本框分开,控制一下显示隐藏的逻辑。这种做法会引来许多问题,大小文本框的隐藏显示会导致页面跳动,如果Grid查询无数据,初始化加载无数据,都要让大文本框显示。如果查询有数据,则grid数据会选中第一条,要显示小文本框。就因为这个问题,我感觉到做个Master真的是不容易,有时候就因为这些小问题达不成一致,让我很恼火。做管理不容易,我还要积累经验。

 

今天的话,我们来看一下图书管理系统的图书查询,首先我们先录入一批图书信息。

wKiom1dmz5_Au-guAABOCwNIpDM526.png

数据准备完成后,我们看一下页面代码。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

#book_retrieve(ng-controller='bookRetrieveCtrl')

 .row

  .col-md-4

   label Book Name:

    span.k-textbox.k-space-right(style='margin-left:5px;width:70%')

     input(type='text' ng-keydown='getBook($event)' ng-model='Search.BookName')

     a.k-icon.k-i-search(href="javascript:void(0)" ng-click='getBook()')

  .col-md-4

   label ISBN:

    span.k-textbox.k-space-right(style='margin-left:5px;width:70%')

     input(type='text' ng-keydown='getBook($event)' ng-model='Search.ISBN')

     a.k-icon.k-i-search(href='javascript:void(0)' ng-click='getBook()')

 hr.panel-line

 kendo-grid(options='bookGridOptions' k-data-source='BookList')

 div(kendo-window='modals' k-width='500' k-modal='true' k-visible='false' k-title='"Book Image Upload"' k-on-close='uploadClose()')

  .row.row-margin

   .col-md-11

     kendo-upload(type='file' name='files' k-multiple='false' k-success='onUploadSuccess' k-upload='upload' k-error='onUploadError' k-async='{saveUrl:"/book/upload", autoUpload: false}')

  

block scripts

  script(type='text/javascript' src='/javascripts/local/book/bookRetrieve.js')

由于51cto使用的百度的这个富文本编辑器不支持jade语法的高亮显示,所以我把图贴进来。

wKioL1dm0KiS_zveAADJdaU4Oak593.png

两个查询条件,BookName,ISBN,支持回车查询。

然后绑定数据,我们使用kendo-grid,绑定的datasource是BookList。然后我们定义了一个弹出页,用kendo-upload来上传图片,上传图片的路径为/book/upload。

 

ok,接着我们看一下js部分。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

var appModule = angular.module('bookRetrieveModule', ["kendo.directives"]);

appModule.config(function ($locationProvider) {

    $locationProvider.html5Mode(true);

});

 

appModule.controller('bookRetrieveCtrl'function ($scope, $http, $location) {

    Messenger.options = {

        extraClasses: 'messenger-fixed messenger-on-top messenger-on-center',

        theme: 'flat'

    }

     

    $scope.showMsg = function (type, msg) {

        Messenger().post({

            message: msg,

            type: type,

            hideAfter: 2,

            showCloseButton: true

        });

    }

     

    $scope.BookId = '';

    $scope.Search = {};

     

    $scope.uploadWindow = {

        open: function () {

            $scope.modals.center().open();

        }

    };

     

    $scope.bookGridOptions = {

        height: 700,

        sortable: true,

        pageable: {

            refresh: true,

            pageSizes: [10, 20, 50, 100],

            buttonCount: 5

        },

        resizable: true,

        selectable: "single",

        columns: [{

                template: "<div class='center-align-text'>" +

                    "<a href='/book/image/#=Image#'><img src='/book/image/#=Image#' class='img-inline'/></a></div>" ,

                field: "Image",

                title: "Image",

                width: 130

            }, {

                field: "Title",

                title: "Title"

            }, {

                field: "Author",

                title: "Author"

            }, {

                field: "Price",

                title: "Price",

                width: 60,

            }, {

                field: "ISBN",

                title: "ISBN"

            }, {

                field: "Press",

                title: "Press"

            }, {

                command: [

                    {

                        name: "Upload",

                        text: "Upload"

                        imageClass: 'k-icon k-insertImage',

                        click: function (e) {

                            var dataItem = this.dataItem($(e.currentTarget).closest("tr"));

                            $scope.BookId = dataItem._id;

                            $scope.uploadWindow.open();

                        }

                    }],

                width: 150,

            }], dataBound: function (rowBoundEvent) {

            $("div.center-align-text a").popImage();

        }

    }

     

    $scope.getBook = function (event) {

        if (!event || event.keyCode == 13) {

            $scope.BookList = new kendo.data.DataSource({

                "pageSize": 15,

                "serverPaging"true,

                transport: {

                    read: function (e) {

                        var url = '/book?pageIndex=' + (e.data.page-1) + '&pageSize=' + e.data.pageSize;

                        if ($scope.Search.BookName) {

                            url += '?bookName=' + $scope.Search.BookName

                        }

                         

                        if ($scope.Search.ISBN) {

                            url += '&ISBN=' + $scope.Search.ISBN;

                        }

                         

                        $http.get(url).success(function (data) {

                            e.success(data);

                        });

                    }

                },

                schema: {

                    data: function (dataset) {

                        return dataset.books || [];

                    },

                    total: function (dataset) {

                        return dataset.totalCount || 0;

                    }

                }

            });

        }

    }

     

    $scope.onUploadError = function (error) {

        $scope.showMsg('error', angular.fromJson(error.XMLHttpRequest.responseText).error);

    }

     

    $scope.onUploadSuccess = function (e) {

        var response = e.response;

        if (response.isSuc) {

            $scope.showMsg('success''Upload completed successfully!');

        }

        else {

            $scope.showMsg('error', response.msg);

        }

    }

     

    $scope.upload = function (e) {

        e.sender.options.async.saveUrl = "/book/upload?bookId=" + $scope.BookId;

    }

});

angular.element('#book_retrieve').data('$injector''');

angular.bootstrap(angular.element('#book_retrieve'), ['bookRetrieveModule']);

首先我们初始化一个messager,然后$scope.bookId用来记录要上传的图书的id,$scope.Search用来绑定两个查询条件。接着$scope.uploadWindow来初始化一个modal页,用于弹出上传图片modal页(div(kendo-window='modals')。接着我们定义了kendoGrid,注意这里的Command,拿到当前行绑定的id,然后赋给$scope.BookId,再弹出上传modal页。

wKioL1dm1bDwrrL5AAAjs6Ar8AM325.png

接下来是dataBound事件,即每绑定完成一行,就会触发这个事件,这里我们将div下所有的超链接让他支持弹出图片预览。

接下来的$scop.GetBook就是调用api查询了,没什么可说的。下面处理图片上传的回调方法也没什么好说的。

OK,我们接下来看一下服务端。

1

router.get('/book', bookRoutes.getBookList);

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

exports.getBookList = function (req, res) {

    var bookName = req.query.bookName;

    var ISBN = req.query.ISBN;

    var pageIndex = req.query.pageIndex;

    var pageSize = req.query.pageSize;

     

    var query = bookModel.find({});

    if (bookName) {

        query = query.where({ 'Title': { "$regex": bookName, "$options""i" } });

    }

     

    if (ISBN) {

        var regexp = new RegExp("^" + ISBN);

        query = query.where({ 'ISBN': regexp });

    }

     

    query.count().exec(function (error, count) {

        query.limit(pageSize)

        .skip(pageIndex * pageSize)

        .sort('-PressDate')

        .exec('find'function (error, doc) {

            res.json({ books: doc, totalCount: count });

        });

    });

}

按两个条件可以模糊查询。

wKioL1dm1inDi48NAAEZ_KjRPMQ278.png

无条件查询

wKiom1dm1ovhulxaAAFcmmVoD0A898.png

注意这里的暂无图片,如果/book/image/#=Image#能取到,则显示,否则显示默认图片。

1

router.get('/book/image/:id', bookRoutes.getBookImageById);

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

var fs = require('fs');

var Grid = require('gridfs-stream');

var gfs = new Grid(mongoose.connection.db, mongoose.mongo);

 

exports.getBookImageById = function (req, res) {

    gfs.exist({ _id: req.params.id }, function (error, exists) {

        if (!exists) {

            var rstream = fs.createReadStream('./public/images/noimage.jpg');

            rstream.pipe(res);

        }

        else {

            var readstream = gfs.createReadStream({

                _id: new mongoose.Types.ObjectId(req.params.id)

            });

             

            readstream.on('error'function (err) {

                console.log(err);

                res.send(500, err);

            });

             

            readstream.pipe(res);

        }

    })

}

在这里,大家应该还记得上篇文章中提到的book model的定义,Image是一个ObjectId,其实就是GridFs中存储的图片的id。所以在这里读取的时候,只需要传id,就会查出图片并向客户端输出文件流。

OK,最后我们看一下上传

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

var bookSchemas = require('../model/bookinfo.js');

var bookMsgRes = require('../framework/message/book_msg.js');

var validator = require('validator');

var fs = require('fs');

var Busboy = require('busboy');

var mongoose = require('mongoose');

var Grid = require('gridfs-stream');

 

var gfs = new Grid(mongoose.connection.db, mongoose.mongo);

exports.fileupload = function (req, res, next) {

    if (!/multipart\/form-data/i.test(req.headers['content-type'])) {

        return res.status(500).end('Wrong content type');

    }

     

    var busboy = new Busboy({

        headers: req.headers, limits: {

            files: 1,

            fileSize: 1024 * 1024 * 2

        }

    });

     

    busboy.on('file'function (fieldname, file, filename, encoding, mimetype) {

        if (mimetype != 'image/jpeg' 

            && mimetype != 'image/png' 

            && mimetype != 'image/bmp') {

            res.status(403).json({ isSuc: false, error: 'Can\'t upload(' + filename + ') if file\'s type is not image(jpg,png,bmp)!' });

            return;

        }

         

        var fileId = new mongoose.Types.ObjectId();

        var readstream = gfs.createWriteStream({

            _id: fileId,

            mode: 'w',

            content_type: mimetype,

            filename: filename,

            metadata: {

                uploaddate: Date.now()

            }

        });

         

        file.pipe(readstream);

        bookModel.findByIdAndUpdate(req.query.bookId, { $set: { Image: fileId } }, function (error, doc) { });

    });

     

    busboy.on('finish'function () {

        res.json({ isSuc: true });

    });

     

    busboy.on('error'function (err) {

        res.status(500).end({ isSuc: false, msg: err });

    });

     

    req.pipe(busboy);

};

在这里我们需要使用busBoy上传图片把并存储至gridfs,上传成功的话,修改book的Image字段。

上传失败,如下

wKioL1dm3j6QH1BjAADfoenxFFk609.png

如果成功,如下

wKioL1dm4GHSlVYgAADy5i37I1I810.png

OK,最后我们看一下整体效果

wKiom1dm4YjAhVTkAAL8Ss7iiJc075.png

 

wKioL1dm4ZnyQnrPAAQP7v-e4Ks572.png

OK,到此的话,图书查询就全部结束了,下节我们继续看图书Gallery。

原文转自:乐搏学院http://www.learnbo.com/front/article/cmsIndex

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值