MaiXinRong 8 年 前
コミット
bde1921744

+ 48 - 0
modules/pm/controllers/pm_controller.js

@@ -0,0 +1,48 @@
+/**
+ * Created by Mai on 2017/1/18.
+ */
+var ProjectsData = require('../models/project');
+
+//统一回调函数
+var callback = function(req, res, err, message, data){
+    res.json({error: err, message: message, data: data});
+}
+
+module.exports = {
+    getProjects: function(req, res){
+        var data = JSON.parse(req.body.data);
+        ProjectsData.getUserProjects(data.user_id, function(err, message, projects){
+            if (projects) {
+                callback(req, res, err, message, projects);
+            } else {
+                callback(req, res, err, message, null);
+            }
+        });
+    },
+    updateProjects: function (req, res) {
+        var data = JSON.parse(req.body.data);
+        ProjectsData.updateUserProjects(data.user_id, data.updateData, function (err, message, data) {
+            if (err === 0) {
+                callback(req, res, err, message, data);
+            } else {
+                callback(req, res, err, message, null);
+            }
+        })
+    },
+    rename: function (req, res) {
+        var data = JSON.parse(req.body.data);
+        ProjectsData.rename(data.user_id, data.id, data.newName, function (err, message) {
+            callback(req, res, err, message, null);
+        })
+    },
+    getProject: function(req, res){
+        var data = JSON.parse(req.body.data);
+        ProjectsData.getUserProject(data.user_id, data.proj_id, function(err, message, data){
+            if (err === 0) {
+                callback(req, res, err, message, data);
+            } else {
+                callback(req, res, err, message, null);
+            }
+        })
+    }
+}

+ 6 - 0
modules/pm/db/pm_db.js

@@ -0,0 +1,6 @@
+/**
+ * Created by Mai on 2017/3/8.
+ */
+var mg = require('mongoose');
+mg.connect('mongodb://localhost/usersManages');
+exports.mongoose = mg;

+ 110 - 0
modules/pm/models/project.js

@@ -0,0 +1,110 @@
+/**
+ * Created by Mai on 2017/1/18.
+ */
+
+// 对Date的扩展,将 Date 转化为指定格式的String
+// 月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符,
+// 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字)
+// 例子:
+// (new Date()).Format("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423
+// (new Date()).Format("yyyy-M-d h:m:s.S")      ==> 2006-7-2 8:9:4.18
+Date.prototype.Format = function (fmt) {
+    var o = {
+        "M+": this.getMonth() + 1, //月份
+        "d+": this.getDate(), //日
+        "h+": this.getHours(), //小时
+        "m+": this.getMinutes(), //分
+        "s+": this.getSeconds(), //秒
+        "q+": Math.floor((this.getMonth() + 3) / 3), //季度
+        "S": this.getMilliseconds() //毫秒
+    };
+    if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
+    for (var k in o)
+        if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
+    return fmt;
+};
+
+var db = require('../db/pm_db');
+var Schema = db.mongoose.Schema;
+var ProjectSchema = new Schema({
+    "name": String,
+    "projType": String,
+    "lastDateTime": String,
+    "createDateTime": String,
+    "id": Number,
+    "parentId": Number,
+    "nextId": Number,
+    "userId": Number,
+    "deleted": Boolean,
+    'deleteDateTime': String,
+    'deleteFullFolder': Array
+});
+
+var Projects = db.mongoose.model("projects", ProjectSchema);
+
+var ProjectsDAO = function(){};
+
+ProjectsDAO.prototype.getUserProjects = function(userId, callback){
+    Projects.find({userId: userId, deleted: { $in: [false, null]}}, '-_id', function(err, templates){
+        if (err) {
+            callback(1, 'Error', null);
+        } else {
+            callback(0, '', templates);
+        }
+    });
+};
+
+ProjectsDAO.prototype.getUserProject = function (userId, ProjId, callback) {
+    Projects.findOne({userId: userId, id: ProjId}, '-_id', function(err, template){
+        if (err) {
+            callback(1, '找不到标段数据', null);
+        } else {
+            callback(0, '', template);
+        }
+    });
+}
+
+ProjectsDAO.prototype.updateUserProjects = function(userId, datas, callback){
+    var data, project, updateLength = 0, hasError = false;
+    var updateAll = function (err) {
+            if (!err){
+                updateLength += 1;
+                if (updateLength === datas.length) {
+                    callback(0, '', datas);
+                }
+            } else {
+                hasError = true;
+                callback(1, '提交数据出错.', null);
+            }
+        };
+    if (datas){
+        for (var i = 0; i < datas.length && !hasError; i++){
+            data = datas[i];
+            if (data.updateType === 'update') {
+                Projects.update({userId: userId, id: data.updateData.id}, data.updateData, updateAll)
+            } else if (data.updateType === 'new') {
+                data.updateData['userId'] = userId;
+                if (data.updateData.projType === 'Tender') {
+                    data.updateData['createDateTime'] = new Date().Format("yyyy-MM-dd");
+                }
+                newProject = new Projects(data.updateData);
+                newProject.save(updateAll);
+            } else if (data.updateType === 'delete') {
+                data.updateData['deleteDateTime'] = new Date().Format("yyyy-MM-dd");
+                Projects.update({userId: userId, id: data.updateData.id}, data.updateData, updateAll);
+            }
+        }
+    }
+};
+
+ProjectsDAO.prototype.rename = function (userId, projectId, newName, callback){
+    Projects.update({userId: userId, id: projectId}, {name: newName}, function(err){
+       if (err){
+           callback(1, '项目不存在.');
+       } else {
+           callback(0, '');
+       }
+    });
+};
+
+module.exports = new ProjectsDAO();

+ 25 - 0
modules/pm/routes/pm_route.js

@@ -0,0 +1,25 @@
+/**
+ * Created by Mai on 2017/3/8.
+ */
+
+var express = require('express');
+var pmRouter = express.Router();
+var htmlPath = require('path').join(__dirname,'../../','web/pm/html/');
+var pmController = require('./../controllers/pm_controller');
+
+pmRouter.get('/pm', function(req, res){
+    if(!req.session.userAccount){
+        res.redirect('/login');
+    }
+    else{
+        res.render('pm/html/project-management.html',
+            {userAccount: req.session.userAccount,
+            userID: req.session.userID});
+    }
+});
+
+pmRouter.post('/getProjects', pmController.getProjects);
+pmRouter.post('/updateProjects', pmController.updateProjects);
+pmRouter.post('/renameProject', pmController.rename);
+
+module.exports = pmRouter;

+ 7 - 13
modules/users/routes/users_route.js

@@ -6,26 +6,20 @@ var uc = require('../controllers/users_controller');
 var htmlPath = path.join(__dirname,'../../../','web/users/');
 
 router.get('/', function(req, res) {
-    res.redirect('/project-management');
-});
-
-router.get('/login', function(req, res) {
-    res.render('users/login', {});
-});
-
-router.post('/login', uc.userLogin);
-
-router.get('/project-management', function(req, res) {
     if(!req.session.userAccount){
         res.redirect('/login');
     }
     else{
-        res.render('users/project-management',
-            {userAccount: req.session.userAccount,
-                userID: req.session.userID});
+        res.redirect('/pm');
     }
 });
 
+router.get('/login', function(req, res) {
+  res.render('users/login', {});
+});
+
+router.post('/login', uc.userLogin);
+
 router.get('/reg', function(req, res, next) {
   res.render('reg',{});
 });

+ 3 - 0
server.js

@@ -36,6 +36,9 @@ var users = require('./modules/users/routes/users_route');
 app.use('/', users);
 // -----------------------------------------------------------------end
 
+var pmRouter = require('./modules/pm/routes/pm_route');
+app.use('/', pmRouter);
+
 var apiRouter = express.Router();
 
 app.use('/api', apiRouter);

+ 581 - 0
web/pm/html/project-management.html

