瀏覽代碼

1. 修改service/ledger.js,调整增删改6个基本操作返回数据,并同步修改单元测试代码
2. jquery-contextmenu
3. 台账分解页面,增删改6项基本操作(代码待优化)

MaiXinRong 7 年之前
父節點
當前提交
4417c212ee

+ 186 - 1
app/controller/ledger_controller.js

@@ -50,6 +50,11 @@ module.exports = app => {
             await this.layout('ledger/explode.ejs', renderData);
         }
 
+        /**
+         * 获取子节点
+         * @param ctx
+         * @returns {Promise<void>}
+         */
         async getChildren(ctx) {
             const responseData = {
                 err: 0,
@@ -74,7 +79,187 @@ module.exports = app => {
             }
 
             ctx.body = responseData;
-        }
+        };
+
+        /**
+         * 新增节点
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async addNode(ctx) {
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: []
+            };
+            try {
+                const tenderId = ctx.session.sessionUser.tenderId;
+                if (!tenderId) {
+                    throw '当前未打开标段';
+                }
+                const data = JSON.parse(ctx.request.body.data);
+                if (isNaN(data.id) || data.id <= 0) {
+                    throw '参数错误';
+                }
+
+                responseData.data = await ctx.service.ledger.addNode(tenderId, data.id);
+            } catch (err) {
+                responseData.err = 1;
+                responseData.msg = err;
+            }
+
+            ctx.body = responseData;
+        };
+
+        /**
+         * 删除节点
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async deleteNode(ctx) {
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: []
+            };
+            try {
+                const tenderId = ctx.session.sessionUser.tenderId;
+                if (!tenderId) {
+                    throw '当前未打开标段';
+                }
+                const data = JSON.parse(ctx.request.body.data);
+                if (isNaN(data.id) || data.id <= 0) {
+                    throw '参数错误';
+                }
+
+                responseData.data = await ctx.service.ledger.deleteNode(tenderId, data.id);
+            } catch (err) {
+                responseData.err = 1;
+                responseData.msg = err;
+            }
+
+            ctx.body = responseData;
+        };
+
+        /**
+         * 上移节点
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async upMove(ctx) {
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: []
+            };
+            try {
+                const tenderId = ctx.session.sessionUser.tenderId;
+                if (!tenderId) {
+                    throw '当前未打开标段';
+                }
+                const data = JSON.parse(ctx.request.body.data);
+                if (isNaN(data.id) || data.id <= 0) {
+                    throw '参数错误';
+                }
+
+                responseData.data = await ctx.service.ledger.upMoveNode(tenderId, data.id);
+            } catch (err) {
+                responseData.err = 1;
+                responseData.msg = err;
+            }
+
+            ctx.body = responseData;
+        };
+
+        /**
+         * 下移节点
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async downMove(ctx) {
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: []
+            };
+            try {
+                const tenderId = ctx.session.sessionUser.tenderId;
+                if (!tenderId) {
+                    throw '当前未打开标段';
+                }
+                const data = JSON.parse(ctx.request.body.data);
+                if (isNaN(data.id) || data.id <= 0) {
+                    throw '参数错误';
+                }
+
+                responseData.data = await ctx.service.ledger.downMoveNode(tenderId, data.id);
+            } catch (err) {
+                responseData.err = 1;
+                responseData.msg = err;
+            }
+
+            ctx.body = responseData;
+        };
+
+        /**
+         * 升级节点
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async upLevel(ctx) {
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: []
+            };
+            try {
+                const tenderId = ctx.session.sessionUser.tenderId;
+                if (!tenderId) {
+                    throw '当前未打开标段';
+                }
+                const data = JSON.parse(ctx.request.body.data);
+                if (isNaN(data.id) || data.id <= 0) {
+                    throw '参数错误';
+                }
+
+                responseData.data = await ctx.service.ledger.upLevelNode(tenderId, data.id);
+            } catch (err) {
+                responseData.err = 1;
+                responseData.msg = err;
+            }
+
+            ctx.body = responseData;
+        };
+
+        /**
+         * 降级节点
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async downLevel(ctx) {
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: []
+            };
+            try {
+                const tenderId = ctx.session.sessionUser.tenderId;
+                if (!tenderId) {
+                    throw '当前未打开标段';
+                }
+                const data = JSON.parse(ctx.request.body.data);
+                if (isNaN(data.id) || data.id <= 0) {
+                    throw '参数错误';
+                }
+
+                responseData.data = await ctx.service.ledger.downLevelNode(tenderId, data.id);
+            } catch (err) {
+                responseData.err = 1;
+                responseData.msg = err;
+            }
+
+            ctx.body = responseData;
+        };
     }
 
     return LedgerController;

+ 287 - 0
app/public/css/jquery-contextmenu/jquery.contextMenu.css

@@ -0,0 +1,287 @@
+@charset "UTF-8";
+/*!
+ * jQuery contextMenu - Plugin for simple contextMenu handling
+ *
+ * Version: v2.4.5
+ *
+ * Authors: Björn Brala (SWIS.nl), Rodney Rehm, Addy Osmani (patches for FF)
+ * Web: http://swisnl.github.io/jQuery-contextMenu/
+ *
+ * Copyright (c) 2011-2017 SWIS BV and contributors
+ *
+ * Licensed under
+ *   MIT License http://www.opensource.org/licenses/mit-license
+ *
+ * Date: 2017-05-05T14:40:37.763Z
+ */
+@-webkit-keyframes cm-spin {
+  0% {
+    -webkit-transform: translateY(-50%) rotate(0deg);
+            transform: translateY(-50%) rotate(0deg);
+  }
+  100% {
+    -webkit-transform: translateY(-50%) rotate(359deg);
+            transform: translateY(-50%) rotate(359deg);
+  }
+}
+@-o-keyframes cm-spin {
+  0% {
+    -webkit-transform: translateY(-50%) rotate(0deg);
+         -o-transform: translateY(-50%) rotate(0deg);
+            transform: translateY(-50%) rotate(0deg);
+  }
+  100% {
+    -webkit-transform: translateY(-50%) rotate(359deg);
+         -o-transform: translateY(-50%) rotate(359deg);
+            transform: translateY(-50%) rotate(359deg);
+  }
+}
+@keyframes cm-spin {
+  0% {
+    -webkit-transform: translateY(-50%) rotate(0deg);
+         -o-transform: translateY(-50%) rotate(0deg);
+            transform: translateY(-50%) rotate(0deg);
+  }
+  100% {
+    -webkit-transform: translateY(-50%) rotate(359deg);
+         -o-transform: translateY(-50%) rotate(359deg);
+            transform: translateY(-50%) rotate(359deg);
+  }
+}
+
+@font-face {
+  font-family: "context-menu-icons";
+  font-style: normal; 
+  font-weight: normal;
+
+  src: url("font/context-menu-icons.eot?lnvb");
+  src: url("font/context-menu-icons.eot?lnvb#iefix") format("embedded-opentype"), url("font/context-menu-icons.woff2?lnvb") format("woff2"), url("font/context-menu-icons.woff?lnvb") format("woff"), url("font/context-menu-icons.ttf?lnvb") format("truetype");
+}
+
+.context-menu-icon-add:before {
+  content: "\EA01";
+}
+
+.context-menu-icon-copy:before {
+  content: "\EA02";
+}
+
+.context-menu-icon-cut:before {
+  content: "\EA03";
+}
+
+.context-menu-icon-delete:before {
+  content: "\EA04";
+}
+
+.context-menu-icon-edit:before {
+  content: "\EA05";
+}
+
+.context-menu-icon-loading:before {
+  content: "\EA06";
+}
+
+.context-menu-icon-paste:before {
+  content: "\EA07";
+}
+
+.context-menu-icon-quit:before {
+  content: "\EA08";
+}
+
+.context-menu-icon::before {
+  position: absolute;
+  top: 50%;
+  left: 0;
+  width: 2em; 
+  font-family: "context-menu-icons";
+  font-size: 1em;
+  font-style: normal;
+  font-weight: normal;
+  line-height: 1;
+  color: #2980b9;
+  text-align: center;
+  -webkit-transform: translateY(-50%);
+      -ms-transform: translateY(-50%);
+       -o-transform: translateY(-50%);
+          transform: translateY(-50%);
+
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.context-menu-icon.context-menu-hover:before {
+  color: #fff;
+}
+
+.context-menu-icon.context-menu-disabled::before {
+  color: #bbb;
+}
+
+.context-menu-icon.context-menu-icon-loading:before {
+  -webkit-animation: cm-spin 2s infinite;
+       -o-animation: cm-spin 2s infinite;
+          animation: cm-spin 2s infinite;
+}
+
+.context-menu-icon.context-menu-icon--fa {
+  display: list-item;
+  font-family: inherit;
+}
+.context-menu-icon.context-menu-icon--fa::before {
+  position: absolute;
+  top: 50%;
+  left: 0;
+  width: 2em; 
+  font-family: FontAwesome;
+  font-size: 1em;
+  font-style: normal;
+  font-weight: normal;
+  line-height: 1;
+  color: #2980b9;
+  text-align: center;
+  -webkit-transform: translateY(-50%);
+      -ms-transform: translateY(-50%);
+       -o-transform: translateY(-50%);
+          transform: translateY(-50%);
+
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+.context-menu-icon.context-menu-icon--fa.context-menu-hover:before {
+  color: #fff;
+}
+.context-menu-icon.context-menu-icon--fa.context-menu-disabled::before {
+  color: #bbb;
+}
+
+.context-menu-list {
+  position: absolute; 
+  display: inline-block;
+  min-width: 13em;
+  max-width: 26em;
+  padding: .25em 0;
+  margin: .3em;
+  font-family: inherit;
+  font-size: inherit;
+  list-style-type: none;
+  background: #fff;
+  border: 1px solid #bebebe;
+  border-radius: .2em;
+  -webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, .5);
+          box-shadow: 0 2px 5px rgba(0, 0, 0, .5);
+}
+
+.context-menu-item {
+  position: relative;
+  padding: .2em 2em;
+  color: #2f2f2f;
+  -webkit-user-select: none;
+     -moz-user-select: none;
+      -ms-user-select: none;
+          user-select: none; 
+  background-color: #fff;
+}
+
+.context-menu-separator {
+  padding: 0; 
+  margin: .35em 0;
+  border-bottom: 1px solid #e6e6e6;
+}
+
+.context-menu-item > label > input,
+.context-menu-item > label > textarea {
+  -webkit-user-select: text;
+     -moz-user-select: text;
+      -ms-user-select: text;
+          user-select: text;
+}
+
+.context-menu-item.context-menu-hover {
+  color: #fff;
+  cursor: pointer; 
+  background-color: #2980b9;
+}
+
+.context-menu-item.context-menu-disabled {
+  color: #bbb;
+  cursor: default; 
+  background-color: #fff;
+}
+
+.context-menu-input.context-menu-hover {
+  color: #2f2f2f; 
+  cursor: default;
+}
+
+.context-menu-submenu:after {
+  position: absolute;
+  top: 50%;
+  right: .5em;
+  z-index: 1; 
+  width: 0;
+  height: 0;
+  content: '';
+  border-color: transparent transparent transparent #2f2f2f;
+  border-style: solid;
+  border-width: .25em 0 .25em .25em;
+  -webkit-transform: translateY(-50%);
+      -ms-transform: translateY(-50%);
+       -o-transform: translateY(-50%);
+          transform: translateY(-50%);
+}
+
+/**
+ * Inputs
+ */
+.context-menu-item.context-menu-input {
+  padding: .3em .6em;
+}
+
+/* vertically align inside labels */
+.context-menu-input > label > * {
+  vertical-align: top;
+}
+
+/* position checkboxes and radios as icons */
+.context-menu-input > label > input[type="checkbox"],
+.context-menu-input > label > input[type="radio"] {
+  position: relative;
+  top: .12em; 
+  margin-right: .4em;
+}
+
+.context-menu-input > label {
+  margin: 0;
+}
+
+.context-menu-input > label,
+.context-menu-input > label > input[type="text"],
+.context-menu-input > label > textarea,
+.context-menu-input > label > select {
+  display: block;
+  width: 100%; 
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+
+.context-menu-input > label > textarea {
+  height: 7em;
+}
+
+.context-menu-item > .context-menu-list {
+  top: .3em; 
+  /* re-positioned by js */
+  right: -.3em;
+  display: none;
+}
+
+.context-menu-item.context-menu-visible > .context-menu-list {
+  display: block;
+}
+
+.context-menu-accesskey {
+  text-decoration: underline;
+}

File diff suppressed because it is too large
+ 16 - 0
app/public/css/jquery-contextmenu/jquery.contextMenu.min.css


+ 167 - 212
app/public/css/main.css

@@ -9,9 +9,12 @@ body {
     font-size: 0.9rem
 }
 .btn.disabled, .btn:disabled {
-    color:#999
+  color:#999
 }
 /*自定义css*/
+.sjs-height-1,.sjs-height-2{
+  overflow: hidden;
+}
 .form-signin {
     max-width: 500px;
     margin: 150px auto;
@@ -56,104 +59,104 @@ body {
 }
 /*2.主体框架*/
 .header {
-    background:#fff;
-    position: fixed;
-    z-index: 10;
-    width: 100%;
-    height: 50px;
-    top: 0;
-    left: 0
+  background:#fff;
+  position: fixed;
+  z-index: 10;
+  width: 100%;
+  height: 50px;
+  top: 0;
+  left: 0
 }
 .main{
-    position: relative;
-    z-index: 4;
+  position: relative;
+  z-index: 4;
 }
 .main-nav {
-    position: fixed;
-    z-index: 99;
-    width:120px;
-    left: 0;
-    top: 0;
-    height: 100%;
-    background: #33425b;
+  position: fixed;
+  z-index: 99;
+  width:120px;
+  left: 0;
+  top: 0;
+  height: 100%;
+  background: #33425b;
 }
 .main-panel{
-    padding-left:120px;
-    box-sizing: border-box;
+  padding-left:120px;
+  box-sizing: border-box;
 }
 .panel-sidebar{
-    box-sizing: border-box;
-    background: #fbfcfd;
-    position: fixed;
-    height: 100%;
-    z-index: 4;
-    left:120px;
-    padding-top: 100px;
-    border-right: 1px solid #ddd;
-    width: 250px;
+  box-sizing: border-box;
+  background: #fbfcfd;
+  position: fixed;
+  height: 100%;
+  z-index: 4;
+  left:120px;
+  padding-top: 100px;
+  border-right: 1px solid #ddd;
+  width: 250px;
 }
 .panel-content{
-    padding:115px 0 0;
-    position: relative;
-    z-index: 3;
-    box-sizing: border-box;
-    overflow-y: auto;
-    height: 100vh;
+  padding:115px 0 0;
+  position: relative;
+  z-index: 3;
+  box-sizing: border-box;
+  overflow-y: auto;
+  height: 100vh;
 }
 .panel-content .content-wrap{
-    margin:0 15px 15px;
+  margin:0 15px 15px;
 }
 .panel-sidebar+.panel-content{
-    padding: 115px 0 0 250px;
+  padding: 115px 0 0 250px;
 }
 .panel-title, .panel-title>.title-bar {
-    height:50px;
-    line-height: 50px
+  height:50px;
+  line-height: 50px
 }
 .panel-title{
-    position: fixed;
-    top: 50px;
-    z-index: 98;
-    width: 100%;
-    box-sizing: border-box;
-    background: #fff;
-    box-shadow: 0 1px 3px rgba(0,0,0,.05);
-    border-top: 1px solid #ddd;
+  position: fixed;
+  top: 50px;
+  z-index: 98;
+  width: 100%;
+  box-sizing: border-box;
+  background: #fff;
+  box-shadow: 0 1px 3px rgba(0,0,0,.05);
+  border-top: 1px solid #ddd;
 }
 .panel-sidebar .panel-title{
-    width:250px;
-    border-right: 1px solid #ddd;
-    box-shadow: 0 1px 3px rgba(0,0,0,.1);
+  width:250px;
+  border-right: 1px solid #ddd;
+  box-shadow: 0 1px 3px rgba(0,0,0,.1);
 }
 .panel-content .panel-title{
-    left: 0;
-    padding-left: 370px;
-    padding-right: 20px;
+  left: 0;
+  padding-left: 370px;
+  padding-right: 20px;
 }
 .panel-content .panel-title.fluid{
-    padding-left:120px
+  padding-left:120px
 }
 .panel-title>.title-bar{
-    padding-left: 20px
+  padding-left: 20px
 }
 .panel-title>.title-bar>h2,.panel-title>.title-main>h2{
-    font-size: 16px;
-    margin:0;
-    height: 50px;
-    line-height: 50px;
-    display:block
+  font-size: 16px;
+  margin:0;
+  height: 50px;
+  line-height: 50px;
+  display:block
 }
 .panel-title>.title-bar>h2 .btn{
-    margin-right:15px
+  margin-right:15px
 }
 .panel-title>.title-main .btn.pull-right {
-    margin:10px 0 0 10px
+  margin:10px 0 0 10px
 }
 .panel-title>.title-main .form-control {
-    margin:10px 0 0 0
+  margin:10px 0 0 0
 }
 .panel-title>.title-main{
-    padding-left: 15px
+  padding-left: 15px
 }
 /*滚动*/
 .scrollbar-auto {
@@ -176,228 +179,180 @@ body {
 }
 /*头部*/
 .header .logo {
-    float: left;
-    box-shadow: 1px 0 6px rgba(0,0,0,.06);
-    margin-right: 20px;
-    margin:0
+  float: left;
+  box-shadow: 1px 0 6px rgba(0,0,0,.06);
+  margin-right: 20px;
+  margin:0
 }
 .header .logo>a{
-    width:120px;
-    height:50px;
-    line-height: 50px;
-    display: inline-block;
-    color:#fff;
-    font-size:24px;
-    padding:0 10px;
-    transition: all ease .4s;
-    background:#207fd1 url(logo.png) no-repeat;
-    text-indent: -9999px;
-    vertical-align: top
+  width:120px;
+  height:50px;
+  line-height: 50px;
+  display: inline-block;
+  color:#fff;
+  font-size:24px;
+  padding:0 10px;
+  transition: all ease .4s;
+  background:#207fd1 url(logo.png) no-repeat;
+  text-indent: -9999px;
+  vertical-align: top
 }
 .header .logo>a:hover{
-    background-color:#5596cf;
-    text-decoration: none;
+  background-color:#5596cf;
+  text-decoration: none;
 }
 .header-user > div {
-    float:left
+  float:left
 }
 .avatar .pic {
-    height: 35px;
-    width: 35px;
-    border-radius: 100%;
-    display: inline-block;
-    float:left;
-    margin:7px 7px 0 0
+  height: 35px;
+  width: 35px;
+  border-radius: 100%;
+  display: inline-block;
+  float:left;
+  margin:7px 7px 0 0
 }
 .avatar .pic img{
-    display: block;
-    width: 100%;
-    height: 100%;
-    border-radius: 100%;
+  display: block;
+  width: 100%;
+  height: 100%;
+  border-radius: 100%;
 }
 .avatar > a,.msg >a{
-    display: block;
-    height:50px;
-    line-height: 50px;
-    color:#666;
-    padding:0 15px;
-    cursor: pointer;
+  display: block;
+  height:50px;
+  line-height: 50px;
+  color:#666;
+  padding:0 15px;
+  cursor: pointer;
 }
 .avatar > a:hover,.msg > a:hover{
-    text-decoration: none;
-    box-shadow: inset 0 3px 5px rgba(0,0,0,.125)
+  text-decoration: none;
+  box-shadow: inset 0 3px 5px rgba(0,0,0,.125)
 }
 .header-user .msg{
-    border-left:1px solid #eee
+  border-left:1px solid #eee
 }
 .header-user .msg .glyphicon{
-    font-size:20px;
-    vertical-align: middle;
+  font-size:20px;
+  vertical-align: middle;
 }
 .header-user .msg .badge{
-    margin:0 0 0 5px
+  margin:0 0 0 5px
 }
 .header .poj-name {
-    float:left;
-    padding:0 0 0 15px;
-    font-size:18px
+  float:left;
+  padding:0 0 0 15px;
+  font-size:18px
 }
 .header .poj-name a{
-    color:#666
+  color:#666
 }
 .header .poj-name > span{
-    height:50px;
-    line-height:50px;
+  height:50px;
+  line-height:50px;
 }
 /*登陆相关*/
 .login-body{
-    background: #fff
+  background: #fff
 }
 .login-infoinput {
-    margin-top:15%
+  margin-top:15%
 }
 /*侧栏主菜单*/
 .nav-top{
-    padding-top: 50px
+  padding-top: 50px
 }
 .bg-nav a{
-    color:#7786ab;
-    width:120px;
-    height: 55px;
-    line-height: 55px;
-    display: inline-block;
-    padding:0 0 0 10px
+  color:#7786ab;
+  width:120px;
+  height: 55px;
+  line-height: 55px;
+  display: inline-block;
+  padding:0 0 0 10px
 }
 .bg-nav > li{
-    width:120px
+  width:120px
 }
 .bg-nav > li.active{
-    background: #192948
+  background: #192948
 }
 .bg-nav > li.active a{
-    border-radius: 0;
-    background: #192948
+  border-radius: 0;
+  background: #192948
 }
 .bg-nav > li > a:hover,.bg-nav > li.active > a:hover{
-    background: #192948;
-    color:#f2f2f2;
-    text-decoration: none;
+  background: #192948;
+  color:#f2f2f2;
+  text-decoration: none;
 }
 .bg-nav > li + li {
     margin-top:0;
 }
 .bg-nav .sub-menu {
-    list-style:none;
-    padding:0 0 0 20px;
-    width:120px;
-    display: none
+  list-style:none;
+  padding:0 0 0 20px;
+  width:120px;
+  display: none
 }
 .bg-nav .sub-menu a {
-    width:100px;
-    height:30px;
-    line-height:30px
+  width:100px;
+  height:30px;
+  line-height:30px
 }
 .bg-nav .sub-menu:last-child{
-    margin:0 0 20px 0
+  margin:0 0 20px 0
 }
 .bg-nav .menu-arrow{
-    margin:22px 8px 0 0
+  margin:22px 8px 0 0
 }
 .nav-box h3{
-    font-size: 14px;
-    font-weight: 700;
-    padding-bottom: 4px;
-    border-bottom: 1px solid #e2eaec;
-    padding-right: 15px;
-    margin-bottom: 10px;
-    margin-left: 20px
+  font-size: 14px;
+  font-weight: 700;
+  padding-bottom: 4px;
+  border-bottom: 1px solid #e2eaec;
+  padding-right: 15px;
+  margin-bottom: 10px;
+  margin-left: 20px
 }
 .nav-list li a{
-    color: #333;
-    display: block;
-    height: 35px;
-    line-height: 35px;
-    box-sizing: border-box;
-    padding-left: 17px;
-    padding-right: 45px;
-    text-overflow: ellipsis;
-    position: relative;
+  color: #333;
+  display: block;
+  height: 35px;
+  line-height: 35px;
+  box-sizing: border-box;
+  padding-left: 17px;
+  padding-right: 45px;
+  text-overflow: ellipsis;
+  position: relative;
 }
 .nav-list li a:hover{
-    text-decoration: none;
-    background:#e4e7ea;
-    cursor: pointer;
+  text-decoration: none;
+  background:#e4e7ea;
+  cursor: pointer;
 }
 .nav-list li a .badge{
-    position: absolute;
-    right:17px;
-    top:9px
+  position: absolute;
+  right:17px;
+  top:9px
 }
 .nav-list li.active a{
-    background:#e4e7ea;
-    font-weight: 600
+  background:#e4e7ea;
+  font-weight: 600
 }
 /*内容区*/
 .c-header {
-    padding:0 0 5px
+  padding:0 0 5px
 }
 .c-body{
-    padding:15px;
-    background:#fff;
+  padding:15px;
+  background:#fff;
 }
 .form-group .necessary{
-    font-size:18px;
-    color:#f90000
+  font-size:18px;
+  color:#f90000
 }
 
 .bg-gray {
-    background-color:#bbb!important;
+ background-color:#bbb!important;
 }
-
-.toast{
-    position: absolute;
-    top: 0;
-    width: 65%;
-    left: 0;
-    right: 0;
-    margin: 0 auto;
-    opacity: 0.8;
-    box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
-    z-index: 999;
-    padding: 15px;
-    border-radius: 3px;
-    color: #ffffff;
-    display: none;
-}
-.toast .message{
-    padding-left: 50px;
-    display: inline-block;
-    font-size: 14px;
-}
-.toast i.icon{
-    font-size: 25px;
-    position: absolute;
-    left: 23px;
-    top: 13px;
-}
-.toast.success{
-    background: rgba(16, 196, 105, 0.8);
-    border: 2px solid #10c469
-}
-.toast.error{
-    background-color: rgba(255, 91, 91, 0.8);
-    border: 2px solid #ff5b5b;
-}
-.toast.warning {
-    background-color: rgba(249, 200, 81, 0.8);
-    border: 2px solid #f9c851;
-}
-.toast.info {
-    background-color: rgba(53, 184, 224, 0.8);
-    border: 2px solid #35b8e0;
-}
-label.required::after {
-    color: red;
-    content: "*";
-    margin-left: 4px;
-}

+ 29 - 71
app/public/js/global.js

@@ -1,31 +1,17 @@
 /*全局自适应高度*/
 function autoFlashHeight(){
-    var headerHeight = $(".header").height();
-    var toolsbarHeight = $(".toolsbar").height();
-    var ftoolsbarHeight = $(".toolsbar-f").height();
-    var bottomContentHeight = $(".bottom-content").height();
-    var toolsBarHeightQ = $(".tools-bar-height-q").height();
-    var toolsBarHeightD = $(".tools-bar-height-d").height();
-    $(".main-data-side-q").height($(window).height()-headerHeight-toolsbarHeight-toolsBarHeightQ-202);
-    $(".main-data-side-d").height($(window).height()-headerHeight-toolsbarHeight-toolsBarHeightD-202);
-    $(".main-data-top").height($(window).height()-headerHeight-toolsbarHeight-bottomContentHeight-1);
-    $(".main-data-full").height($(window).height()-headerHeight-toolsbarHeight-1);
-    $(".main-data-full-fl").height($(window).height()-headerHeight-toolsbarHeight-37);
-    $(".main-data-not").height($(window).height()-headerHeight-1);
-    $(".main-data-side-search").height($(window).height()-headerHeight-toolsbarHeight-64);
-    $(".side-content").height($(window).height()-headerHeight );
-    $(".poj-list").height($(window).height()-headerHeight-toolsbarHeight);
-    $(".form-view").height($(window).height()-headerHeight-ftoolsbarHeight);
-    $(".form-list").height($(window).height()-headerHeight-50 );
+    var cHeader = $(".c-header").height();
+    $(".sjs-height-1").height($(window).height()-cHeader-160);
+    $(".sjs-height-2").height($(window).height()-cHeader-191);
 };
 $(window).resize(autoFlashHeight);
 /*全局自适应高度结束*/
 $(function(){
-    /*侧滑*/
-    $(".open-sidebar").click(function(){
-        $(".slide-sidebar").animate({width:"800"}).addClass("open");
-    });
-    $("body").click(function(event){
+/*侧滑*/
+$(".open-sidebar").click(function(){
+    $(".slide-sidebar").animate({width:"800"}).addClass("open");
+});
+$("body").click(function(event){
         var e = event || window.event; //浏览器兼容性
         if(!$(event.target).is('a')) {
             var elem = event.target || e.srcElement;
@@ -39,55 +25,27 @@ $(function(){
         }
 
     });
-    /*侧滑*/
-    /*工具提示*/
-    $(function () {
-        $('[data-toggle="tooltip"]').tooltip()
-    });
-    /*侧栏菜单*/
-    $(".bg-nav > li > a").click(function() {
-        var self = $(this);
-        var subMenu = $(this).siblings('ul.sub-menu');
-        if(subMenu.length > 0) {
-            if(subMenu.is(":visible")) {
-                self.find('.menu-arrow').removeClass('fa-angle-up').addClass('fa-angle-down');
-                subMenu.slideUp('fast');
-                self.parent().removeClass('active');
-            }else{
-                self.parent().addClass('active');
-                self.find('.menu-arrow').removeClass('fa-angle-down').addClass('fa-angle-up');
-                subMenu.slideDown('fast');
-            }
-        }
-    });
-
-    // 数据提交
-    $("#submit-form").click(function() {
-        $("#save-form").submit();
-    });
-
+/*侧滑*/
+/*工具提示*/
+$(function () {
+  $('[data-toggle="tooltip"]').tooltip()
 });
+/*侧栏菜单*/
+$(".bg-nav > li > a").click(function() {
+      var self = $(this);
+      var subMenu = $(this).siblings('ul.sub-menu');
+      if(subMenu.length > 0) {
+          if(subMenu.is(":visible")) {
+              self.find('.menu-arrow').removeClass('fa-angle-up').addClass('fa-angle-down');
+              subMenu.slideUp('fast');
+              self.parent().removeClass('active');
+          }else{
+              self.parent().addClass('active');
+              self.find('.menu-arrow').removeClass('fa-angle-down').addClass('fa-angle-up');
+              subMenu.slideDown('fast');
+          }
+      }
+  });
 
-/**
- * 提示框
- *
- * @param string message
- * @param string type
- * @param string icon
- * @return void
- */
-function toast(message, type, icon)
-{
-    var toast = $(".toast");
-    toast.addClass(type);
-    toast.children('.message').html(message);
-    var iconClass = 'fa-' + icon;
-    toast.children('.icon').addClass(iconClass);
-    toast.fadeIn(500);
 
-    setTimeout(function() {
-        toast.fadeOut('fast');
-        toast.children('.message').text('');
-        toast.children('.icon').removeClass(iconClass);
-    }, 3000);
-}
+});

File diff suppressed because it is too large
+ 2051 - 0
app/public/js/jquery-contextmenu/jquery.contextMenu.js


File diff suppressed because it is too large
+ 2 - 0
app/public/js/jquery-contextmenu/jquery.contextMenu.min.js


+ 513 - 0
app/public/js/jquery-contextmenu/jquery.ui.position.js

@@ -0,0 +1,513 @@
+/*! jQuery UI - v1.12.1 - 2016-09-16
+ * http://jqueryui.com
+ * Includes: position.js
+ * Copyright jQuery Foundation and other contributors; Licensed MIT */
+
+(function( factory ) {
+    if ( typeof define === "function" && define.amd ) {
+
+        // AMD. Register as an anonymous module.
+        define([ "jquery" ], factory );
+    } else {
+
+        // Browser globals
+        factory( jQuery );
+    }
+}(function( $ ) {
+
+    $.ui = $.ui || {};
+
+    var version = $.ui.version = "1.12.1";
+
+
+    /*!
+     * jQuery UI Position 1.12.1
+     * http://jqueryui.com
+     *
+     * Copyright jQuery Foundation and other contributors
+     * Released under the MIT license.
+     * http://jquery.org/license
+     *
+     * http://api.jqueryui.com/position/
+     */
+
+//>>label: Position
+//>>group: Core
+//>>description: Positions elements relative to other elements.
+//>>docs: http://api.jqueryui.com/position/
+//>>demos: http://jqueryui.com/position/
+
+
+    ( function() {
+        var cachedScrollbarWidth,
+            max = Math.max,
+            abs = Math.abs,
+            rhorizontal = /left|center|right/,
+            rvertical = /top|center|bottom/,
+            roffset = /[\+\-]\d+(\.[\d]+)?%?/,
+            rposition = /^\w+/,
+            rpercent = /%$/,
+            _position = $.fn.position;
+
+        function getOffsets( offsets, width, height ) {
+            return [
+                parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
+                parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
+            ];
+        }
+
+        function parseCss( element, property ) {
+            return parseInt( $.css( element, property ), 10 ) || 0;
+        }
+
+        function getDimensions( elem ) {
+            var raw = elem[ 0 ];
+            if ( raw.nodeType === 9 ) {
+                return {
+                    width: elem.width(),
+                    height: elem.height(),
+                    offset: { top: 0, left: 0 }
+                };
+            }
+            if ( $.isWindow( raw ) ) {
+                return {
+                    width: elem.width(),
+                    height: elem.height(),
+                    offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
+                };
+            }
+            if ( raw.preventDefault ) {
+                return {
+                    width: 0,
+                    height: 0,
+                    offset: { top: raw.pageY, left: raw.pageX }
+                };
+            }
+            return {
+                width: elem.outerWidth(),
+                height: elem.outerHeight(),
+                offset: elem.offset()
+            };
+        }
+
+        $.position = {
+            scrollbarWidth: function() {
+                if ( cachedScrollbarWidth !== undefined ) {
+                    return cachedScrollbarWidth;
+                }
+                var w1, w2,
+                    div = $( "<div " +
+                        "style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'>" +
+                        "<div style='height:100px;width:auto;'></div></div>" ),
+                    innerDiv = div.children()[ 0 ];
+
+                $( "body" ).append( div );
+                w1 = innerDiv.offsetWidth;
+                div.css( "overflow", "scroll" );
+
+                w2 = innerDiv.offsetWidth;
+
+                if ( w1 === w2 ) {
+                    w2 = div[ 0 ].clientWidth;
+                }
+
+                div.remove();
+
+                return ( cachedScrollbarWidth = w1 - w2 );
+            },
+            getScrollInfo: function( within ) {
+                var overflowX = within.isWindow || within.isDocument ? "" :
+                        within.element.css( "overflow-x" ),
+                    overflowY = within.isWindow || within.isDocument ? "" :
+                        within.element.css( "overflow-y" ),
+                    hasOverflowX = overflowX === "scroll" ||
+                        ( overflowX === "auto" && within.width < within.element[ 0 ].scrollWidth ),
+                    hasOverflowY = overflowY === "scroll" ||
+                        ( overflowY === "auto" && within.height < within.element[ 0 ].scrollHeight );
+                return {
+                    width: hasOverflowY ? $.position.scrollbarWidth() : 0,
+                    height: hasOverflowX ? $.position.scrollbarWidth() : 0
+                };
+            },
+            getWithinInfo: function( element ) {
+                var withinElement = $( element || window ),
+                    isWindow = $.isWindow( withinElement[ 0 ] ),
+                    isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9,
+                    hasOffset = !isWindow && !isDocument;
+                return {
+                    element: withinElement,
+                    isWindow: isWindow,
+                    isDocument: isDocument,
+                    offset: hasOffset ? $( element ).offset() : { left: 0, top: 0 },
+                    scrollLeft: withinElement.scrollLeft(),
+                    scrollTop: withinElement.scrollTop(),
+                    width: withinElement.outerWidth(),
+                    height: withinElement.outerHeight()
+                };
+            }
+        };
+
+        $.fn.position = function( options ) {
+            if ( !options || !options.of ) {
+                return _position.apply( this, arguments );
+            }
+
+            // Make a copy, we don't want to modify arguments
+            options = $.extend( {}, options );
+
+            var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
+                target = $( options.of ),
+                within = $.position.getWithinInfo( options.within ),
+                scrollInfo = $.position.getScrollInfo( within ),
+                collision = ( options.collision || "flip" ).split( " " ),
+                offsets = {};
+
+            dimensions = getDimensions( target );
+            if ( target[ 0 ].preventDefault ) {
+
+                // Force left top to allow flipping
+                options.at = "left top";
+            }
+            targetWidth = dimensions.width;
+            targetHeight = dimensions.height;
+            targetOffset = dimensions.offset;
+
+            // Clone to reuse original targetOffset later
+            basePosition = $.extend( {}, targetOffset );
+
+            // Force my and at to have valid horizontal and vertical positions
+            // if a value is missing or invalid, it will be converted to center
+            $.each( [ "my", "at" ], function() {
+                var pos = ( options[ this ] || "" ).split( " " ),
+                    horizontalOffset,
+                    verticalOffset;
+
+                if ( pos.length === 1 ) {
+                    pos = rhorizontal.test( pos[ 0 ] ) ?
+                        pos.concat( [ "center" ] ) :
+                        rvertical.test( pos[ 0 ] ) ?
+                            [ "center" ].concat( pos ) :
+                            [ "center", "center" ];
+                }
+                pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
+                pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
+
+                // Calculate offsets
+                horizontalOffset = roffset.exec( pos[ 0 ] );
+                verticalOffset = roffset.exec( pos[ 1 ] );
+                offsets[ this ] = [
+                    horizontalOffset ? horizontalOffset[ 0 ] : 0,
+                    verticalOffset ? verticalOffset[ 0 ] : 0
+                ];
+
+                // Reduce to just the positions without the offsets
+                options[ this ] = [
+                    rposition.exec( pos[ 0 ] )[ 0 ],
+                    rposition.exec( pos[ 1 ] )[ 0 ]
+                ];
+            } );
+
+            // Normalize collision option
+            if ( collision.length === 1 ) {
+                collision[ 1 ] = collision[ 0 ];
+            }
+
+            if ( options.at[ 0 ] === "right" ) {
+                basePosition.left += targetWidth;
+            } else if ( options.at[ 0 ] === "center" ) {
+                basePosition.left += targetWidth / 2;
+            }
+
+            if ( options.at[ 1 ] === "bottom" ) {
+                basePosition.top += targetHeight;
+            } else if ( options.at[ 1 ] === "center" ) {
+                basePosition.top += targetHeight / 2;
+            }
+
+            atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
+            basePosition.left += atOffset[ 0 ];
+            basePosition.top += atOffset[ 1 ];
+
+            return this.each( function() {
+                var collisionPosition, using,
+                    elem = $( this ),
+                    elemWidth = elem.outerWidth(),
+                    elemHeight = elem.outerHeight(),
+                    marginLeft = parseCss( this, "marginLeft" ),
+                    marginTop = parseCss( this, "marginTop" ),
+                    collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) +
+                        scrollInfo.width,
+                    collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) +
+                        scrollInfo.height,
+                    position = $.extend( {}, basePosition ),
+                    myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );
+
+                if ( options.my[ 0 ] === "right" ) {
+                    position.left -= elemWidth;
+                } else if ( options.my[ 0 ] === "center" ) {
+                    position.left -= elemWidth / 2;
+                }
+
+                if ( options.my[ 1 ] === "bottom" ) {
+                    position.top -= elemHeight;
+                } else if ( options.my[ 1 ] === "center" ) {
+                    position.top -= elemHeight / 2;
+                }
+
+                position.left += myOffset[ 0 ];
+                position.top += myOffset[ 1 ];
+
+                collisionPosition = {
+                    marginLeft: marginLeft,
+                    marginTop: marginTop
+                };
+
+                $.each( [ "left", "top" ], function( i, dir ) {
+                    if ( $.ui.position[ collision[ i ] ] ) {
+                        $.ui.position[ collision[ i ] ][ dir ]( position, {
+                            targetWidth: targetWidth,
+                            targetHeight: targetHeight,
+                            elemWidth: elemWidth,
+                            elemHeight: elemHeight,
+                            collisionPosition: collisionPosition,
+                            collisionWidth: collisionWidth,
+                            collisionHeight: collisionHeight,
+                            offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
+                            my: options.my,
+                            at: options.at,
+                            within: within,
+                            elem: elem
+                        } );
+                    }
+                } );
+
+                if ( options.using ) {
+
+                    // Adds feedback as second argument to using callback, if present
+                    using = function( props ) {
+                        var left = targetOffset.left - position.left,
+                            right = left + targetWidth - elemWidth,
+                            top = targetOffset.top - position.top,
+                            bottom = top + targetHeight - elemHeight,
+                            feedback = {
+                                target: {
+                                    element: target,
+                                    left: targetOffset.left,
+                                    top: targetOffset.top,
+                                    width: targetWidth,
+                                    height: targetHeight
+                                },
+                                element: {
+                                    element: elem,
+                                    left: position.left,
+                                    top: position.top,
+                                    width: elemWidth,
+                                    height: elemHeight
+                                },
+                                horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
+                                vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
+                            };
+                        if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
+                            feedback.horizontal = "center";
+                        }
+                        if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
+                            feedback.vertical = "middle";
+                        }
+                        if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
+                            feedback.important = "horizontal";
+                        } else {
+                            feedback.important = "vertical";
+                        }
+                        options.using.call( this, props, feedback );
+                    };
+                }
+
+                elem.offset( $.extend( position, { using: using } ) );
+            } );
+        };
+
+        $.ui.position = {
+            fit: {
+                left: function( position, data ) {
+                    var within = data.within,
+                        withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
+                        outerWidth = within.width,
+                        collisionPosLeft = position.left - data.collisionPosition.marginLeft,
+                        overLeft = withinOffset - collisionPosLeft,
+                        overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
+                        newOverRight;
+
+                    // Element is wider than within
+                    if ( data.collisionWidth > outerWidth ) {
+
+                        // Element is initially over the left side of within
+                        if ( overLeft > 0 && overRight <= 0 ) {
+                            newOverRight = position.left + overLeft + data.collisionWidth - outerWidth -
+                                withinOffset;
+                            position.left += overLeft - newOverRight;
+
+                            // Element is initially over right side of within
+                        } else if ( overRight > 0 && overLeft <= 0 ) {
+                            position.left = withinOffset;
+
+                            // Element is initially over both left and right sides of within
+                        } else {
+                            if ( overLeft > overRight ) {
+                                position.left = withinOffset + outerWidth - data.collisionWidth;
+                            } else {
+                                position.left = withinOffset;
+                            }
+                        }
+
+                        // Too far left -> align with left edge
+                    } else if ( overLeft > 0 ) {
+                        position.left += overLeft;
+
+                        // Too far right -> align with right edge
+                    } else if ( overRight > 0 ) {
+                        position.left -= overRight;
+
+                        // Adjust based on position and margin
+                    } else {
+                        position.left = max( position.left - collisionPosLeft, position.left );
+                    }
+                },
+                top: function( position, data ) {
+                    var within = data.within,
+                        withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
+                        outerHeight = data.within.height,
+                        collisionPosTop = position.top - data.collisionPosition.marginTop,
+                        overTop = withinOffset - collisionPosTop,
+                        overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
+                        newOverBottom;
+
+                    // Element is taller than within
+                    if ( data.collisionHeight > outerHeight ) {
+
+                        // Element is initially over the top of within
+                        if ( overTop > 0 && overBottom <= 0 ) {
+                            newOverBottom = position.top + overTop + data.collisionHeight - outerHeight -
+                                withinOffset;
+                            position.top += overTop - newOverBottom;
+
+                            // Element is initially over bottom of within
+                        } else if ( overBottom > 0 && overTop <= 0 ) {
+                            position.top = withinOffset;
+
+                            // Element is initially over both top and bottom of within
+                        } else {
+                            if ( overTop > overBottom ) {
+                                position.top = withinOffset + outerHeight - data.collisionHeight;
+                            } else {
+                                position.top = withinOffset;
+                            }
+                        }
+
+                        // Too far up -> align with top
+                    } else if ( overTop > 0 ) {
+                        position.top += overTop;
+
+                        // Too far down -> align with bottom edge
+                    } else if ( overBottom > 0 ) {
+                        position.top -= overBottom;
+
+                        // Adjust based on position and margin
+                    } else {
+                        position.top = max( position.top - collisionPosTop, position.top );
+                    }
+                }
+            },
+            flip: {
+                left: function( position, data ) {
+                    var within = data.within,
+                        withinOffset = within.offset.left + within.scrollLeft,
+                        outerWidth = within.width,
+                        offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
+                        collisionPosLeft = position.left - data.collisionPosition.marginLeft,
+                        overLeft = collisionPosLeft - offsetLeft,
+                        overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
+                        myOffset = data.my[ 0 ] === "left" ?
+                            -data.elemWidth :
+                            data.my[ 0 ] === "right" ?
+                                data.elemWidth :
+                                0,
+                        atOffset = data.at[ 0 ] === "left" ?
+                            data.targetWidth :
+                            data.at[ 0 ] === "right" ?
+                                -data.targetWidth :
+                                0,
+                        offset = -2 * data.offset[ 0 ],
+                        newOverRight,
+                        newOverLeft;
+
+                    if ( overLeft < 0 ) {
+                        newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth -
+                            outerWidth - withinOffset;
+                        if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
+                            position.left += myOffset + atOffset + offset;
+                        }
+                    } else if ( overRight > 0 ) {
+                        newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset +
+                            atOffset + offset - offsetLeft;
+                        if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
+                            position.left += myOffset + atOffset + offset;
+                        }
+                    }
+                },
+                top: function( position, data ) {
+                    var within = data.within,
+                        withinOffset = within.offset.top + within.scrollTop,
+                        outerHeight = within.height,
+                        offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
+                        collisionPosTop = position.top - data.collisionPosition.marginTop,
+                        overTop = collisionPosTop - offsetTop,
+                        overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
+                        top = data.my[ 1 ] === "top",
+                        myOffset = top ?
+                            -data.elemHeight :
+                            data.my[ 1 ] === "bottom" ?
+                                data.elemHeight :
+                                0,
+                        atOffset = data.at[ 1 ] === "top" ?
+                            data.targetHeight :
+                            data.at[ 1 ] === "bottom" ?
+                                -data.targetHeight :
+                                0,
+                        offset = -2 * data.offset[ 1 ],
+                        newOverTop,
+                        newOverBottom;
+                    if ( overTop < 0 ) {
+                        newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight -
+                            outerHeight - withinOffset;
+                        if ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) {
+                            position.top += myOffset + atOffset + offset;
+                        }
+                    } else if ( overBottom > 0 ) {
+                        newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset +
+                            offset - offsetTop;
+                        if ( newOverTop > 0 || abs( newOverTop ) < overBottom ) {
+                            position.top += myOffset + atOffset + offset;
+                        }
+                    }
+                }
+            },
+            flipfit: {
+                left: function() {
+                    $.ui.position.flip.left.apply( this, arguments );
+                    $.ui.position.fit.left.apply( this, arguments );
+                },
+                top: function() {
+                    $.ui.position.flip.top.apply( this, arguments );
+                    $.ui.position.fit.top.apply( this, arguments );
+                }
+            }
+        };
+
+    } )();
+
+    var position = $.ui.position;
+
+
+
+
+}));

