Jelajahi Sumber

初始化代码

MaiXinRong 7 tahun lalu
melakukan
ba294c1f93
65 mengubah file dengan 6934 tambahan dan 0 penghapusan
  1. 30 0
      .autod.conf.js
  2. 1 0
      .eslintignore
  3. 3 0
      .eslintrc
  4. 12 0
      .gitignore
  5. 10 0
      .travis.yml
  6. 33 0
      README.md
  7. 39 0
      README.zh-CN.md
  8. 15 0
      app.js
  9. 49 0
      app/base/base_controller.js
  10. 25 0
      app/controller/compare_controller.js
  11. 28 0
      app/controller/lib_controller.js
  12. 66 0
      app/controller/login_controller.js
  13. 25 0
      app/controller/template_controller.js
  14. 38 0
      app/middleware/session_auth.js
  15. 27 0
      app/middleware/url_parse.js
  16. 7 0
      app/public/css/bootstrap/bootstrap.min.css
  17. 1 0
      app/public/css/datepicker/datepicker.min.css
  18. 4 0
      app/public/css/font-awesome/font-awesome.min.css
  19. TEMPAT SAMPAH
      app/public/css/font-awesome/fonts/FontAwesome.otf
  20. TEMPAT SAMPAH
      app/public/css/font-awesome/fonts/fontawesome-webfont.eot
  21. 2671 0
      app/public/css/font-awesome/fonts/fontawesome-webfont.svg
  22. TEMPAT SAMPAH
      app/public/css/font-awesome/fonts/fontawesome-webfont.ttf
  23. TEMPAT SAMPAH
      app/public/css/font-awesome/fonts/fontawesome-webfont.woff
  24. TEMPAT SAMPAH
      app/public/css/font-awesome/fonts/fontawesome-webfont.woff2
  25. TEMPAT SAMPAH
      app/public/css/logo.png
  26. 453 0
      app/public/css/main.css
  27. TEMPAT SAMPAH
      app/public/css/ztree/img/diy/1_close.png
  28. TEMPAT SAMPAH
      app/public/css/ztree/img/diy/1_open.png
  29. TEMPAT SAMPAH
      app/public/css/ztree/img/diy/2.png
  30. TEMPAT SAMPAH
      app/public/css/ztree/img/diy/3.png
  31. TEMPAT SAMPAH
      app/public/css/ztree/img/diy/4.png
  32. TEMPAT SAMPAH
      app/public/css/ztree/img/diy/5.png
  33. TEMPAT SAMPAH
      app/public/css/ztree/img/diy/6.png
  34. TEMPAT SAMPAH
      app/public/css/ztree/img/diy/7.png
  35. TEMPAT SAMPAH
      app/public/css/ztree/img/diy/8.png
  36. TEMPAT SAMPAH
      app/public/css/ztree/img/diy/9.png
  37. TEMPAT SAMPAH
      app/public/css/ztree/img/line_conn.gif
  38. TEMPAT SAMPAH
      app/public/css/ztree/img/loading.gif
  39. TEMPAT SAMPAH
      app/public/css/ztree/img/zTreeStandard.gif
  40. TEMPAT SAMPAH
      app/public/css/ztree/img/zTreeStandard.png
  41. 97 0
      app/public/css/ztree/zTreeStyle.css
  42. TEMPAT SAMPAH
      app/public/images/avatar.png
  43. 7 0
      app/public/js/bootstrap/bootstrap.min.js
  44. 2 0
      app/public/js/datepicker/datepicker.min.js
  45. 12 0
      app/public/js/datepicker/datepicker.zh.js
  46. 32 0
      app/public/js/echarts/echarts.min.js
  47. 53 0
      app/public/js/global.js
  48. 4 0
      app/public/js/jquery/jquery-3.2.1.min.js
  49. 5 0
      app/public/js/popper/popper.min.js
  50. 1913 0
      app/public/js/ztree/jquery.ztree.core.js
  51. 628 0
      app/public/js/ztree/jquery.ztree.excheck.js
  52. 25 0
      app/router.js
  53. 44 0
      app/service/customer.js
  54. 162 0
      app/view/compare/index.ejs
  55. 66 0
      app/view/layout/layout.ejs
  56. 48 0
      app/view/lib/index.ejs
  57. 45 0
      app/view/login/login.ejs
  58. 39 0
      app/view/template/index.ejs
  59. 39 0
      app/view/template/modal.ejs
  60. 14 0
      appveyor.yml
  61. 52 0
      config/config.default.js
  62. 32 0
      config/menu.js
  63. 9 0
      config/plugin.js
  64. 48 0
      package.json
  65. 21 0
      test/app/controller/home.test.js

+ 30 - 0
.autod.conf.js

@@ -0,0 +1,30 @@
+'use strict';
+
+module.exports = {
+  write: true,
+  prefix: '^',
+  plugin: 'autod-egg',
+  test: [
+    'test',
+    'benchmark',
+  ],
+  dep: [
+    'egg',
+    'egg-scripts',
+  ],
+  devdep: [
+    'egg-ci',
+    'egg-bin',
+    'egg-mock',
+    'autod',
+    'autod-egg',
+    'eslint',
+    'eslint-config-egg',
+    'webstorm-disable-index',
+  ],
+  exclude: [
+    './test/fixtures',
+    './dist',
+  ],
+};
+

+ 1 - 0
.eslintignore

@@ -0,0 +1 @@
+coverage

+ 3 - 0
.eslintrc

@@ -0,0 +1,3 @@
+{
+  "extends": "eslint-config-egg"
+}

+ 12 - 0
.gitignore

@@ -0,0 +1,12 @@
+logs/
+npm-debug.log
+yarn-error.log
+node_modules/
+package-lock.json
+yarn.lock
+coverage/
+.idea/
+run/
+.DS_Store
+*.sw*
+*.un~

+ 10 - 0
.travis.yml

@@ -0,0 +1,10 @@
+sudo: false
+language: node_js
+node_js:
+  - '8'
+install:
+  - npm i npminstall && npminstall
+script:
+  - npm run ci
+after_script:
+  - npminstall codecov && codecov

+ 33 - 0
README.md

