终于如愿以偿的去了趟青海湖,感觉美美的。我想着老了(50岁),去那里帮牧民看牦牛,放羊。看着蓝天白云,一望无际碧绿的湖水,想想写程序的这些年,想想城市生活,想想人的一生到底是为了什么,人的一生难道只是为了比别人有钱,比别人赚更多的钱。万一想不通,就去塔尔寺面试,当个和尚喇嘛什么的。如果人家不收我,就在寺庙跪拜10万次,靠着虔诚的心我也能积攒些许功德。
好了,逛了一圈,也该写写博客了,今天我们还是继续看Node.js,本篇主要是介绍excel在线,俗话说,无图无真相。
看到了吧,这就是主界面,首先我们来看一下My Document的View部分。在看View之前,我们先看一下Mode的设计。
var mongoose = require('mongoose'); var Schema = mongoose.Schema; var authSchema = require('./fileauth.js'); require('string.prototype.endswith'); var fileSchema = new Schema({ _id: Schema.Types.ObjectId, name: { type: String , trim: true, index: true }, isshared: { type: Boolean, default: false }, content: { type: String , trim: true, select: false }, createuserid: { type: String, index: { sparse: true } }, createuser: { type: String}, createdate: { type: Date, default: Date.now, index: { sparse: true } }, lasteditdate: { type: Date, default: Date.now }, lastedituserid: String, lastedituser: String, auth:[{ type: Schema.ObjectId, ref: 'fileauth' }] }, { strict: true , toObject: { virtuals: true }, toJSON: { virtuals: true } }); fileSchema.pre('update', function (next) { this.update({}, { $set: { lasteditdate: Date.now() } }); next(); }); var fileSubGroupSchema = new Schema({ _id: Schema.Types.ObjectId, name: { type: String, trim: true , required: true }, userid: { type: String, required: true , index: true }, file: [{ type: Schema.ObjectId, ref: 'file' }] }, { strict: true, toObject: { virtuals: true }, toJSON: { virtuals: true } }); var fileGroupSchema = new Schema({ userid: { type: String, required: true , index: true }, name: { type: String, trim: true , required: true }, file: [{ type: Schema.ObjectId, ref: 'file' }], subgroup: [fileSubGroupSchema] }, { strict: true, toObject: { virtuals: true }, toJSON: { virtuals: true } });
我们设计了文件Schema,fileSchema,注意这里的sparse:true,意思是发散索引,即非聚集索引。
fileSchema.pre('update', function (next) { this.update({}, { $set: { lasteditdate: Date.now() } }); next(); });
还有这里,我们预定义一个方法,当更新fileModel的时候,会先更新lasteditdate,再更新其他字段,因为我们更新的话,最后修改日期肯定是当前时间,也不需要用户每次都去传,所以这里预定义还是很有用处的。
接下来就是文件组和文件子组,总共两级,文件组和包含文件和子组,子组只能包含文件,所以这里设置及的时候,fileGroup中有个subgroup的定义,注意这里的subgroup它不是一个引用ref,而是嵌入的文档,是一个整体,但是它对file是只是一个主键的引用。
最后我们将这些定义好的model模块化公开出去。
fileGroupSchema.set('collection', 'filegroups'); fileSchema.set('collection', 'files'); var fileGroupModel = mongoose.model("fileGroup", fileGroupSchema); var fileModel = mongoose.model("file", fileSchema); exports.fileGroupModel = fileGroupModel; exports.fileModel = fileModel;
接下来我们先看一下View页面的定义。
#file_tab ul li View li Maintain include partial/docview.jade include partial/docedit.jade block scripts script(type='text/javascript' src='/javascripts/local/doc/docself.js')
在我的文档我们包含了两个页面,dicview.jade和docedit.jade,看一下docview。
#view .row-margin-large label(style='font-size:17px') Owned Documents: span#div_ownTotal .line-height-30(style='width:100%') div(style='width:50%;float:left') label Date: input#dp_ownStart(onkeydown='return false;') input#dp_ownEnd(onkeydown='return false;') div(style='width:30%;float:left') label File Name: input#txt_ownFileName(type='text') div(style='width:20%;float:left;text-align:center') button#btn_searchOwn.k-button.k-primary Search .clear-float .row-margin hr.panel-line #div_own(style='min-height:350px') div#div_retriveOwn(style='top:10%;left:45%;position:relative;display:none') img(src='/stylesheets/images/loading-large.gif') label(style='color:#666666') retriving...... .row-margin-large.clear-float #own_pager br .row-margin-large label(style='font-size:17px') Shared Documents: span#div_sharedTotal .line-height-30(style='width:100%') div(style='width:50%;float:left') label Date: input#dp_shareStart(onkeydown='return false;') input#dp_shareEnd(onkeydown='return false;') div(style='width:30%;float:left') label File Name: input#txt_sharedFileName(type='text') div(style='width:20%;float:left;text-align:center') button#btn_searchShared.k-button.k-primary Search .clear-float .row-margin hr.panel-line #div_share(style='min-height:350px;margin-bottom:10px') div#div_retriveShared(style='top:10%;left:45%;position:relative;display:none') img(src='/stylesheets/images/loading-large.gif') label(style='color:#666666') retriving...... #shared_pager
这个页面包含一个Owned Document和Shared Document,我们看一下js部分。
$("#txt_ownFileName").keydown(function (e) { if (e.keyCode == 13) { $("#btn_searchOwn").click(); } }); $("#btn_searchOwn").click(function () { getOwnedFileList(); }); $("#btn_searchOwn").click();
页面load完成后,直接查询。
function getOwnedFileList() { var fileName = $.trim($("#txt_ownFileName").val()); var startDate = $("#dp_ownStart").data("kendoDatePicker").value(); var endDate = $("#dp_ownEnd").data("kendoDatePicker").value(); var postData = { fileName: fileName, startDate: startDate, endDate: endDate, pageIndex: $("#own_pager").data('kendoPager').page() - 1 < 0? 0:$("#own_pager").data('kendoPager').page() - 1, pageSize: $("#own_pager").data('kendoPager').pageSize(), userId: userObj.UserID }; isOwnSearch = true; $("#div_retriveOwn").show(); $.post('/file/owned/retrieve', { data: JSON.stringify(postData) } , function (data) { $("#div_retriveOwn").hide(); var tempSource = []; for (var i = 0; i < data.totalCount; i++) { tempSource.push(i); } var dataSource = new kendo.data.DataSource({ data: tempSource, pageSize: $("#own_pager").data('kendoPager').pageSize() }); $("#own_pager").data('kendoPager').setDataSource(dataSource); $("#own_pager").data('kendoPager').page(postData.pageIndex + 1); $("#div_own").html(''); $("#div_ownTotal").css('color', 'red').html('(0)'); if (data.totalCount && data.totalCount > 0) { $("#div_ownTotal").css({ 'color': 'red' , 'font-weight': 'bold' }).html("(" + data.totalCount + ")"); for (var i = 0; i < data.files.length; i++) { if (i % 10 == 0) { $("#div_own").append('<div class="clear-float"/>'); } $("#div_own").append('<div style="width:10%;float:left">' + '<div class="row-margin center-align-text">' + '<a href="javascript:void(0)" style="text-decoration: none;color:#666666">' + '<img id="' + data.files[i]._id + '" class="excel-img" src="/images/excel.png"/></a>' + '<div style="word-break: break-word; width:90px">' + data.files[i].name + '</div>' + '</div></div>'); } $("#div_own img").each(function () { $(this).dblclick(function () { window.location.href = '/index?file_id=' + $(this).attr('id'); }); }); } }); }
拼好查询json对象后,post到api去查询,然后根据返回的数据去构造kendoPager,最后我们去构造文件列表,每个文件在双击的时候,都会跳转到index界面读取文件的内容展示在kendospread sheet上。我们看一下效果。
分页,点击2到第二页
分页是没有问题的,美观大方。OK,对于Shared Document的js代码,和上面的基本一样。
看上去只是多了个tooltip的说明,其实这个效果是bootstrap提供的,我们只需要写如下的代码即可。
$("#div_share").append('<div style="width:10%;float:left">' + '<div class="row-margin center-align-text tooltip-options" data-toggle="tooltip" data-placement="bottom" title="' + tooltipTemplate + '">' + '<a href="javascript:void(0)" style="text-decoration: none;color:#666666">' + '<img id="' + data.files[i]._id + '" class="excel-img" src="/images/excel_share.png"/></a>' + '<div style="word-break: break-word;width:90px">' + data.files[i].name + '</div>' + '</div></div>'); $("#div_share .tooltip-options").tooltip({ html : true });
OK最后我们看一下rest api。
var fileRoutes = require('../controller/filemng.js'); router.post('/file/owned/retrieve', fileRoutes.getOwnedFileList); router.post('/file/shared/retrieve', fileRoutes.getSharedFileList);
根据url我们知道方法在filemng.js中。
exports.getOwnedFileList = function (req, res) { var data = JSON.parse(req.body.data); var fileName = data.fileName; var startDate = data.startDate; var endDate = data.endDate; var pageIndex = data.pageIndex; var pageSize = data.pageSize; var userId = data.userId; var query = fileModel.find({}); query = query.where('createuserid', userId); if (fileName) { var regex = new RegExp(fileName, 'i'); query = query.where('name', regex); } if (startDate && endDate) { query = query.where('createdate').gte(startDate).lte(endDate); } query.count().exec(function (error, count) { query.limit(pageSize).skip(pageIndex * pageSize).sort('-createdate') .exec("find", function (error, docs) { res.json({ files: docs, totalCount: count }); }); }); }
注意这里的查询,对于filename的模糊查询,用正则表达式。对于日期的查询,我们用gte和lte,大于和小于。最后我们先算出总数,再拿到分页的数据,将数据拼成json对象输出到客户端。
用robomongo查了一下,确实也是22条数据
共享的文件也只有2个
OK,今天就到这里,如果大家想要源码,估计要到实战15以后了,慢慢等吧。