File diff suppressed because it is too large
+ 6 - 0
app/public/js/jquery-contextmenu/jquery.ui.position.min.js


+ 164 - 0
app/public/js/ledger.js

@@ -6,6 +6,7 @@
  * @version
  */
 $(document).ready(function() {
+    autoFlashHeight();
     const ledgerSpread = SpreadJsObj.createNewSpread($('#ledger-spread')[0]);
     const ledgerTree = createNewPathTree({
         id: 'ledger_id',
@@ -32,4 +33,167 @@ $(document).ready(function() {
         emptyRows: 3
     });
     SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), 'tree', ledgerTree);
+
+    const treeOperationObj = {
+        addNode: function (spread) {
+            const sheet = spread.getActiveSheet();
+            const row = sheet.getSelections()[0].row;
+
+            const tree = sheet.zh_tree;
+            if (!tree) { return; }
+
+            const node = spread.zh_tree.nodes[row];
+            if (!node) { return; }
+
+            SpreadJsObj.massOperationSheet(sheet, function () {
+                tree.addNode('add-node', node, function (newNodes) {
+                    newNodes.sort(function (a, b) {
+                        const aIndex = tree.nodes.indexOf(a);
+                        const bIndex = tree.nodes.indexOf(b);
+                        return aIndex - bIndex;
+                    });
+                    for (const node of newNodes) {
+                        const index = tree.nodes.indexOf(node);
+                        sheet.addRows(index, 1);
+                    }
+                });
+            });
+        },
+        deleteNode: function (spread) {
+            const sheet = spread.getActiveSheet();
+            const row = sheet.getSelections()[0].row;
+
+            const tree = sheet.zh_tree;
+            if (!tree) { return; }
+
+            const node = spread.zh_tree.nodes[row];
+            if (!node) { return; }
+
+            const count = ledgerTree.getPosterity(node).length;
+            tree.deleteNode('delete-node', node, function () {
+                sheet.deleteRows(row, count + 1);
+            });
+        },
+        upMove: function (spread) {
+            const sheet = spread.getActiveSheet();
+            const sel = sheet.getSelections()[0];
+            const row = sel.row;
+
+            const tree = sheet.zh_tree;
+            if (!tree) { return; }
+
+            const node = tree.nodes[row];
+            if (!node) { return; }
+
+            tree.upMoveNode('up-move', node, function (datas) {
+                for (const data of datas) {
+                    SpreadJsObj.reLoadRowData(sheet, tree.nodes.indexOf(data), tree.getPosterity(data).length + 1);
+                }
+                sheet.setSelection(tree.nodes.indexOf(node), sel.col, sel.rowCount, sel.colCount);
+                //sheet.moveTo(row, -1, tree.nodes.indexOf(node), -1, tree.getPosterity(node).length + 1, -1, GC.Spread.Sheets.CopyToOptions.value);
+            });
+        },
+        downMove: function (spread) {
+            const sheet = spread.getActiveSheet();
+            const sel = sheet.getSelections()[0];
+            const row = sel.row;
+
+            const tree = sheet.zh_tree;
+            if (!tree) { return; }
+
+            const node = tree.nodes[row];
+            if (!node) { return; }
+
+            tree.downMoveNode('down-move', node, function (datas) {
+                for (const data of datas) {
+                    SpreadJsObj.reLoadRowData(sheet, tree.nodes.indexOf(data), tree.getPosterity(data).length + 1);
+                }
+                sheet.setSelection(tree.nodes.indexOf(node), sel.col, sel.rowCount, sel.colCount);
+            });
+        },
+        upLevel: function (spread) {
+            const sheet = spread.getActiveSheet();
+            const row = sheet.getSelections()[0].row;
+
+            const tree = sheet.zh_tree;
+            if (!tree) { return; }
+
+            const node = tree.nodes[row];
+            if (!node) { return; }
+
+            tree.upLevelNode('up-level', node, function () {
+                sheet.repaint();
+            });
+        },
+        downLevel: function (spread) {
+            const sheet = spread.getActiveSheet();
+            const row = sheet.getSelections()[0].row;
+
+            const tree = sheet.zh_tree;
+            if (!tree) { return; }
+
+            const node = tree.nodes[row];
+            if (!node) { return; }
+
+            tree.downLevelNode('down-level', node, function () {
+                sheet.repaint();
+            });
+        }
+    };
+
+    // 绑定 删除等 顶部按钮
+    $('#delete').click(function () {
+        treeOperationObj.deleteNode(ledgerSpread);
+    });
+    $('#up-move').click(function () {
+        treeOperationObj.upMove(ledgerSpread);
+    });
+    $('#down-move').click(function () {
+        treeOperationObj.downMove(ledgerSpread);
+    });
+    $('#up-level').click(function () {
+        treeOperationObj.upLevel(ledgerSpread);
+    });
+    $('#down-level').click(function () {
+        treeOperationObj.downLevel(ledgerSpread);
+    });
+
+    // 右键菜单
+    $.contextMenu({
+        selector: '#ledger-spread',
+        build: function ($trigger, e) {
+            const target = SpreadJsObj.safeRightClickSelection($trigger, e, ledgerSpread);
+            return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
+        },
+        items: {
+            'create': {
+                name: '新增',
+                icon: 'fa-sign-in',
+                callback: function (key, opt) {
+                    treeOperationObj.addNode(ledgerSpread);
+                },
+                visible: function(key, opt){
+                    const sheet = ledgerSpread.getActiveSheet();
+                    const selection = sheet.getSelections();
+                    const row = selection[0].row;
+                    const select = ledgerTree.nodes[row];
+                    return select;
+                }
+            },
+            'delete': {
+                name: '删除',
+                icon: 'fa-remove',
+                callback: function (key, opt) {
+                    treeOperationObj.deleteNode(ledgerSpread);
+                },
+                visible: function (key, opt) {
+                    const sheet = ledgerSpread.getActiveSheet();
+                    const selection = sheet.getSelections();
+                    const row = selection[0].row;
+                    const select = ledgerTree.nodes[row];
+                    return select;
+                }
+            }
+        }
+    });
 });