@@ -0,0 +1,581 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+    <meta http-equiv="x-ua-compatible" content="ie=edge">
+    <title>项目管理-Smartcost</title>
+    <link rel="stylesheet" href="web/css/bootstrap/bootstrap.min.css">
+    <link rel="stylesheet" href="web/css/main.css">
+    <link rel="stylesheet" href="web/css/font-awesome/font-awesome.min.css">
+    <!--zTree-->
+  	<link rel="stylesheet" href="web/css/ztree/zTreeStyle.css" type="text/css">
+
+    <script>
+        // 这里的变量供页面调用
+        var userAccount = '<%- userAccount %>';
+        var userID = '<%- userID %>';
+    </script>
+</head>
+
+<body>
+    <div class="header">
+        <div class="top-msg clearfix">
+            <div class="alert alert-warning mb-0 py-0" role="alert">
+                <button type="button" class="close" data-dismiss="alert" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+                <strong>注意!</strong> 这是一条消息通知 <a href="#">链接</a>
+            </div>
+        </div>
+        <nav class="navbar navbar-toggleable-lg navbar-light bg-faded p-0 justify-content-between">
+            <span class="header-logo px-2">Smartcost</span>
+            <div class="navbar-text pt-0">
+                <div class="dropdown d-inline-block">
+                    <button class="btn btn-link btn-sm dropdown-toggle" type="button" data-toggle="dropdown"><%- userAccount %></button>
+                    <div class="dropdown-menu dropdown-menu-right">
+                        <a class="dropdown-item" href="user-info.html" target="_blank">账号资料</a>
+                        <a class="dropdown-item" href="user-buy.html" target="_blank">产品购买</a>
+                        <a class="dropdown-item" href="user-set.html" target="_blank">偏好设置</a>
+                    </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 href="/logout" class="btn btn-link btn-sm">注销</a>
+            </div>
+        </nav>
+        <nav class="navbar navbar-toggleable-lg justify-content-between navbar-light p-0">
+            <ul class="nav navbar-nav px-1">
+                <li class="nav-item dropdown">
+                    <a class="nav-link dropdown-toggle" href="http://example.com" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">文件</a>
+                    <div class="dropdown-menu" aria-labelledby="supportedContentDropdown">
+                        <a class="dropdown-item" href="#">Action</a>
+                        <a class="dropdown-item" href="#">Another action</a>
+                        <a class="dropdown-item" href="#">Something else here</a>
+                    </div>
+                </li>
+                <li class="nav-item dropdown">
+                    <a class="nav-link dropdown-toggle" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">编辑</a>
+                    <div class="dropdown-menu">
+                        <a class="dropdown-item" href="#">Action</a>
+                        <a class="dropdown-item" href="#">Another action</a>
+                        <a class="dropdown-item" href="#">Something else here</a>
+                    </div>
+                </li>
+                <li class="nav-item dropdown">
+                    <a class="nav-link dropdown-toggle" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">工具</a>
+                    <div class="dropdown-menu">
+                        <a class="dropdown-item" href="#">Action</a>
+                        <a class="dropdown-item" href="#">Another action</a>
+                        <a class="dropdown-item" href="#">Something else here</a>
+                    </div>
+                </li>
+                <li class="nav-item">
+                    <a class="nav-link" href="#" aria-haspopup="true" aria-expanded="false">帮助</a>
+                </li>
+            </ul>
+            <form class="form-inline">
+                <input class="form-control form-control-sm mr-1" type="text" placeholder="告诉我你想做什么">
+            </form>
+        </nav>
+    </div>
+    <div class="main">
+        <div class="poj-manage container-fluid">
+            <div class="row">
+                <div class="col-lg-2">
+                    <div class="poj-cate">
+                        <input type="text" class="my-2 form-control form-control-sm" placeholder="搜索所有工程">
+                        <ul class="nav nav-pills flex-column">
+                            <li class="nav-item">
+                                <a class="nav-link active" href="#">全部</a>
+                            </li>
+                            <li class="nav-item">
+                                <a class="nav-link" href="#">最近使用</a>
+                            </li>
+                            <li class="nav-item">
+                                <a class="nav-link" href="#">共享</a>
+                            </li>
+                            <li class="nav-item">
+                                <a class="nav-link" href="#">协同工作</a>
+                            </li>
+                            <li class="nav-item">
+                                <a class="nav-link" href="#">归档</a>
+                            </li>
+                            <li class="nav-item">
+                                <a class="nav-link" href="#">回收站</a>
+                            </li>
+                        </ul>
+                    </div>
+                </div>
+                <div class="col-lg-10">
+                    <div class="toolsbar">
+                        <div class="tools-btn btn-group align-top">
+                            <a href="#;" class="btn btn-sm" id="addProjBtn">新建工程</a>
+                            <a href="#" class="btn btn-sm" id="addFolderBtn"><i class="fa fa-folder-o"></i>&nbsp;新建文件夹</a>
+                            <a href="#" class="btn btn-sm" id="renameBtn">重命名</a>
+                            <a href="#" class="btn btn-sm" id="delBtn">删除</a>
+                            <a href="#" class="btn btn-sm" id="movetoBtn">移动到...</a>
+                            <a href="#" class="btn btn-sm" id="copytoBtn">复制到...</a>
+                            <a href="#" class="btn btn-sm" id="shareBtn">共享</a>
+                            <a href="#" class="btn btn-sm" id="cooperateBtn">协同</a>
+                        </div>
+                    </div>
+                    <div class="poj-list">
+                        <legend>全部</legend>
+                        <table class="table table-hover table-sm" id="ProjTree">
+                            <thead>
+                                <tr>
+                                    <th width="40"></th>
+                                    <th width="78%">工程列表</th>
+                                    <th width="10%">最近使用</th>
+                                    <th width="10%">创建日期</th>
+                                </tr>
+                            </thead>
+                            <tbody>
+                                <tr>
+                                    <td></td>
+                                    <td class="in-1"><a href="#" class="tree-open" title="收起"><i class="fa fa-minus-square-o mr-1"></i></a><i class="fa fa-folder-open-o"></i>&nbsp;XX项目文件夹</td>
+                                    <td></td>
+                                    <td></td>
+                                </tr>
+                                  <tr>
+                                      <td></td>
+                                      <td class="in-2"><a href="#" class="tree-open" title="收起"><i class="fa fa-minus-square-o mr-1"></i></a><i class="fa fa-folder-open-o"></i>&nbsp;XX项目文件夹</td>
+                                      <td></td>
+                                      <td></td>
+                                  </tr>
+                                <tr>
+                                    <td></td>
+                                    <td class="in-3"><a href="#" class="tree-open" title="收起"><i class="fa fa-minus-square-o mr-1"></i></a><i class="fa fa-folder-open-o"></i>&nbsp;XX项目文件夹</td>
+                                    <td></td>
+                                    <td></td>
+                                </tr>
+                                <tr>
+                                    <td></td>
+                                    <td class="in-4"><a href="#" class="tree-open" title="收起"><i class="fa fa-minus-square-o mr-1"></i></a><i class="fa fa-folder-open-o"></i>&nbsp;<a href="#">某某某某工厂工厂某某工厂建设某某工厂建设</a></td>
+                                    <td></td>
+                                    <td></td>
+                                </tr>
+                                <tr>
+                                    <td></td>
+                                    <td class="in-5"><a href="#" class="tree-open" title="收起"><i class="fa fa-minus-square-o mr-1"></i></a><i class="fa fa-folder-open-o"></i>&nbsp;<a href="#" class="open-sidebar">左1号生产车间(click)</a></td>
+                                    <td></td>
+                                    <td></td>
+                                </tr>
+                                <tr>
+                                    <td><i class="fa fa-sort" data-toggle="tooltip" data-placement="top" title="长安拖动"></i></td>
+                                    <td class="in-6"><span class="in-3 poj-icon">└</span><a href="zaojiashu.html">建筑工程(click)</a></td>
+                                    <td>2016-01-01</td>
+                                    <td>2016-01-01</td>
+                                </tr>
+                                <tr>
+                                    <td></td>
+                                    <td class="in-6"><span class="in-3 poj-icon">└</span><a href="#">机械设备安装工程</a></td>
+                                    <td>2016-01-01</td>
+                                    <td>2016-01-01</td>
+                                </tr>
+                                <tr>
+                                    <td></td>
+                                    <td class="in-5"><a href="#" class="tree-close" title="展开"><i class="fa fa-plus-square-o  mr-1"></i></a><i class="fa fa-folder-o"></i>&nbsp;<a href="#">2号生产车间</a></td>
+                                    <td>......</td>
+                                    <td>......</td>
+                                </tr>
+                            </tbody>
+                        </table>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="slide-sidebar">
+            <div class="side-content">
+                <div class="p-3">
+                    <legend>1号生产车间 汇总</legend>
+                    <table class="table table-bordered table-hover table-sm">
+                        <thead>
+                            <tr>
+                                <th rowspan="2"></th>
+                                <th rowspan="2">序号</th>
+                                <th rowspan="2">名称</th>
+                                <th rowspan="2">金额</th>
+                                <th colspan="6">其中</th>
+                                <th rowspan="2">占造价比例(%)</th>
+                                <th rowspan="2">建筑面积</th>
+                                <th rowspan="2">单方造价</th>
+                            </tr>
+                            <tr>
+                                <th>分部分项合计</th>
+                                <th>措施项目合计</th>
+                                <th>其他项目合计</th>
+                                <th>安全文明施工费</th>
+                                <th>规费</th>
+                                <th>税金</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            <tr>
+                                <td>1</td>
+                                <td>一</td>
+                                <td>建筑工程</td>
+                                <td>0</td>
+                                <td>0</td>
+                                <td>0</td>
+                                <td>0</td>
+                                <td>0</td>
+                                <td>0</td>
+                                <td>0</td>
+                                <td>0</td>
+                                <td>0</td>
+                                <td>0</td>
+                            </tr>
+                            <tr>
+                                <td>2</td>
+                                <td>二</td>
+                                <td>建筑工程</td>
+                                <td>0</td>
+                                <td>0</td>
+                                <td>0</td>
+                                <td>0</td>
+                                <td>0</td>
+                                <td>0</td>
+                                <td>0</td>
+                                <td>0</td>
+                                <td>0</td>
+                                <td>0</td>
+                            </tr>
+                            <tr>
+                                <td>3</td>
+                                <td> </td>
+                                <td> </td>
+                                <td> </td>
+                                <td> </td>
+                                <td> </td>
+                                <td> </td>
+                                <td> </td>
+                                <td> </td>
+                                <td> </td>
+                                <td> </td>
+                                <td> </td>
+                                <td> </td>
+                            </tr>
+                            <tr>
+                                <td>4</td>
+                                <td>一</td>
+                                <td>合计</td>
+                                <td>0</td>
+                                <td>0</td>
+                                <td>0</td>
+                                <td>0</td>
+                                <td>0</td>
+                                <td>0</td>
+                                <td>0</td>
+                                <td> </td>
+                                <td> </td>
+                                <td> </td>
+                            </tr>
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+    <!--弹出新建工程-->
+    <div class="modal fade" id="addProj" data-backdrop="static">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                  <h5 class="modal-title">新建工程</h5>
+                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                        <span aria-hidden="true">&times;</span>
+                    </button>
+                </div>
+                <div class="modal-body">
+                    <form>
+                        <div class="collapse" id="moreinfo">
+                            <div class="form-group">
+                                <label>建设项目</label>
+                                <input type="text" class="form-control" placeholder="输入建设项目名称" id='buildName'>
+                            </div>
+                            <div class="form-group">
+                                <label>单项工程</label>
+                                <input type="text" class="form-control" placeholder="输入单项工程名称" id="xiangName">
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <label>单位工程</label>
+                            <input type="text" class="form-control" placeholder="输入单位工程名称" id="tenderName">
+                        </div>
+                        <div class="form-group" >
+                            <label class="custom-control custom-checkbox" id="isAddFolder">
+                                <input type="checkbox" class="custom-control-input">
+                                <span class="custom-control-indicator" ></span>
+                                <span class="custom-control-description">建立 <b>建设项目</b> 和 <b>单项工程</b> 文件夹</span>
+                            </label>
+                        </div>
+                        <div class="form-group">
+                            <label>计价方式</label>
+                            <div>
+                                <label class="custom-control custom-radio">
+                                    <input id="radio1" name="radio" type="radio" class="custom-control-input">
+                                    <span class="custom-control-indicator"></span>
+                                    <span class="custom-control-description">清单计价</span>
+                                </label>
+                                <label class="custom-control custom-radio">
+                                    <input id="radio2" name="radio" type="radio" class="custom-control-input">
+                                    <span class="custom-control-indicator"></span>
+                                    <span class="custom-control-description">定额计价</span>
+                                </label>
+                            </div>
+                        </div>
+                    </form>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                    <a href="#" class="btn btn-primary" id='addProjOk'>确定</a>
+                </div>
+            </div>
+        </div>
+    </div>
+    <!--弹出新建文件夹-->
+    <div class="modal fade" id="addFolder" data-backdrop="static">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                  <h5 class="modal-title">新建文件夹</h5>
+                  <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                      <span aria-hidden="true">&times;</span>
+                  </button>
+                </div>
+                <div class="modal-body">
+                    <form>
+                        <div class="form-group">
+                            <label>文件夹</label>
+                            <input type="text" class="form-control" placeholder="输入文件夹名称" id="folder-name-input">
+                            <span class="form-text text-muted">Smartcost为你提供了灵活的工程管理功能,如:</span>
+                            <span class="form-text text-muted">当你想汇总多个 <b>单位工程</b> 时,只需把它们都放在一个文件夹即可。</span>
+                        </div>
+                    </form>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                    <a href="#" class="btn btn-primary" id="addFolderOk">确定</a>
+                </div>
+            </div>
+        </div>
+    </div>
+    <!--弹出重命名-->
+    <div class="modal fade" id="rename" data-backdrop="static">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 class="modal-title">重命名</h5>
+                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                        <span aria-hidden="true">&times;</span>
+                    </button>
+                </div>
+                <div class="modal-body">
+                    <form>
+                        <div class="form-group">
+                            <input type="text" class="form-control" placeholder="输入名称" id="newName">
+                        </div>
+                    </form>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                    <a href="#" class="btn btn-primary" id="renameOk">确定</a>
+                </div>
+            </div>
+        </div>
+    </div>
+    <!--弹出删除-->
+    <div class="modal fade" id="del" data-backdrop="static">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 class="modal-title">删除确认</h5>
+                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                        <span aria-hidden="true">&times;</span>
+                    </button>
+                </div>
+                <div class="modal-body">
+                    <h5 class="text-danger" id="tenderHint">删除 "建筑工程" ?</h5>
+                    <h5 class="text-danger" id="folderHint">删除 "XX项目文件夹" 以及它包含的子项?</h5>
+                    <p class="" id = 'restoreHint'>删除后,你可以在回收站找到它。</p>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                    <a href="#" class="btn btn-danger" id="deleteProjOk">删除</a>
+                </div>
+            </div>
+        </div>
+    </div>
+    <!--弹出移动到-->
+    <div class="modal fade" id="moveto" data-backdrop="static">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                  <h5 class="modal-title">移动到...</h5>
+                  <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                  </button>
+                </div>
+                <div class="modal-body">
+                  <ul id="treeDemo" class="ztree"></ul>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                    <a href="#" class="btn btn-primary" id="movetoOk">确定</a>
+                </div>
+            </div>
+        </div>
+    </div>
+    <!--弹出复制到-->
+    <div class="modal fade" id="copyto" data-backdrop="static">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 class="modal-title">复制到...</h5>
+                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                        <span aria-hidden="true">&times;</span>
+                    </button>
+                </div>
+                <div class="modal-body">
+                    <ul id="treeDemo2" class="ztree"></ul>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                    <a href="#" class="btn btn-primary" id="copytoOk">确定</a>
+                </div>
+            </div>
+        </div>
+    </div>
+    <!-- JS. -->
+    <script src="lib/jquery/jquery.min.js"></script>
+    <script src="lib/tether/tether.min.js"></script>
+    <script src="lib/bootstrap/bootstrap.min.js"></script>
+    <script src="lib/global.js"></script>
+    <script src="web/scripts/tree_table.js"></script>
+    <script src="web/pm/js/pm_ajax.js"></script>
+    <script src="web/pm/js/pm_main.js" charset="UTF-8"></script>
+    <!-- zTree -->
+  	<script type="text/javascript" src="lib/ztree/jquery.ztree.core.js"></script>
+  	<script type="text/javascript" src="lib/ztree/jquery.ztree.excheck.js"></script>
+    <SCRIPT type="text/javascript">
+  		<!--
+  		var setting = {	};
+
+  		var zNodes =[
+  			{ name:"父节点1 - 展开", open:true,
+  				children: [
+  					{ name:"父节点11 - 折叠",
+  						children: [
+  							{ name:"叶子节点111"},
+  							{ name:"叶子节点112"},
+  							{ name:"叶子节点113"},
+  							{ name:"叶子节点114"}
+  						]},
+  					{ name:"父节点12 - 折叠",
+  						children: [
+  							{ name:"叶子节点121"},
+  							{ name:"叶子节点122"},
+  							{ name:"叶子节点123"},
+  							{ name:"叶子节点124"}
+  						]},
+  					{ name:"父节点13 - 没有子节点", isParent:true}
+  				]},
+  			{ name:"父节点2 - 折叠",
+  				children: [
+  					{ name:"父节点21 - 展开", open:true,
+  						children: [
+  							{ name:"叶子节点211"},
+  							{ name:"叶子节点212"},
+  							{ name:"叶子节点213"},
+  							{ name:"叶子节点214"}
+  						]},
+  					{ name:"父节点22 - 折叠",
+  						children: [
+  							{ name:"叶子节点221"},
+  							{ name:"叶子节点222"},
+  							{ name:"叶子节点223"},
+  							{ name:"叶子节点224"}
+  						]},
+  					{ name:"父节点23 - 折叠",
+  						children: [
+  							{ name:"叶子节点231"},
+  							{ name:"叶子节点232"},
+  							{ name:"叶子节点233"},
+  							{ name:"叶子节点234"}
+  						]}
+  				]},
+  			{ name:"父节点3 - 没有子节点", isParent:true}
+
+  		];
+
+  		$(document).ready(function(){
+  			$.fn.zTree.init($("#treeDemo"), setting, zNodes);
+  		});
+  		//-->
+  	</SCRIPT>
+    <SCRIPT type="text/javascript">
+  		<!--
+  		var setting = {	};
+
+  		var zNodes =[
+  			{ name:"父节点1 - 展开", open:true,
+  				children: [
+  					{ name:"父节点11 - 折叠",
+  						children: [
+  							{ name:"叶子节点111"},
+  							{ name:"叶子节点112"},
+  							{ name:"叶子节点113"},
+  							{ name:"叶子节点114"}
+  						]},
+  					{ name:"父节点12 - 折叠",
+  						children: [
+  							{ name:"叶子节点121"},
+  							{ name:"叶子节点122"},
+  							{ name:"叶子节点123"},
+  							{ name:"叶子节点124"}
+  						]},
+  					{ name:"父节点13 - 没有子节点", isParent:true}
+  				]},
+  			{ name:"父节点2 - 折叠",
+  				children: [
+  					{ name:"父节点21 - 展开", open:true,
+  						children: [
+  							{ name:"叶子节点211"},
+  							{ name:"叶子节点212"},
+  							{ name:"叶子节点213"},
+  							{ name:"叶子节点214"}
+  						]},
+  					{ name:"父节点22 - 折叠",
+  						children: [
+  							{ name:"叶子节点221"},
+  							{ name:"叶子节点222"},
+  							{ name:"叶子节点223"},
+  							{ name:"叶子节点224"}
+  						]},
+  					{ name:"父节点23 - 折叠",
+  						children: [
+  							{ name:"叶子节点231"},
+  							{ name:"叶子节点232"},
+  							{ name:"叶子节点233"},
+  							{ name:"叶子节点234"}
+  						]}
+  				]},
+  			{ name:"父节点3 - 没有子节点", isParent:true}
+
+  		];
+
+  		$(document).ready(function(){
+  			$.fn.zTree.init($("#treeDemo2"), setting, zNodes);
+  		});
+  		//-->
+  	</SCRIPT>
+</body>
+<script type="text/javascript">
+    autoFlashHeight();
+</script>
+
+</html>

