2017年12月25日 21:31
原创作品,转载时请务必以超链接形式标明文章原始出处,否则将追究法律责任。

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


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

wKiom1dmz5_Au-guAABOCwNIpDM526.png

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

#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部分。

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页。

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


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

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

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

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#能取到,则显示,否则显示默认图片。

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

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,最后我们看一下上传

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。

发表评论
匿名  
用户评论
暂无评论