+ 108 - 9
app/public/js/path_tree.js

@@ -20,11 +20,11 @@ const createNewPathTree = function (setting) {
                         successCallback(result.data);
                     }
                 } else {
-                    toast('error: ' + result.message, 'error', 'exclamation-circle');
+                    alert('error: ' + result.message, 'error', 'exclamation-circle');
                 }
             },
             error: function(jqXHR, textStatus, errorThrown){
-                toast('error ' + textStatus + " " + errorThrown, 'error', 'exclamation-circle');
+                alert('error ' + textStatus + " " + errorThrown, 'error', 'exclamation-circle');
             }
         });
     };
@@ -76,17 +76,20 @@ const createNewPathTree = function (setting) {
     /**
      * 加载数据(动态),只加载不同部分
      * @param {Array} datas
+     * @return {Array} 加载到树的数据
      * @privateA
      */
     proto._loadData = function (datas) {
+        const loadedData = [];
         for (const data of datas) {
-            let node = this.getItems(data[treeSetting]);
+            let node = this.getItems(data[treeSetting.id]);
             if (node) {
-                for (const prop of node.propertyNameList) {
+                for (const prop in node) {
                     if (data[prop]) {
                         node[prop] = data[prop];
                     }
                 }
+                loadedData.push(node);
             } else {
                 const keyName = itemsPre + data[treeSetting.id];
                 const node = JSON.parse(JSON.stringify(data));
@@ -94,10 +97,26 @@ const createNewPathTree = function (setting) {
                 this.datas.push(node);
                 node.expanded = false;
                 node.visible = true;
+                loadedData.push(node);
             }
         }
         this.sortTreeNode();
-    }
+        return loadedData;
+    };
+    proto._freeData = function (datas) {
+        const removeArrayData = function (array, data) {
+            const index = array.indexOf(data);
+            array.splice(index, 1);
+        };
+        for (const data of datas) {
+            const node = this.getItems(data[treeSetting.id]);
+            if (node) {
+                delete this.items[itemsPre + node[treeSetting.id]];
+                removeArrayData(this.datas, node);
+                removeArrayData(this.nodes, node);
+            }
+        }
+    };
     /**
      * 根据id获取树结构节点数据
      * @param {Number} id
@@ -135,7 +154,7 @@ const createNewPathTree = function (setting) {
      * @returns {Array}
      */
     proto.getPosterity = function (node) {
-        const reg = new RegExp('^' + node.full_path);
+        const reg = new RegExp('^' + node.full_path + '.');
         return this.datas.filter(function (x) {
             return reg.test(x.full_path);
         })
@@ -172,10 +191,10 @@ const createNewPathTree = function (setting) {
     };
 
     /**
-     * 以下方法需等待响应
+     * 以下方法需等待响应, 通过callback刷新界面
      */
     /**
-     * 加载子节点, callback中应刷新界面
+     * 加载子节点
      * @param {Object} node
      * @param {function} callback
      */
@@ -185,7 +204,87 @@ const createNewPathTree = function (setting) {
             self._loadData(data);
             callback();
         });
-    }
+    };
+    /**
+     * 新增节点
+     * @param {string} url - 请求连接
+     * @param {object} node - 当前选中节点
+     * @param {function} callback
+     */
+    proto.addNode = function (url, node, callback) {
+        const self = this;
+        postData(url, {id: node[treeSetting.id]}, function (data) {
+            self._loadData(data.update);
+            const newNodes = self._loadData(data.create);
+            callback(newNodes);
+        });
+    };
+    /**
+     * 删除节点
+     * @param {String} url - 请求地址
+     * @param {Object} node - 要删除的节点
+     * @param {function} callback
+     */
+    proto.deleteNode = function (url, node, callback){
+        const self = this;
+        postData(url, {id: node[treeSetting.id]}, function (data) {
+            self._loadData(data.update);
+            self._freeData(data.delete);
+            callback();
+        });
+    };
+    /**
+     * 上移
+     * @param {String} url
+     * @param {Object} node
+     * @param {function} callback
+     */
+    proto.upMoveNode = function (url, node, callback) {
+        const self = this;
+        postData(url, {id: node[treeSetting.id]}, function (data) {
+            const nodes = self._loadData(data.update);
+            callback(nodes);
+        });
+    };
+    /**
+     * 下移
+     * @param {String} url
+     * @param {Object} node
+     * @param {function} callback
+     */
+    proto.downMoveNode = function (url, node, callback) {
+        const self = this;
+        postData(url, {id: node[treeSetting.id]}, function (data) {
+            const nodes = self._loadData(data.update);
+            callback(nodes);
+        });
+    };
+    /**
+     * 升级
+     * @param {String} url
+     * @param {Object} node
+     * @param {function} callback
+     */
+    proto.upLevelNode = function (url, node, callback) {
+        const self = this;
+        postData(url, {id: node[treeSetting.id]}, function (data) {
+            self._loadData(data.update);
+            callback();
+        });
+    };
+    /**
+     * 降级
+     * @param {String} url
+     * @param {Object} node
+     * @param {function} callback
+     */
+    proto.downLevelNode = function (url, node, callback) {
+        const self = this;
+        postData(url, {id: node[treeSetting.id]}, function (data) {
+            self._loadData(data.update);
+            callback();
+        });
+    };
 
     return new PathTree();
 }