+ 68 - 0
web/pm/js/pm_ajax.js

@@ -0,0 +1,68 @@
+/**
+ * Created by Mai on 2017/3/8.
+ */
+// 获取用户全部项目数据
+var GetAllProjectData = function (callback) {
+    $.ajax({
+        type:"POST",
+        url: '/getProjects',
+        data: {'data': JSON.stringify({"user_id": userID})},
+        dataType: 'json',
+        cache: false,
+        timeout: 50000,
+        success: function(result){
+            if (result.error === 0) {
+                callback(result.data);
+                //Tree = $.fn.treeTable.init(table, ProjTreeSetting, result.data);
+            } else {
+                alert('error: ' + result.message);
+            }
+        },
+        error: function(jqXHR, textStatus, errorThrown){
+            alert('error ' + textStatus + " " + errorThrown);
+        }
+    });
+}
+// 更新数据到服务器
+var UpdateProjectData = function (updateData, callback) {
+    $.ajax({
+        type:"POST",
+        url: '/updateProjects',
+        data: {'data': JSON.stringify({"user_id": userID, "updateData": updateData})},
+        dataType: 'json',
+        cache: false,
+        timeout: 50000,
+        success: function(result){
+            if (result.error === 0) {
+                callback(result.data);
+            } else {
+                alert('error: ' + result.message);
+            }
+        },
+        error: function(jqXHR, textStatus, errorThrown){
+            alert('error ' + textStatus + " " + errorThrown);
+        }
+    });
+};
+
+// 重命名项目
+var RenameProject = function(projId, newName, callback) {
+    $.ajax({
+        type: "POST",
+        url: '/renameProject',
+        data: {'data': JSON.stringify({"user_id": userID, "id": projId, "newName": newName})},
+        dataType: 'json',
+        cache: false,
+        timeout: 15000,
+        success: function(result){
+            if (result.error === 0) {
+                callback();
+            } else {
+                alert('error' + result.message);
+            }
+        },
+        error: function(iqXHR, textStatus, errorThrown){
+            alert('error ' + textStatus + " " + errorThrown)
+        }
+    });
+}

