ソースを参照

前台新增消息读取模块

olym 7 年 前
コミット
6c1d3a1468

+ 10 - 1
modules/common/base/base_controller.js

@@ -7,6 +7,7 @@
  */
  */
 import Moment from "moment";
 import Moment from "moment";
 import Url from "url";
 import Url from "url";
+import UserMessageModel from "../../users/models/user_message_model";
 
 
 class BaseController {
 class BaseController {
 
 
@@ -36,7 +37,7 @@ class BaseController {
      * @param {function} next
      * @param {function} next
      * @return {void}
      * @return {void}
      */
      */
-    init(request, response, next) {
+    async init(request, response, next) {
         response.locals.title = 'test';
         response.locals.title = 'test';
 
 
         // moment工具
         // moment工具
@@ -46,6 +47,14 @@ class BaseController {
         let urlInfo = Url.parse(request.originalUrl, true);
         let urlInfo = Url.parse(request.originalUrl, true);
         response.locals.urlQuery = JSON.stringify(urlInfo.query);
         response.locals.urlQuery = JSON.stringify(urlInfo.query);
 
 
+        // 获取当前用户数据
+        let sessionUser = request.session.sessionUser;
+
+        // 获取当前用户消息数量
+        let userMessageModel = new UserMessageModel();
+        let messageUnreadCount = await userMessageModel.count({user_id: sessionUser.id, is_read: 0, is_delete: 0});
+        response.locals.unreadCount = messageUnreadCount;
+
         next();
         next();
     }
     }
 }
 }

+ 19 - 4
modules/common/helper/mongoose_helper.js

@@ -28,10 +28,15 @@ class MongooseHelper {
      *
      *
      * @param {object} conditions
      * @param {object} conditions
      * @param {object} fields
      * @param {object} fields
+     * @param {Object} option
      * @return {Promise}
      * @return {Promise}
      */
      */
-    findOne(conditions, fields = null) {
+    findOne(conditions, fields = null, option = null) {
         let self = this;
         let self = this;
+        let sort = {};
+        if (option !== null && Object.keys(option).length > 0) {
+            sort = option.sort !== undefined ? option.sort : sort;
+        }
         return new Promise(function (resolve, reject) {
         return new Promise(function (resolve, reject) {
             conditions = self._convertId(conditions);
             conditions = self._convertId(conditions);
             self.model.findOne(conditions, fields, function (error, data) {
             self.model.findOne(conditions, fields, function (error, data) {
@@ -40,7 +45,7 @@ class MongooseHelper {
                 } else {
                 } else {
                     resolve(data);
                     resolve(data);
                 }
                 }
-            });
+            }).sort(sort);
         });
         });
     }
     }
 
 
@@ -60,6 +65,7 @@ class MongooseHelper {
             limit = option.pageSize !== undefined ? option.pageSize : limit;
             limit = option.pageSize !== undefined ? option.pageSize : limit;
             skip = option.offset !== undefined ? option.offset : skip;
             skip = option.offset !== undefined ? option.offset : skip;
         }
         }
+
         return new Promise(function (resolve, reject) {
         return new Promise(function (resolve, reject) {
             self.model.find(conditions, fields, option, function (error, data) {
             self.model.find(conditions, fields, option, function (error, data) {
                 if (error) {
                 if (error) {
@@ -76,13 +82,22 @@ class MongooseHelper {
      *
      *
      * @param {object} conditions
      * @param {object} conditions
      * @param {object} fields
      * @param {object} fields
+     * @param {Object} option
      * @param {String|Object} populate
      * @param {String|Object} populate
      * @return {Promise}
      * @return {Promise}
      */
      */
-    findWithPopulate(conditions, fields = null, populate = '') {
+        findWithPopulate(conditions, fields = null, option, populate = '') {
         let self = this;
         let self = this;
+        let limit = 0;
+        let skip = 0;
+        if (option !== null && Object.keys(option).length > 0) {
+            limit = option.pageSize !== undefined ? option.pageSize : limit;
+            skip = option.offset !== undefined ? option.offset : skip;
+        }
+        conditions = self._convertId(conditions);
         return new Promise(function (resolve, reject) {
         return new Promise(function (resolve, reject) {
-            self.model.find(conditions, fields).populate(populate).exec(function(error, data) {
+            self.model.find(conditions, fields).skip(skip).limit(limit)
+                .populate(populate).exec(function(error, data) {
                 if (error) {
                 if (error) {
                     reject(error);
                     reject(error);
                 } else {
                 } else {

+ 3 - 1
modules/pm/controllers/pm_controller.js

@@ -107,7 +107,9 @@ module.exports = {
     index: async function(request, response) {
     index: async function(request, response) {
         // 获取编办信息
         // 获取编办信息
         let sessionCompilation = request.session.sessionCompilation;
         let sessionCompilation = request.session.sessionCompilation;
-
+        if (sessionCompilation === undefined) {
+            return response.redirect('/logout');
+        }
 
 
         // 清单计价
         // 清单计价
         let billValuation = sessionCompilation.bill_valuation !== undefined ?
         let billValuation = sessionCompilation.bill_valuation !== undefined ?

+ 5 - 0
modules/users/controllers/boot_controller.js

@@ -8,6 +8,7 @@
 import BaseController from "../../common/base/base_controller";
 import BaseController from "../../common/base/base_controller";
 import UserModel from "../models/user_model";
 import UserModel from "../models/user_model";
 import CompilationModel from "../models/compilation_model";
 import CompilationModel from "../models/compilation_model";
+import UserMessageModel from "../models/user_message_model";
 
 
 class BootController extends BaseController {
 class BootController extends BaseController {
 
 
@@ -31,6 +32,10 @@ class BootController extends BaseController {
 
 
             request.session.sessionCompilation = compilationData;
             request.session.sessionCompilation = compilationData;
         }
         }
+        // 消息处理
+        let userMessageModel = new UserMessageModel();
+        await userMessageModel.initMessage(sessionUser.id);
+
         // 判断是否已填写信息
         // 判断是否已填写信息
         let userData = await userModel.findDataByCondition({_id: sessionUser.id});
         let userData = await userModel.findDataByCondition({_id: sessionUser.id});
         if (userData.company !== '' && userData.real_name !== '') {
         if (userData.company !== '' && userData.real_name !== '') {

+ 126 - 0
modules/users/controllers/message_controller.js

@@ -0,0 +1,126 @@
+/**
+ * 消息通知相关控制器
+ *
+ * @author CaiAoLin
+ * @date 2017/9/25
+ * @version
+ */
+import UserMessageModel from "../models/user_message_model";
+
+class MessageController {
+
+    /**
+     * 初始化函数
+     * @param {object} request
+     * @param {object} response
+     * @param {Object} next
+     * @return {void|Object}
+     */
+    init(request, response, next) {
+        let responseData = {
+            err: 0,
+            msg: ''
+        };
+        try {
+            // 所有接口都需要保证访问者是已登录状态
+            let sessionUserData = request.session.sessionUser;
+            if (sessionUserData === undefined || sessionUserData.id === undefined || sessionUserData.id === '') {
+                throw '用户未登录';
+            }
+            next();
+        } catch (error) {
+            responseData.err = 1;
+            responseData.msg = error;
+            return response.json();
+        }
+
+    }
+
+    /**
+     * 获取消息列表数据
+     *
+     * @param {object} request
+     * @param {object} response
+     * @return {void}
+     */
+    async list(request, response) {
+        let responseData = {
+            err: 0,
+            msg: ''
+        };
+        try {
+            let sessionUserData = request.session.sessionUser;
+            let userMessageModel = new UserMessageModel();
+            let messageList = await userMessageModel.getList(sessionUserData.id, 15, {is_read: -1});
+            if (messageList === null) {
+                throw '获取数据失败!';
+            }
+            responseData.data = messageList;
+        } catch (error) {
+            console.log(error);
+            responseData.err = 1;
+            responseData.msg = error;
+        }
+
+        response.json(responseData);
+    }
+
+    /**
+     * 获取详细的消息数据
+     *
+     * @param {object} request
+     * @param {object} response
+     * @return {void}
+     */
+    async detail(request, response) {
+        let responseData = {
+            err: 0,
+            msg: ''
+        };
+        try {
+            let sessionUserData = request.session.sessionUser;
+            let id = request.body.id;
+            let userMessageModel = new UserMessageModel();
+            let messageData = await userMessageModel.getDetail(sessionUserData.id, id);
+            if (messageData === null) {
+                throw '获取数据失败!';
+            }
+            responseData.data = messageData;
+        } catch (error) {
+            console.log(error);
+            responseData.err = 1;
+            responseData.msg = error;
+        }
+
+        response.json(responseData);
+    }
+
+    /**
+     *
+     *
+     * @param {object} request
+     * @param {object} response
+     * @return {void}
+     */
+    async allRead(request, response) {
+        let responseData = {
+            err: 0,
+            msg: ''
+        };
+        try {
+            let sessionUserData = request.session.sessionUser;
+            let userMessageModel = new UserMessageModel();
+            let result = await userMessageModel.setAllRead(sessionUserData.id);
+            if (result === null) {
+                throw '设置失败!';
+            }
+        } catch (error) {
+            responseData.err = 1;
+            responseData.msg = error;
+        }
+
+        response.json(responseData);
+    }
+}
+
+export default MessageController;

+ 40 - 0
modules/users/models/message_model.js

@@ -0,0 +1,40 @@
+/**
+ * 消息业务逻辑
+ *
+ * @author CaiAoLin
+ * @date 2017/9/21
+ * @version
+ */
+import BaseModel from "../../common/base/base_model";
+import MessageSchema from "./schema/message";
+
+class MessageModel extends BaseModel {
+
+
+    /**
+     * 构造函数
+     *
+     * @return {void}
+     */
+    constructor() {
+        let parent = super();
+        parent.model = MessageSchema;
+        parent.init();
+    }
+
+    /**
+     * 获取对应时间点之后的数据
+     *
+     * @param {Number} lastTime
+     * @return {Promise}
+     */
+    async getListByTime(lastTime) {
+        let messageList = await this.findDataByCondition({release_time: {'$gte': lastTime}, message_type: 1, status: 1},
+            null, false);
+        return messageList;
+    }
+
+
+}
+
+export default MessageModel;

+ 51 - 0
modules/users/models/schema/message.js

@@ -0,0 +1,51 @@
+/**
+ * 消息通知数据模型
+ *
+ * @author CaiAoLin
+ * @date 2017/9/21
+ * @version
+ */
+import mongoose from "mongoose";
+
+let Schema = mongoose.Schema;
+let collectionName = 'message';
+let modelSchema = {
+    // 消息标题
+    title: String,
+    // 消息内容
+    content: String,
+    // 消息类型
+    message_type: {
+        type: Number,
+        default: 1
+    },
+    // 消息状态
+    status: {
+        type: Number,
+        default: 0
+    },
+    // 发布时间
+    release_time: {
+        type: Number,
+        default: 0
+    },
+    // 创建时间
+    create_time: Number,
+    // 最后修改时间
+    update_time: {
+        type: Number,
+        default: 0
+    },
+    // 创建者
+    creator: String,
+    // 发布者
+    release_user: String,
+    // 最后修改人
+    last_update: {
+        type: String,
+        default: ''
+    }
+
+};
+let model = mongoose.model(collectionName, new Schema(modelSchema, {versionKey: false, collection: collectionName}));
+export {model as default, collectionName as collectionName};

+ 39 - 0
modules/users/models/schema/user_message.js

@@ -0,0 +1,39 @@
+/**
+ * 用户消息数据模型
+ *
+ * @author CaiAoLin
+ * @date 2017/9/22
+ * @version
+ */
+let mongoose = require("mongoose");
+let Schema = mongoose.Schema;
+
+// 表名
+let collectionName = 'user_message';
+
+// 表结构
+let schema = {
+    // 用户id
+    user_id: {
+        type: String,
+    },
+    // 是否已读
+    is_read: {
+        type: Number,
+        default: 0
+    },
+    // 是否删除
+    is_delete: {
+        type: Number,
+        default: 0
+    },
+    // 消息关联数据
+    message: {
+        type: Schema.Types.ObjectId,
+        ref: 'message'
+    },
+    // 创建时间
+    create_time: Number,
+};
+
+export default mongoose.model(collectionName, new Schema(schema, {versionKey: false}));

+ 115 - 0
modules/users/models/user_message_model.js

@@ -0,0 +1,115 @@
+/**
+ * 用户消息业务逻辑
+ *
+ * @author CaiAoLin
+ * @date 2017/9/22
+ * @version
+ */
+import BaseModel from "../../common/base/base_model";
+import UserMessageSchema from "./schema/user_message";
+import MessageModel from "./message_model";
+
+class UserMessageModel extends BaseModel {
+
+    /**
+     * 构造函数
+     *
+     * @return {void}
+     */
+    constructor() {
+        let parent = super();
+        parent.model = UserMessageSchema;
+        parent.init();
+    }
+
+    /**
+     * 初始化用户消息
+     *
+     * @param {String} userId
+     * @return {boolean}
+     */
+    async initMessage(userId) {
+        userId = userId.toString();
+        let result = true;
+        // 获取最后一条消息的信息
+        let lastMessageData = await this.db.findOne({user_id: userId}, null, {create_time: -1});
+        let lastTime = lastMessageData === null ? 0 : lastMessageData.create_time;
+
+        // 查找已发布的数据
+        let messageModel = new MessageModel();
+        let messageData = await messageModel.getListByTime(lastTime);
+        if (messageData === null) {
+            return result;
+        }
+
+        // 组合新增数据
+        let insertData = [];
+        let currentTime = new Date().getTime();
+        for(let message of messageData) {
+            let tmp = {
+                message: message._id,
+                user_id: userId,
+                create_time: currentTime
+            };
+            insertData.push(tmp);
+        }
+
+        result = await this.db.create(insertData);
+        return result;
+    }
+
+    /**
+     * 获取列表
+     *
+     * @param {String} userId
+     * @param {Number} pageSize
+     * @param {Object} sort
+     * @return {promise}
+     */
+    async getList(userId, pageSize = 15, sort = {}) {
+        let option = {pageSize: pageSize, offset: 0};
+        option.sort = sort;
+
+        let messageList = await this.db.findWithPopulate({user_id: userId, is_delete: 0}, null, option, 'message');
+        messageList = messageList.length > 0 ? messageList : [];
+
+        return messageList;
+    }
+
+    /**
+     * 获取详细数据
+     *
+     * @param {String} userId
+     * @param {String} id
+     * @return {Promise}
+     */
+    async getDetail(userId, id) {
+        let result = {};
+        let messageList = await this.db.findWithPopulate({user_id: userId, is_delete: 0, _id: id}, null, null, 'message');
+        if (messageList === null) {
+            return result;
+        }
+        result = messageList.length > 0 ? messageList[0] : result;
+        // 如果是未读数据则更改状态
+        if (result.is_read === 0) {
+            let updateData = {is_read: 1};
+            await this.db.update({_id: id}, updateData);
+        }
+
+        return result;
+    }
+
+    /**
+     * 设置全部已读
+     *
+     * @param {String} userId
+     * @return {Promise}
+     */
+    async setAllRead(userId) {
+        let updateData = {is_read: 1};
+        return await this.db.update({user_id: userId}, updateData)
+    }
+
+}
+
+export default UserMessageModel;

+ 22 - 0
modules/users/routes/message_route.js

@@ -0,0 +1,22 @@
+/**
+ * 消息相关路由
+ *
+ * @author CaiAoLin
+ * @date 2017/9/25
+ * @version
+ */
+import Express from "express";
+import MessageController from "../controllers/message_controller";
+
+module.exports = function (app) {
+
+    const router = Express.Router();
+    const messageController = new MessageController();
+
+    // action定义区域
+    router.post('/list', messageController.init, messageController.list);
+    router.post('/detail', messageController.init, messageController.detail);
+    router.post('/read', messageController.init, messageController.allRead);
+
+    app.use('/message',router);
+};

+ 159 - 0
web/building_saas/js/message.js

@@ -0,0 +1,159 @@
+/**
+ * 消息通知相关js
+ *
+ * @author CaiAoLin
+ * @date 2017/9/25
+ * @version
+ */
+let isLoading = false;
+$(document).ready(function() {
+
+    // 消息框弹出
+    $("#msg").on("shown.bs.modal", function() {
+        let loadingElement = $("#message-loading");
+        $.ajax({
+            url: '/message/list',
+            type: 'post',
+            data: '',
+            dataType: 'json',
+            error: function() {
+                loadingElement.text('加载数据失败');
+            },
+            beforeSend: function() {
+                loadingElement.text('正在加载数据').show();
+            },
+            success: function(response) {
+                loadingElement.text('').hide();
+                $("#message-list").show();
+                if (response.err === 1 || response.err === undefined) {
+                    let msg = response.msg !== undefined ? response.msg : '读取数据失败!';
+                    alert(msg);
+                    return false;
+                }
+                let listData = response.data;
+                if (listData.length <= 0) {
+                    return false;
+                }
+                let listHtml = '';
+                for(let tmp of listData) {
+                    let unreadHtml = tmp.is_read === 0 ? '<i class="fa fa-circle-o text-danger unread" title="未读"></i> ' : '';
+                    let dateString = moment(tmp.message.release_time).format('YYYY-MM-DD');
+                    let dateTimeString = moment(tmp.message.release_time).format('YYYY-MM-DD HH:mm:ss');
+                    listHtml += '<tr>' +
+                        '<td>'+ unreadHtml +'<a href="javascript:void(0);" class="detail" data-id="'+ tmp._id +'">'+ tmp.message.title +'</a></td>' +
+                        '<td><span title="'+ dateTimeString +'">'+ dateString +'</span></td>';
+                }
+
+                $("#message-list tbody").html(listHtml);
+            }
+        });
+    });
+
+    // 获取详细消息
+    $("#msg").on("click", "a.detail", function() {
+        let self = $(this);
+        let id = self.data("id");
+        if (id === undefined || id === '' || isLoading) {
+            return false;
+        }
+        let isUnread = self.prev().is(".unread");
+        let loadingElement = $("#message-loading");
+        let listElement = $("#message-list");
+        $.ajax({
+            url: '/message/detail',
+            type: 'post',
+            data: 'id=' + id,
+            dataType: 'json',
+            error: function() {
+                isLoading = false;
+                listElement.show();
+                loadingElement.text('').hide();
+            },
+            beforeSend: function() {
+                isLoading = true;
+                listElement.hide();
+                loadingElement.text('正在加载详细内容').show();
+            },
+            success: function(response) {
+                isLoading = false;
+                loadingElement.text('').hide();
+                let messageData = response.data;
+                if (response.err === 1 || response.err === undefined || messageData === undefined) {
+                    let msg = response.msg !== undefined ? response.msg : '读取数据失败!';
+                    listElement.show();
+                    loadingElement.text('').hide();
+                    alert(msg);
+                    return false;
+                }
+                // 移除未读标记
+                if (isUnread) {
+                    self.prev('i').remove();
+                    changeNumByStep(-1);
+                }
+                $("#message-content .title").text(messageData.message.title);
+                $("#message-content .time").text('发布:' + moment(messageData.message.release_time).format('YYYY-MM-DD HH:mm:ss'));
+                $("#message-content .content").html(messageData.message.content);
+                $("#message-content").show();
+            }
+        });
+    });
+
+    // 设置全部已读
+    $("#set-all-read").click(function() {
+        if (isLoading) {
+            return false;
+        }
+        $.ajax({
+            url: '/message/read',
+            type: 'post',
+            data: '',
+            dataType: 'json',
+            error: function() {
+                isLoading = false;
+            },
+            beforeSend: function() {
+                isLoading = true;
+            },
+            success: function(response) {
+                isLoading = false;
+                if (response.err === 0) {
+                    $("#message-list tbody .unread").remove();
+                    changeNumByStep(0);
+                } else {
+                    let msg = response.msg !== undefined ? response.msg : '未知错误!';
+                    alert(msg);
+                }
+            }
+        });
+    });
+
+    // 返回按钮
+    $("#message-content .back").click(function() {
+        $("#message-content").hide();
+        $("#message-list").show();
+    });
+
+});
+
+/**
+ * 设置头部信封数量
+ *
+ * @param {Number} step
+ * @return {void}
+ */
+function changeNumByStep(step) {
+    let messageElement = $("#message");
+    let unreadElement = $("#unread-num");
+    if (step === 0) {
+        messageElement.removeClass('new-msg');
+        unreadElement.text(0);
+    } else {
+        let currentNum = unreadElement.text();
+        currentNum = parseInt(currentNum);
+        let num = currentNum + step;
+        if (num === 0) {
+            messageElement.removeClass('new-msg');
+        }
+        unreadElement.text(num);
+    }
+}

ファイルの差分が大きいため隠しています
+ 7 - 0
web/building_saas/js/moment.min.js


+ 45 - 5
web/common/html/header.html

@@ -1,6 +1,6 @@
 <nav class="navbar navbar-toggleable-lg navbar-light bg-faded p-0 justify-content-between">
 <nav class="navbar navbar-toggleable-lg navbar-light bg-faded p-0 justify-content-between">
     <span class="header-logo px-2">Smartcost</span>
     <span class="header-logo px-2">Smartcost</span>
-    <div class="navbar-text"><a href="project-management.html">项目管理</a></div>
+    <div class="navbar-text"><a href="/pm">项目管理</a></div>
     <div class="float-lg-right navbar-text pt-0">
     <div class="float-lg-right navbar-text pt-0">
         <div class="dropdown d-inline-block">
         <div class="dropdown d-inline-block">
             <button class="btn btn-link btn-sm dropdown-toggle" type="button" data-toggle="dropdown"><%= sessionUser.email %></button>
             <button class="btn btn-link btn-sm dropdown-toggle" type="button" data-toggle="dropdown"><%= sessionUser.email %></button>
@@ -10,9 +10,49 @@
                 <a class="dropdown-item" href="user-set.html" target="_blank">偏好设置</a>
                 <a class="dropdown-item" href="user-set.html" target="_blank">偏好设置</a>
             </div>
             </div>
         </div>
         </div>
-        <span class="btn btn-link btn-sm new-msg">
-                  <i class="fa fa-envelope-o" aria-hidden="true"></i>&nbsp;2
-                </span>
+        <a class="btn btn-link btn-sm <% if (unreadCount > 0) { %>new-msg<% } %>" id="message" data-toggle="modal" data-target="#msg" href="javacript:void(0);">
+            <i class="fa fa-envelope-o" aria-hidden="true"></i>&nbsp;<span id="unread-num"><%= unreadCount %></span>
+        </a>
         <a class="btn btn-link btn-sm" href="/logout">注销</a>
         <a class="btn btn-link btn-sm" href="/logout">注销</a>
     </div>
     </div>
-</nav>
+</nav>
+<!--弹出消息-->
+<div class="modal fade" id="msg" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title"><i class="fa fa-envelope-o"></i> 消息通知</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body modal-auto-height">
+                <div id="message-loading" style="text-align: center; padding-top: 100px;"></div>
+                <!--消息列表 默认显示10条-->
+                <table class="table table-bordered" id="message-list" style="display: none;">
+                    <thead>
+                        <tr>
+                            <th>标题<a href="javascript:void(0);" id="set-all-read" class="float-right btn btn-sm">设置全部已读</a></th>
+                            <th width="120">发布时间</th>
+                        </tr>
+                    </thead>
+                    <tbody>
+                    </tbody>
+                </table>
+                <div id="message-content" style="display: none;">
+                    <!--点击标题查看内容-->
+                    <a href="javascript:void(0);" class="back">返回</a>
+                    <h4 class="text-center title"></h4>
+                    <p class="text-center time"></p>
+                    <div class="mx-3 mb-5 content"></div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
+<script src="/lib/jquery/jquery.min.js"></script>
+<script type="text/javascript" src="/web/building_saas/js/moment.min.js"></script>
+<script type="text/javascript" src="/web/building_saas/js/message.js"></script>