+ 4 - 2
app/public/js/spreadjs_rela/extend_celltype.js

@@ -9,6 +9,8 @@
 SpreadJsExtendCellType = {
     /**
      * 获取树结构CellType
+     * 通过SpreadJsObj.loadSheetData(sheet, 'tree', tree)加载树结构数据
+     * 要求tree类型为PathTree, 节点必须含有{id, pid, level, order, is_leaf}数据
      * @returns {TreeNodeCellType}
      */
     getTreeNodeCellType: function () {
@@ -153,7 +155,7 @@ SpreadJsExtendCellType = {
                         }
                     }
                     // Draw Expand Box
-                    if (!node.isleaf) {
+                    if (!node.is_leaf) {
                         drawExpandBox(canvas, x, y, w, h, centerX, centerY, node.expanded);
                     }
                     // Draw Parent Line
@@ -215,7 +217,7 @@ SpreadJsExtendCellType = {
 
             // 点击展开节点时,如果已加载子项,则展开,反之这加载子项,展开
             if (Math.abs(hitinfo.x - centerX) < halfBoxLength && Math.abs(hitinfo.y - centerY) < halfBoxLength) {
-                if (!node.expanded && !node.isleaf && tree.getChildren(node).length === 0) {
+                if (!node.expanded && !node.is_leaf && tree.getChildren(node).length === 0) {
                     tree.loadChildren(node, function () {
                         node.expanded = true;
                         const children = tree.getChildren(node);

+ 0 - 1
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -221,7 +221,6 @@ const SpreadJsObj = {
             // 单元格重新写入数据
             for (let i = row; i < row + count; i++) {
                 const data = sortData[i];
-                console.log(data);
                 sheet.zh_setting.cols.forEach(function (col, j) {
                     const cell = sheet.getCell(i, j);
                     if (col.field !== '' && data[col.field]) {

+ 6 - 0
app/router.js

@@ -34,6 +34,12 @@ module.exports = app => {
     // 台账管理相关
     app.get('/ledger/explode', sessionAuth, 'ledgerController.explode');
     app.post('/ledger/get-children', sessionAuth, 'ledgerController.getChildren');
+    app.post('/ledger/add-node', sessionAuth, 'ledgerController.addNode');
+    app.post('/ledger/delete-node', sessionAuth, 'ledgerController.deleteNode');
+    app.post('/ledger/up-move', sessionAuth, 'ledgerController.upMove');
+    app.post('/ledger/down-move', sessionAuth, 'ledgerController.downMove');
+    app.post('/ledger/up-level', sessionAuth, 'ledgerController.upLevel');
+    app.post('/ledger/down-level', sessionAuth, 'ledgerController.downLevel');
 
     // 个人账号相关
     app.get('/profile/info', sessionAuth, 'profileController.info');

+ 67 - 14
app/service/ledger.js

@@ -355,6 +355,7 @@ module.exports = app => {
             data.level = selectData.level;
             data.order = selectData.order + 1;
             data.full_path = selectData.full_path.replace(selectData.ledger_id, data.ledger_id);
+            data.is_leaf = true;
             const result = await this.transaction.insert(this.tableName, data);
 
             this.cache.set(cacheKey, maxId + 1, 'EX', this.ctx.app.config.cacheTime);
@@ -393,8 +394,9 @@ module.exports = app => {
             }
 
             // 查询应返回的结果
-            const resultData = this.getNextsData(selectData.tender_id, selectData.ledger_pid, selectData.order);
-            return resultData;
+            const createData = await this.getDataByParentAndOrder(selectData.tender_id, selectData.ledger_pid, [selectData.order + 1]);
+            const updateData = await this.getNextsData(selectData.tender_id, selectData.ledger_pid, selectData.order + 1);
+            return {create: createData, update: updateData};
         }
 
         /**
@@ -412,12 +414,13 @@ module.exports = app => {
             if (!selectData) {
                 throw '删除节点数据错误';
             }
+            const parentData = await this.getDataByNodeId(tenderId, selectData.ledger_pid);
 
             this.transaction = await this.db.beginTransaction();
-            let resultData = [];
+            let deleteData = [];
             try {
                 // 获取将要被删除的数据
-                resultData = await this.getDataByFullPath(tenderId, selectData.full_path + '%');
+                deleteData = await this.getDataByFullPath(tenderId, selectData.full_path + '%');
                 // 删除
                 this.initSqlBuilder();
                 this.sqlBuilder.setAndWhere('tender_id', {
@@ -430,13 +433,35 @@ module.exports = app => {
                 });
                 const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'delete');
                 const operate = await this.transaction.query(sql, sqlParam);
-                this.transaction.commit();
+                // 选中节点--父节点 只有一个子节点时,应升级is_leaf
+                if (parentData) {
+                    const count = this.db.count(this.tableName, {ledger_pid: selectData.ledger_pid});
+                    if (count === 1) {
+                        await this.transaction.update({
+                            id: parentData.id,
+                            is_leaf: true
+                        });
+                    }
+                }
+                // 选中节点--全部后节点 order--
+                await this._updateSelectNextsOrder(selectData, -1);
+                await this.transaction.commit();
             } catch(err) {
-                resultData = [];
+                deleteData = [];
                 await this.transaction.rollback();
                 throw err;
             }
-            return resultData;
+            // 查询结果
+            let updateData = [];
+            if (deleteData.length > 0) {
+                updateData = await this.getNextsData(tenderId, selectData.ledger_pid, selectData.order - 1);
+                updateData = updateData ? updateData : [];
+                const updateData2 = await this.getDataByNodeId(tenderId, selectData.ledger_pid);
+                if (updateData2.is_leaf === parentData.is_leaf) {
+                    updateData.push(updateData2);
+                }
+            }
+            return {delete: deleteData, update: updateData};
         }
 
         /**
@@ -470,7 +495,7 @@ module.exports = app => {
             }
 
             const resultData = await this.getDataByParentAndOrder(tenderId, selectData.ledger_pid, [selectData.order, preData.order]);
-            return resultData;
+            return {update: resultData};
         }
 
         /**
@@ -504,7 +529,7 @@ module.exports = app => {
             }
 
             const resultData = await this.getDataByParentAndOrder(tenderId, selectData.ledger_pid, [selectData.order, nextData.order]);
-            return resultData;
+            return {update: resultData};
         }
 
         /**
@@ -569,6 +594,14 @@ module.exports = app => {
                 const [sql1, sqlParam1] = this.sqlBuilder.build(this.tableName, 'update');
                 await this.transaction.query(sql1, sqlParam1);
 
+                // 选中节点 is_leaf应为false
+                if (selectData.is_leaf) {
+                    const updateData = {id: selectData.id,
+                        is_leaf: false
+                    }
+                    await this.transaction.update(this.tableName, updateData);
+                }
+
                 // 修改nextsData及其子节点的full_path
                 const oldSubStr = this.db.escape(selectData.ledger_pid + '.');
                 const newSubStr = this.db.escape(selectData.ledger_id + '.');
@@ -612,6 +645,12 @@ module.exports = app => {
             this.transaction = await this.db.beginTransaction();
             const newFullPath = selectData.full_path.replace(selectData.ledger_pid + '.', '');
             try {
+                if (selectData.order === 1) {
+                    this.transaction.update(this.tableName, {
+                        id: parentData.id,
+                        is_leaf: true
+                    })
+                }
                 // 选中节点--父节点--全部后兄弟节点 order+1
                 await this._updateSelectNextsOrder(parentData);
                 // 选中节点 修改pid, order, full_path
@@ -635,7 +674,11 @@ module.exports = app => {
             // 查询修改的数据
             const resultData1 = await this.getDataByFullPath(tenderId, newFullPath + '%');
             const resultData2 = await this.getNextsData(tenderId, parentData.ledger_pid, parentData.order + 1);
-            return resultData1.concat(resultData2);
+            if (selectData.order === 1) {
+                const preParent = await this.getDataByNodeId(tenderId, parentData.ledger_id);
+                resultData2.push(preParent);
+            }
+            return {update: resultData1.concat(resultData2)};
         }
 
         /**
@@ -697,7 +740,7 @@ module.exports = app => {
                 await this._updateSelectNextsOrder(selectData, -1);
                 // 选中节点 修改pid, level, order, full_path
                 const updateData = {id: selectData.id,
-                    ledger_pid: preData.ledger_pid,
+                    ledger_pid: preData.ledger_id,
                     order: preLastChildData ? preLastChildData.order + 1 : 1,
                     level: selectData.level + 1,
                     full_path: newFullPath
@@ -705,6 +748,13 @@ module.exports = app => {
                 await this.transaction.update(this.tableName, updateData);
                 // 选中节点--全部子节点(含孙) level++, full_path
                 await this._syncDownlevelChildren(selectData, preData);
+                // 选中节点--前兄弟节点 is_leaf应为false
+                if (preData.is_leaf) {
+                    const updateData2 = {id: preData.id,
+                        is_leaf: false
+                    }
+                    await this.transaction.update(this.tableName, updateData);
+                }
                 this.transaction.commit();
             } catch (err) {
                 this.transaction.rollback();
@@ -712,9 +762,12 @@ module.exports = app => {
             }
 
             // 查询修改的数据
-            const resultData1 = await this.getDataByFullPath(tenderId, newFullPath + '%')
-            const resultData2 = await this.getNextsData(tenderId, preData.ledger_pid, preData.order);
-            return resultData1.concat(resultData2);
+            // 选中节点及子节点
+            const resultData1 = await this.getDataByFullPath(tenderId, newFullPath + '%');
+            // 选中节点--原前兄弟节点&全部后兄弟节点
+            const queryOrder = preData.is_leaf ? preData.order - 1 : preData.order;
+            const resultData2 = await this.getNextsData(tenderId, preData.ledger_pid, queryOrder);
+            return {update: resultData1.concat(resultData2)};
         }
     }
 

+ 3 - 0
app/view/layout/layout.ejs

@@ -10,6 +10,7 @@
     <link rel="stylesheet" href="/public/css/main.css">
     <link rel="stylesheet" href="/public/css/font-awesome/font-awesome.min.css">
     <link rel="stylesheet" href="/public/css/spreadjs/sheets/gc.spread.sheets.excel2013lightGray.10.0.1.css">
+    <link rel="stylesheet" href="/public/css/jquery-contextmenu/jquery.contextMenu.min.css">
     <!-- JS. -->
     <script src="/public/js/jquery/jquery-3.2.1.min.js"></script>
     <script src="/public/js/jquery/jquery.validate.js"></script>
@@ -20,6 +21,8 @@
     <script src="/public/js/vue/vue.js"></script>
     <script src="/public/js/component/input.js"></script>
     <script src="/public/js/cookies.js"></script>
+    <script src="/public/js/jquery-contextmenu/jquery.ui.position.min.js"></script>
+    <script src="/public/js/jquery-contextmenu/jquery.contextMenu.min.js"></script>
 </head>
 
 <body>

+ 6 - 6
app/view/ledger/explode.ejs

@@ -9,15 +9,15 @@
                        data-original-title="剪切"><i class="fa fa-scissors" aria-hidden="true"></i></a>
                     <a href="" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title=""
                        data-original-title="粘贴"><i class="fa fa-clipboard" aria-hidden="true"></i></a>
-                    <a href="" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title=""
+                    <a href="javascript:void(0)" id="delete-node" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title=""
                        data-original-title="删除"><i class="fa fa-remove" aria-hidden="true"></i></a>
-                    <a href="" class="btn btn-sm disabled" data-toggle="tooltip" data-placement="bottom" title=""
+                    <a href="javascript:void(0)" id="up-level" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title=""
                        data-original-title="升级"><i class="fa fa-arrow-left" aria-hidden="true"></i></a>
-                    <a href="" class="btn btn-sm disabled" data-toggle="tooltip" data-placement="bottom" title=""
+                    <a href="javascript:void(0)" id="down-level" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title=""
                        data-original-title="降级"><i class="fa fa-arrow-right" aria-hidden="true"></i></a>
-                    <a href="" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title=""
+                    <a href="javascript:void(0)" id="down-move" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title=""
                        data-original-title="下移"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
-                    <a href="" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title=""
+                    <a href="javascript:void(0)" id="up-move" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title=""
                        data-original-title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
                 </div>
                 <div class="btn-group">
@@ -61,7 +61,7 @@
             </ul>
         </div>
         <div class="c-body col-8">
-            <div id="ledger-spread" style="border: 1px solid gray; height: 750px"></div>
+            <div id="ledger-spread" class="sjs-height-1"></div>
             <!--<table class="table table-bordered">-->
                 <!--<tr>-->
                     <!--<th></th>-->

+ 1 - 0
package.json

@@ -5,6 +5,7 @@
   "private": true,
   "dependencies": {
     "egg": "^1.7.0",
+    "egg-js-validator": "^1.0.0",
     "egg-mysql": "^3.0.0",
     "egg-redis": "^1.0.2",
     "egg-scripts": "^1.0.0",

+ 66 - 39
test/app/service/ledger.test.js

@@ -25,26 +25,32 @@
     { ledger_id: 5, ledger_pid: 1, order: 4, level: 2, full_path: '1.5', code: '1-4' },
 ];*/
 const testNodeData = [
-    { ledger_id: 1, ledger_pid: -1, order: 1, level: 1, full_path: '1', code: '1' },
-    { ledger_id: 2, ledger_pid: 1, order: 1, level: 2, full_path: '1.2', code: '1-1' },
-    { ledger_id: 6, ledger_pid: 2, order: 1, level: 3, full_path: '1.2.6', code: '1-1-1' },
-    { ledger_id: 7, ledger_pid: 6, order: 1, level: 4, full_path: '1.2.6.7', code: '202-1' },
-    { ledger_id: 10, ledger_pid: 7, order: 2, level: 5, full_path: '1.2.6.7.10', code: '202-1-a' },
-    { ledger_id: 9, ledger_pid: 7, order: 1, level: 5, full_path: '1.2.6.7.9', code: '202-1-b' },
-    { ledger_id: 8, ledger_pid: 6, order: 2, level: 4, full_path: '1.2.6.8', code: '202-2' },
-    { ledger_id: 11, ledger_pid: 8, order: 1, level: 5, full_path: '1.2.6.8.11', code: '202-2-c' },
-    { ledger_id: 12, ledger_pid: 8, order: 2, level: 5, full_path: '1.2.6.8.12', code: '202-2-e' },
-    { ledger_id: 13, ledger_pid: 2, order: 2, level: 3, full_path: '1.2.13', code: '1-1-2' },
-    { ledger_id: 14, ledger_pid: 2, order: 3, level: 3, full_path: '1.2.14', code: '1-1-3' },
-    { ledger_id: 3, ledger_pid: 1, order: 2, level: 2, full_path: '1.3', code: '1-2' },
-    { ledger_id: 15, ledger_pid: 3, order: 1, level: 3, full_path: '1.3.15', code: '1-2-1'},
-    { ledger_id: 4, ledger_pid: 1, order: 3, level: 2, full_path: '1.4', code: '1-3' },
-    { ledger_id: 16, ledger_pid: 4, order: 1, level: 3, full_path: '1.4.16', code: '1-3-1'},
-    { ledger_id: 5, ledger_pid: 1, order: 4, level: 2, full_path: '1.5', code: '1-4' },
+    { ledger_id: 1, ledger_pid: -1, order: 1, level: 1, full_path: '1', code: '1', is_leaf: false },
+    { ledger_id: 2, ledger_pid: 1, order: 1, level: 2, full_path: '1.2', code: '1-1', is_leaf: false },
+    { ledger_id: 6, ledger_pid: 2, order: 1, level: 3, full_path: '1.2.6', code: '1-1-1', is_leaf: false },
+    { ledger_id: 7, ledger_pid: 6, order: 1, level: 4, full_path: '1.2.6.7', code: '202-1', is_leaf: false },
+    { ledger_id: 10, ledger_pid: 7, order: 2, level: 5, full_path: '1.2.6.7.10', code: '202-1-a', is_leaf: true },
+    { ledger_id: 9, ledger_pid: 7, order: 1, level: 5, full_path: '1.2.6.7.9', code: '202-1-b', is_leaf: true },
+    { ledger_id: 8, ledger_pid: 6, order: 2, level: 4, full_path: '1.2.6.8', code: '202-2', is_leaf: false },
+    { ledger_id: 11, ledger_pid: 8, order: 1, level: 5, full_path: '1.2.6.8.11', code: '202-2-c', is_leaf: true },
+    { ledger_id: 12, ledger_pid: 8, order: 2, level: 5, full_path: '1.2.6.8.12', code: '202-2-e', is_leaf: true },
+    { ledger_id: 13, ledger_pid: 2, order: 2, level: 3, full_path: '1.2.13', code: '1-1-2', is_leaf: true },
+    { ledger_id: 14, ledger_pid: 2, order: 3, level: 3, full_path: '1.2.14', code: '1-1-3', is_leaf: true },
+    { ledger_id: 3, ledger_pid: 1, order: 2, level: 2, full_path: '1.3', code: '1-2', is_leaf: false },
+    { ledger_id: 15, ledger_pid: 3, order: 1, level: 3, full_path: '1.3.15', code: '1-2-1', is_leaf: true },
+    { ledger_id: 4, ledger_pid: 1, order: 3, level: 2, full_path: '1.4', code: '1-3', is_leaf: false },
+    { ledger_id: 16, ledger_pid: 4, order: 1, level: 3, full_path: '1.4.16', code: '1-3-1', is_leaf: true },
+    { ledger_id: 5, ledger_pid: 1, order: 4, level: 2, full_path: '1.5', code: '1-4', is_leaf: true },
 ];
 const testTenderId = 3;
 
 const { app, assert } = require('egg-mock/bootstrap');
+const findById = function (nodes, Id) {
+    const filters = nodes.filter(function (x) {
+        return x.ledger_id === Id;
+    });
+    return filters.length > 0 ? filters[0] : undefined;
+}
 
 describe('test/app/service/ledger.test.js', () => {
     it('clear history test data', function* () {
@@ -66,36 +72,39 @@ describe('test/app/service/ledger.test.js', () => {
         const ctx = app.mockContext();
         // 选中1-1-1,插入节点
         const resultData = yield ctx.service.ledger.addNode(testTenderId, 6);
-        assert(resultData.length === 3);
+        assert(resultData.create.length === 1);
+        assert(resultData.update.length === 2);
+        assert(resultData.create[0].is_leaf === 1);
     });
 
     it('test deleteNode', function* () {
         const ctx = app.mockContext();
         // 选中202-1,删除节点
         const resultData = yield ctx.service.ledger.deleteNode(testTenderId, 7);
-        assert(resultData.length === 3);
+        assert(resultData.delete.length === 3);
+        assert(resultData.update.length === 1);
     });
 
     it('test upMoveNode', function* () {
         const ctx = app.mockContext();
         // 选中202-2-e上移
         let resultData = yield ctx.service.ledger.upMoveNode(testTenderId, 12);
-        resultData.sort(function (x, y) {
+        resultData.update.sort(function (x, y) {
             return x.order - y.order;
         });
-        assert(resultData.length === 2);
-        assert(resultData[0].code === '202-2-e');
+        assert(resultData.update.length === 2);
+        assert(resultData.update[0].code === '202-2-e');
     });
 
     it('test downMoveNode', function* () {
         const ctx = app.mockContext();
         // 选中202-2-e下移
         let resultData = yield ctx.service.ledger.downMoveNode(testTenderId, 12);
-        resultData.sort(function (x, y) {
+        resultData.update.sort(function (x, y) {
             return x.order - y.order;
         });
-        assert(resultData.length === 2);
-        assert(resultData[0].code === '202-2-c');
+        assert(resultData.update.length === 2);
+        assert(resultData.update[0].code === '202-2-c');
     });
 
     it('test upLevelNode', function* () {
@@ -103,26 +112,44 @@ describe('test/app/service/ledger.test.js', () => {
         // 选中 1-1-2 升级
         let resultData = yield ctx.service.ledger.upLevelNode(testTenderId, 13);
         assert(resultData);
-        assert(resultData.length === 5);
-        assert(resultData[0].full_path === '1.13');
-        assert(resultData[0].ledger_pid === 1);
-        assert(resultData[1].ledger_pid === 13);
-        assert(resultData[1].full_path === '1.13.14');
-        assert(resultData[2].order === 3);
-        assert(resultData[3].order === 4);
-        assert(resultData[4].order === 5);
+        assert(resultData.update.length === 5);
+
+        let node = findById(resultData.update, 13);
+        assert(node.full_path === '1.13');
+        assert(node.ledger_pid === 1);
+        assert(!node.is_leaf);
+
+        node = findById(resultData.update, 14);
+        assert(node.ledger_pid === 13);
+        assert(node.full_path === '1.13.14');
+
+        node = findById(resultData.update, 3);
+        assert(node.order === 3);
+
+        node = findById(resultData.update, 4);
+        assert(node.order === 4);
+
+        node = findById(resultData.update, 5);
+        assert(node.order === 5);
     });
 
     it('test downLevelNode', function* () {
         const ctx = app.mockContext();
         // 选中1-3 降级
         let resultData = yield ctx.service.ledger.downLevelNode(testTenderId, 4);
-        assert(resultData.length === 3);
-        assert(resultData[0].full_path === '1.3.4');
-        assert(resultData[0].level === 3);
-        assert(resultData[0].order === 2);
-        assert(resultData[1].level === 4);
-        assert(resultData[1].full_path === '1.3.4.16');
-        assert(resultData[2].order === 4);
+        assert(resultData.update.length === 3);
+
+        let node = findById(resultData.update, 4);
+        assert(node.full_path === '1.3.4');
+        assert(node.ledger_pid === 3);
+        assert(node.level === 3);
+        assert(node.order === 2);
+
+        node = findById(resultData.update, 16);
+        assert(node.level === 4);
+        assert(node.full_path === '1.3.4.16');
+
+        node = findById(resultData.update, 5);
+        assert(node.order === 4);
     })
 });