@@ -0,0 +1,33 @@
+# index_sys
+
+SmartCost Index System
+
+## QuickStart
+
+<!-- add docs here for user -->
+
+see [egg docs][egg] for more detail.
+
+### Development
+
+```bash
+$ npm i
+$ npm run dev
+$ open http://localhost:7001/
+```
+
+### Deploy
+
+```bash
+$ npm start
+$ npm stop
+```
+
+### npm scripts
+
+- Use `npm run lint` to check code style.
+- Use `npm test` to run unit test.
+- Use `npm run autod` to auto detect dependencies upgrade, see [autod](https://www.npmjs.com/package/autod) for more detail.
+
+
+[egg]: https://eggjs.org

+ 39 - 0
README.zh-CN.md

@@ -0,0 +1,39 @@
+# index_sys
+
+SmartCost Index System
+
+## 快速入门
+
+<!-- 在此次添加使用文档 -->
+
+如需进一步了解,参见 [egg 文档][egg]。
+
+### 本地开发
+
+```bash
+$ npm i
+$ npm run dev
+$ open http://localhost:7001/
+```
+
+### 部署
+
+```bash
+$ npm start
+$ npm stop
+```
+
+### 单元测试
+
+- [egg-bin] 内置了 [mocha], [thunk-mocha], [power-assert], [istanbul] 等框架,让你可以专注于写单元测试,无需理会配套工具。
+- 断言库非常推荐使用 [power-assert]。
+- 具体参见 [egg 文档 - 单元测试](https://eggjs.org/zh-cn/core/unittest)。
+
+### 内置指令
+
+- 使用 `npm run lint` 来做代码风格检查。
+- 使用 `npm test` 来执行单元测试。
+- 使用 `npm run autod` 来自动检测依赖更新,详细参见 [autod](https://www.npmjs.com/package/autod) 。
+
+
+[egg]: https://eggjs.org

+ 15 - 0
app.js

@@ -0,0 +1,15 @@
+'use strict';
+
+/**
+ * 自定义启动文件
+ *
+ * @author Mai
+ * @date 2018/4/18
+ * @version
+ */
+
+const BaseController = require('./app/base/base_controller');
+module.exports = app => {
+    // app内定义基类,方便继承
+    app.BaseController = BaseController;
+}

+ 49 - 0
app/base/base_controller.js

@@ -0,0 +1,49 @@
+'use strict';
+
+/**
+ * 控制器基类
+ *
+ * @author CaiAoLin
+ * @date 2017/10/11
+ * @version
+ */
+const moment = require('moment');
+const Controller = require('egg').Controller;
+const menuList = require('../../config/menu');
+class BaseController extends Controller {
+
+    /**
+     * 构造函数
+     *
+     * @param {Object} ctx - egg全局context
+     * @return {void}
+     */
+    constructor(ctx) {
+        super(ctx);
+        ctx.menuList = menuList;
+        ctx.menu = menuList[ctx.controllerName] ? menuList[ctx.controllerName] : {};
+        ctx.title = ctx.menu.name ? ctx.menu.name : '';
+    }
+
+    /**
+     * 渲染layout
+     *
+     * @param {String} view - 渲染的view
+     * @param {Object} data - 渲染的数据
+     * @param {String} modal - 渲染的modal
+     * @return {void}
+     */
+    async layout(view, data = {}, modal = '') {
+        data.moment = moment;
+
+        const contentString = await this.ctx.renderView(view, data);
+        const modalString = modal === '' ? '' : await this.ctx.renderView(modal);
+        const renderData = {
+            content: contentString,
+            modal: modalString,
+        };
+        await this.ctx.render('layout/layout.ejs', renderData);
+    }
+}
+
+module.exports = BaseController;

+ 25 - 0
app/controller/compare_controller.js

@@ -0,0 +1,25 @@
+'use strict';
+
+/**
+ * 指标对比控制器
+ *
+ * @author Mai
+ * @data 2018/4/19
+ * @version
+ */
+
+module.exports = app => {
+    class CompareController extends app.BaseController {
+        /**
+         * 指标对比页面
+         *
+         * @param {object} ctx - egg全局context
+         * @return {void}
+         */
+        async index (ctx) {
+            await this.layout('compare/index.ejs', {}, '');
+        }
+    }
+
+    return CompareController;
+}

+ 28 - 0
app/controller/lib_controller.js

@@ -0,0 +1,28 @@
+'use strict';
+
+/**
+ * 指标库控制器
+ *
+ * @author Mai
+ * @data 2018/4/19
+ * @version
+ */
+
+module.exports = app => {
+    class LibController extends app.BaseController {
+
+
+
+        /**
+         * 指标库页面
+         *
+         * @param {object} ctx - egg全局context
+         * @return {void}
+         */
+        async index (ctx) {
+            await this.layout('lib/index.ejs', {}, '');
+        }
+    }
+
+    return LibController;
+}

+ 66 - 0
app/controller/login_controller.js

@@ -0,0 +1,66 @@
+'use strict';
+
+/**
+ * 登录页面控制器
+ *
+ * @author Mai
+ * @data 2018/4/18
+ * @version
+ */
+
+module.exports = app => {
+    class LoginController extends app.BaseController {
+
+        /**
+         * 登录页面
+         *
+         * @param {object} ctx - egg全局context
+         * @return {void}
+         */
+        async index(ctx) {
+            const errorMessage = ctx.session.loginError;
+            // 显示完删除
+            ctx.session.loginError = null;
+
+            const renderData = {
+                errorMessage,
+            };
+            await ctx.render('login/login.ejs', renderData);
+        }
+
+        /**
+         * 登录操作
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async login(ctx) {
+            try {
+                const result = await ctx.service.customer.validLogin(ctx.request.body);
+
+                if (!result) {
+                    throw '登录失败';
+                }
+
+                ctx.redirect('/lib');
+            } catch(err) {
+                console.log(err);
+                ctx.session.loginError = '用户名或密码错误';
+                ctx.redirect('/login');
+            }
+        }
+
+        /**
+         * 退出登录
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async logout(ctx) {
+            // 删除session并跳转
+            ctx.session = null;
+            ctx.redirect('/');
+        }
+    }
+    return LoginController;
+};

+ 25 - 0
app/controller/template_controller.js

@@ -0,0 +1,25 @@
+'use strict';
+
+/**
+ * 指标模板控制器
+ *
+ * @author Mai
+ * @data 2018/4/19
+ * @version
+ */
+
+module.exports = app => {
+    class TemplateController extends app.BaseController {
+        /**
+         * 指标模板页面
+         *
+         * @param {object} ctx - egg全局context
+         * @return {void}
+         */
+        async index (ctx) {
+            await this.layout('template/index.ejs', {}, 'template/modal.ejs');
+        }
+    }
+
+    return TemplateController;
+}

+ 38 - 0
app/middleware/session_auth.js

@@ -0,0 +1,38 @@
+'use strict';
+
+// 加密类
+const crypto = require('crypto');
+module.exports = options => {
+    /**
+     * session判断中间件
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* sessionAuth(next) {
+        try {
+            // 判断session
+            const userSession = this.session.userSession;
+            if (userSession === undefined) {
+                throw '不存在session';
+            }
+            // 校验session
+            if (userSession.username === undefined || userSession.loginTime === undefined) {
+                throw '用户数据不完整';
+            }
+            // 校验session
+            const sessionToken = crypto.createHmac('sha1', userSession.loginTime + '')
+                .update(userSession.username).digest().toString('base64');
+            if (sessionToken !== userSession.sessionToken) {
+                throw 'session数据错误';
+            }
+
+            // 同步消息
+            //yield this.service.notify.syncNotifyData();
+        } catch (error) {
+            console.log(error);
+            return this.redirect('/');
+        }
+        yield next;
+    };
+};

+ 27 - 0
app/middleware/url_parse.js

@@ -0,0 +1,27 @@
+'use strict';
+
+// url类
+const Url = require('url');
+module.exports = options => {
+    /**
+     * 控制器以及动作的获取
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* urlParse(next) {
+        this.actionName = '';
+        // 获取当前控制器和动作名称
+        const urlInfo = Url.parse(this.request.originalUrl, true);
+        const url = urlInfo.pathname.substr(1);
+        const actionInfo = url.split('/');
+
+        this.controllerName = typeof actionInfo === 'object' && actionInfo[0] !== undefined ? actionInfo[0] : '';
+        this.actionName = typeof actionInfo === 'object' && actionInfo[1] !== undefined ? actionInfo[1] : '';
+
+        this.urlInfo = urlInfo;
+        // 防止为空
+        this.request.headers.referer = this.request.headers.referer === undefined ? '/dashboard' : this.request.headers.referer;
+        yield next;
+    };
+};

File diff ditekan karena terlalu besar
+ 7 - 0
app/public/css/bootstrap/bootstrap.min.css


File diff ditekan karena terlalu besar
+ 1 - 0
app/public/css/datepicker/datepicker.min.css


File diff ditekan karena terlalu besar
+ 4 - 0
app/public/css/font-awesome/font-awesome.min.css


TEMPAT SAMPAH
app/public/css/font-awesome/fonts/FontAwesome.otf


TEMPAT SAMPAH
app/public/css/font-awesome/fonts/fontawesome-webfont.eot


File diff ditekan karena terlalu besar
+ 2671 - 0
app/public/css/font-awesome/fonts/fontawesome-webfont.svg


TEMPAT SAMPAH
app/public/css/font-awesome/fonts/fontawesome-webfont.ttf


TEMPAT SAMPAH
app/public/css/font-awesome/fonts/fontawesome-webfont.woff


TEMPAT SAMPAH
app/public/css/font-awesome/fonts/fontawesome-webfont.woff2


TEMPAT SAMPAH
app/public/css/logo.png


+ 453 - 0
app/public/css/main.css

@@ -0,0 +1,453 @@
+/*building SAAS 0.1*/
+/*bootstrap 初始化*/
+body {
+    font-size: 0.9rem;
+    overflow: hidden;
+    background: #e4e7ea
+}
+.dropdown-menu {
+    font-size: 0.9rem
+}
+.btn.disabled, .btn:disabled {
+  color:#999
+}
+/*自定义css*/
+/*滚动条*/
+/* 滚动条 */
+::-webkit-scrollbar-thumb:horizontal { /*水平滚动条的样式*/
+	width: 5px;
+	background-color: #ddd;
+	-webkit-border-radius: 6px;
+}
+::-webkit-scrollbar-track-piece {
+	background-color: #fff; /*滚动条的背景颜色*/
+	-webkit-border-radius: 0; /*滚动条的圆角宽度*/
+}
+::-webkit-scrollbar {
+	width: 10px; /*滚动条的宽度*/
+	height: 8px; /*滚动条的高度*/
+}
+::-webkit-scrollbar-thumb:vertical { /*垂直滚动条的样式*/
+	height: 50px;
+	background-color: #ddd;
+	-webkit-border-radius: 6px;
+	outline: 1px solid #fff;
+	outline-offset: -1px;
+	border: 1px solid #fff;
+}
+::-webkit-scrollbar-thumb:hover { /*滚动条的hover样式*/
+	height: 50px;
+	background-color: #999;
+	-webkit-border-radius: 6px;
+}
+.sjs-height-1,.sjs-height-2{
+  overflow: hidden;
+}
+.sjs-bottom{
+  height:400px;
+  overflow-y: auto;
+}
+.sjs-bottom-2{
+  height:360px;
+  overflow-y: auto;
+}
+.form-signin {
+    max-width: 500px;
+    margin: 150px auto;
+}
+.has-danger {
+    -webkit-animation: shake 1s .2s ease both;
+    -moz-animation: shake 1s .2s ease both;
+    animation: shake 1s .2s ease both;
+}
+@-webkit-keyframes shake {
+    0%, 100% {
+        -webkit-transform: translateX(0);
+    }
+    10%, 30%, 50%, 70%, 90% {
+        -webkit-transform: translateX(-10px);
+    }
+    20%, 40%, 60%, 80% {
+        -webkit-transform: translateX(10px);
+    }
+}
+@-moz-keyframes shake {
+    0%, 100% {
+        -moz-transform: translateX(0);
+    }
+    10%, 30%, 50%, 70%, 90% {
+        -moz-transform: translateX(-10px);
+    }
+    20%, 40%, 60%, 80% {
+        -moz-transform: translateX(10px);
+    }
+}
+@keyframes shake {
+    0%, 100% {
+        transform: translateX(0);
+    }
+    10%, 30%, 50%, 70%, 90% {
+        transform: translateX(-10px);
+    }
+    20%, 40%, 60%, 80% {
+        transform: translateX(10px);
+    }
+}
+/*2.主体框架*/
+.header {
+  background:#fff;
+  position: fixed;
+  z-index: 10;
+  width: 100%;
+  height: 50px;
+  top: 0;
+  left: 0
+}
+.main{
+  position: relative;
+  z-index: 4;
+}
+.main-nav {
+  position: fixed;
+  z-index: 99;
+  width:120px;
+  left: 0;
+  top: 0;
+  height: 100%;
+  background: #33425b;
+}
+.main-panel{
+  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;
+}
+.panel-content{
+  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;
+}
+.panel-sidebar+.panel-content{
+  padding: 115px 0 0 250px;
+}
+.panel-title, .panel-title>.title-bar {
+  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;
+}
+.panel-sidebar .panel-title{
+  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;
+}
+.panel-content .panel-title.fluid{
+  padding-left:120px
+}
+.panel-title>.title-bar{
+  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
+}
+.panel-title>.title-bar>h2 .btn{
+  margin-right:15px
+}
+.panel-title>.title-main .btn.pull-right {
+  margin:10px 0 0 10px
+}
+.panel-title>.title-main .form-control {
+  margin:10px 0 0 0
+}
+.panel-title>.title-main{
+  padding-left: 15px
+}
+/*滚动*/
+.scrollbar-auto {
+    overflow-y: auto;
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    top: 0;
+    right: 0;
+}
+.panel-sidebar .scrollbar-auto{
+    padding-top: 20px;
+    box-sizing: border-box;
+}
+.panel-sidebar .scrollbar-auto {
+    height: 100%;
+    width: 100%;
+    overflow-y: auto;
+    position: static;
+}
+/*头部*/
+.header .logo {
+  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:#ff6501 url(logo.png) no-repeat;
+  text-indent: -9999px;
+  vertical-align: top
+}
+.header .logo>a:hover{
+  background-color:#ff7821;
+  text-decoration: none;
+}
+.header-user > div {
+  float:left
+}
+.avatar .pic {
+  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%;
+}
+.avatar > a,.msg >a{
+  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)
+}
+.header-user .msg{
+  border-left:1px solid #eee
+}
+.header-user .msg .glyphicon{
+  font-size:20px;
+  vertical-align: middle;
+}
+.header-user .msg .badge{
+  margin:0 0 0 5px
+}
+.header .poj-name {
+  float:left;
+  padding:0 0 0 15px;
+  font-size:18px
+}
+.header .poj-name a{
+  color:#666
+}
+.header .poj-name > span{
+  height:50px;
+  line-height:50px;
+}
+/*登陆相关*/
+.login-body{
+  background: #fff
+}
+.login-infoinput {
+  margin-top:15%
+}
+/*侧栏主菜单*/
+.nav-top{
+  padding-top: 50px
+}
+.bg-nav a{
+  color:#7786ab;
+  width:120px;
+  height: 55px;
+  line-height: 55px;
+  display: inline-block;
+  padding:0 0 0 10px
+}
+.bg-nav > li{
+  width:120px
+}
+.bg-nav > li.active{
+  background: #192948
+}
+.bg-nav > li.active a{
+  border-radius: 0;
+  background: #192948
+}
+.bg-nav > li > a:hover,.bg-nav > li.active > a:hover{
+  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
+}
+.bg-nav .sub-menu a {
+  width:100px;
+  height:30px;
+  line-height:30px
+}
+.bg-nav .sub-menu:last-child{
+  margin:0 0 20px 0
+}
+.bg-nav .menu-arrow{
+  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
+}
+.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;
+}
+.nav-list li a:hover{
+  text-decoration: none;
+  background:#e4e7ea;
+  cursor: pointer;
+}
+.nav-list li a .badge{
+  position: absolute;
+  right:17px;
+  top:9px
+}
+.nav-list li.active a{
+  background:#e4e7ea;
+  font-weight: 600
+}
+/*内容区*/
+.c-header {
+  padding:0 0 5px
+}
+.c-body{
+  padding:15px;
+  background:#fff;
+}
+.right-nav{
+  width:18px
+}
+.right-nav .nav-link.active{
+  background: #fff;
+  color:#495057
+}
+.form-group .necessary{
+  font-size:18px;
+  color:#f90000
+}
+.bg-gray {
+ background-color:#bbb!important;
+}
+.datepickers-container {
+  z-index: 9999
+}
+.modal-height-500{
+  height:500px;
+  overflow: hidden
+}
+.modal-lgx {
+  max-width:1000px
+}
+.title-main .nav{
+  line-height: 16px;
+  margin-top:8px
+}
+/*草图编辑器*/
+.img-view{
+  height:400px;
+  border:.2rem solid #ccc;
+  position: relative;
+  width:100%;
+  overflow: hidden;
+}
+.img-view::after{
+  content:"草图编辑区";
+  color:#ddd;
+  position: absolute;
+  left:50%;
+  top:50%;
+  margin-left:-80px;
+  margin-top:-24px;
+  font-size:36px
+}
+.img-view .img-item{
+  position: absolute;
+
+}
+.img-view .img-item .img-bar{
+  position:absolute;
+  right:0;
+  top:0;
+  display:none
+}
+.img-item:hover .img-bar{
+  display: block;
+}
+.batch-l-t,.batch-l-b{
+  height: 200px;
+  overflow: hidden
+}
+.batch-r {
+  height:400px;
+  overflow: hidden
+}

TEMPAT SAMPAH
app/public/css/ztree/img/diy/1_close.png


TEMPAT SAMPAH
app/public/css/ztree/img/diy/1_open.png


TEMPAT SAMPAH
app/public/css/ztree/img/diy/2.png


TEMPAT SAMPAH
app/public/css/ztree/img/diy/3.png


TEMPAT SAMPAH
app/public/css/ztree/img/diy/4.png


TEMPAT SAMPAH
app/public/css/ztree/img/diy/5.png


TEMPAT SAMPAH
app/public/css/ztree/img/diy/6.png


TEMPAT SAMPAH
app/public/css/ztree/img/diy/7.png


TEMPAT SAMPAH
app/public/css/ztree/img/diy/8.png


TEMPAT SAMPAH
app/public/css/ztree/img/diy/9.png


TEMPAT SAMPAH
app/public/css/ztree/img/line_conn.gif


TEMPAT SAMPAH
app/public/css/ztree/img/loading.gif


TEMPAT SAMPAH
app/public/css/ztree/img/zTreeStandard.gif


TEMPAT SAMPAH
app/public/css/ztree/img/zTreeStandard.png


+ 97 - 0
app/public/css/ztree/zTreeStyle.css

@@ -0,0 +1,97 @@
+/*-------------------------------------
+zTree Style
+
+version:	3.5.19
+author:		Hunter.z
+email:		hunter.z@263.net
+website:	http://code.google.com/p/jquerytree/
+
+-------------------------------------*/
+
+.ztree * {padding:0; margin:0; font-size:12px; font-family: Verdana, Arial, Helvetica, AppleGothic, sans-serif}
+.ztree {margin:0; padding:5px; color:#333}
+.ztree li{padding:0; margin:0; list-style:none; line-height:14px; text-align:left; white-space:nowrap; outline:0}
+.ztree li ul{ margin:0; padding:0 0 0 18px}
+.ztree li ul.line{ background:url(./img/line_conn.gif) 0 0 repeat-y;}
+
+.ztree li a {padding:1px 3px 0 0; margin:0; cursor:pointer; height:17px; color:#333; background-color: transparent;
+	text-decoration:none; vertical-align:top; display: inline-block}
+.ztree li a:hover {text-decoration:underline}
+.ztree li a.curSelectedNode {padding-top:0px; background-color:#FFE6B0; color:black; height:16px; border:1px #FFB951 solid; opacity:0.8;}
+.ztree li a.curSelectedNode_Edit {padding-top:0px; background-color:#FFE6B0; color:black; height:16px; border:1px #FFB951 solid; opacity:0.8;}
+.ztree li a.tmpTargetNode_inner {padding-top:0px; background-color:#316AC5; color:white; height:16px; border:1px #316AC5 solid;
+	opacity:0.8; filter:alpha(opacity=80)}
+.ztree li a.tmpTargetNode_prev {}
+.ztree li a.tmpTargetNode_next {}
+.ztree li a input.rename {height:14px; width:80px; padding:0; margin:0;
+	font-size:12px; border:1px #7EC4CC solid; *border:0px}
+.ztree li span {line-height:16px; margin-right:2px}
+.ztree li span.button {line-height:0; margin:0; width:16px; height:16px; display: inline-block; vertical-align:middle;
+	border:0 none; cursor: pointer;outline:none;
+	background-color:transparent; background-repeat:no-repeat; background-attachment: scroll;
+	background-image:url("./img/zTreeStandard.png"); *background-image:url("./img/zTreeStandard.gif")}
+
+.ztree li span.button.chk {width:13px; height:13px; margin:0 3px 0 0; cursor: auto}
+.ztree li span.button.chk.checkbox_false_full {background-position:0 0}
+.ztree li span.button.chk.checkbox_false_full_focus {background-position:0 -14px}
+.ztree li span.button.chk.checkbox_false_part {background-position:0 -28px}
+.ztree li span.button.chk.checkbox_false_part_focus {background-position:0 -42px}
+.ztree li span.button.chk.checkbox_false_disable {background-position:0 -56px}
+.ztree li span.button.chk.checkbox_true_full {background-position:-14px 0}
+.ztree li span.button.chk.checkbox_true_full_focus {background-position:-14px -14px}
+.ztree li span.button.chk.checkbox_true_part {background-position:-14px -28px}
+.ztree li span.button.chk.checkbox_true_part_focus {background-position:-14px -42px}
+.ztree li span.button.chk.checkbox_true_disable {background-position:-14px -56px}
+.ztree li span.button.chk.radio_false_full {background-position:-28px 0}
+.ztree li span.button.chk.radio_false_full_focus {background-position:-28px -14px}
+.ztree li span.button.chk.radio_false_part {background-position:-28px -28px}
+.ztree li span.button.chk.radio_false_part_focus {background-position:-28px -42px}
+.ztree li span.button.chk.radio_false_disable {background-position:-28px -56px}
+.ztree li span.button.chk.radio_true_full {background-position:-42px 0}
+.ztree li span.button.chk.radio_true_full_focus {background-position:-42px -14px}
+.ztree li span.button.chk.radio_true_part {background-position:-42px -28px}
+.ztree li span.button.chk.radio_true_part_focus {background-position:-42px -42px}
+.ztree li span.button.chk.radio_true_disable {background-position:-42px -56px}
+
+.ztree li span.button.switch {width:18px; height:18px}
+.ztree li span.button.root_open{background-position:-92px -54px}
+.ztree li span.button.root_close{background-position:-74px -54px}
+.ztree li span.button.roots_open{background-position:-92px 0}
+.ztree li span.button.roots_close{background-position:-74px 0}
+.ztree li span.button.center_open{background-position:-92px -18px}
+.ztree li span.button.center_close{background-position:-74px -18px}
+.ztree li span.button.bottom_open{background-position:-92px -36px}
+.ztree li span.button.bottom_close{background-position:-74px -36px}
+.ztree li span.button.noline_open{background-position:-92px -72px}
+.ztree li span.button.noline_close{background-position:-74px -72px}
+.ztree li span.button.root_docu{ background:none;}
+.ztree li span.button.roots_docu{background-position:-56px 0}
+.ztree li span.button.center_docu{background-position:-56px -18px}
+.ztree li span.button.bottom_docu{background-position:-56px -36px}
+.ztree li span.button.noline_docu{ background:none;}
+
+.ztree li span.button.ico_open{margin-right:2px; background-position:-110px -16px; vertical-align:top; *vertical-align:middle}
+.ztree li span.button.ico_close{margin-right:2px; background-position:-110px 0; vertical-align:top; *vertical-align:middle}
+.ztree li span.button.ico_docu{margin-right:2px; background-position:-110px -32px; vertical-align:top; *vertical-align:middle}
+.ztree li span.button.edit {margin-right:2px; background-position:-110px -48px; vertical-align:top; *vertical-align:middle}
+.ztree li span.button.remove {margin-right:2px; background-position:-110px -64px; vertical-align:top; *vertical-align:middle}
+
+.ztree li span.button.ico_loading{margin-right:2px; background:url(./img/loading.gif) no-repeat scroll 0 0 transparent; vertical-align:top; *vertical-align:middle}
+
+ul.tmpTargetzTree {background-color:#FFE6B0; opacity:0.8; filter:alpha(opacity=80)}
+
+span.tmpzTreeMove_arrow {width:16px; height:16px; display: inline-block; padding:0; margin:2px 0 0 1px; border:0 none; position:absolute;
+	background-color:transparent; background-repeat:no-repeat; background-attachment: scroll;
+	background-position:-110px -80px; background-image:url("./img/zTreeStandard.png"); *background-image:url("./img/zTreeStandard.gif")}
+
+ul.ztree.zTreeDragUL {margin:0; padding:0; position:absolute; width:auto; height:auto;overflow:hidden; background-color:#cfcfcf; border:1px #00B83F dotted; opacity:0.8; filter:alpha(opacity=80)}
+.zTreeMask {z-index:10000; background-color:#cfcfcf; opacity:0.0; filter:alpha(opacity=0); position:absolute}
+
+/* level style*/
+/*.ztree li span.button.level0 {
+	display:none;
+}
+.ztree li ul.level0 {
+	padding:0;
+	background:none;
+}*/

TEMPAT SAMPAH
app/public/images/avatar.png


File diff ditekan karena terlalu besar
+ 7 - 0
app/public/js/bootstrap/bootstrap.min.js


File diff ditekan karena terlalu besar
+ 2 - 0
app/public/js/datepicker/datepicker.min.js


+ 12 - 0
app/public/js/datepicker/datepicker.zh.js

@@ -0,0 +1,12 @@
+;(function (jQuery) { jQuery.fn.datepicker.language['zh'] = {
+    days: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
+    daysShort: ['日', '一', '二', '三', '四', '五', '六'],
+    daysMin: ['日', '一', '二', '三', '四', '五', '六'],
+    months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
+    monthsShort: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
+    today: '今天',
+    clear: '清除',
+    dateFormat: 'yyyy-mm-dd',
+    timeFormat: 'hh:ii',
+    firstDay: 0
+}; })(jQuery);

File diff ditekan karena terlalu besar
+ 32 - 0
app/public/js/echarts/echarts.min.js


+ 53 - 0
app/public/js/global.js

@@ -0,0 +1,53 @@
+/*全局自适应高度*/
+function autoFlashHeight(){
+    var cHeader = $(".c-header").height();
+    var sBar = $(".sjs-bar").height();
+    $(".sjs-height-1").height($(window).height()-cHeader-160);
+    $(".sjs-height-2").height($(window).height()-cHeader-191);
+    $(".sjs-height-3").height($(window).height()-cHeader-sBar-560);
+};
+$(window).resize(autoFlashHeight);
+/*全局自适应高度结束*/
+$(function(){
+/*侧滑*/
+$(".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;
+            while (elem) { //循环判断至跟节点,防止点击的是div子元素
+                if (elem.className == "open-sidebar" || elem.className == 'slide-sidebar open') {
+                    return false;
+                }
+                elem = elem.parentNode;
+            }
+            $(".slide-sidebar").animate({width:"0"}).removeClass("open")// 关闭处理
+        }
+
+    });
+/*侧滑*/
+/*工具提示*/
+$(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');
+          }
+      }
+  });
+
+
+});

File diff ditekan karena terlalu besar
+ 4 - 0
app/public/js/jquery/jquery-3.2.1.min.js


File diff ditekan karena terlalu besar
+ 5 - 0
app/public/js/popper/popper.min.js


File diff ditekan karena terlalu besar
+ 1913 - 0
app/public/js/ztree/jquery.ztree.core.js


+ 628 - 0
app/public/js/ztree/jquery.ztree.excheck.js

@@ -0,0 +1,628 @@
+/*
+ * JQuery zTree excheck v3.5.28
+ * http://treejs.cn/
+ *
+ * Copyright (c) 2010 Hunter.z
+ *
+ * Licensed same as jquery - MIT License
+ * http://www.opensource.org/licenses/mit-license.php
+ *
+ * email: hunter.z@263.net
+ * Date: 2017-01-20
+ */
+(function($){
+	//default consts of excheck
+	var _consts = {
+		event: {
+			CHECK: "ztree_check"
+		},
+		id: {
+			CHECK: "_check"
+		},
+		checkbox: {
+			STYLE: "checkbox",
+			DEFAULT: "chk",
+			DISABLED: "disable",
+			FALSE: "false",
+			TRUE: "true",
+			FULL: "full",
+			PART: "part",
+			FOCUS: "focus"
+		},
+		radio: {
+			STYLE: "radio",
+			TYPE_ALL: "all",
+			TYPE_LEVEL: "level"
+		}
+	},
+	//default setting of excheck
+	_setting = {
+		check: {
+			enable: false,
+			autoCheckTrigger: false,
+			chkStyle: _consts.checkbox.STYLE,
+			nocheckInherit: false,
+			chkDisabledInherit: false,
+			radioType: _consts.radio.TYPE_LEVEL,
+			chkboxType: {
+				"Y": "ps",
+				"N": "ps"
+			}
+		},
+		data: {
+			key: {
+				checked: "checked"
+			}
+		},
+		callback: {
+			beforeCheck:null,
+			onCheck:null
+		}
+	},
+	//default root of excheck
+	_initRoot = function (setting) {
+		var r = data.getRoot(setting);
+		r.radioCheckedList = [];
+	},
+	//default cache of excheck
+	_initCache = function(treeId) {},
+	//default bind event of excheck
+	_bindEvent = function(setting) {
+		var o = setting.treeObj,
+		c = consts.event;
+		o.bind(c.CHECK, function (event, srcEvent, treeId, node) {
+			event.srcEvent = srcEvent;
+			tools.apply(setting.callback.onCheck, [event, treeId, node]);
+		});
+	},
+	_unbindEvent = function(setting) {
+		var o = setting.treeObj,
+		c = consts.event;
+		o.unbind(c.CHECK);
+	},
+	//default event proxy of excheck
+	_eventProxy = function(e) {
+		var target = e.target,
+		setting = data.getSetting(e.data.treeId),
+		tId = "", node = null,
+		nodeEventType = "", treeEventType = "",
+		nodeEventCallback = null, treeEventCallback = null;
+
+		if (tools.eqs(e.type, "mouseover")) {
+			if (setting.check.enable && tools.eqs(target.tagName, "span") && target.getAttribute("treeNode"+ consts.id.CHECK) !== null) {
+				tId = tools.getNodeMainDom(target).id;
+				nodeEventType = "mouseoverCheck";
+			}
+		} else if (tools.eqs(e.type, "mouseout")) {
+			if (setting.check.enable && tools.eqs(target.tagName, "span") && target.getAttribute("treeNode"+ consts.id.CHECK) !== null) {
+				tId = tools.getNodeMainDom(target).id;
+				nodeEventType = "mouseoutCheck";
+			}
+		} else if (tools.eqs(e.type, "click")) {
+			if (setting.check.enable && tools.eqs(target.tagName, "span") && target.getAttribute("treeNode"+ consts.id.CHECK) !== null) {
+				tId = tools.getNodeMainDom(target).id;
+				nodeEventType = "checkNode";
+			}
+		}
+		if (tId.length>0) {
+			node = data.getNodeCache(setting, tId);
+			switch (nodeEventType) {
+				case "checkNode" :
+					nodeEventCallback = _handler.onCheckNode;
+					break;
+				case "mouseoverCheck" :
+					nodeEventCallback = _handler.onMouseoverCheck;
+					break;
+				case "mouseoutCheck" :
+					nodeEventCallback = _handler.onMouseoutCheck;
+					break;
+			}
+		}
+		var proxyResult = {
+			stop: nodeEventType === "checkNode",
+			node: node,
+			nodeEventType: nodeEventType,
+			nodeEventCallback: nodeEventCallback,
+			treeEventType: treeEventType,
+			treeEventCallback: treeEventCallback
+		};
+		return proxyResult
+	},
+	//default init node of excheck
+	_initNode = function(setting, level, n, parentNode, isFirstNode, isLastNode, openFlag) {
+		if (!n) return;
+		var checkedKey = setting.data.key.checked;
+		if (typeof n[checkedKey] == "string") n[checkedKey] = tools.eqs(n[checkedKey], "true");
+		n[checkedKey] = !!n[checkedKey];
+		n.checkedOld = n[checkedKey];
+		if (typeof n.nocheck == "string") n.nocheck = tools.eqs(n.nocheck, "true");
+		n.nocheck = !!n.nocheck || (setting.check.nocheckInherit && parentNode && !!parentNode.nocheck);
+		if (typeof n.chkDisabled == "string") n.chkDisabled = tools.eqs(n.chkDisabled, "true");
+		n.chkDisabled = !!n.chkDisabled || (setting.check.chkDisabledInherit && parentNode && !!parentNode.chkDisabled);
+		if (typeof n.halfCheck == "string") n.halfCheck = tools.eqs(n.halfCheck, "true");
+		n.halfCheck = !!n.halfCheck;
+		n.check_Child_State = -1;
+		n.check_Focus = false;
+		n.getCheckStatus = function() {return data.getCheckStatus(setting, n);};
+
+		if (setting.check.chkStyle == consts.radio.STYLE && setting.check.radioType == consts.radio.TYPE_ALL && n[checkedKey] ) {
+			var r = data.getRoot(setting);
+			r.radioCheckedList.push(n);
+		}
+	},
+	//add dom for check
+	_beforeA = function(setting, node, html) {
+		var checkedKey = setting.data.key.checked;
+		if (setting.check.enable) {
+			data.makeChkFlag(setting, node);
+			html.push("<span ID='", node.tId, consts.id.CHECK, "' class='", view.makeChkClass(setting, node), "' treeNode", consts.id.CHECK, (node.nocheck === true?" style='display:none;'":""),"></span>");
+		}
+	},
+	//update zTreeObj, add method of check
+	_zTreeTools = function(setting, zTreeTools) {
+		zTreeTools.checkNode = function(node, checked, checkTypeFlag, callbackFlag) {
+			var checkedKey = this.setting.data.key.checked;
+			if (node.chkDisabled === true) return;
+			if (checked !== true && checked !== false) {
+				checked = !node[checkedKey];
+			}
+			callbackFlag = !!callbackFlag;
+
+			if (node[checkedKey] === checked && !checkTypeFlag) {
+				return;
+			} else if (callbackFlag && tools.apply(this.setting.callback.beforeCheck, [this.setting.treeId, node], true) == false) {
+				return;
+			}
+			if (tools.uCanDo(this.setting) && this.setting.check.enable && node.nocheck !== true) {
+				node[checkedKey] = checked;
+				var checkObj = $$(node, consts.id.CHECK, this.setting);
+				if (checkTypeFlag || this.setting.check.chkStyle === consts.radio.STYLE) view.checkNodeRelation(this.setting, node);
+				view.setChkClass(this.setting, checkObj, node);
+				view.repairParentChkClassWithSelf(this.setting, node);
+				if (callbackFlag) {
+					this.setting.treeObj.trigger(consts.event.CHECK, [null, this.setting.treeId, node]);
+				}
+			}
+		}
+
+		zTreeTools.checkAllNodes = function(checked) {
+			view.repairAllChk(this.setting, !!checked);
+		}
+
+		zTreeTools.getCheckedNodes = function(checked) {
+			var childKey = this.setting.data.key.children;
+			checked = (checked !== false);
+			return data.getTreeCheckedNodes(this.setting, data.getRoot(this.setting)[childKey], checked);
+		}
+
+		zTreeTools.getChangeCheckedNodes = function() {
+			var childKey = this.setting.data.key.children;
+			return data.getTreeChangeCheckedNodes(this.setting, data.getRoot(this.setting)[childKey]);
+		}
+
+		zTreeTools.setChkDisabled = function(node, disabled, inheritParent, inheritChildren) {
+			disabled = !!disabled;
+			inheritParent = !!inheritParent;
+			inheritChildren = !!inheritChildren;
+			view.repairSonChkDisabled(this.setting, node, disabled, inheritChildren);
+			view.repairParentChkDisabled(this.setting, node.getParentNode(), disabled, inheritParent);
+		}
+
+		var _updateNode = zTreeTools.updateNode;
+		zTreeTools.updateNode = function(node, checkTypeFlag) {
+			if (_updateNode) _updateNode.apply(zTreeTools, arguments);
+			if (!node || !this.setting.check.enable) return;
+			var nObj = $$(node, this.setting);
+			if (nObj.get(0) && tools.uCanDo(this.setting)) {
+				var checkObj = $$(node, consts.id.CHECK, this.setting);
+				if (checkTypeFlag == true || this.setting.check.chkStyle === consts.radio.STYLE) view.checkNodeRelation(this.setting, node);
+				view.setChkClass(this.setting, checkObj, node);
+				view.repairParentChkClassWithSelf(this.setting, node);
+			}
+		}
+	},
+	//method of operate data
+	_data = {
+		getRadioCheckedList: function(setting) {
+			var checkedList = data.getRoot(setting).radioCheckedList;
+			for (var i=0, j=checkedList.length; i<j; i++) {
+				if(!data.getNodeCache(setting, checkedList[i].tId)) {
+					checkedList.splice(i, 1);
+					i--; j--;
+				}
+			}
+			return checkedList;
+		},
+		getCheckStatus: function(setting, node) {
+			if (!setting.check.enable || node.nocheck || node.chkDisabled) return null;
+			var checkedKey = setting.data.key.checked,
+			r = {
+				checked: node[checkedKey],
+				half: node.halfCheck ? node.halfCheck : (setting.check.chkStyle == consts.radio.STYLE ? (node.check_Child_State === 2) : (node[checkedKey] ? (node.check_Child_State > -1 && node.check_Child_State < 2) : (node.check_Child_State > 0)))
+			};
+			return r;
+		},
+		getTreeCheckedNodes: function(setting, nodes, checked, results) {
+			if (!nodes) return [];
+			var childKey = setting.data.key.children,
+			checkedKey = setting.data.key.checked,
+			onlyOne = (checked && setting.check.chkStyle == consts.radio.STYLE && setting.check.radioType == consts.radio.TYPE_ALL);
+			results = !results ? [] : results;
+			for (var i = 0, l = nodes.length; i < l; i++) {
+				if (nodes[i].nocheck !== true && nodes[i].chkDisabled !== true && nodes[i][checkedKey] == checked) {
+					results.push(nodes[i]);
+					if(onlyOne) {
+						break;
+					}
+				}
+				data.getTreeCheckedNodes(setting, nodes[i][childKey], checked, results);
+				if(onlyOne && results.length > 0) {
+					break;
+				}
+			}
+			return results;
+		},
+		getTreeChangeCheckedNodes: function(setting, nodes, results) {
+			if (!nodes) return [];
+			var childKey = setting.data.key.children,
+			checkedKey = setting.data.key.checked;
+			results = !results ? [] : results;
+			for (var i = 0, l = nodes.length; i < l; i++) {
+				if (nodes[i].nocheck !== true && nodes[i].chkDisabled !== true && nodes[i][checkedKey] != nodes[i].checkedOld) {
+					results.push(nodes[i]);
+				}
+				data.getTreeChangeCheckedNodes(setting, nodes[i][childKey], results);
+			}
+			return results;
+		},
+		makeChkFlag: function(setting, node) {
+			if (!node) return;
+			var childKey = setting.data.key.children,
+			checkedKey = setting.data.key.checked,
+			chkFlag = -1;
+			if (node[childKey]) {
+				for (var i = 0, l = node[childKey].length; i < l; i++) {
+					var cNode = node[childKey][i];
+					var tmp = -1;
+					if (setting.check.chkStyle == consts.radio.STYLE) {
+						if (cNode.nocheck === true || cNode.chkDisabled === true) {
+							tmp = cNode.check_Child_State;
+						} else if (cNode.halfCheck === true) {
+							tmp = 2;
+						} else if (cNode[checkedKey]) {
+							tmp = 2;
+						} else {
+							tmp = cNode.check_Child_State > 0 ? 2:0;
+						}
+						if (tmp == 2) {
+							chkFlag = 2; break;
+						} else if (tmp == 0){
+							chkFlag = 0;
+						}
+					} else if (setting.check.chkStyle == consts.checkbox.STYLE) {
+						if (cNode.nocheck === true || cNode.chkDisabled === true) {
+							tmp = cNode.check_Child_State;
+						} else if (cNode.halfCheck === true) {
+							tmp = 1;
+						} else if (cNode[checkedKey] ) {
+							tmp = (cNode.check_Child_State === -1 || cNode.check_Child_State === 2) ? 2 : 1;
+						} else {
+							tmp = (cNode.check_Child_State > 0) ? 1 : 0;
+						}
+						if (tmp === 1) {
+							chkFlag = 1; break;
+						} else if (tmp === 2 && chkFlag > -1 && i > 0 && tmp !== chkFlag) {
+							chkFlag = 1; break;
+						} else if (chkFlag === 2 && tmp > -1 && tmp < 2) {
+							chkFlag = 1; break;
+						} else if (tmp > -1) {
+							chkFlag = tmp;
+						}
+					}
+				}
+			}
+			node.check_Child_State = chkFlag;
+		}
+	},
+	//method of event proxy
+	_event = {
+
+	},
+	//method of event handler
+	_handler = {
+		onCheckNode: function (event, node) {
+			if (node.chkDisabled === true) return false;
+			var setting = data.getSetting(event.data.treeId),
+			checkedKey = setting.data.key.checked;
+			if (tools.apply(setting.callback.beforeCheck, [setting.treeId, node], true) == false) return true;
+			node[checkedKey] = !node[checkedKey];
+			view.checkNodeRelation(setting, node);
+			var checkObj = $$(node, consts.id.CHECK, setting);
+			view.setChkClass(setting, checkObj, node);
+			view.repairParentChkClassWithSelf(setting, node);
+			setting.treeObj.trigger(consts.event.CHECK, [event, setting.treeId, node]);
+			return true;
+		},
+		onMouseoverCheck: function(event, node) {
+			if (node.chkDisabled === true) return false;
+			var setting = data.getSetting(event.data.treeId),
+			checkObj = $$(node, consts.id.CHECK, setting);
+			node.check_Focus = true;
+			view.setChkClass(setting, checkObj, node);
+			return true;
+		},
+		onMouseoutCheck: function(event, node) {
+			if (node.chkDisabled === true) return false;
+			var setting = data.getSetting(event.data.treeId),
+			checkObj = $$(node, consts.id.CHECK, setting);
+			node.check_Focus = false;
+			view.setChkClass(setting, checkObj, node);
+			return true;
+		}
+	},
+	//method of tools for zTree
+	_tools = {
+
+	},
+	//method of operate ztree dom
+	_view = {
+		checkNodeRelation: function(setting, node) {
+			var pNode, i, l,
+			childKey = setting.data.key.children,
+			checkedKey = setting.data.key.checked,
+			r = consts.radio;
+			if (setting.check.chkStyle == r.STYLE) {
+				var checkedList = data.getRadioCheckedList(setting);
+				if (node[checkedKey]) {
+					if (setting.check.radioType == r.TYPE_ALL) {
+						for (i = checkedList.length-1; i >= 0; i--) {
+							pNode = checkedList[i];
+							if (pNode[checkedKey] && pNode != node) {
+								pNode[checkedKey] = false;
+								checkedList.splice(i, 1);
+
+								view.setChkClass(setting, $$(pNode, consts.id.CHECK, setting), pNode);
+								if (pNode.parentTId != node.parentTId) {
+									view.repairParentChkClassWithSelf(setting, pNode);
+								}
+							}
+						}
+						checkedList.push(node);
+					} else {
+						var parentNode = (node.parentTId) ? node.getParentNode() : data.getRoot(setting);
+						for (i = 0, l = parentNode[childKey].length; i < l; i++) {
+							pNode = parentNode[childKey][i];
+							if (pNode[checkedKey] && pNode != node) {
+								pNode[checkedKey] = false;
+								view.setChkClass(setting, $$(pNode, consts.id.CHECK, setting), pNode);
+							}
+						}
+					}
+				} else if (setting.check.radioType == r.TYPE_ALL) {
+					for (i = 0, l = checkedList.length; i < l; i++) {
+						if (node == checkedList[i]) {
+							checkedList.splice(i, 1);
+							break;
+						}
+					}
+				}
+
+			} else {
+				if (node[checkedKey] && (!node[childKey] || node[childKey].length==0 || setting.check.chkboxType.Y.indexOf("s") > -1)) {
+					view.setSonNodeCheckBox(setting, node, true);
+				}
+				if (!node[checkedKey] && (!node[childKey] || node[childKey].length==0 || setting.check.chkboxType.N.indexOf("s") > -1)) {
+					view.setSonNodeCheckBox(setting, node, false);
+				}
+				if (node[checkedKey] && setting.check.chkboxType.Y.indexOf("p") > -1) {
+					view.setParentNodeCheckBox(setting, node, true);
+				}
+				if (!node[checkedKey] && setting.check.chkboxType.N.indexOf("p") > -1) {
+					view.setParentNodeCheckBox(setting, node, false);
+				}
+			}
+		},
+		makeChkClass: function(setting, node) {
+			var checkedKey = setting.data.key.checked,
+			c = consts.checkbox, r = consts.radio,
+			fullStyle = "";
+			if (node.chkDisabled === true) {
+				fullStyle = c.DISABLED;
+			} else if (node.halfCheck) {
+				fullStyle = c.PART;
+			} else if (setting.check.chkStyle == r.STYLE) {
+				fullStyle = (node.check_Child_State < 1)? c.FULL:c.PART;
+			} else {
+				fullStyle = node[checkedKey] ? ((node.check_Child_State === 2 || node.check_Child_State === -1) ? c.FULL:c.PART) : ((node.check_Child_State < 1)? c.FULL:c.PART);
+			}
+			var chkName = setting.check.chkStyle + "_" + (node[checkedKey] ? c.TRUE : c.FALSE) + "_" + fullStyle;
+			chkName = (node.check_Focus && node.chkDisabled !== true) ? chkName + "_" + c.FOCUS : chkName;
+			return consts.className.BUTTON + " " + c.DEFAULT + " " + chkName;
+		},
+		repairAllChk: function(setting, checked) {
+			if (setting.check.enable && setting.check.chkStyle === consts.checkbox.STYLE) {
+				var checkedKey = setting.data.key.checked,
+				childKey = setting.data.key.children,
+				root = data.getRoot(setting);
+				for (var i = 0, l = root[childKey].length; i<l ; i++) {
+					var node = root[childKey][i];
+					if (node.nocheck !== true && node.chkDisabled !== true) {
+						node[checkedKey] = checked;
+					}
+					view.setSonNodeCheckBox(setting, node, checked);
+				}
+			}
+		},
+		repairChkClass: function(setting, node) {
+			if (!node) return;
+			data.makeChkFlag(setting, node);
+			if (node.nocheck !== true) {
+				var checkObj = $$(node, consts.id.CHECK, setting);
+				view.setChkClass(setting, checkObj, node);
+			}
+		},
+		repairParentChkClass: function(setting, node) {
+			if (!node || !node.parentTId) return;
+			var pNode = node.getParentNode();
+			view.repairChkClass(setting, pNode);
+			view.repairParentChkClass(setting, pNode);
+		},
+		repairParentChkClassWithSelf: function(setting, node) {
+			if (!node) return;
+			var childKey = setting.data.key.children;
+			if (node[childKey] && node[childKey].length > 0) {
+				view.repairParentChkClass(setting, node[childKey][0]);
+			} else {
+				view.repairParentChkClass(setting, node);
+			}
+		},
+		repairSonChkDisabled: function(setting, node, chkDisabled, inherit) {
+			if (!node) return;
+			var childKey = setting.data.key.children;
+			if (node.chkDisabled != chkDisabled) {
+				node.chkDisabled = chkDisabled;
+			}
+			view.repairChkClass(setting, node);
+			if (node[childKey] && inherit) {
+				for (var i = 0, l = node[childKey].length; i < l; i++) {
+					var sNode = node[childKey][i];
+					view.repairSonChkDisabled(setting, sNode, chkDisabled, inherit);
+				}
+			}
+		},
+		repairParentChkDisabled: function(setting, node, chkDisabled, inherit) {
+			if (!node) return;
+			if (node.chkDisabled != chkDisabled && inherit) {
+				node.chkDisabled = chkDisabled;
+			}
+			view.repairChkClass(setting, node);
+			view.repairParentChkDisabled(setting, node.getParentNode(), chkDisabled, inherit);
+		},
+		setChkClass: function(setting, obj, node) {
+			if (!obj) return;
+			if (node.nocheck === true) {
+				obj.hide();
+			} else {
+				obj.show();
+			}
+            obj.attr('class', view.makeChkClass(setting, node));
+		},
+		setParentNodeCheckBox: function(setting, node, value, srcNode) {
+			var childKey = setting.data.key.children,
+			checkedKey = setting.data.key.checked,
+			checkObj = $$(node, consts.id.CHECK, setting);
+			if (!srcNode) srcNode = node;
+			data.makeChkFlag(setting, node);
+			if (node.nocheck !== true && node.chkDisabled !== true) {
+				node[checkedKey] = value;
+				view.setChkClass(setting, checkObj, node);
+				if (setting.check.autoCheckTrigger && node != srcNode) {
+					setting.treeObj.trigger(consts.event.CHECK, [null, setting.treeId, node]);
+				}
+			}
+			if (node.parentTId) {
+				var pSign = true;
+				if (!value) {
+					var pNodes = node.getParentNode()[childKey];
+					for (var i = 0, l = pNodes.length; i < l; i++) {
+						if ((pNodes[i].nocheck !== true && pNodes[i].chkDisabled !== true && pNodes[i][checkedKey])
+						|| ((pNodes[i].nocheck === true || pNodes[i].chkDisabled === true) && pNodes[i].check_Child_State > 0)) {
+							pSign = false;
+							break;
+						}
+					}
+				}
+				if (pSign) {
+					view.setParentNodeCheckBox(setting, node.getParentNode(), value, srcNode);
+				}
+			}
+		},
+		setSonNodeCheckBox: function(setting, node, value, srcNode) {
+			if (!node) return;
+			var childKey = setting.data.key.children,
+			checkedKey = setting.data.key.checked,
+			checkObj = $$(node, consts.id.CHECK, setting);
+			if (!srcNode) srcNode = node;
+
+			var hasDisable = false;
+			if (node[childKey]) {
+				for (var i = 0, l = node[childKey].length; i < l; i++) {
+					var sNode = node[childKey][i];
+					view.setSonNodeCheckBox(setting, sNode, value, srcNode);
+					if (sNode.chkDisabled === true) hasDisable = true;
+				}
+			}
+
+			if (node != data.getRoot(setting) && node.chkDisabled !== true) {
+				if (hasDisable && node.nocheck !== true) {
+					data.makeChkFlag(setting, node);
+				}
+				if (node.nocheck !== true && node.chkDisabled !== true) {
+					node[checkedKey] = value;
+					if (!hasDisable) node.check_Child_State = (node[childKey] && node[childKey].length > 0) ? (value ? 2 : 0) : -1;
+				} else {
+					node.check_Child_State = -1;
+				}
+				view.setChkClass(setting, checkObj, node);
+				if (setting.check.autoCheckTrigger && node != srcNode && node.nocheck !== true && node.chkDisabled !== true) {
+					setting.treeObj.trigger(consts.event.CHECK, [null, setting.treeId, node]);
+				}
+			}
+
+		}
+	},
+
+	_z = {
+		tools: _tools,
+		view: _view,
+		event: _event,
+		data: _data
+	};
+	$.extend(true, $.fn.zTree.consts, _consts);
+	$.extend(true, $.fn.zTree._z, _z);
+
+	var zt = $.fn.zTree,
+	tools = zt._z.tools,
+	consts = zt.consts,
+	view = zt._z.view,
+	data = zt._z.data,
+	event = zt._z.event,
+	$$ = tools.$;
+
+	data.exSetting(_setting);
+	data.addInitBind(_bindEvent);
+	data.addInitUnBind(_unbindEvent);
+	data.addInitCache(_initCache);
+	data.addInitNode(_initNode);
+	data.addInitProxy(_eventProxy, true);
+	data.addInitRoot(_initRoot);
+	data.addBeforeA(_beforeA);
+	data.addZTreeTools(_zTreeTools);
+
+	var _createNodes = view.createNodes;
+	view.createNodes = function(setting, level, nodes, parentNode, index) {
+		if (_createNodes) _createNodes.apply(view, arguments);
+		if (!nodes) return;
+		view.repairParentChkClassWithSelf(setting, parentNode);
+	}
+	var _removeNode = view.removeNode;
+	view.removeNode = function(setting, node) {
+		var parentNode = node.getParentNode();
+		if (_removeNode) _removeNode.apply(view, arguments);
+		if (!node || !parentNode) return;
+		view.repairChkClass(setting, parentNode);
+		view.repairParentChkClass(setting, parentNode);
+	}
+
+	var _appendNodes = view.appendNodes;
+	view.appendNodes = function(setting, level, nodes, parentNode, index, initFlag, openFlag) {
+		var html = "";
+		if (_appendNodes) {
+			html = _appendNodes.apply(view, arguments);
+		}
+		if (parentNode) {
+			data.makeChkFlag(setting, parentNode);
+		}
+		return html;
+	}
+})(jQuery);

+ 25 - 0
app/router.js

@@ -0,0 +1,25 @@
+'use strict';
+
+/**
+ * @param {Egg.Application} app - egg application
+ */
+module.exports = app => {
+
+    // session验证中间件
+    const sessionAuth = app.middlewares.sessionAuth();
+
+    // 登入登出
+    app.get('/login', 'loginController.index');
+    app.get('/', 'loginController.index');
+    app.get('/logout', 'loginController.logout');
+    app.post('/login', 'loginController.login');
+
+    // 指标库
+    app.get('/lib', sessionAuth, 'libController.index');
+
+    // 指标模板
+    app.get('/template', sessionAuth, 'templateController.index');
+
+    // 指标对比
+    app.get('/compare', sessionAuth, 'compareController.index');
+};

+ 44 - 0
app/service/customer.js

@@ -0,0 +1,44 @@
+'use strict';
+
+/**
+ * 用户管理业务类
+ *
+ * @author Mai
+ * @date 2018/4/19
+ * @version
+ */
+
+const Service = require('egg').Service;
+// 加密类
+const crypto = require('crypto');
+module.exports = app => {
+    class Customer extends Service {
+        async validLogin (data) {
+            let result = false;
+
+            // 验证本地用户
+            if (data.account === 'admin') {
+                result = data.password === 'admin';
+            } else {
+                result = false;
+            }
+
+            if (result) {
+                // 成功后写入session
+                const currentTime = new Date().getTime();
+                const sessionToken = crypto.createHmac('sha1', currentTime + '').update(data.account)
+                    .digest().toString('base64');
+                const userSession = {
+                    username: data.account,
+                    loginTime: currentTime,
+                    sessionToken,
+                };
+                this.ctx.session.userSession = userSession;
+            }
+
+            return result;
+        }
+    }
+
+    return Customer;
+}

+ 162 - 0
app/view/compare/index.ejs

@@ -0,0 +1,162 @@
+<div class="panel-sidebar">
+    <div class="panel-title">
+        <div class="title-bar">
+            <h2>指标数据源</h2>
+        </div>
+    </div>
+    <div class="scrollbar-auto">
+        <div class="nav-box">
+            <ul class="nav-list list-unstyled">
+                <li><a href="#" class="text-truncate"><label><input type="checkbox" checked> <span>XXX标段 2018-03-14 17:12:23</span></label></a></li>
+                <li class="bg-warning"><a href="#" class="text-truncate"><label><input type="checkbox"> <span>YYY标段 2018-03-13 17:12:23</span></label></a></li>
+                <li><a href="#" class="text-truncate"><label><input type="checkbox" checked> <span>ZZZ标段 2018-03-11 17:12:23</span></label></a></li>
+                <li><a href="#" class="text-truncate"><label><input type="checkbox" checked> <span>AAA标段 2018-03-11 17:12:23</span></label></a></li>
+                <li><a href="#" class="text-truncate"><label><input type="checkbox" checked> <span>BBB标段 2018-03-11 17:12:23</span></label></a></li>
+                <li><a href="#" class="text-truncate"><label><input type="checkbox" checked> <span>CCC标段 2018-03-11 17:12:23</span></label></a></li>
+            </ul>
+        </div>
+    </div>
+</div>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main">
+            <div class="btn-group col-3 pr-0">
+                <input type="text" class="form-control form-control-sm m-0" placeholder="输入项目节名称检索" >
+            </div>
+            <div class="btn-group">
+                <button class="btn btn-sm btn-primary " type="button">检索指标</button>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-header p-0 col-12">
+            <div class="card text-white bg-dark">
+                <div class="card-body row">
+                    <div class="col-auto">
+                        <span class="btn btn-light btn-sm"><i class="fa fa-search"></i> 路侧护栏</span>
+                    </div>
+                    <div class="col-auto">
+                        <span class="btn btn-light btn-sm">数据源:5</span>
+                        <span class="btn btn-light btn-sm">参与计算:3</span>
+                    </div>
+                    <div class="col-2">
+                        <div class="input-group input-group-sm mb-0">
+                            <div class="input-group-prepend">
+                                <span class="input-group-text" id="basic-addon1">数量</span>
+                            </div>
+                            <input type="text" class="form-control" value="7000" >
+                            <div class="input-group-append">
+                                <a class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown" >组成</a>
+                                <div class="dropdown-menu p-2">
+                                    <table class="table table-sm table-bordered mb-0">
+                                        <tr><th class="text-center">单位</th><th class="text-center">数量</th><th class="text-center">合计</th></tr>
+                                        <tr><td>单位1</td><td class="text-right">3000</td><td><input type="checkbox" class="form-control" checked></td></tr>
+                                        <tr><td>单位2</td><td class="text-right">4000</td><td><input type="checkbox" class="form-control" checked></td></tr>
+                                        <tr><td>合计</td><td  class="text-right" colspan="2">7000</td></tr>
+                                    </table>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="col-2">
+                        <div class="input-group input-group-sm mb-0">
+                            <div class="input-group-prepend">
+                                <span class="input-group-text" id="basic-addon1">经济指标</span>
+                            </div>
+                            <input type="text" class="form-control" value="66020" readonly>
+
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="card text-white bg-danger">
+                <div class="card-body row">
+                    <div class="col-auto">
+                        <h4>检索 "关键词" 没有结果</h4>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="c-body">
+            <div class="sjs-height-1">
+                <table class="table table-bordered">
+                    <tr>
+                        <th class="text-center">序号</th>
+                        <th class="text-center">参与计算</th>
+                        <th class="text-center">编号</th>
+                        <th class="text-center">名称</th>
+                        <th class="text-center">单位</th>
+                        <th class="text-center">设计数量1</th>
+                        <th class="text-center">设计数量2</th>
+                        <th class="text-center">经济指标</th>
+                        <th class="text-center">金额</th>
+                        <th class="text-center">所属标段</th>
+                        <th class="text-center">上传时间</th>
+                        <th class="text-center">所在位置</th>
+                        <th class="text-center">备注</th>
+                    </tr>
+                    <tr>
+                        <td>1</td>
+                        <td><input type="checkbox" checked  class="form-control"></td>
+                        <td>编号</td>
+                        <td>名称</td>
+                        <td class="text-center">单位</td>
+                        <td class="text-right">设计数量1</td>
+                        <td class="text-right">设计数量2</td>
+                        <td class="text-right">经济指标</td>
+                        <td class="text-right">金额</td>
+                        <td>所属标段</td>
+                        <td>上传时间</td>
+                        <td>所在位置</td>
+                        <td>备注</td>
+                    </tr>
+                    <tr class="table-secondary">
+                        <td>2</td>
+                        <td><input type="checkbox" class="form-control"></td>
+                        <td>编号</td>
+                        <td>名称</td>
+                        <td class="text-center">单位</td>
+                        <td class="text-right">设计数量1</td>
+                        <td class="text-right">设计数量2</td>
+                        <td class="text-right">经济指标</td>
+                        <td class="text-right">金额</td>
+                        <td>所属标段</td>
+                        <td>上传时间</td>
+                        <td>所在位置</td>
+                        <td>备注</td>
+                    </tr>
+                    <tr>
+                        <td>3</td>
+                        <td><input type="checkbox" checked class="form-control"></td>
+                        <td>编号</td>
+                        <td>名称</td>
+                        <td class="text-center">单位</td>
+                        <td class="text-right">设计数量1</td>
+                        <td class="text-right">设计数量2</td>
+                        <td class="text-right">经济指标</td>
+                        <td class="text-right">金额</td>
+                        <td>所属标段</td>
+                        <td>上传时间</td>
+                        <td>所在位置</td>
+                        <td>备注</td>
+                    </tr>
+                    <tr>
+                        <td>4</td>
+                        <td><input type="checkbox" checked class="form-control"></td>
+                        <td>编号</td>
+                        <td>名称</td>
+                        <td class="text-center">单位</td>
+                        <td class="text-right">设计数量1</td>
+                        <td class="text-right">设计数量2</td>
+                        <td class="text-right">经济指标</td>
+                        <td class="text-right">金额</td>
+                        <td>所属标段</td>
+                        <td>上传时间</td>
+                        <td>所在位置</td>
+                        <td>备注</td>
+                    </tr>
+                </table>
+            </div>
+        </div>
+    </div>
+</div>

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

@@ -0,0 +1,66 @@
+<!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>指标库-纵横经济指标系统</title>
+    <link rel="stylesheet" href="public/css/bootstrap/bootstrap.min.css">
+    <link rel="stylesheet" href="public/css/main.css">
+    <link rel="stylesheet" href="public/css/font-awesome/font-awesome.min.css">
+
+    <script src="/public/js/jquery/jquery-3.2.1.min.js"></script>
+    <script src="/public/js/popper/popper.min.js"></script>
+    <script src="/public/js/bootstrap/bootstrap.min.js"></script>
+</head>
+
+<body>
+<div class="header">
+    <h1 class="logo"><a>指标库-纵横经济指标系统</a></h1>
+    <div class="poj-name">
+        <span class="name"><%= ctx.title %></span>
+    </div>
+    <div class="header-box">
+        <div class="header-nav"></div>
+        <div class="header-user pull-right">
+            <div class="avatar btn-group">
+                <a class="dropdown-toggle" data-toggle="dropdown">
+                    <span class="pic"><img src="public/images/avatar.png"></span>
+                    <span><%= ctx.session.userSession.username %></span>
+                    <span class="caret"></span>
+                </a>
+                <div class="dropdown-menu">
+                    <a href="/logout" class="dropdown-item">退出登录</a>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<div class="main">
+    <div class="main-nav">
+        <div class="nav-top">
+            <ul class="nav nav-pills flex-column bg-nav">
+                <% for (const index in ctx.menuList) { %>
+                <% if (ctx.menuList[index].display === undefined || !ctx.menuList[index].display) { continue } %>
+                <li class="nav-item <% if (ctx.controllerName === index) { %> active <% }%>">
+                    <a href="<%= ctx.menuList[index].url %>" title="<%= ctx.menuList[index].name %>">
+                        <i class="fa <%= ctx.menuList[index].icon %>"></i> <%= ctx.menuList[index].name %>
+                    </a>
+                </li>
+                <% } %>
+            </ul>
+        </div>
+    </div>
+    <div class="main-panel">
+        <%- content %>
+    </div>
+</div>
+<%- modal %>
+<script src="/public/js/global.js"></script>
+<script type="text/javascript">
+    autoFlashHeight();
+</script>
+</body>
+
+</html>

+ 48 - 0
app/view/lib/index.ejs

@@ -0,0 +1,48 @@
+<div class="panel-content">
+    <div class="panel-title fluid">
+        <div class="title-main d-flex justify-content-between">
+            <div>
+                <div class="btn-group">
+                    <ul class="nav nav-pills m-0">
+                        <li class="nav-item">
+                            <a class="nav-link" href="#">全部</a>
+                        </li>
+                        <li class="nav-item">
+                            <a class="nav-link" href="#">待处理 <span class="badge badge-secondary">4</span></a>
+                        </li>
+                        <li class="nav-item"><a class="nav-link active" href="#">已入库 <span class="badge badge-light">4</span></a></li>
+                    </ul>
+                </div>
+            </div>
+            <div>
+                <a href="#upload" data-toggle="modal" data-target="#upload" class="btn btn-primary btn-sm pull-right">导入指标源</a>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-header p-0 col-12"></div>
+        <div class="c-body">
+            <div class="sjs-height-1">
+                <table class="table table-bordered">
+                    <tr>
+                        <th class="">指标源名称</th>
+                        <th class="">导入时间</th>
+                        <th class=""></th>
+                    </tr>
+                    <!--待处理-->
+                    <tr>
+                        <td><a href="my-Library-detail.html">XXX标段 2018-03-14 17:12:23</a></td>
+                        <td>2018-03-14 17:36:10 </td>
+                        <td><a href="my-Library-detail.html" class="btn btn-sm btn-outline-primary">填写参数</a> </td>
+                    </tr>
+                    <!--已入库-->
+                    <tr>
+                        <td><a href="my-Library-detail.html">YYY标段 2018-03-14 17:12:23</a></td>
+                        <td>2018-03-14 17:36:10 </td>
+                        <td>2018-03-14 17:36:10 入库</td>
+                    </tr>
+                </table>
+            </div>
+        </div>
+    </div>
+</div>

+ 45 - 0
app/view/login/login.ejs

@@ -0,0 +1,45 @@
+<!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>用户登录-纵横经济指标系统</title>
+    <link rel="stylesheet" href="public/css/bootstrap/bootstrap.min.css">
+    <link rel="stylesheet" href="public/css/main.css">
+    <link rel="stylesheet" href="public/css/font-awesome/font-awesome.min.css">
+</head>
+<body class="login-body">
+<div class="container">
+    <!--演示版-->
+    <form class="form-signin" method="post" action="/login">
+        <h1 class="d-flex justify-content-center mb-4">纵横经济指标系统</h1>
+        <nav class="nav nav-tabs nav-justified mb-3" role="tablist" >
+        </nav>
+        <div class="form-group <% if (errorMessage !== undefined && errorMessage !== null) { %>has-danger<% } %>">
+            <input id="inputEmail" name="account" class="form-control " placeholder="账号" required="" autofocus="">
+        </div>
+        <div class="form-group <% if (errorMessage !== undefined && errorMessage !== null) { %>has-danger<% } %>">
+            <input id="inputPassword" name="password" class="form-control " placeholder="输入密码" required="" type="password">
+        </div>
+        <% if(errorMessage !== undefined && errorMessage !== null) { %>
+        <div class="form-group">
+            <div class="alert alert-danger" role="alert">
+                <strong>登录失败</strong> <%= errorMessage %>
+            </div>
+        </div>
+        <% } %>
+        <div class="form-group">
+            <button class="btn btn-primary btn-block" type="submit">登录</button>
+            <input type="hidden" name="_csrf" value="<%= ctx.csrf %>" />
+        </div>
+    </form>
+</div>
+<!-- JS. -->
+<script src="/public/js/jquery/jquery-3.2.1.min.js"></script>
+<script src="/public/js/popper/popper.min.js"></script>
+<script src="/public/js/bootstrap/bootstrap.min.js"></script>
+<script src="/public/js/global.js"></script>
+</body>
+
+</html>

+ 39 - 0
app/view/template/index.ejs

@@ -0,0 +1,39 @@
+<div class="panel-content">
+    <div class="panel-title fluid">
+        <div class="title-main"><h2></h2></div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-header m-0">
+        </div>
+        <div class="c-body">
+            <table class="table table-bordered">
+                <tr>
+                    <th>指标编号</th>
+                    <th>项目或费用名称</th>
+                    <th colspan="2">指标单位</th>
+                    <th>合价(元)</th>
+                    <th>计算规则</th>
+                    <th>设置规则</th>
+                </tr>
+                <tr>
+                    <td>z2-e</td>
+                    <td>排水工程</td>
+                    <td></td>
+                    <td>km</td>
+                    <td></td>
+                    <td>指路基公里长度</td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td>z2-e-1</td>
+                    <td>排水工程公路公里造价</td>
+                    <td>元</td>
+                    <td>公路公里</td>
+                    <td></td>
+                    <td>合价/路线总长度</td>
+                    <td>hj(合价)/lxzcd(路线总长度)<a href="#set-count" data-toggle="modal" data-target="#set-count"><i class="fa fa-cog"></i></a></td>
+                </tr>
+            </table>
+        </div>
+    </div>
+</div>

+ 39 - 0
app/view/template/modal.ejs

@@ -0,0 +1,39 @@
+<div id="set-count" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">
+    <div class="modal-dialog">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">设置规则</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
+            </div>
+            <div class="modal-body">
+                <h5 class="mt-3">hj(合价)/lxzcd(路线总长度)</h5>
+                <div class="form-group">
+                    <label>设置计算规则</label>
+                    <input class="form-control" value="hj/lxzcd" type="text">
+                </div>
+                <div class="row">
+                    <div class="col-6">
+                        <table class="table table-bordered">
+                            <legend>全局参数</legend>
+                            <tr><th>名称</th><th>代码</th><th>描述</th></tr>
+                            <tr><td>总造价</td><td>zzj</td><td><a href=""><i class="fa fa-info"></i></a></td></tr>
+                            <tr><td>路线总长度</td><td>lxzcd</td><td><a href=""><i class="fa fa-info"></i></a></td></tr>
+                            <tr><td>输入框</td><td>input</td><td><a href=""><i class="fa fa-info"></i></a></td></tr>
+                        </table>
+                    </div>
+                    <div class="col-6">
+                        <table class="table table-bordered">
+                            <legend>本项目节参数</legend>
+                            <tr><th>名称</th><th>代码</th><th>描述</th></tr>
+                            <tr><td>合价</td><td>hj</td><td><a href=""><i class="fa fa-info"></i></a></td></tr>
+                        </table>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button class="btn btn-primary">确定</button>
+                <button class="btn btn-secondary" data-dismiss="modal" aria-hidden="true">取消</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 14 - 0
appveyor.yml

@@ -0,0 +1,14 @@
+environment:
+  matrix:
+    - nodejs_version: '8'
+
+install:
+  - ps: Install-Product node $env:nodejs_version
+  - npm i npminstall && node_modules\.bin\npminstall
+
+test_script:
+  - node --version
+  - npm --version
+  - npm run test
+
+build: off

+ 52 - 0
config/config.default.js

@@ -0,0 +1,52 @@
+'use strict';
+
+const path = require('path');
+module.exports = appInfo => {
+    const config = exports = {};
+
+    // use for cookie sign key, should change to your own and keep security
+    config.keys = appInfo.name + '_smartcost3850888';
+
+    // 数据库配置
+    config.mysql = {
+        client: {
+            // host
+            host: '127.0.0.1',
+            // 端口号
+            port: '3306',
+            // 用户名
+            user: 'root',
+            // 密码
+            password: 'admin',
+            // 数据库名
+            database: 'index_sys',
+        },
+        // 是否加载到 app 上,默认开启
+        app: true,
+        // 是否加载到 agent 上,默认关闭
+        agent: false,
+    };
+
+    // view
+    config.view = {
+        mapping: {
+            '.ejs': 'ejs',
+        },
+        root: [
+            path.join(appInfo.baseDir, 'app/view'),
+        ].join(','),
+    };
+
+    // session
+    config.session = {
+        key: 'ZHC_SESS',
+        maxAge: 3600 * 1000,
+        httpOnly: true,
+        encrypt: true,
+    };
+
+    // add your config here
+    config.middleware = ['urlParse'];
+
+    return config;
+};

+ 32 - 0
config/menu.js

@@ -0,0 +1,32 @@
+'use strict';
+
+/**
+ * 菜单配置
+ *
+ * @author Mai
+ * @date 2018/4/19
+ * @version
+ */
+
+const menu = {
+    lib: {
+        name: '指标库',
+        icon: 'fa-list-ul',
+        display: true,
+        url: '/lib',
+    },
+    template: {
+        name: '指标模板',
+        icon: 'fa-file-text-o',
+        display: true,
+        url: '/template',
+    },
+    compare: {
+        name: '指标对比',
+        icon: 'fa-search',
+        display: true,
+        url: '/compare',
+    }
+};
+
+module.exports = menu;

+ 9 - 0
config/plugin.js

@@ -0,0 +1,9 @@
+'use strict';
+
+// had enabled by egg
+// exports.static = true;
+// ejs插件
+exports.ejs = {
+    enable: true,
+    package: 'egg-view-ejs',
+};

+ 48 - 0
package.json

@@ -0,0 +1,48 @@
+{
+  "name": "index_sys",
+  "version": "1.0.0",
+  "description": "SmartCost Index System",
+  "private": true,
+  "dependencies": {
+    "egg": "^2.2.1",
+    "egg-mysql": "^3.0.0",
+    "egg-redis": "^2.0.0",
+    "egg-scripts": "^2.5.0",
+    "egg-view": "^2.1.0",
+    "egg-view-ejs": "^2.0.0"
+  },
+  "devDependencies": {
+    "autod": "^3.0.1",
+    "autod-egg": "^1.0.0",
+    "egg-bin": "^4.3.5",
+    "egg-ci": "^1.8.0",
+    "egg-mock": "^3.14.0",
+    "eslint": "^4.11.0",
+    "eslint-config-egg": "^6.0.0",
+    "webstorm-disable-index": "^1.2.0"
+  },
+  "engines": {
+    "node": ">=8.9.0"
+  },
+  "scripts": {
+    "start": "egg-scripts start --daemon --title=egg-server-index_sys",
+    "stop": "egg-scripts stop --title=egg-server-index_sys",
+    "dev": "egg-bin dev",
+    "debug": "egg-bin debug",
+    "test": "npm run lint -- --fix && npm run test-local",
+    "test-local": "egg-bin test",
+    "cov": "egg-bin cov",
+    "lint": "eslint .",
+    "ci": "npm run lint && npm run cov",
+    "autod": "autod"
+  },
+  "ci": {
+    "version": "8"
+  },
+  "repository": {
+    "type": "git",
+    "url": ""
+  },
+  "author": "Mai",
+  "license": "MIT"
+}

+ 21 - 0
test/app/controller/home.test.js

@@ -0,0 +1,21 @@
+'use strict';
+
+const { app, assert } = require('egg-mock/bootstrap');
+
+describe('test/app/controller/home.test.js', () => {
+
+  it('should assert', function* () {
+    const pkg = require('../../../package.json');
+    assert(app.config.keys.startsWith(pkg.name));
+
+    // const ctx = app.mockContext({});
+    // yield ctx.service.xx();
+  });
+
+  it('should GET /', () => {
+    return app.httpRequest()
+      .get('/')
+      .expect('hi, egg')
+      .expect(200);
+  });
+});