+ 533 - 0
web/pm/js/pm_main.js

@@ -0,0 +1,533 @@
+/**
+ * Created by Mai on 2017/2/24.
+ */
+var Tree = null, movetoZTree = null, copytoZTree = null;
+var ProjTreeSetting = {
+    viewEvent: {
+        beforeSelect: function (node) {
+            if (node && node.row) {
+                $('td:eq(0)', node.row).children().remove();
+            }
+        },
+        onSelectNode: function (node) {
+            // 新建文件夹 是否可见
+            if (node.data.projType === 'Tender') {
+                $('#addFolderBtn').hide();
+            } else {
+                $('#addFolderBtn').show();
+            }
+            // 重命名可见
+            $('#renameBtn').show();
+            // 删除可见
+            $('#delBtn').show();
+            // 移动到、复制到、共享、协同 是否可见
+            if (node.data.projType === 'Tender') {
+                $('#movetoBtn').show();
+                $('#copytoBtn').show();
+                $('#shareBtn').show();
+                $('#cooperateBtn').show();
+            } else {
+                $('#movetoBtn').hide();
+                $('#copytoBtn').hide();
+                $('#shareBtn').hide();
+                $('#cooperateBtn').hide();
+            }
+            $('td:eq(0)', node.row).append($('<i class="fa fa-sort" data-toggle="tooltip" data-placement="top" title="长安拖动"></i>'));
+        }
+    }
+}
+// 从服务器拉取数据
+var LoadProjTree = function () {
+    var table = $('#ProjTree');
+    $('thead', table).remove();
+    $('tbody', table).remove();
+    GetAllProjectData(function (data) {
+        Tree = $.fn.treeTable.init(table, ProjTreeSetting, data);
+    });
+};
+
+var GetNeedUpdatePreNode = function (parent, next) {
+    if (parent && parent.id() !== -1) {
+        if (next && next.preSibling()) {
+            return next.preSibling();
+        } else {
+            return parent.lastChild();
+        }
+    } else {
+        return null;
+    }
+};
+var GetPreNodeUpdateData = function (pre, nid) {
+    var data = {};
+    data['updateType'] = 'update';
+    data['updateData'] = {id: pre.id(), nextId: nid};
+    return data;
+}
+// 获取新建项目数据
+var GetAddProjUpdateData = function (parent, next, name) {
+    var datas = [], updateData, pre, newId = Tree.maxNodeId() + 1;
+    updateData = {};
+    updateData['updateType'] = 'new';
+    updateData['updateData'] = {};
+    updateData['updateData']['id'] = newId;
+    updateData['updateData']['parentId'] = parent ? parent.id() : -1;
+    updateData['updateData']['nextId'] = next ? next.id() : -1;
+    updateData['updateData']['name'] = name;
+    updateData['updateData']['projType'] = 'Tender';
+    datas.push(updateData);
+    return datas;
+};
+var GetAddFolderProjUpdateData = function (parent, next, folderName1, folderName2, name) {
+    var datas = [], updateData, folderData1, folderData2, pre, newId = Tree.maxNodeId() + 1;
+    var addUpdateData = function (parentId, nextId, name, projType) {
+        var data = {};
+        data['updateType'] = 'new';
+        data['updateData'] = {};
+        data['updateData']['id'] = newId;
+        data['updateData']['parentId'] = parentId;
+        data['updateData']['nextId'] = nextId;
+        data['updateData']['name'] = name;
+        data['updateData']['projType'] = projType;
+        newId += 1;
+        datas.push(data);
+        return data;
+    }
+    folderData1 = addUpdateData(parent.id(), next ? next.id() : -1, folderName1, 'Folder');
+    folderData2 = addUpdateData(folderData1.updateData.id, -1, folderName2, 'Folder');
+    addUpdateData(folderData2.updateData.id, -1, name, 'Tender');
+    return datas;
+};
+// 获取新建文件夹数据
+var GetAddForlderUpdateData = function (parent, next, folderName) {
+    var datas = [], updateData, pre, newId = Tree.maxNodeId() + 1;
+    updateData = {};
+    updateData['updateType'] = 'new';
+    updateData['updateData'] = {};
+    updateData['updateData']['id'] = newId;
+    updateData['updateData']['parentId'] = parent ? parent.id() : -1;
+    updateData['updateData']['nextId'] = next ? next.id() : -1;
+    updateData['updateData']['name'] = folderName;
+    updateData['updateData']['projType'] = 'Folder';
+    datas.push(updateData);
+
+    pre = GetNeedUpdatePreNode(parent, next);
+    if (pre) {
+        datas.push(GetPreNodeUpdateData(pre, newId));
+    }
+    return datas;
+};
+
+var GetNextChangeUpdateData = function (datas, node, next) {
+    var data = null;
+    if (node && node.id() !== -1) {
+        data = {};
+        data['updateType'] = 'update';
+        data['updateData'] = {};
+        data['updateData']['id'] = node.id();
+        data['updateData']['nextId'] = next ? next.id() : -1;
+        datas.push(data);
+    }
+    return data;
+}
+var GetFullName = function (node) {
+    var fullName = [],
+        cur = node;
+    while (cur && cur.data) {
+        fullName.unshift(cur.data.name);
+        cur = cur.parent;
+    }
+    return fullName;
+}
+var GetDeleteUpdateData = function (node) {
+    var datas = [], updateData,
+        pre = node.preSibling(),
+        deleteNodeData = function (node) {
+            var data = {};
+            data['updateType'] = 'delete';
+            data['updateData'] = {};
+            data['updateData']['id'] = node.id();
+            data['updateData']['deleted'] = true;
+            if (node.data.projType === 'Tender') {
+                data['updateData']['deleteFullFolder'] = GetFullName(node.parent);
+            }
+            return data;
+        },
+        addDeleteChildren = function (children) {
+            children.forEach(function(child){
+                datas.push(deleteNodeData(child));
+                addDeleteChildren(child.children);
+            });
+        };
+    if (pre && pre.id() !== -1) {
+        updateData = {};
+        updateData['updateType'] = 'update';
+        updateData['updateData'] = {};
+        updateData['updateData']['id'] = pre.id();
+        updateData['updateData']['nextId'] = node ? node.nid() : -1;
+        datas.push(updateData);
+    }
+    datas.push(deleteNodeData(node));
+    addDeleteChildren(node.children);
+    return datas;
+};
+
+var GetMoveUpdateData = function (node, parent, next) {
+    var datas = [], updateData;
+    updateData = GetNextChangeUpdateData(datas, node.preSibling(), node.nextSibling);
+    if (next) {
+        updateData = GetNextChangeUpdateData(datas, next.preSibling(), node);
+    }
+    updateData = {};
+    updateData['updateType'] = 'update';
+    updateData['updateData'] = {};
+    updateData['updateData']['id'] = node.id();
+    updateData['updateData']['parentId'] = parent ? parent.id() : -1;
+    updateData['updateData']['nextId'] = next ? next.id() : -1;
+    datas.push(updateData);
+    return datas;
+};
+
+var GetCopyUpdateData = function (node, parent, next){
+    var datas = [], updateData, pre;
+    updateData = {};
+    updateData['updateType'] = 'new';
+    updateData['updateData'] = {};
+    updateData['updateData']['id'] = node.tree.maxNodeId() + 1;
+    updateData['updateData']['parentId'] = parent ? parent.id() : -1;
+    updateData['updateData']['nextId'] = next ? next.id() : -1;
+    updateData['updateData']['name'] = node.data.name;
+    updateData['updateData']['projType'] = node.data.projType;
+    datas.push(updateData);
+
+    pre = GetNeedUpdatePreNode(parent, next);
+    if (pre) {
+        updateData = {};
+        updateData['updateType'] = 'update';
+        updateData['updateData'] = {};
+        updateData['updateData']['id'] = pre.id();
+        updateData['updateData']['nextId'] = node.tree.maxNodeId() + 1;
+        datas.push(updateData);
+    }
+    return datas;
+}
+
+var ConvertTreeToZtree = function (Tree, zTreeObj, filterNode) {
+    var setting = {
+            data: {
+                simpleData: {
+                    enable:true,
+                    idKey: "id",
+                    pIdKey: "pId",
+                    rootPId: "-1"
+                }
+            }},
+        zTreeData = [],
+        exportNodesData = function (nodes) {
+            nodes.forEach(function (node) {
+                if (node !== filterNode) {
+                    var data = {};
+                    data['id'] = node.data['id'];
+                    data['pId'] = node.pid();// === -1 ? 0 : node.pid();
+                    data['name'] = node.data['name'];
+                    data['isParent'] = node.data.projType === 'Folder';//(node.data.projType === 'Folder' && node.children.length === 0);
+                    data['open'] = node.data.projType === 'Folder';//node.children.length !== 0;
+                    zTreeData.push(data);
+                    exportNodesData(node.children);
+                }
+            })
+        };
+    exportNodesData(Tree._root.children);
+    return $.fn.zTree.init(zTreeObj, setting, zTreeData);
+};
+var GetTargetTreeNode = function (zTreeObj) {
+    var ztree_selected;
+    if (zTreeObj && Tree) {
+        ztree_selected = zTreeObj.getSelectedNodes().length === 0 ? null : zTreeObj.getSelectedNodes()[0];
+        return ztree_selected ? Tree.findNode(ztree_selected.id) : null;
+    } else {
+        return null;
+    }
+};
+
+var AddFolderChildValid = function (node) {
+    if (node.data.projType === 'Folder') {
+        if (node.children.length === 0) {
+            return true;
+        } else {
+            return (node.firstChild().data.projType === 'Folder');
+        }
+    } else {
+        return false;
+    }
+};
+var AddTenderChildValid = function (node) {
+    if (node.data.projType === 'Folder') {
+        if (node.children.length === 0) {
+            return true;
+        } else {
+            return (node.firstChild().data.projType === 'Tender');
+        }
+    } else {
+        return false;
+    }
+};
+
+LoadProjTree();
+$('#movetoBtn').hide();
+$('#copytoBtn').hide();
+$('#shareBtn').hide();
+$('#cooperateBtn').hide();
+
+// 新建文件夹
+$('#addFolderBtn').click(function () {
+    if (Tree) {
+        $('#addFolder').modal('show');
+    }
+});
+$('#addFolderOk').click(function () {
+    var form = $('#addFolder');
+    var name = $('#folder-name-input').val();
+    var updateData, parent, next;
+    if (name) {
+        if (Tree.selected()) {
+            if (Tree.selected().children.length === 0 || Tree.selected().firstChild().data.projType === 'Folder') {
+                parent = Tree.selected();
+                next = Tree.selected().firstChild();
+            } else {
+                parent = Tree.selected().parent;
+                next = Tree.selected().nextSibling;
+            }
+        } else {
+            parent = Tree._root;
+            next = Tree.firstNode();
+        }
+
+        updateData = GetAddForlderUpdateData(parent, next, name);
+        UpdateProjectData(updateData, function(datas){
+            datas.forEach(function (data) {
+                if (data.updateType === 'new') {
+                    Tree.addNodeData(data.updateData, parent, next);
+                }
+            });
+            form.modal('hide');
+        });
+    }
+});
+
+// 新建工程
+var AddProj = function () {
+    var name = $('#tenderName').val(), updateData, parent, next;
+    if (name !== '') {
+        // if (Tree.selected()){
+        //     if (Tree.selected().data.projType === 'Tender') {
+        //         parent = Tree.selected().parent;
+        //         next = Tree.selected().next;
+        //     } else {
+        //         if (Tree.selected().firstNode.data.projType === 'Tender') {
+        //             parent = Tree.selected();
+        //             next = Tree.selected().firstNode();
+        //         } else {
+        //             return;
+        //         }
+        //     }
+        // } else {
+        //     parent = Tree._root();
+        //     next = Tree.firstNode();
+        // }
+        updateData = GetAddProjUpdateData(Tree._root, Tree.firstNode(), name);
+        UpdateProjectData(updateData, function (datas) {
+            datas.forEach(function (data) {
+                var parent, next;
+                if (data.updateType === 'new') {
+                    parent = data.updateData.parentId === -1 ?  Tree._root : Tree.findNode(data.updateData.parentId);
+                    next = data.updateData.nextId === -1 ? null : Tree.findNode(data.updateData.nextId);
+                    Tree.addNodeData(data.updateData, parent, next);
+                }
+            });
+            $('#addProj').modal('hide');
+        });
+    }
+}
+var AddFolderProj = function () {
+    var nameB = $('#buildName').val(), nameX = $('#xiangName').val(), name = $('#tenderName').val(), updateData;
+    if (nameB !== '' && nameX !== '' && name !== '') {
+        updateData = GetAddFolderProjUpdateData(Tree._root, Tree.firstNode(), nameB, nameX, name);
+        UpdateProjectData(updateData, function (datas) {
+            datas.forEach(function (data) {
+                var parent, next;
+                if (data.updateType === 'new') {
+                    parent = data.updateData.parentId === -1 ?  Tree._root : Tree.findNode(data.updateData.parentId);
+                    next = data.updateData.nextId === -1 ? null : Tree.findNode(data.updateData.nextId);
+                    Tree.addNodeData(data.updateData, parent, next);
+                }
+            });
+            $('#addProj').modal('hide');
+        });
+    }
+}
+$('#addProjBtn').click(function () {
+    if (Tree) {
+        $('#addProj').modal('show');
+    }
+});
+$('#addProjOk').click(function () {
+    var hasFolder = $('#isAddFolder>input').is(':checked');
+    if (hasFolder) {
+        AddFolderProj();
+    } else {
+        AddProj();
+    }
+});
+$('#isAddFolder').change(function () {
+    if ($('input',this).is(':checked')) {
+        $('#moreinfo').collapse('show');
+    } else {
+        $('#moreinfo').collapse('hide');
+    }
+});
+
+// 重命名
+$('#renameBtn').click(function() {
+    if (Tree && Tree.selected()) {
+        $('#rename').modal('show');
+    }
+})
+$('#rename').on('show.bs.modal', function () {
+    $('#newName').attr('placeholder', Tree.selected().data.name);
+});
+$('#renameOk').click(function () {
+    var select = Tree.selected(),
+        newName = $('#newName').val(),
+        form = $('#rename');
+    if (select && newName !== select.data.name) {
+        RenameProject(select.id(), newName, function () {
+            form.modal('hide');
+            select.data.name = newName;
+            Tree.refreshNodesDom([select]);
+        });
+    } else {
+        form.modal('hide');
+    }
+});
+
+// 删除
+$('#delBtn').click(function() {
+    if (Tree && Tree.selected()) {
+        $('#del').modal('show');
+    }
+});
+$('#del').on('show.bs.modal', function() {
+    var hasTenderChild = function (children) {
+        var i;
+        for (i = 0; i < children.length; i++) {
+            if (children[i].children.length === 0) {
+                if (children[i].data.projType === 'Tender') {
+                    return true;
+                }
+            } else if (hasTenderChild(children[i].children)) {
+                return true;
+            }
+        }
+        return false;
+    };
+    if (Tree.selected().children.length == 0) {
+        $('#tenderHint').show();
+        $('#tenderHint').text('删除 "' + Tree.selected().data.name +'" ?');
+        $('#folderHint').hide();
+    } else {
+        $('#tenderHint').hide();
+        $('#folderHint').show();
+        $('#folderHint').text('删除 "'+ Tree.selected().data.name +'" 以及它包含的子项?');
+    }
+    if (hasTenderChild([Tree.selected()])) {
+        $('#restoreHint').show();
+    } else {
+        $('#restoreHint').hide();
+    }
+});
+$('#deleteProjOk').click(function () {
+    var updateData, form = $('#del');
+    if (Tree) {
+        updateData = GetDeleteUpdateData(Tree.selected());
+        UpdateProjectData(updateData, function () {
+            form.modal('hide');
+            Tree.removeNode(Tree.selected());
+        });
+    }
+});
+
+// 移动至
+$('#movetoBtn').click(function () {
+    if (Tree && Tree.selected()) {
+        $('#moveto').modal('show');
+    }
+});
+$('#moveto').on('show.bs.modal', function () {
+    movetoZTree = ConvertTreeToZtree(Tree, $('#treeDemo'), Tree.selected());
+});
+$('#movetoOk').click(function () {
+    var updateData, form = $('#moveto'),
+        target = GetTargetTreeNode($.fn.zTree.getZTreeObj('treeDemo')),
+        parent, next, cur = Tree.selected();
+    if (target) {
+        if (target.data.projType === 'Tender') {
+            parent = target.parent;
+            next = target.nextSibling;
+        } else {
+            parent = target;
+            next = target.firstChild();
+        }
+
+        if (parent !== cur.parent || (next !== cur && next !== cur.nextSibling)){
+            updateData = GetMoveUpdateData(Tree.selected(), parent, next);
+            UpdateProjectData(updateData, function (data) {
+                form.modal('hide');
+                Tree.move(Tree.selected(), parent, next);
+            });
+        } else {
+            form.modal('hide');
+        }
+    } else {
+        form.modal('hide');
+    }
+})
+
+// 复制到
+$('#copytoBtn').click(function () {
+    if (Tree && Tree.selected()) {
+        $('#copyto').modal('show');
+    }
+});
+$('#copyto').on('show.bs.modal', function () {
+    copytoZTree = ConvertTreeToZtree(Tree, $('#treeDemo2'));
+});
+$('#copytoOk').click(function() {
+    var updateData, form = $('#copyto'),
+        target = GetTargetTreeNode($.fn.zTree.getZTreeObj('treeDemo2')),
+        parent, next, cur = Tree.selected();
+    if (target && (target.data.projType === 'Tender' || target.children.length === 0 || target.firstChild().data.projType === 'Tender')) {
+        if (target.data.projType === 'Tender') {
+            parent = target.parent;
+            next = target.nextSibling;
+        } else {
+            parent = target;
+            next = target.firstChild();
+        }
+
+        if (parent !== cur.parent || (next !== cur && next !== cur.nextSibling)){
+            updateData = GetCopyUpdateData(Tree.selected(), parent, next);
+            UpdateProjectData(updateData, function (data) {
+                form.modal('hide');
+                data.forEach(function (nodeData) {
+                    if (nodeData.updateType === 'new') {
+                        Tree.addNodeData(nodeData.updateData, parent, next);
+                    }
+                });
+            });
+        } else {
+            form.modal('hide');
+        }
+    } else {
+        form.modal('hide');
+    }
+});

+ 650 - 0
web/scripts/tree_table.js

@@ -0,0 +1,650 @@
+/*
+ * Created by MaiXinRong on 2017/2/9.
+ * 项目管理 专用树结构
+ */
+
+(function($) {
+    var _setting = {
+ 		tree: {
+ 			id: 'id',
+ 			pid: 'parentId',
+ 			nid: 'nextId',
+ 			btnColumn: 1,
+ 			iconCol: 'projType'
+ 		},
+ 		columns: [
+ 		{
+ 			head: '',
+ 			data: '<i class="fa fa-sort" data-toggle="tooltip" data-placement="top" title="长安拖动"></i>',
+ 			static: true,
+ 			width: '40'
+ 		},
+ 		{
+ 			head: '工程列表',
+ 			data: 'name',
+ 			static: false,
+ 			width: '78%',
+			url: '/tender',
+			urlParam: [{
+				name: 'id',
+				value: 'id'
+			}]
+ 		},
+ 		{
+ 			head: '最近使用',
+ 			data: 'lastDateTime',
+ 			static: false,
+ 			width: '10%'
+ 		},
+ 		{
+ 			head: '创建日期',
+ 			data: 'createDateTime',
+ 			static: false,
+ 			width: '10%'
+ 		}
+ 		],
+ 		dataTemp: {
+			id: -1,
+			parentId: -1,
+			nextId: -1
+ 		},
+        viewEvent: {
+            beforeSelect: null,
+            onSelectNode: null
+        }
+ 	};
+
+ 	var Node = (function () {
+ 		function Node(tree, data) {
+ 			this.parent = null;
+ 			this.nextSibling = null;
+ 			this.children = [];
+
+ 			this.tree = tree;
+ 			this.data = data;
+            this.setting = tree.setting;
+
+ 			this.expanded = true;
+
+ 			this.row = null;
+ 			this.expandBtn = null;
+ 		}
+
+ 		Node.prototype.firstChild = function() {
+            return (this.children.length === 0) ? null : this.children[0];
+ 		};
+
+ 		Node.prototype.lastChild = function () {
+ 			return (this.children.length === 0) ? null : this.children[this.children.length - 1];
+ 		}
+
+        Node.prototype.deepestRow = function () {
+            return (this.children.length === 0) ? this.row : this.lastChild().deepestRow();
+        }
+
+ 		Node.prototype.addChild = function (child, childNext) {
+ 			if (childNext){
+ 				this.children.push(child);
+ 			} else {
+ 				if (this.childIndex(childNext) > -1){
+ 					this.children.splice(this.childIndex(childNext) - 1, 0, child);
+ 				} else {
+ 					this.children.push(child);
+ 				}
+ 			}
+ 			return child;
+ 		};
+
+ 		Node.prototype.childIndex = function (child) {
+ 			return this.children.indexOf(child);
+ 		};
+
+ 		Node.prototype.depth = function () {
+			return this.parent ? this.parent.depth() + 1 : 0;
+ 		};
+
+ 		Node.prototype.domId = function () {
+            return this.data ? this.tree.domId + '_' + this.data[this.setting.tree.id] : '';
+ 		};
+
+ 		Node.prototype.expand = function (bool) {
+ 			this.expanded = bool;
+ 			_view._refreshTreeBtn(this);
+ 			if (this.expanded) {
+ 				_view._showNodes(this.children);
+ 			} else {
+ 				_view._hideNodes(this.children);
+ 			}
+ 		};
+
+		Node.prototype.preSibling = function () {
+			var iIndex = this.parent.childIndex(this);
+			if (iIndex === -1){
+				return null;
+			} else {
+				return iIndex > 0 ? this.parent.children[iIndex-1] : null;
+			}
+		};
+
+ 		Node.prototype.setParent = function (parent) {
+ 			if (parent && this.parent !== parent) {
+ 				this.parent = parent;
+ 				this.data[this.setting.tree.pid] = this.pid();
+ 			}
+ 		};
+
+ 		Node.prototype.setNextSibling = function (nextSibling) {
+ 			if (this.nextSibling !== nextSibling) {
+ 				this.nextSibling = nextSibling;
+                this.data[this.setting.tree.nid] = this.nid();
+ 			}
+ 		}
+
+ 		Node.prototype.id = function () {
+			return this.data ? this.data[this.setting.tree.id] : -1;
+ 		};
+
+ 		Node.prototype.pid = function () {
+			return this.parent ? this.parent.id() : -1;
+ 		};
+
+ 		Node.prototype.nid = function () {
+			return this.nextSibling ? this.nextSibling.id() : -1;
+ 		};
+
+		Node.prototype.propertyJoin = function (dataName) {
+			return this.parent ? this.parent.propertyJoin(dataName) + ';' + this.data[dataName] : this.data[dataName];
+		}
+
+ 		return Node;
+ 	})();
+
+ 	var Tree = (function () {
+ 		function Tree(obj, setting) {
+ 			this._root = new Node(this);
+
+ 			this.treeObj = obj;
+ 			this.domId = obj.attr('id');
+ 			this.treeHeadObj = _view._makeTableHead(this.treeObj, setting);
+ 			this.treeBodyObj = _view._makeTableBody(this.treeObj);
+
+            this.setting = setting;
+
+ 			var _maxNodeId = 0;
+ 			this.newNodeId = function (id) {
+ 				if (arguments.length > 0){
+ 					_maxNodeId = (id > _maxNodeId) ? id : _maxNodeId;
+ 				} else {
+ 					_maxNodeId += 1;
+ 					return _maxNodeId;
+ 				}
+ 			};
+			this.maxNodeId = function (){
+				return _maxNodeId;
+			}
+ 		};
+
+ 		Tree.prototype.firstNode = function (){
+ 			return this._root.firstChild();
+ 		};
+
+ 		Tree.prototype.traverseDF = function(callback){
+ 			var recurse = function (node) {
+ 				var i;
+ 				if (node !== this._root) {
+ 					callback(node);
+ 				}
+ 				for (i = 0; i < node.children.length; i++){
+ 					recurse(node.children[i]);
+ 				}
+ 			}
+ 			recurse(this._root);
+ 		};
+
+ 		Tree.prototype.findNode = function (id){
+ 			var treenode = null,
+ 				callback = function (node) {
+ 					if (node.data && node.data[node.setting.tree.id] === id){
+ 						treenode = node;
+ 					}
+ 				};
+ 			this.traverseDF.call(this, callback);
+ 			return treenode;
+ 		};
+
+ 		Tree.prototype.findNodeByDomId = function (domId) {
+ 			var treenode = null,
+ 				callback = function (node) {
+ 					if (node.domId === domId) {
+ 						treenode = node;
+ 					}
+ 				};
+ 			this.traverseDF.call(this, callback);
+ 			return treenode;
+ 		};
+
+ 		Tree.prototype.removeNode = function (node) {
+ 			var iIndex;
+ 			if (node) {
+ 				iIndex = node.parent.childIndex(node);
+ 				if (iIndex > 0) {
+ 					node.parent.children[iIndex - 1].setNextSibling(node.nextSibling);
+ 				}
+ 				node.parent.children.splice(iIndex, 1);
+ 			}
+ 			_view._removeNodesRowDom([node]);
+ 		};
+
+ 		Tree.prototype.loadData = function (arrData) {
+ 			var i, that = this;
+			var createTempNode = function (id, setting) {
+				var tempData = {};
+				tempData[setting.tree.id] = id;
+				return new Node(that, tempData);
+			};
+			var loadNode = function (data, setting) {
+				var	node = that.findNode(data[setting.tree.id]) || null,
+ 					parent = that.findNode(data[setting.tree.pid]) || null,
+ 					next = that.findNode(data[setting.tree.nid]) || null,
+ 					tempData;
+
+ 				if (!node) {
+ 					node = new Node(that, data);
+ 				}
+ 				node.data = data;
+				that.newNodeId(node.id());
+
+				if (!parent){
+					if (data[setting.tree.pid] === -1) {
+ 						parent = that._root;
+ 					} else {
+ 						parent = createTempNode(data[setting.tree.pid], setting);
+ 						parent.parent = that._root;
+ 						that._root.children.push(parent);
+ 					}
+ 				}
+ 				if (node.parent && node.parent !== parent) {
+ 					node.parent.children.splice(node.parent.childIndex(node), 1);
+ 				}
+ 				node.parent = parent;
+
+ 				if (!next && data[setting.tree.nid] !== -1) {
+ 					next = createTempNode(data[setting.tree.nid], setting);
+ 					next.parent = parent;
+ 				}
+ 				node.nextSibling = next;
+
+ 				if (parent.childIndex(node) === -1){
+ 					if (!next){
+ 						parent.children.push(node);
+ 					} else if (parent.childIndex(next) === -1){
+ 						parent.children.push(node);
+ 						parent.children.push(next);
+ 					} else {
+ 						parent.children.splice(parent.childIndex(next), 0, node);
+ 					}
+ 				}
+			};
+
+ 			for (i = 0; i < arrData.length; i++){
+ 				loadNode(arrData[i], this.setting);
+ 			}
+ 		};
+
+    	Tree.prototype.refreshNodesDom = function (nodes, recurse){
+			var that = this;
+			nodes.forEach(function (node) {
+				if (node.row) {
+					$('td', node.row).remove();
+					_view._makeTableRowCells(node.row, node);
+				} else if (node !== that._root) {
+					_view._makeRowDom(that.treeBodyObj, node);
+				}
+				if (recurse) {
+					that.refreshNodesDom(node.children, recurse);
+				}
+			})
+    	};
+
+ 		Tree.prototype.refreshTreeDom = function () {
+			this.refreshNodesDom(this._root.children, true);
+ 		};
+
+		Tree.prototype.addNodeData = function (data, parent, nextSibling) {
+			var node = null;
+			var pNode = parent ? parent : this._root;
+			if (!nextSibling || (nextSibling.parent === pNode && pNode.childIndex(nextSibling) > -1)) {
+				node = new Node(this, data);
+                this.newNodeId(data[this.setting.tree.id]);
+				node.row = _view._makeRowDom(this.treeBodyObj, node);
+				this.move(node, pNode, nextSibling);
+			}
+			return node;
+		}
+
+ 		Tree.prototype.move = function(node, parent, nextSibling) {
+ 			var iIndex = -1, pre;
+ 			if (parent && (!nextSibling || (nextSibling.parent === parent && parent.childIndex(nextSibling) > -1))) {
+ 				if (node) {
+ 					if (node.parent) {
+ 						iIndex = node.parent.childIndex(node);
+ 						if (iIndex > 0) {
+ 							node.parent.children[iIndex - 1].setNextSibling(node.nextSibling);
+ 						}
+ 						node.parent.children.splice(iIndex, 1);
+						this.refreshNodesDom([node.parent], false);
+ 					}
+
+ 					if (nextSibling) {
+ 						iIndex = parent.childIndex(nextSibling);
+ 						if (iIndex > 0){
+ 							pre = parent.children[iIndex - 1];
+ 							pre.setNextSibling(node);
+ 							parent.children.splice(iIndex - 1, 0, node);
+ 						} else {
+ 							parent.children.splice(0, 0, node);
+ 						}
+ 					} else {
+ 						if (parent.children.length > 0){
+ 							pre = parent.lastChild();
+ 							pre.setNextSibling(node);
+ 						}
+ 						parent.children.push(node);
+ 					}
+ 					node.setParent(parent);
+ 					node.setNextSibling(nextSibling);
+ 					if (node.row) {
+ 						if (pre) {
+ 							_view._moveRowDom(node, pre.deepestRow());
+ 						} else if (parent.id() !== -1) {
+                            _view._moveRowDom(node, parent.row);
+                        } else if (nextSibling) {
+ 							_view._moveRowDomBefore(node, nextSibling.row);
+ 						}
+ 					}
+                    if (node.parent.row) {
+                        this.refreshNodesDom([node.parent], false);
+                    }
+ 				}
+ 			} else {
+ 				this.e.throw('Error(information of moving node has mistake).');
+ 			}
+ 		};
+
+ 		Tree.prototype.selected = (function () {
+ 			var selectNode = null;
+
+ 			var select = function (node) {
+ 				if (arguments.length === 0) {
+ 					return selectNode;
+ 				} else {
+ 					if (node) {
+ 						if (selectNode && selectNode.row) {
+ 							selectNode.row.removeClass('table-active');
+ 						}
+ 						selectNode = node;
+ 						node.row.addClass('table-active');
+ 					}
+ 				}
+ 			}
+ 			return select;
+ 		})();
+
+ 		return Tree;
+ 	})();
+
+ 	_data = {
+		clone: function (obj) {
+			if (obj === null) return null;
+
+			var o = _data.isArray(obj) ? [] : {};
+			for (var i in obj) {
+				o[i] = (obj[i] instanceof Date) ? new Date(obj[i].getTime()) : (typeof obj[i] === "object" ? _data.clone(obj[i]) : obj[i]);
+			}
+			return o;
+		},
+		isArray: function (arr) {
+			return Object.prototype.toString.apply(arr) === "[object Array]";
+		}
+ 	},
+
+ 	_event = {
+ 		_selectProj: function (node) {
+            if (node.setting.viewEvent.beforeSelect) {
+                node.setting.viewEvent.beforeSelect(node.tree.selected());
+            }
+ 			node.tree.selected(node);
+            if (node.setting.viewEvent.onSelectNode) {
+                node.setting.viewEvent.onSelectNode(node);
+            }
+ 		}
+ 	}
+
+ 	_view = {
+ 		_makeTableHeadCell: function (obj, col){
+ 			var th;
+ 			th = $('<th>');
+ 			th.attr('width', col.width);
+ 			th.text(col.head);
+ 			obj.append(th);
+ 		},
+ 		_makeTableHead: function (obj, setting){
+ 			var thead, tr, i;
+ 			thead = $('<thead>');
+ 			tr = $('<tr>');
+ 			for (i = 0; i < setting.columns.length; i++){
+ 				_view._makeTableHeadCell(tr, setting.columns[i]);
+ 			}
+ 			thead.append(tr);
+ 			obj.append(thead);
+ 			return thead;
+ 		},
+ 		_makeTableBody: function (obj){
+ 			var tbody;
+ 			tbody = $('<tbody>');
+ 			obj.append(tbody);
+ 			return tbody;
+ 		},
+		/*_makeTreeExpandBtn: function (obj, node){
+ 			var span = $('<a href="#" class="tree-open" title="收起"><i class="fa fa-minus-square-o mr-1"></i></a>'),
+ 				btn = $('i', span);
+ 			obj.append(span);
+ 			node.expandBtn = span;
+ 			btn.bind('click', {node: node}, function(e){
+ 				e.data.node.expand(!e.data.proj.expand());
+ 			});
+ 			if (node.children.length === 0) {
+ 				span.hide();
+ 			} else {
+ 				span.show();
+ 			}
+ 			return span;
+ 		},*/
+ 		_makeTableRowCell: function (obj, node, columns, index) {
+ 			var html = [], td, i;
+
+ 			html.push('<td ');
+ 			if (index === node.setting.tree.btnColumn){
+ 				html.push('class=', 'in-', node.depth().toString(), '>');
+                // to Do 此部分中Folder与Tender的区别应由外部控制
+ 				if (node.children.length !== 0) {
+ 					html.push('<a href="#" class="tree-open" title="收起"><i class="fa fa-minus-square-o mr-1"></i></a>');
+ 				} else if (node.data.projType === 'Folder') {
+ 					html.push('<a href="#" class="tree-open" title="收起"><i class="in-2"></i></a>');
+ 				} else {
+                    if (node.parent === node.tree._root) {
+                        html.push('<span class="in-1 poj-icon">└</span>');
+                    } else {
+                        html.push('<span class="in-3 poj-icon">└</span>');
+                    }
+                }
+                if (node.data.projType === 'Folder') {
+     				html.push('<i class="tree-icon fa fa-folder-open-o"></i>');
+                } else {
+         			html.push('<i class="tree-icon"></i>');
+                }
+ 			} else {
+ 				html.push('>');
+ 			}
+
+ 			if (!columns.static) {
+ 				//html.push('&nbsp;', node.data[columns.data]);
+                if (node.data.projType === 'Folder') {
+    				html.push('&nbsp;');
+                }
+				html.push('<a ');
+				if (columns.url) {
+					html.push('href="', columns.url);
+					if (columns.urlParam){
+						html.push('?');
+						columns.urlParam.forEach(function(item){
+							html.push(item.name, '=', node.data[item.value]);
+						})
+					}
+					html.push('"');
+				}
+				html.push('>');
+				html.push(node.data[columns.data]);
+				html.push('</a>');
+ 			}
+ 			html.push('</td>');
+
+ 			td = $(html.join(''));
+
+ 			if (index === node.setting.tree.btnColumn) {
+ 				node.expandBtn = $('.tree-open>.fa', td);
+ 				node.expandBtn.bind('click', {node: node}, function(e){
+ 					e.data.node.expand(!e.data.node.expanded);
+ 				});
+
+ 				node.icon = $('.tree-icon', td);
+ 			}
+
+ 			obj.append(td);
+ 			return td;
+ 		},
+ 		_makeTableRowCells: function (obj, node) {
+ 			for (i = 0; i < node.setting.columns.length; i++){
+ 				_view._makeTableRowCell(obj, node, node.setting.columns[i], i);
+ 			}
+ 		},
+ 		_makeRowDom: function (obj, node) {
+ 			var tr, i;
+ 			tr = $('<tr>');
+ 			obj.append(tr);
+ 			tr.attr('id', node.domId());
+ 			node.row = tr;
+ 			_view._makeTableRowCells(tr, node);
+            //tr.click(_event._selectProj(node));
+ 			tr.click(function(){
+                _event._selectProj(node);
+ 			});
+
+			/*$(tr).draggable({
+        		helper: "clone",
+        		opacity: .75,
+        		refreshPositions: true, // Performance?
+        		revert: "invalid",
+        		revertDuration: 300,
+        		scroll: true
+      		});
+
+ 			tr.droppable({
+ 				drop: function (e, ui) {
+ 					var target = node.tree.findNodeByDomId($(this).attr('id')),
+ 						drag = node.tree.findNodeByDomId($(ui).attr('id'));
+ 					tree.move(drag, target);
+ 				}
+ 			});	*/
+ 			return tr;
+ 		},
+        _moveRowDomBefore(node, destination) {
+            var moveNodesRowDomBefore = function (nodes) {
+                nodes.forEach(function (node) {
+                    node.row.insertBefore(destination);
+                    moveNodesRowDomBefore(node.children);
+                });
+            }
+            moveNodesRowDomBefore([node]);
+			_view._refreshNodesLevelCss([node]);
+        },
+ 		_moveRowDom: function (node, destination) {
+ 			var pre = destination,
+ 				moveNodeRowDom = function (node) {
+ 					node.row.insertAfter(pre);
+ 					pre = node.row;
+ 					moveChildrenRowDom(node);
+ 				},
+ 				moveChildrenRowDom = function(node){
+ 					var i;
+ 					for (i = 0; i < node.children.length; i++){
+ 						moveNodeRowDom(node.children[i]);
+ 					}
+ 				};
+ 			moveNodeRowDom(node);
+			_view._refreshNodesLevelCss([node]);
+ 		},
+ 		_refreshTreeBtn: function (node) {
+ 			var i;
+ 			if (node.children.length === 0) {
+ 				node.expandBtn.hide();
+ 			} else {
+ 				node.expandBtn.show();
+ 				if (node.expanded) {
+ 					node.expandBtn.removeClass('fa-plus-square-o');
+ 					node.expandBtn.addClass('fa-minus-square-o');
+ 				} else {
+ 					node.expandBtn.removeClass('fa-minus-square-o');
+ 					node.expandBtn.addClass('fa-plus-square-o');
+ 				}
+ 			}
+ 		},
+		_refreshNodesLevelCss: function (nodes) {
+			nodes.forEach(function(node){
+				var td = $('td:eq(' + node.setting.tree.btnColumn.toString() + ')', node.row);
+				td.attr('class', 'in-'+ node.depth().toString());
+				_view._refreshNodesLevelCss(node.children);
+			})
+		},
+ 		_hideNodes: function (nodes) {
+ 			var i, node;
+ 			for (i = 0; i < nodes.length; i++){
+ 				node = nodes[i];
+ 				node.row.hide();
+ 				_view._hideNodes(node.children);
+ 			}
+ 		},
+ 		_showNodes: function (nodes) {
+ 			var i, node;
+ 			for (i = 0; i < nodes.length; i++){
+ 				node = nodes[i];
+ 				node.row.show();
+ 				if (node.expanded) {
+ 					_view._showNodes(node.children);
+ 				}
+ 			}
+ 		},
+ 		_removeNodesRowDom: function (nodes) {
+ 			var i, node;
+ 			for (i = 0; i < nodes.length; i++){
+ 				node = nodes[i];
+ 				_view._removeNodesRowDom(node.children);
+ 				if (node.row) {
+ 					node.row.remove();
+ 				}
+ 			}
+ 		}
+ 	};
+
+ 	$.fn.treeTable = {
+ 		init: function(obj, setting, arrData){
+            var _treeSetting, _tree;
+            _treeSetting = _data.clone(_setting);
+            $.extend(true, _treeSetting, setting)
+ 			var _tree = new Tree(obj, _treeSetting);
+ 			_tree.loadData(arrData);
+ 			_tree.refreshTreeDom();
+ 			return _tree;
+ 		}
+ 	}
+ })(jQuery);