caipin 4 anos atrás
commit
d9c50e41b5
100 arquivos alterados com 19025 adições e 0 exclusões
  1. 30 0
      .autod.conf.js
  2. 2 0
      .eslintignore
  3. 17 0
      .eslintrc
  4. 12 0
      .gitignore
  5. 13 0
      .travis.yml
  6. 49 0
      README.zh-CN.md
  7. 56 0
      app.js
  8. 93 0
      app/base/base_controller.js
  9. 222 0
      app/base/base_service.js
  10. 981 0
      app/base/base_tree_service.js
  11. 32 0
      app/const/account_group.js
  12. 111 0
      app/const/cld_office.js
  13. 27 0
      app/const/company_const.js
  14. 32 0
      app/const/maintain.js
  15. 15 0
      app/const/message_type.js
  16. 24 0
      app/const/page_show.js
  17. 20 0
      app/const/province_const.js
  18. 24 0
      app/const/report.js
  19. 686 0
      app/const/report_defined_properties.js
  20. 244 0
      app/const/report_ext_code.js
  21. 6827 0
      app/const/report_fields.js
  22. 42 0
      app/const/sms_alitemplate.js
  23. 31 0
      app/const/standard.js
  24. 63 0
      app/const/tender.js
  25. 32 0
      app/controller/bills_template_controller.js
  26. 73 0
      app/controller/customer_controller.js
  27. 30 0
      app/controller/dashboard_controller.js
  28. 144 0
      app/controller/deal_pay_controller.js
  29. 334 0
      app/controller/enterprise_controller.js
  30. 144 0
      app/controller/group_controller.js
  31. 142 0
      app/controller/help_controller.js
  32. 47 0
      app/controller/log_controller.js
  33. 97 0
      app/controller/login_controller.js
  34. 261 0
      app/controller/manager_controller.js
  35. 163 0
      app/controller/message_controller.js
  36. 212 0
      app/controller/permission_controller.js
  37. 654 0
      app/controller/project_account_controller.js
  38. 597 0
      app/controller/project_controller.js
  39. 570 0
      app/controller/report_controller.js
  40. 186 0
      app/controller/report_project_controller.js
  41. 56 0
      app/controller/sql_execute_controller.js
  42. 464 0
      app/controller/standard_lib_controller.js
  43. 34 0
      app/controller/std_gcl_controller.js
  44. 32 0
      app/controller/std_xmj_controller.js
  45. 27 0
      app/controller/ueditor_controller.js
  46. 222 0
      app/controller/valuation_controller.js
  47. 128 0
      app/controller/version_controller.js
  48. 139 0
      app/controller/white_list_controller.js
  49. 383 0
      app/extend/helper.js
  50. 60 0
      app/lib/base_calc.js
  51. 81 0
      app/lib/captcha.js
  52. 106 0
      app/lib/cld.js
  53. 135 0
      app/lib/js_validator.js
  54. 1416 0
      app/lib/rpt_data_analysis.js
  55. 149 0
      app/lib/sms.js
  56. 337 0
      app/lib/sql_builder.js
  57. 94 0
      app/lib/sso.js
  58. 33 0
      app/middleware/auto_logger.js
  59. 18 0
      app/middleware/datetime_fill.js
  60. 25 0
      app/middleware/error_handler.js
  61. 74 0
      app/middleware/permission_filter.js
  62. 37 0
      app/middleware/session_auth.js
  63. 25 0
      app/middleware/sort_filter.js
  64. 27 0
      app/middleware/url_parse.js
  65. 29 0
      app/middleware/white_list_filter.js
  66. 9 0
      app/public/css/bootstrap/bootstrap-datetimepicker.min.css
  67. 7 0
      app/public/css/bootstrap/bootstrap.min.css
  68. 347 0
      app/public/css/codemirror/codemirror.css
  69. 41 0
      app/public/css/codemirror/dracula.css
  70. 54 0
      app/public/css/codemirror/material.css
  71. 41 0
      app/public/css/codemirror/theme/3024-day.css
  72. 39 0
      app/public/css/codemirror/theme/3024-night.css
  73. 32 0
      app/public/css/codemirror/theme/abcdef.css
  74. 5 0
      app/public/css/codemirror/theme/ambiance-mobile.css
  75. 74 0
      app/public/css/codemirror/theme/ambiance.css
  76. 38 0
      app/public/css/codemirror/theme/base16-dark.css
  77. 38 0
      app/public/css/codemirror/theme/base16-light.css
  78. 34 0
      app/public/css/codemirror/theme/bespin.css
  79. 32 0
      app/public/css/codemirror/theme/blackboard.css
  80. 25 0
      app/public/css/codemirror/theme/cobalt.css
  81. 33 0
      app/public/css/codemirror/theme/colorforth.css
  82. 40 0
      app/public/css/codemirror/theme/dracula.css
  83. 35 0
      app/public/css/codemirror/theme/duotone-dark.css
  84. 36 0
      app/public/css/codemirror/theme/duotone-light.css
  85. 23 0
      app/public/css/codemirror/theme/eclipse.css
  86. 13 0
      app/public/css/codemirror/theme/elegant.css
  87. 34 0
      app/public/css/codemirror/theme/erlang-dark.css
  88. 34 0
      app/public/css/codemirror/theme/hopscotch.css
  89. 43 0
      app/public/css/codemirror/theme/icecoder.css
  90. 34 0
      app/public/css/codemirror/theme/isotope.css
  91. 47 0
      app/public/css/codemirror/theme/lesser-dark.css
  92. 95 0
      app/public/css/codemirror/theme/liquibyte.css
  93. 53 0
      app/public/css/codemirror/theme/material.css
  94. 37 0
      app/public/css/codemirror/theme/mbo.css
  95. 46 0
      app/public/css/codemirror/theme/mdn-like.css
  96. 45 0
      app/public/css/codemirror/theme/midnight.css
  97. 36 0
      app/public/css/codemirror/theme/monokai.css
  98. 12 0
      app/public/css/codemirror/theme/neat.css
  99. 43 0
      app/public/css/codemirror/theme/neo.css
  100. 0 0
      app/public/css/codemirror/theme/night.css

+ 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',
+  ],
+};
+

+ 2 - 0
.eslintignore

@@ -0,0 +1,2 @@
+coverage
+app/public/*

+ 17 - 0
.eslintrc

@@ -0,0 +1,17 @@
+{
+    "extends": "eslint-config-egg",
+    "rules": {
+        // 空格4个
+        "indent": ["error", 4, {"SwitchCase": 1}],
+        // 指定数组的元素之间要以空格隔开(,后面), never参数:[ 之前和 ] 之后不能带空格,always参数:[ 之前和 ] 之后必须带空格
+        "array-bracket-spacing": [2, "never"],
+        // if while function 后面的{必须与if在同一行,java风格。
+        "brace-style": [2, "1tbs", {"allowSingleLine": true}],
+        // 未用数据不提示
+        "no-unused-vars": 0,
+        // 链式调用是否强制换行
+        "newline-per-chained-call": 0,
+        // 换行风格
+        "linebreak-style": [0, "windows"]
+    }
+}

+ 12 - 0
.gitignore

@@ -0,0 +1,12 @@
+logs/
+npm-debug.log
+node_modules/
+coverage/
+.idea/
+run/
+app/public/uploads
+app/reports/util/pdf_base_files
+app/reports/util/excel_base_files
+.DS_Store
+*.swp
+package-lock.json

+ 13 - 0
.travis.yml

@@ -0,0 +1,13 @@
+
+language: node_js
+node_js:
+  - '6'
+  - '8'
+before_install:
+  - npm i npminstall -g
+install:
+  - npminstall
+script:
+  - npm run ci
+after_script:
+  - npminstall codecov && codecov

+ 49 - 0
README.zh-CN.md

@@ -0,0 +1,49 @@
+# calculation_paying
+
+计量支付Web
+- 本程序使用mysql数据库作为数据存储
+- 本程序使用redis作为数据缓存
+
+## 快速入门
+
+<!-- 在此次添加使用文档 -->
+
+如需进一步了解,参见 [egg 文档][egg]。
+
+### 本地开发
+- 首先导入sql
+- 开启redis
+- 执行以下命令
+```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)。
+
+```bash
+$ npm run test-local  本地运行单元测试
+$ npm test            线上运行单元测试
+```
+
+> 注意: 运行单元测试必须停掉运行的网站,命令才能执行成功
+
+### 内置指令
+
+- 使用 `npm run lint` 来做代码风格检查。
+- 使用 `npm test` 来执行单元测试。
+- 使用 `npm run autod` 来自动检测依赖更新,详细参见 [autod](https://www.npmjs.com/package/autod) 。
+
+
+[egg]: https://eggjs.org

+ 56 - 0
app.js

@@ -0,0 +1,56 @@
+'use strict';
+
+/**
+ * 自定义启动文件
+ *
+ * @author CaiAoLin
+ * @date 2017/8/29
+ * @version
+ */
+
+const BaseService = require('./app/base/base_service');
+const BaseController = require('./app/base/base_controller');
+module.exports = app => {
+    // 数据模型基类
+    app.BaseService = BaseService;
+
+    // 控制器基类
+    app.BaseController = BaseController;
+
+    // 自定义手机校验规则
+    app.validator.addRule('mobile', (rule, value) => {
+        try {
+            const regPhone = /^1[3456789]\d{9}$/;
+            const allowEmpty = rule.allowEmpty === true;
+            if (allowEmpty && value === '') {
+                return true;
+            }
+            if (!(value.length === 11 && regPhone.test(value))) {
+                throw 'please enter the correct phone number';
+            }
+        } catch (error) {
+            return error;
+        }
+    });
+
+    // 自定义手机IP规则
+    app.validator.addRule('ip', (rule, value) => {
+        try {
+            const allowEmpty = rule.allowEmpty === true;
+            if (allowEmpty && value === '') {
+                return true;
+            }
+            const regIP = /^(\d{2,})\.(\d+)\.(\d+)\.(\d+)$/;
+            if (!regIP.test(value)) {
+                throw 'please enter the correct ip address';
+            }
+        } catch (error) {
+            return error;
+        }
+    });
+
+    app.min = app.config.min ? 'min.' : '';
+
+    app.config.bodyParser.jsonLimit = '5mb';
+    app.config.bodyParser.formLimit = '6mb';
+};

+ 93 - 0
app/base/base_controller.js

@@ -0,0 +1,93 @@
+'use strict';
+
+/**
+ * 控制器基类
+ *
+ * @author CaiAoLin
+ * @date 2017/10/11
+ * @version
+ */
+const moment = require('moment');
+const messageType = require('../const/message_type');
+const Controller = require('egg').Controller;
+class BaseController extends Controller {
+
+    /**
+     * 构造函数
+     *
+     * @param {Object} ctx - egg全局context
+     * @return {void}
+     */
+    constructor(ctx) {
+        super(ctx);
+        this.messageType = messageType;
+        this.jsValidator = this.app.jsValidator;
+    }
+
+    /**
+     * 渲染layout
+     *
+     * @param {String} view - 渲染的view
+     * @param {Object} data - 渲染的数据
+     * @param {String} modal - 渲染的modal
+     * @return {void}
+     */
+    async layout(view, data = {}, modal = '') {
+        data.moment = moment;
+        // 获取消息提示
+        const message = this.ctx.session.message;
+        // 取出后删除
+        this.ctx.session.message = null;
+
+        // 获取菜单栏数据
+        const menuData = await this.ctx.service.permission.getMenu();
+        const sortData = await this.ctx.service.permission.sortMenuByPermission(menuData);
+
+        const viewString = await this.ctx.renderView(view, data);
+        const modalString = modal === '' ? '' : await this.ctx.renderView(modal, data);
+        const renderData = {
+            content: viewString,
+            message: JSON.stringify(message),
+            modal: modalString,
+            menuData: sortData,
+            dropDownMenu: data.dropDownMenu === undefined ? [] : data.dropDownMenu,
+            breadCrumb: data.breadCrumb === undefined ? '' : data.breadCrumb,
+            cbClass: data.cbClass,
+        };
+        await this.ctx.render('layout/layout.ejs', renderData);
+    }
+
+    /**
+     * 设置提示
+     *
+     * @param {String} message - 提示信息
+     * @param {String} type - 提示类型
+     * @return {void}
+     */
+    setMessage(message, type) {
+        let icon = '';
+        switch (type) {
+            case messageType.SUCCESS:
+                icon = 'check';
+                break;
+            case messageType.ERROR:
+                icon = 'exclamation-circle';
+                break;
+            case messageType.INFO:
+                icon = 'info-circle';
+                break;
+            case messageType.WARNING:
+                icon = 'warning';
+                break;
+            default:
+                break;
+        }
+        this.ctx.session.message = {
+            type,
+            icon,
+            message,
+        };
+    }
+}
+
+module.exports = BaseController;

+ 222 - 0
app/base/base_service.js

@@ -0,0 +1,222 @@
+'use strict';
+
+/**
+ * 业务逻辑基类
+ *
+ * @author CaiAoLin
+ * @date 2017/10/11
+ * @version
+ */
+
+const Service = require('egg').Service;
+// sql拼装器
+const SqlBuilder = require('../lib/sql_builder');
+class BaseService extends Service {
+    /**
+     * 构造函数
+     *
+     * @param {Object} ctx - egg全局context
+     * @return {void}
+     */
+    constructor(ctx) {
+        super(ctx);
+        this.db = this.app.mysql;
+        this.transaction = null;
+        this.cache = this.app.redis;
+        this.sqlBuilder = null;
+    }
+
+    /**
+     * 设置表名
+     *
+     * @param {String} table - 表名
+     * @return {void}
+     */
+    set tableName(table) {
+        this._table = this.app.config.tablePrefix + table;
+    }
+
+    /**
+     * 获取表名
+     *
+     * @return {String} - 返回表名
+     */
+    get tableName() {
+        return this._table;
+    }
+
+    /**
+     * 初始化sqlBuilder
+     *
+     * @return {void}
+     */
+    initSqlBuilder() {
+        if (this.sqlBuilder === null) {
+            this.sqlBuilder = new SqlBuilder();
+        }
+    }
+
+    /**
+     * 根据id查找数据
+     *
+     * @param {Number} id - 数据库中的id
+     * @return {Object} - 返回单条数据
+     */
+    async getDataById(id) {
+        return await this.db.get(this.tableName, { id });
+    }
+
+    /**
+     * 根据条件查找单条数据
+     *
+     * @param {Object} condition - 筛选条件
+     * @return {Object} - 返回单条数据
+     */
+    async getDataByCondition(condition) {
+        return await this.db.get(this.tableName, condition);
+    }
+
+    /**
+     * 根据条件查找单条数据
+     *
+     * @param {Object} condition - 筛选条件
+     * @return {Array} - 返回数据
+     */
+    async getAllDataByCondition(condition) {
+        return await this.db.select(this.tableName, condition);
+    }
+
+    /**
+     * 根据id删除数据
+     *
+     * @param {Number} id - 数据库中的id
+     * @return {boolean} - 返回是否删除成功
+     */
+    async deleteById(id) {
+        const result = await this.db.delete(this.tableName, { id });
+        return result.affectedRows > 0;
+    }
+
+    /**
+     * 根据条件删除数据
+     *
+     * @param {Object} condition - 删除条件
+     * @return {boolean} - 返回是否删除成功
+     */
+    async deleteByCondition(condition) {
+        if (Object.keys(condition).length <= 0) {
+            return false;
+        }
+        const handler = this.transaction === null ? this.transaction : this.db;
+        const result = await handler.delete(this.tableName, condition);
+        return result.affectedRows > 0;
+    }
+
+    /**
+     * 获取总数
+     *
+     * @param {Object} option - 过滤条件(参考select方法中的condition)
+     * @return {Number} - 返回查找的总数
+     */
+    async count(option = {}) {
+        const result = await this.db.count(this.tableName, option);
+        return result;
+    }
+
+    /**
+     * 更新数据
+     *
+     * @param {Object} data - 需要更新的数据
+     * @param {Object} condition - 更新的条件筛选
+     * @return {Boolean} - 返回更新结果
+     */
+    async update(data, condition) {
+        if (Object.keys(data).length <= 0 || Object.keys(condition).length <= 0) {
+            return false;
+        }
+        const sqlParam = [];
+        const param = [];
+        for (const key in data) {
+            param.push(key + ' = ?');
+            sqlParam.push(data[key]);
+        }
+        const paramString = param.join(',');
+
+        const where = [];
+        for (const key in condition) {
+            where.push(key + ' = ?');
+            sqlParam.push(condition[key]);
+        }
+        const whereString = where.join(' AND ');
+
+        const sql = 'UPDATE ' + this.tableName + ' SET ' + paramString + ' WHERE ' + whereString;
+        const result = await this.db.query(sql, sqlParam);
+        return result.affectedRows > 0;
+    }
+
+    /**
+     * 获取分页数据(SqlBuilder版本)
+     *
+     * @return {Array} - 返回分页数据
+     */
+    async getListWithBuilder() {
+
+        // 由于egg-mysql不提供like、不等于操作,所以需要自己组sql
+        if (this.sqlBuilder === null) {
+            return [];
+        }
+
+        // 分页相关
+        this.sqlBuilder.limit = this.app.config.pageSize;
+        this.sqlBuilder.offset = this.app.config.pageSize * (this.ctx.page - 1);
+
+        // 数据筛选
+        if (this.ctx.sort !== undefined && this.ctx.sort.length > 0) {
+            this.sqlBuilder.orderBy = [this.ctx.sort];
+        }
+
+        const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+        const list = await this.db.query(sql, sqlParam);
+
+        return list;
+    }
+
+    /**
+     * 获取数据条数(SqlBuilder版本)
+     *
+     * @return {Array} - 返回分页数据
+     */
+    async getCountWithBuilder() {
+        // 由于egg-mysql不提供like、不等于操作,所以需要自己组sql
+        if (this.sqlBuilder === null) {
+            return [];
+        }
+        const [sql, sqlParam] = this.sqlBuilder.buildCount(this.tableName);
+        const result = await this.db.query(sql, sqlParam);
+        return result ? result.length : 0;
+    }
+
+    /**
+     * 获取分页数据
+     *
+     * @param {Object} condition - 搜索条件
+     * @return {Array} - 返回分页数据
+     */
+    async getList(condition) {
+        const page = this.ctx.page;
+
+        // 分页设置
+        condition.limit = condition.limit === undefined ? this.app.config.pageSize : condition.limit;
+        condition.offset = condition.offset === undefined ? this.app.config.pageSize * (page - 1) : condition.offset;
+
+        if (this.ctx.sort !== undefined && this.ctx.sort.length > 0) {
+            condition.orders = [this.ctx.sort];
+        }
+        const list = await this.db.select(this.tableName, condition);
+
+        return list;
+    }
+
+
+}
+module.exports = BaseService;

+ 981 - 0
app/base/base_tree_service.js

@@ -0,0 +1,981 @@
+'use strict';
+
+/**
+ * 提供基础操作:增删改查
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const Service = require('./base_service');
+// sql拼装器
+const SqlBuilder = require('../lib/sql_builder');
+const rootId = -1;
+
+class TreeService extends Service {
+    /**
+     * 构造函数
+     *
+     * @param {Object} ctx - egg全局context
+     * @param {Object} setting - 树结构设置
+     * e.g.: {
+     *     mid: 'tender_id', 分块id(例如tender_id, rid, list_id)
+     *     kid: 'ledger_id', 分块内的树结构id
+     *     pid: 'ledger_pid', 父节点id
+     *     order: 'order',
+     *     level: 'level',
+     *     fullPath: 'full_path',
+     *     isLeaf: 'is_leaf',
+     *     keyPre: 'revise_bills_maxLid:'
+     * }
+     * @return {void}
+     */
+    constructor(ctx, setting) {
+        super(ctx);
+        this.tableName = setting.tableName;
+        this.setting = setting;
+        // 以下字段仅可通过树结构操作改变,不可直接通过update方式从接口提交,发现时过滤
+        this.readOnlyFields = ['id'];
+        this.readOnlyFields.push(this.setting.mid);
+        this.readOnlyFields.push(this.setting.kid);
+        this.readOnlyFields.push(this.setting.pid);
+        this.readOnlyFields.push(this.setting.order);
+        this.readOnlyFields.push(this.setting.level);
+        this.readOnlyFields.push(this.setting.fullPath);
+        this.readOnlyFields.push(this.setting.isLeaf);
+    }
+
+    getCondition (condition) {
+        const result = {};
+        if (condition.mid) result[this.setting.mid] = condition.mid;
+        if (condition.kid) result[this.setting.kid] = condition.kid;
+        if (condition.pid) result[this.setting.pid] = condition.pid;
+        if (condition.order) result[this.setting.order] = condition.order;
+        if (condition.level) result[this.setting.level] = condition.level;
+        if (condition.fullPath) result[this.setting.fullPath] = condition.fullPath;
+        if (condition.isLeaf) result[this.setting.isLeaf] = condition.isLeaf;
+        return result;
+    }
+
+    /**
+     * 获取 修订 清单数据
+     * @param {Number} mid - masterId
+     * @returns {Promise<void>}
+     */
+    async getData(mid, level) {
+        if (level) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('list_id', {
+                operate: '=',
+                value: mid,
+            });
+            this.sqlBuilder.setAndWhere('level', {
+                operate: '<=',
+                value: level,
+            });
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+            return await this.db.query(sql, sqlParam);
+        } else {
+            return await this.db.select(this.tableName, {
+                where: this.getCondition({mid: mid})
+            });
+        }
+    }
+
+    /**
+     * 获取节点数据
+     * @param {Number} mid - masterId
+     * @param {Number} id
+     * @returns {Promise<void>}
+     */
+    async getDataByKid (mid, kid) {
+        return await this.db.get(this.tableName, this.getCondition({
+            mid: mid, kid: kid,
+        }));
+    }
+
+    async getDataByKidAndCount(mid, kid, count) {
+        if ((mid <= 0) || (kid <= 0)) return [];
+        const select = await this.getDataByKid(mid, kid);
+        if (!select) throw '数据错误';
+
+        if (count > 1) {
+            const selects = await this.getNextsData(mid, select.pid, select.order - 1);
+            if (selects.length < count) throw '数据错误';
+            return selects.slice(0, count);
+        } else {
+            return [select];
+        }
+    }
+
+    /**
+     * 获取节点数据
+     * @param id
+     * @returns {Promise<Array>}
+     */
+    async getDataById(id) {
+        if (id instanceof Array) {
+            return await this.db.select(this.tableName, { where: {id: id} });
+        } else {
+            return await this.db.get(this.tableName, { id: id });
+        }
+    }
+
+    /**
+     * 获取最末的子节点
+     * @param {Number} mid - masterId
+     * @param {Number} pid - 父节点id
+     * @return {Object}
+     */
+    async getLastChildData(mid, pid) {
+        this.initSqlBuilder();
+        this.sqlBuilder.setAndWhere(this.setting.mid, {
+            value: mid,
+            operate: '=',
+        });
+        this.sqlBuilder.setAndWhere(this.setting.pid, {
+            value: pid,
+            operate: '=',
+        });
+        this.sqlBuilder.orderBy = [['order', 'DESC']];
+        const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+        const resultData = await this.db.queryOne(sql, sqlParam);
+
+        return resultData;
+    }
+
+    /**
+     * 根据 父节点id 和 节点排序order 获取数据
+     *
+     * @param {Number} mid - master id
+     * @param {Number} pid - 父节点id
+     * @param {Number|Array} order - 排序
+     * @return {Object|Array} - 查询结果
+     */
+    async getDataByParentAndOrder(mid, pid, order) {
+        const result = await this.db.select(this.tableName, {
+            where: this.getCondition({mid: mid, pid: pid, order: order})
+        });
+        return order instanceof Array ? result : (result.length > 0 ? result[0] : null);
+    }
+
+    /**
+     * 根据 父节点ID 和 节点排序order 获取全部后节点数据
+     * @param {Number} mid - master id
+     * @param {Number} pid - 父节点id
+     * @param {Number} order - 排序
+     * @return {Array}
+     */
+    async getNextsData(mid, pid, order) {
+        this.initSqlBuilder();
+        this.sqlBuilder.setAndWhere(this.setting.mid, {
+            value: mid,
+            operate: '=',
+        });
+        this.sqlBuilder.setAndWhere(this.setting.pid, {
+            value: pid,
+            operate: '=',
+        });
+        this.sqlBuilder.setAndWhere(this.setting.order, {
+            value: order,
+            operate: '>',
+        });
+
+        const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+        const data = await this.db.query(sql, sqlParam);
+
+        return data;
+    }
+
+    /**
+     * 根据full_path获取数据 full_path Like ‘1.2.3%’(传参full_path = '1.2.3%')
+     * @param {Number} tenderId - 标段id
+     * @param {String} full_path - 路径
+     * @return {Promise<void>}
+     */
+    async getDataByFullPath(mid, full_path) {
+        this.initSqlBuilder();
+        this.sqlBuilder.setAndWhere(this.setting.mid, {
+            value: mid,
+            operate: '=',
+        });
+        this.sqlBuilder.setAndWhere(this.setting.fullPath, {
+            value: this.db.escape(full_path),
+            operate: 'Like',
+        });
+        const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+        const resultData = await this.db.query(sql, sqlParam);
+        return resultData;
+    }
+    /**
+     * 根据full_path检索自己及所有父项
+     * @param {Number} tenderId - 标段id
+     * @param {Array|String} fullPath - 节点完整路径
+     * @return {Promise<*>}
+     * @private
+     */
+    async getFullLevelDataByFullPath(mid, fullPath) {
+        const explodePath = this.ctx.helper.explodePath(fullPath);
+
+        this.initSqlBuilder();
+        this.sqlBuilder.setAndWhere(this.setting.mid, {
+            value: mid,
+            operate: '=',
+        });
+        this.sqlBuilder.setAndWhere(this.setting.fullPath, {
+            value: explodePath,
+            operate: 'in',
+        });
+
+        const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+        const data = await this.db.query(sql, sqlParam);
+
+        return data;
+    }
+
+    /**
+     * 获取最大节点id
+     *
+     * @param {Number} mid - master id
+     * @return {Number}
+     * @private
+     */
+    async _getMaxLid(mid) {
+        const cacheKey = this.setting.keyPre + mid;
+        let maxId = parseInt(await this.cache.get(cacheKey));
+        if (!maxId) {
+            const sql = 'SELECT Max(??) As max_id FROM ?? Where ' + this.setting.mid + ' = ?';
+            const sqlParam = [this.setting.kid, this.tableName, mid];
+            const queryResult = await this.db.queryOne(sql, sqlParam);
+            maxId = queryResult.max_id || 0;
+            this.cache.set(cacheKey, maxId, 'EX', this.ctx.app.config.cacheTime);
+        }
+        return maxId;
+    }
+
+    /**
+     * 缓存最大节点id
+     *
+     * @param {Number} mid - master id
+     * @param {Number} maxId - 当前最大节点id
+     * @returns {Promise<void>}
+     * @private
+     */
+    _cacheMaxLid(mid, maxId) {
+        this.cache.set(this.setting.keyPre + mid , maxId, 'EX', this.ctx.app.config.cacheTime);
+    }
+
+    /**
+     * 更新order
+     * @param {Number} mid - master id
+     * @param {Number} pid - 父节点id
+     * @param {Number} order - 开始更新的order
+     * @param {Number} incre - 更新的增量
+     * @returns {Promise<*>}
+     * @private
+     */
+    async _updateChildrenOrder(mid, pid, order, incre = 1) {
+        this.initSqlBuilder();
+        this.sqlBuilder.setAndWhere(this.setting.mid, {
+            value: mid,
+            operate: '=',
+        });
+        this.sqlBuilder.setAndWhere(this.setting.order, {
+            value: order,
+            operate: '>=',
+        });
+        this.sqlBuilder.setAndWhere(this.setting.pid, {
+            value: pid,
+            operate: '=',
+        });
+        this.sqlBuilder.setUpdateData(this.setting.order, {
+            value: Math.abs(incre),
+            selfOperate: incre > 0 ? '+' : '-',
+        });
+        const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+        const data = await this.transaction.query(sql, sqlParam);
+
+        return data;
+    }
+
+    /**
+     * 新增数据(新增为selectData的后项,该方法不可单独使用)
+     *
+     * @param {Number} mid - 台账id
+     * @param {Object} select - 选中节点的数据
+     * @param {Object} data - 新增节点的初始数据
+     * @return {Object} - 新增结果
+     * @private
+     */
+    async _addNodeData(mid, select, data) {
+        if (!data) {
+            data = {};
+        }
+        const maxId = await this._getMaxLid(mid);
+
+        if (this.setting.uuid) data.id = this.uuid.v4();
+        data[this.setting.kid] = maxId + 1;
+        data[this.setting.pid] = select ? select[this.setting.pid] : rootId;
+        data[this.setting.mid] = mid;
+        data[this.setting.level] = select ? select[this.setting.level] : 1;
+        data[this.setting.order] = select ? select[this.setting.order] + 1 : 1;
+        data[this.setting.fullPath] = data[this.setting.level] > 1 ? select[this.setting.fullPath].replace('-' + select[this.setting.kid], '-' + data[this.setting.kid]) : data[this.setting.kid] + '';
+        data[this.setting.isLeaf] = true;
+        const result = await this.transaction.insert(this.tableName, data);
+
+        this._cacheMaxLid(mid, maxId + 1);
+
+        return result;
+    }
+
+    /**
+     * 新增节点
+     * @param {Number} mid - 台账id
+     * @param {Number} kid - 清单节点id
+     * @returns {Promise<void>}
+     */
+    async addNode(mid, kid, data) {
+        if (!mid) return null;
+        const select = kid ? await this.getDataByKid(mid, kid) : null;
+        if (kid && !select) throw '新增节点数据错误';
+
+        this.transaction = await this.db.beginTransaction();
+        try {
+            if (select) await this._updateChildrenOrder(mid, select[this.setting.pid], select[this.setting.order]+1);
+            const newNode = await this._addNodeData(mid, select, data);
+            if (newNode.affectedRows !== 1) throw '新增节点数据额错误';
+            await this.transaction.commit();
+            this.transaction = null;
+        } catch (err) {
+            await this.transaction.rollback();
+            this.transaction = null;
+            throw err;
+        }
+
+        if (select) {
+            const createData = await this.getDataByParentAndOrder(mid, select[this.setting.pid], [select[this.setting.order] + 1]);
+            const updateData = await this.getNextsData(mid, select[this.setting.pid], select[this.setting.order] + 1);
+            return {create: createData, update: updateData};
+        } else {
+            const createData = await this.getDataByParentAndOrder(mid, -1, [1]);
+            return {create: createData};
+        }
+    }
+
+    /**
+     * 删除节点
+     * @param {Number} tenderId - 标段id
+     * @param {Object} deleteData - 删除节点数据
+     * @return {Promise<*>}
+     * @private
+     */
+    async _deleteNodeData(mid, deleteNode) {
+        this.initSqlBuilder();
+        this.sqlBuilder.setAndWhere(this.setting.mid, {
+            value: mid,
+            operate: '=',
+        });
+        this.sqlBuilder.setAndWhere(this.setting.fullPath, {
+            value: this.db.escape(deleteNode[this.setting.fullPath] + '%'),
+            operate: 'Like',
+        });
+        const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'delete');
+        const result = await this.transaction.query(sql, sqlParam);
+
+        return result;
+    }
+
+    /**
+     *  tenderId标段中, 删除选中节点及其子节点
+     *
+     * @param {Number} tenderId - 标段id
+     * @param {Number} selectId - 选中节点id
+     * @return {Array} - 被删除的数据
+     */
+    async deleteNode(mid, kid) {
+        if ((mid <= 0) || (kid <= 0)) return [];
+        const select = await this.getDataByKid(mid, kid);
+        if (!select) throw '删除节点数据错误';
+        const parent = await this.getDataByKid(mid, select[this.setting.pid]);
+        // 获取将要被删除的数据
+        const deleteData = await this.getDataByFullPath(mid, select[this.setting.fullPath] + '%');
+        if (deleteData.length === 0) throw '删除节点数据错误';
+
+        this.transaction = await this.db.beginTransaction();
+        try {
+            // 删除
+            const operate = await this._deleteNodeData(mid, select);
+            // 选中节点--父节点 只有一个子节点时,应升级is_leaf
+            if (parent) {
+                const count = await this.db.count(this.tableName, this.getCondition({mid: mid, pid: select[this.setting.pid]}));
+                if (count === 1) {
+                    const updateParent = {id: parent.id };
+                    updateParent[this.setting.isLeaf] = true;
+                    await this.transaction.update(this.tableName, updateParent);
+                }
+            }
+            // 选中节点--全部后节点 order--
+            await this._updateChildrenOrder(mid, select[this.setting.pid], select[this.setting.order] + 1, -1);
+            // 删除部位明细
+            //await this.ctx.service.pos.deletePosData(this.transaction, tenderId, this._.map(deleteData, 'id'));
+            await this.transaction.commit();
+            this.transaction = null;
+        } catch (err) {
+            await this.transaction.rollback();
+            this.transaction = null;
+            throw err;
+        }
+        // 查询结果
+        const updateData = await this.getNextsData(mid, select[this.setting.pid], select[this.setting.order] - 1);
+        if (parent) {
+            const updateData1 = await this.getDataByKid(mid, select[this.setting.pid]);
+            if (updateData1[this.setting.isLeaf]) {
+                updateData.push(updateData1);
+            }
+        }
+        return { delete: deleteData, update: updateData };
+    }
+
+    async deleteNodes(mid, kid, count) {
+        if ((mid <= 0) || (kid <= 0) || (count <= 0)) return [];
+        const selects = await this.getDataByKidAndCount(mid, kid, count);
+        const first = selects[0];
+        const parent = await this.getDataByKid(mid, first[this.setting.pid]);
+        const childCount = parent ? this.count(this.getCondition({mid: mid, pid: parent[this.setting.id]})) : -1;
+        let deleteData = [];
+        for (const s of selects) {
+            deleteData = deleteData.concat(await this.getDataByFullPath(mid, s[this.setting.fullPath] + '%'));
+        }
+
+        this.transaction = await this.db.beginTransaction();
+        try {
+            // 删除
+            for (const s of selects) {
+                const operate = await this._deleteNodeData(mid, s);
+            }
+            // 选中节点--父节点 只有一个子节点时,应升级is_leaf
+            if (parent && childCount === count) {
+                const updateParent = {id: parent.id };
+                updateParent[this.setting.isLeaf] = true;
+                await this.transaction.update(this.tableName, updateParent);
+            }
+            // 选中节点--全部后节点 order--
+            await this._updateChildrenOrder(mid, first[this.setting.pid], first[this.setting.order] + count, -count);
+            await this.transaction.commit();
+            this.transaction = null;
+
+            const updateData = await this.getNextsData(mid, first[this.setting.pid], first[this.setting.order] - 1);
+            if (parent && childCount === count) {
+                const updateData1 = await this.getDataByKid(mid, parent[this.setting.id]);
+                updateData.push(updateData1);
+            }
+            return { delete: deleteData, update: updateData };
+        } catch (err) {
+            if (this.transaction) {
+                await this.transaction.rollback();
+                this.transaction = null;
+            }
+            throw err;
+        }
+    }
+
+    async delete(mid, kid, count) {
+        if (count && count > 1) {
+            return await this.deleteNodes(mid, kid, count);
+        } else {
+            return await this.deleteNode(mid, kid);
+        }
+    }
+
+    /**
+     * 上移节点
+     *
+     * @param {Number} mid - master id
+     * @param {Number} kid - 选中节点id
+     * @return {Array} - 发生改变的数据
+     */
+    async upMoveNode(mid, kid, count) {
+        if (!count) count = 1;
+        if (!mid || (mid <= 0) || !kid || (kid <= 0)) return null;
+        const selects = await this.getDataByKidAndCount(mid, kid, count);
+        if (selects.length !== count) throw '上移节点数据错误';
+        const first = selects[0];
+        const pre = await this.getDataByParentAndOrder(mid, first[this.setting.pid], first[this.setting.order] - 1);
+        if (!pre) throw '节点不可上移';
+
+        const order = [];
+        this.transaction = await this.db.beginTransaction();
+        try {
+            for (const s of selects) {
+                const sData = await this.transaction.update(this.tableName, { id: s.id, order: s[this.setting.order] - 1 });
+                order.push(s[this.setting.order] - 1);
+            }
+            const pData = await this.transaction.update(this.tableName, { id: pre.id, order: pre[this.setting.order] + count });
+            order.push(pre[this.setting.order] + count);
+            await this.transaction.commit();
+            this.transaction = null;
+        } catch (err) {
+            await this.transaction.rollback();
+            this.transaction = null;
+            throw err;
+        }
+
+        const resultData = await this.getDataByParentAndOrder(mid, first[this.setting.pid], order);
+        return { update: resultData };
+    }
+
+    /**
+     * 下移节点
+     *
+     * @param {Number} mid - master id
+     * @param {Number} kid - 选中节点id
+     * @return {Array} - 发生改变的数据
+     */
+    async downMoveNode(mid, kid, count) {
+        if (!count) count = 1;
+        if (!mid || (mid <= 0) || !kid || (kid <= 0)) return null;
+        const selects = await this.getDataByKidAndCount(mid, kid, count);
+        console.log(selects);
+        if (selects.length !== count) {
+            throw '下移节点数据错误';
+        }
+        const last = selects[count - 1];
+        const next = await this.getDataByParentAndOrder(mid, last[this.setting.pid], last[this.setting.order] + 1);
+        if (!next) {
+            throw '节点不可下移';
+        }
+
+        const order = [];
+        this.transaction = await this.db.beginTransaction();
+        try {
+            for (const s of selects) {
+                const sData = await this.transaction.update(this.tableName, { id: s.id, order: s[this.setting.order] + 1 });
+                order.push(s[this.setting.order] + 1);
+            }
+            const nData = await this.transaction.update(this.tableName, { id: next.id, order: next[this.setting.order] - count });
+            order.push(next[this.setting.order] - count);
+            await this.transaction.commit();
+            this.transaction = null;
+        } catch (err) {
+            await this.transaction.rollback();
+            this.transaction = null;
+            throw err;
+        }
+
+        const resultData = await this.getDataByParentAndOrder(mid, last[this.setting.pid], order);
+        return { update: resultData };
+    }
+
+    /**
+     * 升级selectData, 同步修改所有子节点
+     * @param {Object} selectData - 升级操作,选中节点
+     * @return {Object}
+     * @private
+     */
+    async _syncUplevelChildren(select) {
+        this.initSqlBuilder();
+        this.sqlBuilder.setAndWhere(this.setting.mid, {
+            value: select[this.setting.mid],
+            operate: '=',
+        });
+        this.sqlBuilder.setAndWhere(this.setting.fullPath, {
+            value: this.db.escape(select[this.setting.fullPath] + '-%'),
+            operate: 'like',
+        });
+        this.sqlBuilder.setUpdateData(this.setting.level, {
+            value: 1,
+            selfOperate: '-',
+        });
+        this.sqlBuilder.setUpdateData(this.setting.fullPath, {
+            value: [this.setting.fullPath, this.db.escape(select[this.setting.pid] + '-'), this.db.escape('')],
+            literal: 'Replace',
+        });
+        const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+        const data = await this.transaction.query(sql, sqlParam);
+
+        return data;
+    }
+
+    /**
+     * 选中节点的后兄弟节点,全部变为当前节点的子节点
+     * @param {Object} selectData - 选中节点
+     * @return {Object}
+     * @private
+     */
+    async _syncUpLevelNexts(select) {
+        // 查询selectData的lastChild
+        const lastChild = await this.getLastChildData(select[this.setting.mid], select[this.setting.kid]);
+        const nexts = await this.getNextsData(select[this.setting.mid], select[this.setting.pid], select[this.setting.order]);
+        if (nexts && nexts.length > 0) {
+            // 修改nextsData pid, 排序
+            this.initSqlBuilder();
+            this.sqlBuilder.setUpdateData(this.setting.pid, {
+                value: select[this.setting.kid],
+            });
+            const orderInc = lastChild ? lastChild[this.setting.order] - select[this.setting.order] : - select[this.setting.order];
+            this.sqlBuilder.setUpdateData(this.setting.order, {
+                value: Math.abs(orderInc),
+                selfOperate: orderInc > 0 ? '+' : '-',
+            });
+            this.sqlBuilder.setAndWhere(this.setting.mid, {
+                value: select[this.setting.mid],
+                operate: '=',
+            });
+            this.sqlBuilder.setAndWhere(this.setting.pid, {
+                value: select[this.setting.pid],
+                operate: '=',
+            });
+            this.sqlBuilder.setAndWhere(this.setting.order, {
+                value: select[this.setting.order],
+                operate: '>',
+            });
+            const [sql1, sqlParam1] = this.sqlBuilder.build(this.tableName, 'update');
+            await this.transaction.query(sql1, sqlParam1);
+
+            // 选中节点 is_leaf应为false
+            if (select.is_leaf) {
+                const updateData = { id: select.id, is_leaf: false };
+                await this.transaction.update(this.tableName, updateData);
+            }
+
+            // 修改nextsData及其子节点的full_path
+            const oldSubStr = this.db.escape(select[this.setting.pid] + '-');
+            const newSubStr = this.db.escape(select[this.setting.kid] + '-');
+            const sqlArr = [];
+            sqlArr.push('Update ?? SET `full_path` = Replace(`full_path`,' + oldSubStr + ',' + newSubStr + ') Where');
+            sqlArr.push('(`' + this.setting.mid + '` = ' + select[this.setting.mid] + ')');
+            sqlArr.push(' And (');
+            for (const data of nexts) {
+                sqlArr.push('`' + this.setting.fullPath + '` Like ' + this.db.escape(data[this.setting.fullPath] + '%'));
+                if (nexts.indexOf(data) < nexts.length - 1) {
+                    sqlArr.push(' Or ');
+                }
+            }
+            sqlArr.push(')');
+            const sql = sqlArr.join('');
+            const resultData = await this.transaction.query(sql, [this.tableName]);
+            return resultData;
+        }
+    }
+
+    /**
+     * 升级节点
+     *
+     * @param {Number} tenderId - 标段id
+     * @param {Number} selectId - 选中节点id
+     * @return {Array} - 发生改变的数据
+     */
+    async upLevelNode(mid, kid, count) {
+        if (!count) count = 1;
+        const selects = await this.getDataByKidAndCount(mid, kid, count);
+
+        if (selects.length !== count) throw '升级节点数据错误';
+        const first = selects[0], last = selects[count - 1];
+        const parent = await this.getDataByKid(mid, first[this.setting.pid]);
+        if (!parent) throw '升级节点数据错误';
+
+        const newPath = [];
+        this.transaction = await this.db.beginTransaction();
+        try {
+            // 选中节点--父节点 选中节点为firstChild时,修改is_leaf
+            if (first[this.setting.order] === 1) {
+                await this.transaction.update(this.tableName, {
+                    id: parent.id,
+                    is_leaf: true,
+                });
+            }
+            // 选中节点--父节点--全部后兄弟节点 order+1
+            await this._updateChildrenOrder(mid, parent[this.setting.pid], parent[this.setting.order] + 1, count);
+            for (const [i, s] of selects.entries()) {
+                // 选中节点 修改pid, order, full_path, level, is_leaf, 清空计算项
+                const updateData = { id: s.id };
+                updateData[this.setting.pid] = parent[this.setting.pid];
+                updateData[this.setting.order] = parent[this.setting.order] + i + 1;
+                updateData[this.setting.level] = s[this.setting.level] - 1;
+                updateData[this.setting.fullPath] = s[this.setting.fullPath].replace(s[this.setting.pid] + '-', '');
+                newPath.push(updateData[this.setting.fullPath]);
+                if (s.is_leaf && s.id === last.id) {
+                    const nexts = await this.getNextsData(mid, parent[this.setting.kid], last[this.setting.order]);
+                    if (nexts.length > 0) {
+                        updateData.is_leaf = false;
+                        // updateData.unit_price = null;
+                        // updateData.quantity = null;
+                        // updateData.total_price = null;
+                        // updateData.deal_qty = null;
+                        // updateData.deal_tp = null;
+                    }
+                }
+                await this.transaction.update(this.tableName, updateData);
+                // 选中节点--全部子节点(含孙) level-1, full_path变更
+                await this._syncUplevelChildren(s);
+            }
+            // 选中节点--全部后兄弟节点 收编为子节点 修改pid, order, full_path
+            await this._syncUpLevelNexts(last);
+            await this.transaction.commit();
+            this.transaction = null;
+        } catch (err) {
+            await this.transaction.rollback();
+            this.transaction = null;
+            throw err;
+        }
+
+        // 查询修改的数据
+        let updateData = await this.getNextsData(mid, parent[this.setting.pid], parent[this.setting.order] - 1);
+        for (const path of newPath) {
+            const children = await this.getDataByFullPath(mid, path + '-%');
+            updateData = updateData.concat(children);
+        }
+        return { update: updateData };
+    }
+
+    /**
+     * 降级selectData, 同步修改所有子节点
+     * @param {Object} selectData - 选中节点
+     * @param {Object} preData - 选中节点的前一节点(降级后为父节点)
+     * @return {Promise<*>}
+     * @private
+     */
+    async _syncDownlevelChildren(select, pre) {
+        this.initSqlBuilder();
+        this.sqlBuilder.setAndWhere(this.setting.mid, {
+            value: select[this.setting.mid],
+            operate: '=',
+        });
+        this.sqlBuilder.setAndWhere(this.setting.fullPath, {
+            value: this.db.escape(select[this.setting.fullPath] + '-%'),
+            operate: 'like',
+        });
+        this.sqlBuilder.setUpdateData(this.setting.level, {
+            value: 1,
+            selfOperate: '+',
+        });
+        this.sqlBuilder.setUpdateData(this.setting.fullPath, {
+            value: [this.setting.fullPath, this.db.escape(select[this.setting.kid] + '-'), this.db.escape(pre[this.setting.kid] + '-' + select[this.setting.kid] + '-')],
+            literal: 'Replace',
+        });
+        const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+        const data = await this.transaction.query(sql, sqlParam);
+
+        return data;
+    }
+
+    /**
+     * 降级节点
+     *
+     * @param {Number} tenderId - 标段id
+     * @param {Number} selectId - 选中节点id
+     * @return {Array} - 发生改变的数据
+     */
+    async downLevelNode(mid, kid, count) {
+        if (!count) count = 1;
+        const selects = await this.getDataByKidAndCount(mid, kid, count);
+        if (!selects) throw '降级节点数据错误';
+        const first = selects[0];
+        const pre = await this.getDataByParentAndOrder(mid, first[this.setting.pid], first[this.setting.order] - 1);
+        if (!pre) throw '节点不可降级';
+        const preLastChild = await this.getLastChildData(mid, pre[this.setting.kid]);
+
+        const newPath = [];
+        this.transaction = await this.db.beginTransaction();
+        try {
+            // 选中节点--全部后节点 order--
+            await this._updateChildrenOrder(mid, first[this.setting.pid], first[this.setting.order] + 1, -count);
+
+            for (const [i, s] of selects.entries()) {
+                // 选中节点 修改pid, level, order, full_path
+                const updateData = { id: s.id };
+                updateData[this.setting.pid] = pre[this.setting.kid];
+                updateData[this.setting.order] = preLastChild ? preLastChild[this.setting.order] + i + 1 : i + 1;
+                updateData[this.setting.level] = s[this.setting.level] + 1;
+                const orgLastPath = s[this.setting.level] === 1 ? s[this.setting.kid] : '-' + s[this.setting.kid];
+                const newLastPath = s[this.setting.level] === 1 ? pre[this.setting.kid] + '-' + s[this.setting.kid] : '-' + pre[this.setting.kid] + '-' + s[this.setting.kid];
+                updateData[this.setting.fullPath] = s[this.setting.fullPath].replace(orgLastPath, newLastPath);
+                newPath.push(updateData[this.setting.fullPath]);
+                await this.transaction.update(this.tableName, updateData);
+                // 选中节点--全部子节点(含孙) level++, full_path
+                await this._syncDownlevelChildren(s, pre);
+            }
+            // 选中节点--前兄弟节点 is_leaf应为false, 清空计算相关字段
+            const updateData2 = { id: pre.id };
+            updateData2[this.setting.isLeaf] = false;
+            // updateData2.unit_price = null;
+            // updateData2.quantity = null;
+            // updateData2.total_price = null;
+            // updateData2.deal_qty = null;
+            // updateData2.deal_tp = null;
+            await this.transaction.update(this.tableName, updateData2);
+            await this.transaction.commit();
+            this.transaction = null;
+        } catch (err) {
+            await this.transaction.rollback();
+            this.transaction = null;
+            throw err;
+        }
+
+        // 查询修改的数据
+        let updateData = await this.getNextsData(mid, pre[this.setting.pid], pre[this.setting.order] - 1);
+        // 选中节点及子节点
+        for (const p of newPath) {
+            updateData = updateData.concat(await this.getDataByFullPath(mid, p + '%'));
+        }
+        // 选中节点--原前兄弟节点&全部后兄弟节点
+        return { update: updateData };
+    }
+
+    /**
+     * 过滤data中update方式不可提交的字段
+     * @param {Number} id - 主键key
+     * @param {Object} data
+     * @return {Object<{id: *}>}
+     * @private
+     */
+    _filterUpdateInvalidField(id, data) {
+        const result = {id: id};
+        for (const prop in data) {
+            if (this.readOnlyFields.indexOf(prop) === -1) {
+                result[prop] = data[prop];
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 提交多条数据 - 不影响计算等未提交项
+     * @param {Number} tenderId - 标段id
+     * @param {Array} datas - 提交数据
+     * @return {Array} - 提交后的数据
+     */
+    async updateInfos(mid, datas) {
+        if (mid <= 0) throw '数据错误';
+
+        if (Array.isArray(datas)) {
+            const updateDatas = [];
+            for (const d of datas) {
+                if (mid !== d[this.setting.mid]) throw '提交数据错误';
+                const node = await this.getDataById(d.id);
+                if (!node || mid !== node[this.setting.mid] || d[this.setting.kid] !== node[this.setting.kid]) throw '提交数据错误';
+                updateDatas.push(this._filterUpdateInvalidField(node.id, d));
+            }
+            await this.db.updateRows(this.tableName, updateDatas);
+
+            const resultData = await this.getDataById(this._.map(datas, 'id'));
+            return resultData;
+        } else {
+            if (mid !== datas[this.setting.mid]) throw '提交数据错误';
+            const node = await this.getDataById(datas.id);
+            if (!node || mid !== node[this.setting.mid] || datas[this.setting.kid] !== node[this.setting.kid]) throw '提交数据错误';
+            const updateData = this._filterUpdateInvalidField(node.id, datas);
+            await this.db.update(this.tableName, updateData);
+            return await this.getDataById(datas.id);
+        }
+    }
+
+    async pasteBlockRelaData(relaData) {
+        if (!this.transaction) throw '更新数据错误';
+        // for (const id of relaData) {
+        //     await this.ctx.service.pos.copyBillsPosData(id.org, id.new, this.transaction);
+        // }
+    }
+
+    async getPasteBlockResult(select, copyNodes) {
+        const createData = await this.getDataByIds(newIds);
+        const updateData = await this.getNextsData(selectData[this.setting.mid], selectData[this.setting.pid], selectData[this.setting.order] + copyNodes.length);
+        //const posData = await this.ctx.service.pos.getPosData({ lid: newIds });
+        return {
+            ledger: { create: createData, update: updateData },
+            //pos: posData,
+        };
+    }
+
+    /**
+     * 复制粘贴整块
+     * @param {Number} tenderId - 标段Id
+     * @param {Number} selectId - 选中几点Id
+     * @param {Array} block - 复制节点Id
+     * @return {Object} - 提价后的数据(其中新增粘贴数据,只返回第一层)
+     */
+    async pasteBlock(mid, kid, block) {
+        if ((mid <= 0) || (kid <= 0)) return [];
+
+        const selectData = await this.getDataByKid(mid, kid);
+        if (!selectData) throw '数据错误';
+
+        const copyNodes = await this.getDataByNodeIds(mid, block);
+        if (!copyNodes || copyNodes.length <= 0)  throw '复制数据错误';
+        for (const node of copyNodes) {
+            if (node[this.setting.pid] !== copyNodes[0][this.setting.pid]) throw '复制数据错误:仅可操作同层节点';
+        }
+
+        const newParentPath = selectData[this.setting.fullPath].replace(selectData[this.setting.kid], '');
+        const orgParentPath = copyNodes[0][this.setting.fullPath].replace(copyNodes[0][this.setting.kid], '');
+        const newIds = [];
+        this.transaction = await this.db.beginTransaction();
+        try {
+            // 选中节点的所有后兄弟节点,order+粘贴节点个数
+            await this._updateSelectNextsOrder(selectData, copyNodes.length);
+            for (let iNode = 0; iNode < copyNodes.length; iNode++) {
+                const node = copyNodes[iNode];
+                let datas = await this.getDataByFullPath(mid, node[this.setting.fullPath] + '%');
+                datas = this._.sortBy(datas, 'level');
+
+                const maxId = await this._getMaxLid(mid);
+                this._cacheMaxLid(mid, maxId + datas.length);
+
+                const billsId = [];
+                // 计算粘贴数据中需更新部分
+                for (let index = 0; index < datas.length; index++) {
+                    const data = datas[index];
+                    const newId = maxId + index + 1;
+                    const idChange = {
+                        org: data.id,
+                    };
+                    if (this.setting.uuid) data.id = this.uuid.v4();
+                    idChange.new = data.id;
+                    data[this.setting.mid] = mid;
+                    if (!data[this.setting.isLeaf]) {
+                        for (const children of datas) {
+                            children[this.setting.fullPath] = children[this.setting.fullPath].replace('-' + data[this.setting.kid], '-' + newId);
+                            if (children[this.setting.pid] === data[this.setting.kid]) {
+                                children[this.setting.pid] = newId;
+                            }
+                        }
+                    } else {
+                        data[this.setting.fullPath] = data[this.setting.fullPath].replace('-' + data[this.setting.kid], '-' + newId);
+                    }
+                    data[this.setting.kid] = newId;
+                    data[this.setting.fullPath] = data[this.setting.fullPath].replace(orgParentPath, newParentPath);
+                    if (data[this.setting.pid] === node[this.setting.pid]) {
+                        data[this.setting.pid] = selectData[this.setting.pid];
+                        data[this.setting.order] = selectData[this.setting.order] + iNode + 1;
+                    }
+                    data[this.setting.level] = data[this.setting.level] + selectData[this.setting.level] - copyNodes[0][this.setting.level];
+                    idChange.isLeaf = data[this.setting.isLeaf];
+                    billsId.push(idChange);
+                    newIds.push(data.id);
+                }
+                const newData = await this.transaction.insert(this.tableName, datas);
+                await this.pasteBlockRelaData(billsId);
+            }
+            // 数据库创建新增节点数据
+            await this.transaction.commit();
+            this.transaction = null;
+        } catch (err) {
+            await this.transaction.rollback();
+            this.transaction = null;
+            throw err;
+        }
+
+        // 查询应返回的结果
+        const createData = await this.getDataByIds(newIds);
+        const updateData = await this.getNextsData(selectData[this.setting.mid], selectData[this.setting.pid], selectData[this.setting.order] + copyNodes.length);
+        //const posData = await this.ctx.service.pos.getPosData({ lid: newIds });
+        return {
+            ledger: { create: createData, update: updateData },
+            //pos: posData,
+        };
+    }
+
+}
+
+module.exports = TreeService;

+ 32 - 0
app/const/account_group.js

@@ -0,0 +1,32 @@
+'use strict';
+
+/**
+ * 账号组
+ *
+ * @author ELlisran
+ * @date 2019/3/20
+ * @version
+ */
+const accountGroup = {
+    JSDW: 1,
+    JLDW: 2,
+    SGDW: 3,
+    SJDW: 4,
+    DJDW: 5,
+    GZSJ: 6,
+    QT: 7,
+};
+
+const group = [];
+group[accountGroup.JSDW] = '建设单位';
+group[accountGroup.JLDW] = '监理单位';
+group[accountGroup.SGDW] = '施工单位';
+group[accountGroup.SJDW] = '设计单位';
+group[accountGroup.DJDW] = '代建单位';
+group[accountGroup.GZSJ] = '跟踪审计';
+group[accountGroup.QT] = '其他';
+
+module.exports = {
+    type: accountGroup,
+    group,
+};

+ 111 - 0
app/const/cld_office.js

@@ -0,0 +1,111 @@
+'use strict';
+
+/**
+ * CLD办事处(和CLD里ID相对应)
+ *
+ * @author CaiAoLin
+ * @date 2017/10/31
+ * @version
+ */
+const officeType = {
+    ANHUI: 2,
+    GANSU: 3,
+    GUANGDONG: 4,
+    GUANGXI: 5,
+    JIANGXI: 6,
+    SICHUAN: 7,
+    CHONGQING: 8,
+    NEIMENG: 9,
+    ZHEJIANG: 10,
+    SHANDONG: 11,
+    ZONGBU: 12,
+    YUNNAN: 13,
+    GUIZHOU: 14,
+    BEIJING: 15,
+    FUJIAN: 16,
+    HAINAN: 17,
+    HEBEI: 18,
+    HENAN: 19,
+    HEILONGJIANG: 20,
+    HUBEI: 21,
+    HUNAN: 22,
+    JILIN: 23,
+    JIANGSU: 24,
+    LIAONING: 25,
+    NINGXIA: 26,
+    SHANXI: 27,
+    SHAN3XI: 28,
+    SHANGHAI: 29,
+    TIANJIN: 30,
+    XIZANG: 31,
+    XINJIANG: 32,
+    QINGHAI: 33,
+    YJZX: 100,
+};
+
+const office = [];
+office[officeType.ANHUI] = '安徽办';
+office[officeType.GANSU] = '甘肃办';
+office[officeType.GUANGDONG] = '广东办';
+office[officeType.GUANGXI] = '广西办';
+office[officeType.JIANGXI] = '江西办';
+office[officeType.SICHUAN] = '四川办';
+office[officeType.CHONGQING] = '重庆办';
+office[officeType.NEIMENG] = '内蒙办';
+office[officeType.ZHEJIANG] = '浙江办';
+office[officeType.SHANDONG] = '山东办';
+office[officeType.ZONGBU] = '总部';
+office[officeType.YUNNAN] = '云南办';
+office[officeType.GUIZHOU] = '贵州办';
+office[officeType.BEIJING] = '北京办';
+office[officeType.FUJIAN] = '福建办';
+office[officeType.HAINAN] = '海南办';
+office[officeType.HEBEI] = '河北办';
+office[officeType.HENAN] = '河南办';
+office[officeType.HEILONGJIANG] = '黑龙江办';
+office[officeType.HUBEI] = '湖北办';
+office[officeType.HUNAN] = '湖南办';
+office[officeType.JILIN] = '吉林办';
+office[officeType.JIANGSU] = '江苏办';
+office[officeType.LIAONING] = '辽宁办';
+office[officeType.NINGXIA] = '宁夏办';
+office[officeType.SHANXI] = '山西办';
+office[officeType.SHAN3XI] = '陕西办';
+office[officeType.SHANGHAI] = '上海办';
+office[officeType.TIANJIN] = '天津办';
+office[officeType.XIZANG] = '西藏办';
+office[officeType.XINJIANG] = '新疆办';
+office[officeType.QINGHAI] = '青海办';
+office[officeType.YJZX] = '总部-造价研究中心';
+// const office = [
+//     {id: officeType.ANHUI, name: '安徽办'},
+//     {id: officeType.GANSU, name: '甘肃办'},
+//     {id: officeType.GUANGDONG, name: '广东办'},
+//     {id: officeType.GUANGXI, name: '广西办'},
+//     {id: officeType.JIANGXI, name: '江西办'},
+//     {id: officeType.SICHUAN, name: '四川办'},
+//     {id: officeType.CHONGQING, name: '重庆办'},
+//     {id: officeType.NEIMENG, name: '内蒙办'},
+//     {id: officeType.ZHEJIANG, name: '浙江办'},
+//     {id: officeType.SHANDONG, name: '山东办'},
+//     {id: officeType.ZONGBU, name: '总部'},
+//     {id: officeType.YUNNAN, name: '云南办'},
+//     {id: officeType.GUIZHOU, name: '贵州办'},
+//     {id: officeType.BEIJING, name: '北京办'},
+//     {id: officeType.FUJIAN, name: '福建办'},
+//     {id: officeType.HAINAN, name: '海南办'},
+//     {id: officeType.HEBEI, name: '河北办'},
+//     {id: officeType.HENAN, name: '河南办'},
+//     {id: officeType.HEILONGJIANG, name: '黑龙江办'},
+//     {id: officeType.HUBEI, name: '湖北办'},
+//     {id: officeType.HUNAN, name: '湖南办'},
+//     {id: officeType.JILIN, name: '吉林办'},
+//     {id: officeType.JIANGSU, name: '江苏办'},
+//     {id: officeType.LIAONING, name: '辽宁办'},
+//     {id: officeType.NINGXIA, name: '宁夏办'}
+// ];
+
+module.exports = {
+    type: officeType,
+    list: office,
+};

+ 27 - 0
app/const/company_const.js

@@ -0,0 +1,27 @@
+/**
+ * 公司常量
+ *
+ * @author ELlisRan
+ * @date 2018/10/31
+ * @version
+ */
+
+
+/**
+ * 企业类型
+ *
+ * @var
+ */
+const companyType = ['设计', '施工'];
+
+/**
+ * 企业规模
+ *
+ * @var
+ */
+const companyScale = ['1-20', '20-50', '50-100', '100+'];
+
+module.exports = {
+    companyType,
+    companyScale
+};

+ 32 - 0
app/const/maintain.js

@@ -0,0 +1,32 @@
+'use strict';
+/**
+ * 消息提示类型
+ *
+ * @author CaiAoLin
+ * @date 2017/9/29
+ * @version
+ */
+const duration = {
+    min15: 1,
+    min30: 2,
+    hour1: 3,
+    hour2: 4,
+    forever: 5,
+};
+const maintain_status = {
+    notset: 0,
+    unstart: 1,
+    ongoing: 2,
+};
+const durationString = [];
+durationString[duration.min15] = '15分钟';
+durationString[duration.min30] = '30分钟';
+durationString[duration.hour1] = '1小时';
+durationString[duration.hour2] = '2小时';
+durationString[duration.forever] = '永久';
+
+module.exports = {
+    duration,
+    status: maintain_status,
+    durationString,
+};

+ 15 - 0
app/const/message_type.js

@@ -0,0 +1,15 @@
+'use strict';
+/**
+ * 消息提示类型
+ *
+ * @author CaiAoLin
+ * @date 2017/9/29
+ * @version
+ */
+const messageType = {
+    SUCCESS: 'success',
+    ERROR: 'error',
+    INFO: 'info',
+    WARNING: 'warning',
+};
+module.exports = messageType;

+ 24 - 0
app/const/page_show.js

@@ -0,0 +1,24 @@
+'use strict';
+
+/**
+ * 前台页面展示相关
+ *
+ * @author Ellisran
+ * @date
+ * @version
+ */
+
+const pageStatus = {
+    show: 1,
+    hide: 0,
+};
+
+const pageControl = [
+    { title: '开启「部位台帐」', name: 'bwtz', value: pageStatus.show, type: 'checkbox' },
+];
+
+
+module.exports = {
+    pageStatus,
+    pageControl,
+};

+ 20 - 0
app/const/province_const.js

@@ -0,0 +1,20 @@
+/**
+ * 省份常量
+ *
+ * @author CaiAoLin
+ * @date 2017/8/1
+ * @version
+ */
+const province = {
+    CHONGQING: 1,
+    GUANGDONG: 2
+};
+
+const provinceList = [];
+provinceList[province.CHONGQING] = '重庆';
+provinceList[province.GUANGDONG] = '广东';
+
+module.exports = {
+    province: province,
+    provinceList: provinceList,
+};

+ 24 - 0
app/const/report.js

@@ -0,0 +1,24 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+const JV = require('../reports/rpt_component/jpc_value_define.js');
+
+const rptCustomType = {};
+rptCustomType[JV.NODE_CUS_AUDIT_SELECT] = 1;
+rptCustomType[JV.NODE_CUS_GATHER_SELECT] = 2;
+rptCustomType[JV.NODE_CUS_COMPARE_SELECT] = 3;
+rptCustomType[JV.NODE_CUS_STAGE_SELECT] = 4;
+
+const rptDataType = {};
+rptDataType[JV.NODE_CUS_MATERIAL_SELECT] = 1;
+
+module.exports = {
+    rptCustomType,
+    rptDataType,
+};

+ 686 - 0
app/const/report_defined_properties.js

@@ -0,0 +1,686 @@
+/**
+ * Created by Tony on 2019/12/26.
+ */
+
+module.exports = {
+    ctrls: [
+        {
+            ID: 'Default',
+            Wrap: 'T',
+            Shrink: 'F',
+            Horizon: 'left',
+            ShowZero: 'T',
+            Vertical: 'bottom',
+            CfgDispName: '默认',
+            CloseOutput: 'F',
+            ShrinkFirst: 'F',
+        }, {
+            ID: 'Title',
+            Wrap: 'T',
+            Shrink: 'F',
+            Horizon: 'center',
+            ShowZero: 'T',
+            Vertical: 'center',
+            CfgDispName: '标题',
+            CloseOutput: 'F',
+            ShrinkFirst: 'F',
+        }, {
+            ID: 'Header',
+            Wrap: 'F',
+            Shrink: 'F',
+            Horizon: 'left',
+            ShowZero: 'T',
+            Vertical: 'center',
+            CfgDispName: '表头',
+            CloseOutput: 'F',
+            ShrinkFirst: 'F',
+        }, {
+            ID: 'Header_Right',
+            Wrap: 'F',
+            Shrink: 'F',
+            Horizon: 'right',
+            ShowZero: 'T',
+            Vertical: 'center',
+            CfgDispName: '表头_右',
+            CloseOutput: 'F',
+            ShrinkFirst: 'F',
+        }, {
+            ID: 'Footer',
+            Wrap: 'F',
+            Shrink: 'F',
+            Horizon: 'left',
+            ShowZero: 'T',
+            Vertical: 'center',
+            CfgDispName: '表尾',
+            CloseOutput: 'F',
+            ShrinkFirst: 'F',
+        }, {
+            ID: 'Footer_Right',
+            Wrap: 'F',
+            Shrink: 'F',
+            Horizon: 'right',
+            ShowZero: 'T',
+            Vertical: 'center',
+            CfgDispName: '表尾_右',
+            CloseOutput: 'F',
+            ShrinkFirst: 'F',
+        }, {
+            ID: 'Column',
+            Wrap: 'T',
+            Shrink: 'F',
+            Horizon: 'center',
+            ShowZero: 'F',
+            Vertical: 'center',
+            CfgDispName: '表栏',
+            CloseOutput: 'F',
+            ShrinkFirst: 'F',
+            FillAfterWrap: 'T',
+        }, {
+            ID: 'Column_Left',
+            Wrap: 'T',
+            Shrink: 'F',
+            Horizon: 'left',
+            ShowZero: 'F',
+            Vertical: 'center',
+            CfgDispName: '表栏_左',
+            CloseOutput: 'F',
+            ShrinkFirst: 'F',
+        }, {
+            ID: 'Column_Right',
+            Wrap: 'T',
+            Shrink: 'F',
+            Horizon: 'right',
+            ShowZero: 'F',
+            Vertical: 'center',
+            CfgDispName: '表栏_右',
+            CloseOutput: 'F',
+            ShrinkFirst: 'F',
+        }, {
+            ID: 'Content_Left',
+            Wrap: 'F',
+            Shrink: 'T',
+            Horizon: 'left',
+            ShowZero: 'F',
+            Vertical: 'bottom',
+            CfgDispName: '正文内容',
+            CloseOutput: 'T',
+            ShrinkFirst: 'F',
+        }, {
+            ID: 'Content_Right',
+            Wrap: 'F',
+            Shrink: 'T',
+            Horizon: 'right',
+            ShowZero: 'F',
+            Vertical: 'bottom',
+            CfgDispName: '正文内容_右',
+            CloseOutput: 'T',
+            ShrinkFirst: 'F',
+        }, {
+            ID: 'Content_Center',
+            Wrap: 'F',
+            Shrink: 'T',
+            Horizon: 'center',
+            ShowZero: 'F',
+            Vertical: 'bottom',
+            CfgDispName: '正文内容_中',
+            CloseOutput: 'T',
+            ShrinkFirst: 'F',
+        }, {
+            ID: 'NewContent_Left',
+            Wrap: 'F',
+            Shrink: 'T',
+            Horizon: 'left',
+            ShowZero: 'F',
+            Vertical: 'center',
+            CfgDispName: '正文内容(新)',
+            CloseOutput: 'T',
+            ShrinkFirst: 'F',
+        }, {
+            ID: 'NewContent_Right',
+            Wrap: 'F',
+            Shrink: 'T',
+            Horizon: 'right',
+            ShowZero: 'F',
+            Vertical: 'center',
+            CfgDispName: '正文内容(新)_右',
+            CloseOutput: 'T',
+            ShrinkFirst: 'F',
+        }, {
+            ID: 'NewContent_Center',
+            Wrap: 'F',
+            Shrink: 'T',
+            Horizon: 'center',
+            ShowZero: 'F',
+            Vertical: 'center',
+            CfgDispName: '正文内容(新)_中',
+            CloseOutput: 'T',
+            ShrinkFirst: 'F',
+        }, {
+            ID: 'Currency',
+            Wrap: 'F',
+            Shrink: 'T',
+            Horizon: 'right',
+            ShowZero: 'F',
+            Vertical: 'center',
+            CfgDispName: '金额型',
+            CloseOutput: 'F',
+            ShrinkFirst: 'T',
+        }, {
+            ID: 'Left_Top',
+            Wrap: 'T',
+            Shrink: 'F',
+            Horizon: 'left',
+            ShowZero: 'T',
+            Vertical: 'top',
+            CfgDispName: '上靠_左',
+            CloseOutput: 'T',
+            ShrinkFirst: 'F',
+        }, {
+            ID: 'Center_Top',
+            Wrap: 'T',
+            Shrink: 'F',
+            Horizon: 'center',
+            ShowZero: 'T',
+            Vertical: 'top',
+            CfgDispName: '上靠_中',
+            CloseOutput: 'T',
+            ShrinkFirst: 'F',
+        }, {
+            ID: 'Right_Top',
+            Wrap: 'T',
+            Shrink: 'F',
+            Horizon: 'right',
+            ShowZero: 'T',
+            Vertical: 'top',
+            CfgDispName: '上靠_右',
+            CloseOutput: 'T',
+            ShrinkFirst: 'F',
+        },
+    ],
+    fonts: [
+        {
+            ID: 'ReportTitle_Main',
+            Name: '宋体',
+            FontBold: 'T',
+            FontAngle: '0',
+            FontColor: 'BLACK',
+            FontHeight: '32',
+            FontItalic: 'F',
+            CfgDispName: '主标题',
+            FontStrikeOut: 'F',
+            FontUnderline: 'F',
+        }, {
+            ID: 'ReportTitle_Vice_1',
+            Name: '宋体',
+            FontBold: 'T',
+            FontAngle: '0',
+            FontColor: 'BLACK',
+            FontHeight: '22',
+            FontItalic: 'F',
+            CfgDispName: '副标题',
+            FontStrikeOut: 'F',
+            FontUnderline: 'F',
+        }, {
+            ID: 'ReportTitle_Vice_2',
+            Name: '宋体',
+            FontBold: 'T',
+            FontAngle: '0',
+            FontColor: 'BLACK',
+            FontHeight: '18',
+            FontItalic: 'F',
+            CfgDispName: '副标题2',
+            FontStrikeOut: 'F',
+            FontUnderline: 'F',
+        }, {
+            ID: 'HeaderColumn',
+            Name: '宋体',
+            FontBold: 'F',
+            FontAngle: '0',
+            FontColor: 'BLACK',
+            FontHeight: '12',
+            FontItalic: 'F',
+            CfgDispName: '栏头',
+            FontStrikeOut: 'F',
+            FontUnderline: 'F',
+        }, {
+            ID: 'Header',
+            Name: '宋体',
+            FontBold: 'F',
+            FontAngle: '0',
+            FontColor: 'BLACK',
+            FontHeight: '12',
+            FontItalic: 'F',
+            CfgDispName: '表头',
+            FontStrikeOut: 'F',
+            FontUnderline: 'F',
+        }, {
+            ID: 'FooterColumn',
+            Name: '宋体',
+            FontBold: 'F',
+            FontAngle: '0',
+            FontColor: 'BLACK',
+            FontHeight: '12',
+            FontItalic: 'F',
+            CfgDispName: '栏尾',
+            FontStrikeOut: 'F',
+            FontUnderline: 'F',
+        }, {
+            ID: 'Footer',
+            Name: '宋体',
+            FontBold: 'F',
+            FontAngle: '0',
+            FontColor: 'BLACK',
+            FontHeight: '12',
+            FontItalic: 'F',
+            CfgDispName: '表尾',
+            FontStrikeOut: 'F',
+            FontUnderline: 'F',
+        }, {
+            ID: 'GrandTotal',
+            Name: '宋体',
+            FontBold: 'F',
+            FontAngle: '0',
+            FontColor: 'BLACK',
+            FontHeight: '12',
+            FontItalic: 'F',
+            CfgDispName: '总合计',
+            FontStrikeOut: 'F',
+            FontUnderline: 'F',
+        }, {
+            ID: 'SectionTotal',
+            Name: '宋体',
+            FontBold: 'F',
+            FontAngle: '0',
+            FontColor: 'BLACK',
+            FontHeight: '12',
+            FontItalic: 'F',
+            CfgDispName: '章合计',
+            FontStrikeOut: 'F',
+            FontUnderline: 'F',
+        }, {
+            ID: 'Content',
+            Name: '宋体',
+            FontBold: 'F',
+            FontAngle: '0',
+            FontColor: 'BLACK',
+            FontHeight: '12',
+            FontItalic: 'F',
+            CfgDispName: '正文内容',
+            FontStrikeOut: 'F',
+            FontUnderline: 'F',
+        }, {
+            ID: 'Content_Narrow',
+            Name: 'Arial Narrow',
+            FontBold: 'F',
+            FontAngle: '0',
+            FontColor: 'BLACK',
+            FontHeight: '12',
+            FontItalic: 'F',
+            CfgDispName: '正文内容-窄体',
+            FontStrikeOut: 'F',
+            FontUnderline: 'F',
+        }, {
+            ID: 'Header_V1',
+            Name: '宋体',
+            FontBold: 'F',
+            FontAngle: '90',
+            FontColor: 'BLACK',
+            FontHeight: '12',
+            FontItalic: 'F',
+            FontStrikeOut: 'F',
+            FontUnderline: 'F',
+        }, {
+            ID: 'Header_V2',
+            Name: '宋体',
+            FontBold: 'F',
+            FontAngle: '-90',
+            FontColor: 'BLACK',
+            FontHeight: '12',
+            FontItalic: 'F',
+            FontStrikeOut: 'F',
+            FontUnderline: 'F',
+        },
+    ],
+    styles: [
+        {
+            ID: 'Default_None',
+            CfgDispName: '空白',
+            border_style: [
+                {
+                    Color: 'BLACK',
+                    Position: 'Left',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Right',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Top',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Bottom',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                },
+            ],
+        }, {
+            ID: 'Default',
+            CfgDispName: '默认',
+            border_style: [
+                {
+                    Color: 'BLACK',
+                    Position: 'Left',
+                    DashStyle: 'SOLID',
+                    LineWeight: '1',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Right',
+                    DashStyle: 'SOLID',
+                    LineWeight: '1',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Top',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Bottom',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                },
+            ],
+        }, {
+            ID: 'Default_Normal',
+            CfgDispName: '正常',
+            border_style: [
+                {
+                    Color: 'BLACK',
+                    Position: 'Left',
+                    DashStyle: 'SOLID',
+                    LineWeight: '1',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Right',
+                    DashStyle: 'SOLID',
+                    LineWeight: '1',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Top',
+                    DashStyle: 'SOLID',
+                    LineWeight: '1',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Bottom',
+                    DashStyle: 'SOLID',
+                    LineWeight: '1',
+                },
+            ],
+        }, {
+            ID: 'Label_Underline',
+            CfgDispName: '字符底线',
+            border_style: [
+                {
+                    Color: 'BLACK',
+                    Position: 'Left',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Right',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Top',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Bottom',
+                    DashStyle: 'SOLID',
+                    LineWeight: '1',
+                },
+            ],
+        }, {
+            ID: 'Label_Left',
+            CfgDispName: '左边线',
+            border_style: [
+                {
+                    Color: 'BLACK',
+                    Position: 'Left',
+                    DashStyle: 'SOLID',
+                    LineWeight: '1',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Right',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Top',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Bottom',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                },
+            ],
+        }, {
+            ID: 'Label_Right',
+            CfgDispName: '右边线',
+            border_style: [
+                {
+                    Color: 'BLACK',
+                    Position: 'Left',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Right',
+                    DashStyle: 'SOLID',
+                    LineWeight: '1',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Top',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Bottom',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                },
+            ],
+        }, {
+            ID: 'Label_Top',
+            CfgDispName: '上边线',
+            border_style: [
+                {
+                    Color: 'BLACK',
+                    Position: 'Left',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Right',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Top',
+                    DashStyle: 'SOLID',
+                    LineWeight: '1',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Bottom',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                },
+            ],
+        }, {
+            ID: 'Label_LeftTop',
+            CfgDispName: '左上边线',
+            border_style: [
+                {
+                    Color: 'BLACK',
+                    Position: 'Left',
+                    DashStyle: 'SOLID',
+                    LineWeight: '1',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Right',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Top',
+                    DashStyle: 'SOLID',
+                    LineWeight: '1',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Bottom',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                },
+            ],
+        }, {
+            ID: 'Label_LeftBottom',
+            CfgDispName: '左下边线',
+            border_style: [
+                {
+                    Color: 'BLACK',
+                    Position: 'Left',
+                    DashStyle: 'SOLID',
+                    LineWeight: '1',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Right',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Top',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Bottom',
+                    DashStyle: 'SOLID',
+                    LineWeight: '1',
+                },
+            ],
+        }, {
+            ID: 'Label_RightTop',
+            CfgDispName: '右上边线',
+            border_style: [
+                {
+                    Color: 'BLACK',
+                    Position: 'Left',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Right',
+                    DashStyle: 'SOLID',
+                    LineWeight: '1',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Top',
+                    DashStyle: 'SOLID',
+                    LineWeight: '1',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Bottom',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                },
+            ],
+        }, {
+            ID: 'Label_RightBottom',
+            CfgDispName: '右下边线',
+            border_style: [
+                {
+                    Color: 'BLACK',
+                    Position: 'Left',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Right',
+                    DashStyle: 'SOLID',
+                    LineWeight: '1',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Top',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Bottom',
+                    DashStyle: 'SOLID',
+                    LineWeight: '1',
+                },
+            ],
+        }, {
+            ID: 'Label_TopBottom',
+            CfgDispName: '上下边线',
+            border_style: [
+                {
+                    Color: 'BLACK',
+                    Position: 'Left',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Right',
+                    DashStyle: 'SOLID',
+                    LineWeight: '0',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Top',
+                    DashStyle: 'SOLID',
+                    LineWeight: '1',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Bottom',
+                    DashStyle: 'SOLID',
+                    LineWeight: '1',
+                },
+            ],
+        }, {
+            ID: 'BORDER_ALL_AROUND',
+            CfgDispName: '报表边框',
+            border_style: [
+                {
+                    Color: 'BLACK',
+                    Position: 'Left',
+                    DashStyle: 'SOLID',
+                    LineWeight: '2.0',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Right',
+                    DashStyle: 'SOLID',
+                    LineWeight: '2.0',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Top',
+                    DashStyle: 'SOLID',
+                    LineWeight: '2.0',
+                }, {
+                    Color: 'BLACK',
+                    Position: 'Bottom',
+                    DashStyle: 'SOLID',
+                    LineWeight: '2.0',
+                },
+            ],
+        },
+    ],
+};
+

Diferenças do arquivo suprimidas por serem muito extensas
+ 244 - 0
app/const/report_ext_code.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 6827 - 0
app/const/report_fields.js


+ 42 - 0
app/const/sms_alitemplate.js

@@ -0,0 +1,42 @@
+'use strict';
+
+/**
+ * 阿里云短信通知静态
+ *
+ * @author Ellisran
+ * @date 2019/8/20
+ * @version
+ */
+const accessKey = 'LTAIALMjBHOs9PLA';
+const accessKeySecret = 'HSnULQs87wAJhcziAdyRv3GZ4EYctc';
+const signName = '纵横云计量';
+const regionId = 'cn-hangzhou';
+const endpoint = 'https://dysmsapi.aliyuncs.com';
+const smsTemplate = {
+    yzm: 'SMS_192820305', // 验证码:${code},15分钟内有效。
+    mmcz: 'SMS_192825318', // 账号:${account},密码重置为:${password}
+    ledger_check: 'SMS_192825164', // 项目:${project},标段:${number},台帐需要您审批,请登录系统处理。
+    ledger_result: 'SMS_192830156', // 项目:${project},标段:${number},台帐审批${status},请登录系统处理。
+    stage_check: 'SMS_192820179', // 项目:${project},标段:${number},第${qi}期,需要您审批。
+    stage_result: 'SMS_192835176', // 项目:${project},标段:${number},第${qi}期,审批${status}。
+    change_check: 'SMS_192820171', // 项目:${project},标段:${number},变更:${biangeng},需要您审批。
+    change_result: 'SMS_192830143', // 项目:${project},标段:${number},变更:${biangeng},审批${status}。
+    revise_check: 'SMS_193145529', // 项目:${project},标段:${number},台帐修订需要您审批,请登录系统处理。
+    revise_result: 'SMS_193140537', // 项目:${project},标段:${number},台帐修订审批${status},请登录系统处理。
+    revise_result2: 'SMS_192985611', // 项目:${project},标段:${number},台帐修订审批${status}。
+    revise_report: 'SMS_192985614', // 项目:${project},标段:${number},台帐修订已上报。
+};
+const smsStatus = {
+    back: '退回',
+    success: '通过',
+};
+
+module.exports = {
+    accessKey,
+    accessKeySecret,
+    signName,
+    endpoint,
+    regionId,
+    template: smsTemplate,
+    status: smsStatus,
+};

+ 31 - 0
app/const/standard.js

@@ -0,0 +1,31 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const nodeType = [
+    {text: '', value: 0},
+    {text: '建安费', value: 1},
+    {text: '设备及工(器)具费', value: 2},
+    {text: '工程建设其他费', value: 3},
+    {text: '土地拆迁补偿', value: 4},
+    {text: '预备费', value: 5},
+    {text: '暂列金额', value: 6},
+    {text: '计日工', value: 7},
+    {text: '价差调整', value: 8},
+    {text: '索赔', value: 9},
+    {text: '新增费用', value: 10},
+    {text: '其他费用', value: 11},
+    {text: '回收金额', value: 12},
+    {text: '建设期贷款利息', value: 13},
+    {text: '其他建安工程', value: 14},
+];
+
+module.exports = {
+    nodeType,
+};

+ 63 - 0
app/const/tender.js

@@ -0,0 +1,63 @@
+'use strict';
+
+/**
+ * 标段相关常量
+ *
+ * @author CaiAoLin
+ * @date 2018/2/5
+ * @version
+ */
+
+// 标段状态
+const status = {
+    APPROVAL: 1,
+};
+
+const statusString = [];
+statusString[status.APPROVAL] = '审批中';
+
+// 标段类型
+const type = {
+    TJ: 1,
+    XX: 2,
+    YY: 3
+};
+
+const infoTableCol = [
+    { title: '名称', field: 'name', folderCell: true, },
+    { title: '计量期数', field: '', },
+    { title: '审批状态', field: '', },
+    { title: '0号台帐合同', field: '', },
+    { title: '本期完成', field: '', },
+    { title: '截止本期合同', field: '', },
+    { title: '截止本期变更', field: '', },
+    { title: '截止本期完成', field: '', },
+    { title: '截止上期完成', field: '', },
+    { title: '本期应付', field: '', },
+];
+const progressTableCol = [
+    { title: '名称', field: 'name', folderCell: true, },
+    { title: '完成期数', field: '', },
+    { title: '累计合同计量', field: '', },
+    { title: '截止本期累计完成/本期完成/未完成', field: '', },
+];
+const manageTableCol = [
+    { title: '名称', field: 'name', folderCell: true, },
+    { title: '完成期数', field: '', },
+    { title: '管理', field: '', },
+];
+
+const typeString = [];
+typeString[type.TJ] = '土建标';
+typeString[type.XX] = 'XX标';
+typeString[type.YY] = 'YY标';
+
+module.exports = {
+    status,
+    statusString,
+    type,
+    typeString,
+    infoTableCol,
+    progressTableCol,
+    manageTableCol,
+};

+ 32 - 0
app/controller/bills_template_controller.js

@@ -0,0 +1,32 @@
+'use strict';
+
+/**
+ * 台账模板 相关控制器
+ *
+ * @author CaiAoLin
+ * @date 2018/1/4
+ * @version
+ */
+const StandardLibController = require('./standard_lib_controller');
+module.exports = app => {
+    class BillsTemplateController extends StandardLibController {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx, {
+                model: ctx.service.billsTemplate,
+                listModel: ctx.service.billsTemplateList,
+                title: '台账模板',
+                viewFolder: 'bills_template',
+            });
+            this.app = app;
+        }
+    }
+
+    return BillsTemplateController;
+};

+ 73 - 0
app/controller/customer_controller.js

@@ -0,0 +1,73 @@
+'use strict';
+
+/**
+ * 客户管理相关控制器
+ *
+ * @author CaiAoLin
+ * @date 2017/11/14
+ * @version
+ */
+const provinceConst = require('../const/province_const');
+const companyConst = require('../const/company_const');
+module.exports = app => {
+
+    class CustomerController extends app.BaseController {
+
+        /**
+         * 演示版客户页面
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async demo(ctx) {
+            // 过滤客户数据
+            ctx.service.customer.searchFilter(ctx.request.query);
+            const customerList = await ctx.service.customer.getListWithBuilder();
+            // 获取总数据量
+            const total = await ctx.service.customer.count();
+            // 分页相关
+            const pageInfo = {
+                page: ctx.page,
+                total: Math.ceil(total / app.config.pageSize),
+                queryData: JSON.stringify(ctx.urlInfo.query),
+            };
+
+            const renderData = {
+                customerList,
+                pageInfo,
+                provinceList: provinceConst.provinceList,
+                provinceList2: JSON.stringify(provinceConst.provinceList),
+                companyType: companyConst.companyType,
+                companyType2: JSON.stringify(companyConst.companyType),
+                companyScale: companyConst.companyScale,
+            };
+            await this.layout('customer/demo.ejs', renderData);
+        }
+
+        /**
+         * 检测是否存在对应通行账号数据
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async customerExist(ctx) {
+            let result = false;
+            const customerAccount = ctx.request.body.account;
+
+            try {
+                if (customerAccount === undefined || customerAccount === '') {
+                    throw '数据为空';
+                }
+
+                const customerData = await ctx.service.customer.getDataInfo(customerAccount);
+                result = customerData !== null;
+            } catch (error) {
+                result = false;
+            }
+
+            ctx.body = result;
+        }
+    }
+
+    return CustomerController;
+};

+ 30 - 0
app/controller/dashboard_controller.js

@@ -0,0 +1,30 @@
+'use strict';
+/**
+ * 控制面板相关控制器
+ *
+ * @author CaiAoLin
+ * @date 2017/9/12
+ * @version
+ */
+
+module.exports = app => {
+
+    class DashboardController extends app.BaseController {
+
+        /**
+         * 数据统计页面
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        async index(ctx) {
+            const renderData = {
+                hello: 'hello layout',
+            };
+            await this.layout('dashboard/index.ejs', renderData);
+        }
+    }
+
+    return DashboardController;
+
+};

+ 144 - 0
app/controller/deal_pay_controller.js

@@ -0,0 +1,144 @@
+'use strict';
+
+/**
+ * 合同支付 控制器
+ *
+ * @author Mai
+ * @date 2020/3/4
+ * @version
+ */
+
+const payType = {
+    normal: 1,
+    yf: 2,
+    sf: 3,
+    wc: 4
+};
+
+const payTemplate = [
+    { order: 1, name: '本期应付', ptype: payType.yf, minus: false, expr: null, sexpr: null, rexpr: null },
+    { order: 2, name: '本期实付', ptype: payType.sf, minus: false },
+    { order: 3, name: '本期完成计量', ptype: payType.wc, minus: false, expr: 'bqwc' },
+    { order: 4, name: '质量保证金', ptype: payType.normal, minus: true },
+    { order: 5, name: '扣回开工预付款', ptype: payType.normal, minus: true, expr: '(bqwc/htj)*2*kgyfk', sexpr: 'htj*30%', rexpr: 'kgyfk' },
+];
+
+const calcBase = [
+    { name: '签约合同价', code: 'htj', sort: 10 },
+    { name: '暂列金额', code: 'zlje', sort: 2 },
+    { name: '签约合同价(不含暂列金)', code: 'htjszl', sort: 1 },
+    { name: '签约开工预付款', code: 'kgyfk', sort: 2 },
+    { name: '签约材料预付款', code: 'clyfk', sort: 2 },
+    { name: '本期完成计量', code: 'bqwc', limit: true, sort: 10 },
+    { name: '100章本期完成计量', code: 'ybbqwc', limit: true, sort: 1 },
+];
+
+module.exports = app => {
+
+    class DealPayController extends app.BaseController {
+
+        /**
+         * 项目列表
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async index(ctx) {
+            const projectData = ctx.session.sessionProjectData;
+            try {
+                // 验证数据
+                if (projectData.id === undefined) {
+                    throw '不存在对应的项目数据';
+                }
+
+                const curProjectData = await ctx.service.project.getDataById(projectData.id);
+                let dealpay = JSON.parse(JSON.stringify(payTemplate));
+                try {
+                    if (curProjectData.dealpay_json) {
+                        dealpay = JSON.parse(curProjectData.dealpay_json);
+                    }
+                } catch (err) {
+                    this.setMessage('合同支付数据错误,将重新初始化数据。如需查询详细错误,请勿修改或保存合同支付数据。', this.messageType.ERROR);
+                }
+
+                // 面包屑
+                const breadCrumb = this._getBreadCrumb(projectData);
+                // 渲染数据
+                const renderData = {
+                    projectData,
+                    status: ctx.service.project.displayStatus,
+                    statusClass: ctx.service.project.statusClass,
+                    breadCrumb,
+                    payType,
+                    dealpay: dealpay,
+                    calcBase,
+                };
+                await this.layout('project/deal_pay.ejs', renderData);
+            } catch (error) {
+                ctx.helper.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        async save(ctx) {
+            const projectData = ctx.session.sessionProjectData;
+            try {
+                // 验证数据
+                if (projectData.id === undefined) {
+                    throw '不存在对应的项目数据';
+                }
+
+                const data = ctx.request.body.data;
+                await ctx.service.project.update({ dealpay_json: data }, { id: projectData.id });
+                ctx.redirect('/project/dealpay');
+            } catch (error) {
+                ctx.helper.log(error);
+                if (error.stack) {
+                    this.setMessage('保存合同支付数据失败', this.messageType.ERROR);
+                } else {
+                    this.setMessage(error.toString(), this.messageType.ERROR);
+                }
+            }
+        }
+
+        async reset(ctx) {
+            const projectData = ctx.session.sessionProjectData;
+            try {
+                // 验证数据
+                if (projectData.id === undefined) {
+                    throw '不存在对应的项目数据';
+                }
+
+                await ctx.service.project.update({ dealpay_json: JSON.stringify(payTemplate) }, { id: projectData.id });
+                ctx.redirect('/project/dealpay');
+            } catch (error) {
+                ctx.helper.log(error);
+                if (error.stack) {
+                    this.setMessage('重置合同支付数据失败', this.messageType.ERROR);
+                } else {
+                    this.setMessage(error.toString(), this.messageType.ERROR);
+                }
+            }
+        }
+
+        /**
+         * 获取共用面包屑
+         *
+         * @param {Object} projectData - 项目数据
+         * @return {String} - 返回面包屑
+         */
+        _getBreadCrumb(projectData) {
+            // 面包屑
+            const name = projectData.project_id !== 0 ? '项目版客户' : '企业版客户';
+            const breadCrumb = [
+                { name: this.ctx.topPermission.name },
+                { name: name, url: this.ctx.controllerName },
+                { name: projectData.name },
+            ];
+            return this.ctx.helper.convertBreadCrumb(breadCrumb, 'replace');
+        }
+    }
+
+    return DealPayController;
+};

+ 334 - 0
app/controller/enterprise_controller.js

@@ -0,0 +1,334 @@
+'use strict';
+
+/**
+ * 企业管理控制器
+ *
+ * @author CaiAoLin
+ * @date 2017/11/8
+ * @version
+ */
+
+const cldOfficeConst = require('../const/cld_office');
+module.exports = app => {
+
+    class EnterpriseController extends app.BaseController {
+
+        /**
+         * 企业列表
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async index(ctx) {
+            const page = ctx.page;
+
+            // 过滤数据
+            ctx.service.enterprise.searchFilter(ctx.request.query);
+            // 获取企业列表
+            const enterpriseList = await ctx.service.enterprise.getListWithBuilder();
+            const total = await ctx.service.enterprise.count();
+
+            if (enterpriseList !== null) {
+                // 整理数据
+                let i = 0;
+                for (const enterprise of enterpriseList) {
+                    const manager = await ctx.service.manager.getDataById(enterprise.manager_id);
+                    enterpriseList[i].manager_username = manager !== null ? manager.username : '';
+                    const project_num = await ctx.service.project.count({ enterprise_id: enterprise.id });
+                    enterpriseList[i].project_num = project_num;
+                    i++;
+                }
+            }
+
+            // 分页相关
+            const pageInfo = {
+                page,
+                total: Math.ceil(total / app.config.pageSize),
+                queryData: JSON.stringify(ctx.urlInfo.query),
+            };
+
+            // 下拉菜单
+            const dropDownMenu = [
+                { name: '新增企业', url: ctx.controllerName + '/add' },
+            ];
+            const renderData = {
+                enterpriseList,
+                pageInfo,
+                dropDownMenu,
+                displayStatus: ctx.service.enterprise.displayStatus,
+                statusClass: ctx.service.enterprise.statusClass,
+                officeList: JSON.stringify(cldOfficeConst.list),
+                officeList2: cldOfficeConst.list,
+            };
+            await this.layout('enterprise/index.ejs', renderData);
+        }
+
+        /**
+         * 新增企业页面
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async add(ctx) {
+            // 获取数据规则
+            const rule = ctx.service.enterprise.rule('enterprise');
+            const frontRule = ctx.helper.validateConvert(rule);
+            const managerList = await ctx.service.manager.getManagerList('office', false);
+
+            // 面包屑
+            let breadCrumb = [
+                { name: ctx.topPermission.name },
+                { name: '企业版客户', url: ctx.controllerName },
+                { name: '添加企业' },
+            ];
+            breadCrumb = ctx.helper.convertBreadCrumb(breadCrumb, 'replace');
+            const renderData = {
+                rule: JSON.stringify(frontRule),
+                officeList: JSON.stringify(cldOfficeConst.list),
+                managerList: JSON.stringify(managerList),
+                breadCrumb,
+            };
+            await this.layout('enterprise/add.ejs', renderData);
+        }
+
+        /**
+         * 修改企业页面
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async modify(ctx) {
+            // 获取数据规则
+            const rule = ctx.service.enterprise.rule('enterpriseModify');
+            const frontRule = ctx.helper.validateConvert(rule);
+
+            let id = ctx.params.id;
+            id = parseInt(id);
+
+            try {
+                if (isNaN(id) || id <= 0) {
+                    throw '参数错误';
+                }
+                // 查找数据
+                const enterpriseData = await ctx.service.enterprise.getDataById(id);
+                // 项目数据存入session
+                ctx.session.sessionEnterpriseData = enterpriseData;
+                const managerList = await ctx.service.manager.getManagerList('office', false);
+
+                // 面包屑
+                const breadCrumb = this._getBreadCrumb(enterpriseData);
+                const renderData = {
+                    rule: JSON.stringify(frontRule),
+                    enterpriseData,
+                    officeList: JSON.stringify(cldOfficeConst.list),
+                    status: ctx.service.enterprise.displayStatus,
+                    statusClass: ctx.service.enterprise.statusClass,
+                    managerList: JSON.stringify(managerList),
+                    breadCrumb,
+                };
+
+                await this.layout('enterprise/modify.ejs', renderData);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 保存企业操作
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async save(ctx) {
+            let id = ctx.params.id;
+            id = parseInt(id);
+
+            try {
+                if (isNaN(id) || id < 0) {
+                    throw '参数错误';
+                }
+                // 获取数据规则
+                const scene = id === 0 ? 'enterprise' : 'enterpriseModify';
+                const rule = ctx.service.enterprise.rule(scene);
+                ctx.validate(rule);
+
+                const result = await ctx.service.enterprise.save(ctx.request.body, id);
+
+                if (!result) {
+                    throw '保存企业数据失败';
+                }
+
+                this.setMessage('保存企业数据成功', this.messageType.SUCCESS);
+                ctx.redirect('/enterprise');
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 删除企业
+         *
+         * @param {Object} ctx -egg全局变量
+         * @return {void}
+         */
+        async delete(ctx) {
+            let id = ctx.params.id;
+            id = parseInt(id);
+
+            try {
+                if (isNaN(id) || id <= 0) {
+                    throw '参数错误';
+                }
+                const result = ctx.service.enterprise.deleteProjectById(id);
+
+                if (!result) {
+                    throw '删除企业失败';
+                }
+
+                this.setMessage('删除企业成功', this.messageType.SUCCESS);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+            }
+
+            ctx.redirect(ctx.request.header.referer);
+        }
+
+        /**
+         * 企业设置页面
+         *
+         * @param {Object} ctx -egg 全局变量
+         * @return {void}
+         */
+        async setting(ctx) {
+            // 获取数据规则
+            const rule = ctx.service.enterprise.rule('enterpriseSetting');
+            const frontRule = ctx.helper.validateConvert(rule);
+
+            const enterpriseData = ctx.session.sessionEnterpriseData;
+
+            try {
+                // 查找数据
+                if (enterpriseData.id === undefined) {
+                    throw '不存在对应的企业数据';
+                }
+
+                // 面包屑
+                const breadCrumb = this._getBreadCrumb(enterpriseData);
+
+                const renderData = {
+                    rule: JSON.stringify(frontRule),
+                    enterpriseData,
+                    status: ctx.service.enterprise.displayStatus,
+                    statusClass: ctx.service.enterprise.statusClass,
+                    breadCrumb,
+                };
+                await this.layout('enterprise/setting.ejs', renderData);
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 保存企业设置操作
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async saveSetting(ctx) {
+            let id = ctx.params.id;
+            id = parseInt(id);
+
+            try {
+                if (isNaN(id) || id < 0) {
+                    throw '参数错误';
+                }
+                // 获取数据规则
+                const rule = ctx.service.enterprise.rule('enterpriseSetting');
+                ctx.validate(rule);
+
+                const result = await ctx.service.enterprise.saveSetting(ctx.request.body, id);
+
+                if (!result) {
+                    throw '保存企业数据失败';
+                }
+
+                this.setMessage('保存企业数据成功', this.messageType.SUCCESS);
+                ctx.redirect('/enterprise');
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 企业项目页面
+         *
+         * @param {Object} ctx -egg 全局变量
+         * @return {void}
+         */
+        async project(ctx) {
+            const enterpriseData = ctx.session.sessionEnterpriseData;
+
+            try {
+                // 查找数据
+                if (enterpriseData.id === undefined) {
+                    throw '不存在对应的企业数据';
+                }
+
+                let projectList = await ctx.service.project.getList();
+                if (projectList !== null) {
+                    let i = 0;
+                    for (let project of projectList) {
+                        const projectAccountData = await ctx.service.projectAccount.getDataByCondition({ id: project.user_id });
+                        projectList[i].username = projectAccountData.name;
+                        const tender_num = await ctx.service.tender.count({ project_id: project.id });
+                        projectList[i].tender_num = tender_num;
+                        i++;
+                    }
+                }
+
+                // 面包屑
+                const breadCrumb = this._getBreadCrumb(enterpriseData);
+
+                const renderData = {
+                    enterpriseData,
+                    status: ctx.service.enterprise.displayStatus,
+                    statusClass: ctx.service.enterprise.statusClass,
+                    projectList,
+                    breadCrumb,
+                };
+                await this.layout('enterprise/project.ejs', renderData);
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 获取共用面包屑
+         *
+         * @param {Object} projectData - 企业数据
+         * @return {String} - 返回面包屑
+         */
+        _getBreadCrumb(enterpriseData) {
+            // 面包屑
+            console.log(this.ctx.topPermission);
+            const breadCrumb = [
+                { name: this.ctx.topPermission.name },
+                { name: '企业版客户', url: this.ctx.controllerName },
+                { name: enterpriseData.name },
+            ];
+            return this.ctx.helper.convertBreadCrumb(breadCrumb, 'replace');
+        }
+
+    }
+
+    return EnterpriseController;
+};

+ 144 - 0
app/controller/group_controller.js

@@ -0,0 +1,144 @@
+'use strict';
+
+/**
+ * 用户组相关
+ *
+ * @author CaiAoLin
+ * @date 2017/9/30
+ * @version
+ */
+
+module.exports = app => {
+
+    class GroupController extends app.BaseController {
+
+        /**
+         * 用户组列表
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        async index(ctx) {
+            // 自动获取page并分页
+            const groupList = await ctx.service.group.getGroupList();
+            const total = await ctx.service.group.count();
+
+            // 分页相关
+            const pageInfo = {
+                page: ctx.page,
+                total: Math.ceil(total / app.config.pageSize),
+                queryData: JSON.stringify(ctx.urlInfo.query),
+            };
+
+            const dropDownMenu = [
+                { name: '新增用户组', url: ctx.controllerName + '/add' },
+            ];
+            const renderData = {
+                groupList,
+                pageInfo,
+                dropDownMenu,
+            };
+            await this.layout('group/index.ejs', renderData);
+        }
+
+        /**
+         * 新增用户组页面
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        async add(ctx) {
+            // 获取验证规则
+            const rule = ctx.service.group.rule();
+            const jsValidator = await this.jsValidator.convert(rule).build();
+
+            // 获取菜单数据
+            const menuData = await ctx.service.permission.getMenu();
+
+            let breadCrumb = [
+                { name: '用户组列表', url: ctx.controllerName },
+            ];
+            breadCrumb = ctx.helper.convertBreadCrumb(breadCrumb);
+            const renderData = {
+                jsValidator,
+                menuData,
+                groupData: {},
+                breadCrumb,
+            };
+            await this.layout('group/save.ejs', renderData);
+        }
+
+        /**
+         * 修改用户组页面
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void|Object} - 不返回或者直接跳转到上一页面
+         */
+        async modify(ctx) {
+            try {
+                const id = ctx.params.id;
+                // 获取验证规则
+                const rule = ctx.service.group.rule();
+                const jsValidator = await this.jsValidator.convert(rule).build();
+                // 获取菜单数据
+                const menuData = await ctx.service.permission.getMenu();
+                // 获取用户组数据
+                const groupData = await ctx.service.group.getDataById(id);
+
+                if (groupData === null) {
+                    throw '不存在对应数据';
+                }
+
+                groupData.permission = groupData.permission.split(',');
+
+                let breadCrumb = [
+                    { name: '用户组列表', url: ctx.controllerName },
+                ];
+                breadCrumb = ctx.helper.convertBreadCrumb(breadCrumb);
+                const renderData = {
+                    jsValidator,
+                    menuData,
+                    groupData,
+                    breadCrumb,
+                };
+                await this.layout('group/save.ejs', renderData);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                return ctx.redirect(ctx.request.header.referer);
+            }
+
+        }
+
+        /**
+         * 用户组保存
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {Object} - 跳转页面
+         */
+        async save(ctx) {
+            try {
+                let id = ctx.params.id;
+                id = parseInt(id);
+
+                // 获取校验规则
+                const rule = ctx.service.group.rule();
+                ctx.helper.validate(rule);
+
+                const result = await ctx.service.group.save(ctx.request.body, id);
+                if (!result) {
+                    throw '保存失败';
+                }
+
+                this.setMessage('保存成功', this.messageType.SUCCESS);
+                ctx.redirect('/group');
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                return ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+    }
+
+    return GroupController;
+};

+ 142 - 0
app/controller/help_controller.js

@@ -0,0 +1,142 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const path = require('path');
+const fs = require('fs');
+const analysisDefine = require('../lib/rpt_data_analysis').analysisDefine;
+const messageType = require('../const/message_type');
+const ctx_helper_define = [
+    {key: 'add', note: "    /**\n" +
+        "     * 加法 num1 + num2\n" +
+        "     * @param num1\n" +
+        "     * @param num2\n" +
+        "     * @returns {number}\n" +
+        "     */\n" +
+        "    add(num1, num2)"},
+    {key: 'sub', note: "    /**\n" +
+        "     * 减法 num1 - num2\n" +
+        "     * @param num1\n" +
+        "     * @param num2\n" +
+        "     * @returns {number}\n" +
+        "     */\n" +
+        "    sub(num1, num2)"},
+    {key: 'mul', note: "    /**\n" +
+        "     * 乘法 num1 * num2\n" +
+        "     * @param num1\n" +
+        "     * @param num2\n" +
+        "     * @returns {*}\n" +
+        "     */\n" +
+        "    mul(num1, num2, digit = 6)"},
+    {key: 'div', note: "    /**\n" +
+        "     * 除法 num1 / num2\n" +
+        "     * @param num1 - 被除数\n" +
+        "     * @param num2 - 除数\n" +
+        "     * @returns {*}\n" +
+        "     */\n" +
+        "    div(num1, num2, digit = 6)"},
+    {key: 'round', note: "    /**\n" +
+        "     * 四舍五入(统一,方便以后万一需要置换)\n" +
+        "     * @param {Number} value - 舍入的数字\n" +
+        "     * @param {Number} decimal - 要保留的小数位数\n" +
+        "     * @returns {*}\n" +
+        "     */\n" +
+        "    round(value, decimal)"},
+    {key: 'checkZero', note: "    /**\n" +
+        "     * 检查数字是否为0\n" +
+        "     * @param {Number} value\n" +
+        "     * @return {boolean}\n" +
+        "     */\n" +
+        "    checkZero(value)"},
+    {key: 'checkNumberEqual', note: "    /**\n" +
+        "     * 检查数字是否相等\n" +
+        "     * @param {Number} value1\n" +
+        "     * @param {Number} value2\n" +
+        "     * @returns {boolean}\n" +
+        "     */\n" +
+        "    checkNumberEqual(value1, value2)"},
+    {key: 'compareCode', note: "    /**\n" +
+        "     * 比较编码\n" +
+        "     * @param str1\n" +
+        "     * @param str2\n" +
+        "     * @param symbol\n" +
+        "     * @returns {number} " +
+        "     */\n" +
+        "    compareCode(str1, str2, symbol = '-')"},
+    {key: 'getChapterCode', note: "    /**\n" +
+        "     * 根据 清单编号 获取 章级编号\n" +
+        "     * @param code\n" +
+        "     * @param symbol\n" +
+        "     * @returns {string}\n" +
+        "     */\n" +
+        "    getChapterCode(code, symbol = '-')"},
+    {key: 'addDebugInfo', note: "    /**\n" +
+        "     * 添加debug信息\n" +
+        "     * 在debug模式下,debug信息将传输到浏览器并打印\n" +
+        "     *\n" +
+        "     * @param {String}key\n" +
+        "     * @param {*}data 允许输入不定个数的参数\n" +
+        "     */\n" +
+        "    addDebugInfo(key, ...data)\n" +
+        "    \n" +
+        "    //示例代码:\n" +
+        "    let bCode=$JE.F(2510,$CURRENT_RPT);\n" +
+        "    let mLen=$JE.getFieldDataLen(bCode, $CURRENT_DATA);\n" +
+        "    \n" +
+        "    $CTX_HELPER.addDebugInfo('mLen', mLen);\n" +
+        "    for (let i=0; i&lt;mLen; i++) {\n" +
+        "        let mno1=$JE.getFieldValue(bCode, $CURRENT_DATA, i, '');\n" +
+        "        $CTX_HELPER.addDebugInfo('', '清单编号',mno1);\n" +
+        "       // console.log('清单编号',mno1);\n" +
+        "    }", img: '/public/images/help/rpt-index-addDebugInfo.png', imgInfo: '示例结果如下:'},
+    //{key: 'add', note: ""},
+];
+
+module.exports = app => {
+
+    class helperController extends app.BaseController {
+
+        async index (ctx) {
+            const renderData = {
+                cbClass: 'help-content',
+                analysisDefine,
+                ctx_helper_define
+            };
+            try {
+                if (ctx.query.m && ctx.query.m !== '') {
+                    let htmlPath = 'help';
+                    htmlPath = path.join(htmlPath, ctx.query.m);
+                    if (ctx.query.s && ctx.query.s !== '') {
+                        htmlPath = path.join(htmlPath, ctx.query.s);
+                        if (ctx.query.d && ctx.query.d !== '') {
+                            htmlPath = path.join(htmlPath, ctx.query.d);
+                        }
+                    }
+                    if (fs.existsSync(path.join(ctx.app.baseDir, 'app/view',  htmlPath + '.ejs'))) {
+                        await this.layout(htmlPath + '.ejs', renderData);
+                    } else {
+                        throw '您查询的帮助暂不存在,请耐心等待小编完善';
+                    }
+                } else {
+                    await this.layout('help/index.ejs', renderData);
+                }
+            } catch(err) {
+                ctx.helper.log(err);
+                if (err.stack) {
+                    this.setMessage('查询帮助信息错误', messageType.ERROR);
+                } else {
+                    this.setMessage(err, messageType.INFO);
+                }
+                ctx.redirect('/help');
+            }
+        }
+    }
+
+    return helperController;
+};

+ 47 - 0
app/controller/log_controller.js

@@ -0,0 +1,47 @@
+'use strict';
+
+/**
+ * 日志列表控制器
+ *
+ * @author CaiAoLin
+ * @date 2017/10/30
+ * @version
+ */
+
+module.exports = app => {
+
+    class LogController extends app.BaseController {
+
+        /**
+         * 日志列表
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async index(ctx) {
+            const page = ctx.page;
+
+            // 筛选数据
+            ctx.service.log.searchFilter(ctx.request.query);
+            const logList = await ctx.service.log.getListWithBuilder();
+            const total = await ctx.service.log.count();
+
+            // 分页相关
+            const pageInfo = {
+                page,
+                total: Math.ceil(total / app.config.pageSize),
+                queryData: JSON.stringify(ctx.urlInfo.query),
+            };
+            console.log(pageInfo);
+
+            const renderData = {
+                logList,
+                pageInfo,
+            };
+
+            await this.layout('log/index.ejs', renderData);
+        }
+    }
+
+    return LogController;
+};

+ 97 - 0
app/controller/login_controller.js

@@ -0,0 +1,97 @@
+'use strict';
+
+/**
+ * 登录相关控制器
+ *
+ * @author CaiAoLin
+ * @date 2017/8/29
+ * @version
+ */
+
+// 加密
+const crypto = require('crypto');
+// 验证码
+const Captcha = require('../lib/captcha');
+module.exports = app => {
+
+    class LoginController extends app.Controller {
+
+        /**
+         * 登录页面
+         *
+         * @param {object} ctx - egg全局context
+         * @return {void}
+         */
+        async index(ctx) {
+            // 获取消息提示
+            const loginError = this.ctx.session.loginError;
+            // 取出后删除
+            this.ctx.session.loginError = null;
+
+            const renderData = {
+                loginError,
+            };
+            await ctx.render('login/login.ejs', renderData);
+        }
+
+        /**
+         * 验证码注册
+         *
+         * @param {object} ctx - egg全局context
+         * @return {void}
+         */
+        async captcha(ctx) {
+            const captcha = new Captcha(app);
+            const response = await captcha.register(ctx);
+            ctx.body = response;
+        }
+
+        /**
+         * 登录操作
+         *
+         * @param {object} ctx - egg全局context
+         * @return {void}
+         */
+        async login(ctx) {
+            const username = ctx.request.body.username;
+            const password = ctx.request.body.password;
+
+            try {
+                const captcha = new Captcha(app);
+                const captchaResult = await captcha.validate(ctx);
+                if (!captchaResult) {
+                    throw '验证码错误';
+                }
+                // 获取校验规则
+                const rule = ctx.service.manager.rule('login');
+                ctx.validate(rule);
+
+                // 如果不是admin则进入cld模式判断
+                const result = await ctx.service.manager.validManager(username, password);
+                if (!result) {
+                    throw '用户名或密码错误';
+                }
+            } catch (error) {
+                console.log(error);
+                ctx.session.loginError = error;
+            }
+
+            ctx.redirect('/dashboard');
+        }
+
+        /**
+         * 退出操作
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        async logout(ctx) {
+            // 删除session并跳转
+            ctx.session = null;
+            ctx.redirect('/');
+        }
+
+    }
+
+    return LoginController;
+};

+ 261 - 0
app/controller/manager_controller.js

@@ -0,0 +1,261 @@
+'use strict';
+
+/**
+ * 后台管理用户控制器
+ *
+ * @author CaiAoLin
+ * @date 2017/9/27
+ * @version
+ */
+
+const cldOfficeConst = require('../const/cld_office');
+module.exports = app => {
+
+    class ManagerController extends app.BaseController {
+
+        /**
+         * 用户列表页
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        async index(ctx) {
+            const page = ctx.page;
+            const managerSession = ctx.session.managerSession;
+            ctx.service.manager.searchFilter(ctx.request.query, managerSession);
+            const managerList = await ctx.service.manager.getListWithBuilder();
+            const total = await ctx.service.manager.count();
+
+            // 判断用户权限和所属办事处(PS:只有总部非造价研究中心和超级管理员可查看所有管理员用户,其它只显示该分组用户)
+            let officeList = [];
+            const permission = managerSession.permission;
+            if (permission === 'all' || parseInt(managerSession.office) === 12) {
+                officeList = JSON.stringify(cldOfficeConst.list);
+            } else {
+                const office = [];
+                office[managerSession.office] = cldOfficeConst.list[managerSession.office];
+                officeList = JSON.stringify(office);
+            }
+
+            // 获取用户组数据
+            const groupData = await ctx.service.group.getAllData();
+            const groupList = {};
+            if (groupData !== null) {
+                // 整理数据
+                for (const group of groupData) {
+                    groupList[group.id] = group.name;
+                }
+            }
+
+            // 分页相关
+            const pageInfo = {
+                page,
+                total: Math.ceil(total / app.config.pageSize),
+                queryData: JSON.stringify(ctx.urlInfo.query),
+            };
+
+            // const dropDownMenu = [
+            //     { name: '新增管理员', url: ctx.controllerName + '/add/0' },
+            // ];
+
+            const renderData = {
+                managerList,
+                pageInfo,
+                groupList,
+                officeType: officeList,
+                // dropDownMenu,
+            };
+            await this.layout('manager/index.ejs', renderData);
+        }
+
+        /**
+         * 新增管理员页面
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        async add(ctx) {
+            // 获取验证规则
+            const rule = ctx.service.manager.rule('add');
+            const jsValidator = await this.jsValidator.convert(rule).build();
+
+            // 获取用户组数据
+            const groupData = await ctx.service.group.getAllData();
+
+            let breadCrumb = [
+                { name: '管理员列表', url: ctx.controllerName },
+            ];
+            breadCrumb = ctx.helper.convertBreadCrumb(breadCrumb);
+            const renderData = {
+                managerData: {},
+                groupData,
+                jsValidator,
+                breadCrumb,
+            };
+            await this.layout('manager/save.ejs', renderData);
+        }
+
+        /**
+         * 编辑用户页面
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        async modify(ctx) {
+            try {
+                let id = ctx.params.id;
+                id = parseInt(id);
+                if (id === 1) {
+                    throw '超级管理员不能被修改';
+                }
+                const rule = ctx.service.manager.rule('modify');
+                const jsValidator = await this.jsValidator.convert(rule).build();
+                const managerData = await ctx.service.manager.getDataById(id);
+                if (managerData === null) {
+                    throw '不存在对应管理员';
+                }
+
+                // 获取用户组数据
+                const groupData = await ctx.service.group.getAllData(false, true);
+
+                let breadCrumb = [
+                    { name: '管理员列表', url: ctx.controllerName },
+                ];
+                breadCrumb = ctx.helper.convertBreadCrumb(breadCrumb);
+                const renderData = {
+                    jsValidator,
+                    groupData: JSON.stringify(groupData),
+                    managerData,
+                    breadCrumb,
+                };
+                await this.layout('manager/save.ejs', renderData);
+
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 保存用户数据
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        async save(ctx) {
+            try {
+                const id = ctx.params.id;
+                // 获取验证规则
+                const scene = id > 0 ? 'modify' : 'add';
+                const rule = ctx.service.manager.rule(scene);
+                ctx.validate(rule);
+
+                const result = await ctx.service.manager.save(ctx.request.body, id);
+                if (!result) {
+                    throw '保存失败';
+                }
+
+                this.setMessage('修改用户数据成功', this.messageType.SUCCESS);
+                ctx.redirect('/manager');
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 删除用户
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {Object} - 成功则跳转页面
+         */
+        async delete(ctx) {
+            let id = ctx.params.id;
+            id = parseInt(id);
+            try {
+                if (isNaN(id) || id <= 0) {
+                    throw 'id错误';
+                }
+                const result = await ctx.service.manager.deleteById(id);
+                if (!result) {
+                    throw '删除失败';
+                }
+
+                this.setMessage('删除成功', this.messageType.SUCCESS);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+            }
+
+            return ctx.redirect(ctx.request.header.referer);
+        }
+
+        /**
+         * 管理员个人信息修改页面
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        async profile(ctx) {
+            try {
+                const sessionManagerData = ctx.session.managerSession;
+                const managerData = await ctx.service.manager.getDataById(sessionManagerData.userID);
+
+                if (managerData === null) {
+                    throw '不存在对应用户';
+                }
+                const rule = ctx.service.manager.rule('profile');
+                const jsValidator = await this.jsValidator.convert(rule).build();
+
+                const renderData = {
+                    managerData,
+                    jsValidator,
+                };
+
+                await this.layout('manager/profile.ejs', renderData);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+
+        }
+
+        /**
+         * 保存管理员个人信息
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {Object} - 保存后跳转页面
+         */
+        async saveProfile(ctx) {
+            try {
+                const sessionManagerData = ctx.session.managerSession;
+                const id = sessionManagerData.userID;
+                const postData = ctx.request.body;
+                // 非本地用户则加上用于验证的password字段用于通过验证
+                if (id !== 1) {
+                    postData.password = '';
+                    postData.new_password = '';
+                    postData.confirm_password = '';
+                }
+
+                // 获取验证规则
+                const rule = ctx.service.manager.rule('profile');
+                ctx.validate(rule);
+
+                const result = await ctx.service.manager.save(postData, id);
+                if (!result) {
+                    throw '保存失败';
+                }
+
+                this.setMessage('保存个人信息成功', this.messageType.SUCCESS);
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+            }
+            return ctx.redirect(ctx.request.header.referer);
+        }
+
+    }
+
+    return ManagerController;
+};

+ 163 - 0
app/controller/message_controller.js

@@ -0,0 +1,163 @@
+'use strict';
+
+/**
+ * 消息管理控制器
+ *
+ * @author CaiAoLin
+ * @date 2017/10/25
+ * @version
+ */
+
+module.exports = app => {
+    class MessageController extends app.BaseController {
+
+        /**
+         * 消息列表
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async index(ctx) {
+            const page = ctx.page;
+
+            // 筛选数据
+            ctx.sort = ['id', 'DESC'];
+            ctx.service.message.searchFilter(ctx.request.query);
+            // 获取数据
+            const messageList = await ctx.service.message.getListWithBuilder();
+            const total = await ctx.service.message.count({ type: 2 });
+
+            // 分页相关
+            const pageInfo = {
+                page,
+                total: Math.ceil(total / app.config.pageSize),
+                queryData: JSON.stringify(ctx.urlInfo.query),
+            };
+
+            const dropDownMenu = [
+                { name: '添加新消息', url: ctx.controllerName + '/add' },
+            ];
+
+            const renderData = {
+                messageList: JSON.parse(JSON.stringify(messageList).replace(/\\r\\n/g, '<br>').replace(/\\"/g, '&#34;').replace(/'/g, '&#39;').replace(/\\t/g, '&#9;')),
+                pageInfo,
+                status: ctx.service.message.displayStatus,
+                dropDownMenu,
+            };
+            await this.layout('message/index.ejs', renderData, 'message/modal.ejs');
+        }
+
+        /**
+         * 新增消息页面
+         *
+         * @param {Object} ctx - egg 全局变量
+         * @return {void}
+         */
+        async add(ctx) {
+            // 获取规则
+            const rule = ctx.service.message.rule();
+            const jsValidator = await this.jsValidator.convert(rule).build();
+
+            let breadCrumb = [
+                { name: '消息通知', url: ctx.controllerName },
+            ];
+            breadCrumb = ctx.helper.convertBreadCrumb(breadCrumb);
+            const renderData = {
+                jsValidator,
+                messageData: {},
+                breadCrumb,
+            };
+            await this.layout('message/save.ejs', renderData);
+        }
+
+        /**
+         * 编辑消息页面
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        async modify(ctx) {
+            try {
+                let id = ctx.params.id;
+                id = parseInt(id);
+
+                const rule = ctx.service.message.rule();
+                const jsValidator = await this.jsValidator.convert(rule).build();
+                const messageData = await ctx.service.message.getDataById(id);
+                if (messageData === null) {
+                    throw '不存在对应管理员';
+                }
+
+                let breadCrumb = [
+                    { name: '消息通知', url: ctx.controllerName },
+                ];
+                breadCrumb = ctx.helper.convertBreadCrumb(breadCrumb);
+
+                const renderData = {
+                    jsValidator,
+                    messageData,
+                    breadCrumb,
+                };
+                await this.layout('message/save.ejs', renderData);
+
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 消息保存操作
+         *
+         * @param {Object} ctx - egg 全局变量
+         * @return {void}
+         */
+        async save(ctx) {
+            try {
+                const rule = ctx.service.message.rule();
+                ctx.validate(rule);
+
+                const id = ctx.params.id;
+                const result = await ctx.service.message.save(ctx.request.body, id);
+                if (!result) {
+                    throw '保存失败';
+                }
+
+                this.setMessage('保存消息数据成功', this.messageType.SUCCESS);
+                ctx.redirect('/message');
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 删除消息
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        async delete(ctx) {
+            let id = ctx.params.id;
+            id = parseInt(id);
+            try {
+                if (isNaN(id) || id <= 0) {
+                    throw 'id错误';
+                }
+                const result = await ctx.service.message.deleteById(id);
+                if (!result) {
+                    throw '删除失败';
+                }
+
+                this.setMessage('删除成功', this.messageType.SUCCESS);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+            }
+
+            ctx.redirect(ctx.request.header.referer);
+        }
+    }
+
+    return MessageController;
+};

+ 212 - 0
app/controller/permission_controller.js

@@ -0,0 +1,212 @@
+'use strict';
+
+/**
+ * 权限相关控制器
+ *
+ * @author CaiAoLin
+ * @date 2017/9/27
+ * @version
+ */
+
+const maintainConst = require('../const/maintain');
+module.exports = app => {
+
+    class PermissionController extends app.BaseController {
+
+        /**
+         * 权限列表
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        async index(ctx) {
+            // 获取整理好的列表数据
+            const sortedPermission = await ctx.service.permission.getMenu();
+            const renderData = {
+                permissionList: sortedPermission,
+            };
+            await this.layout('permission/index.ejs', renderData);
+        }
+
+        /**
+         * 新增权限页面
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        async add(ctx) {
+            const pid = ctx.params.id;
+            const rule = ctx.service.permission.rule();
+            const jsValidator = await this.jsValidator.convert(rule).build();
+
+            let breadCrumb = [
+                { name: '权限列表', url: ctx.controllerName },
+            ];
+            breadCrumb = ctx.helper.convertBreadCrumb(breadCrumb);
+            const renderData = {
+                pid,
+                jsValidator,
+                permissionData: {},
+                breadCrumb,
+            };
+            await this.layout('permission/save.ejs', renderData);
+        }
+
+        /**
+         * 编辑权限页面
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void|Object} 不存在数据则跳转
+         */
+        async modify(ctx) {
+            try {
+                const id = ctx.params.id;
+                const rule = ctx.service.permission.rule();
+                const jsValidator = await this.jsValidator.convert(rule).build();
+                const permissionData = await ctx.service.permission.getDataById(id);
+
+                if (permissionData === null) {
+                    throw '不存在对应数据';
+                }
+
+                let breadCrumb = [
+                    { name: '权限列表', url: ctx.controllerName },
+                ];
+                breadCrumb = ctx.helper.convertBreadCrumb(breadCrumb);
+                const renderData = {
+                    pid: permissionData.pid,
+                    jsValidator,
+                    permissionData,
+                    breadCrumb,
+                };
+                await this.layout('permission/save.ejs', renderData);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                return ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 保存新权限
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {Object} - 保存后跳转页面
+         */
+        async save(ctx) {
+            try {
+                let id = ctx.params.id;
+                id = parseInt(id);
+
+                // 获取校验规则
+                const rule = ctx.service.permission.rule('add');
+                ctx.validate(rule);
+
+                const result = await ctx.service.permission.save(ctx.request.body, id);
+                if (!result) {
+                    throw '保存失败';
+                }
+
+                ctx.redirect('/permission');
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                return ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 删除权限
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {Object} - 删除后跳转页面
+         */
+        async delete(ctx) {
+            let id = ctx.params.id;
+            id = parseInt(id);
+            try {
+                if (isNaN(id) || id <= 0) {
+                    throw 'id错误';
+                }
+                const result = await ctx.service.permission.deleteById(id);
+                if (!result) {
+                    throw '删除失败';
+                }
+
+                // 更新缓存
+                ctx.service.permission.flushCache();
+                this.setMessage('删除成功', this.messageType.SUCCESS);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+            }
+
+            return ctx.redirect(ctx.request.header.referer);
+        }
+
+        /**
+         * 维护设置
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {Object} - 删除后跳转页面
+         */
+        async maintain(ctx) {
+            const maintainData = await ctx.service.maintain.getDataById(1);
+            const rule = ctx.service.maintain.rule();
+            const jsValidator = await this.jsValidator.convert(rule).build();
+            const renderData = {
+                maintainData,
+                maintainConst,
+                jsValidator,
+            };
+            await this.layout('permission/maintain.ejs', renderData);
+        }
+
+        /**
+         * 保存维护设置
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {Object} - 保存后跳转页面
+         */
+        async maintainSet(ctx) {
+            try {
+                // 获取校验规则
+                const rule = ctx.service.maintain.rule();
+                ctx.validate(rule);
+
+                const result = await ctx.service.maintain.save(ctx.request.body);
+                if (!result) {
+                    throw '保存失败';
+                }
+
+                ctx.redirect('/maintain');
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                return ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 停止维护设置
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {Object} - 保存后跳转页面
+         */
+        async maintainStop(ctx) {
+            try {
+                const result = await ctx.service.maintain.stop();
+                if (!result) {
+                    throw '保存失败';
+                }
+
+                ctx.redirect('/maintain');
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                return ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+    }
+
+    return PermissionController;
+};

+ 654 - 0
app/controller/project_account_controller.js

@@ -0,0 +1,654 @@
+'use strict';
+
+/**
+ * 项目账户相关控制器
+ *
+ * @author CaiAoLin
+ * @date 2018/1/25
+ * @version
+ */
+
+const accountGroupConst = require('../const/account_group');
+module.exports = app => {
+
+    class ProjectAccountController extends app.BaseController {
+
+        /**
+         * 项目账号页面
+         *
+         * @param {Object} ctx -egg全局变量
+         * @return {void}
+         */
+        async index(ctx) {
+            const projectData = ctx.session.sessionProjectData;
+            try {
+                // 验证数据
+                if (projectData.id === undefined) {
+                    throw '不存在对应的项目数据';
+                }
+                const page = ctx.page;
+
+                // 获取用户数据
+                ctx.service.projectAccount.searchFilter(ctx.request.query, projectData.id);
+                const total = await ctx.service.projectAccount.count({ project_id: projectData.id });
+                // const accountList = await ctx.service.projectAccount.getAccountByProjectId(projectData.id);
+                const accountList = await ctx.service.projectAccount.getListWithBuilder();
+
+                // 面包屑
+                const breadCrumb = this._getBreadCrumb(projectData);
+
+                // 分页相关
+                const pageInfo = {
+                    page,
+                    total: Math.ceil(total / app.config.pageSize),
+                    queryData: JSON.stringify(ctx.urlInfo.query),
+                };
+                // 渲染数据
+                const renderData = {
+                    projectData,
+                    status: ctx.service.project.displayStatus,
+                    statusClass: ctx.service.project.statusClass,
+                    accountList,
+                    breadCrumb,
+                    accountGroup: accountGroupConst.group,
+                    pageInfo,
+                };
+                await this.layout('project/account.ejs', renderData);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 添加账号页面
+         *
+         * @param {Object} ctx -egg全局变量
+         * @return {void}
+         */
+        async add(ctx) {
+            const projectData = ctx.session.sessionProjectData;
+
+            try {
+                // 验证数据
+                if (projectData.id === undefined) {
+                    throw '不存在对应的项目数据';
+                }
+
+                // 获取验证规则
+                const rule = ctx.service.projectAccount.rule('add');
+                const frontRule = ctx.helper.validateConvert(rule);
+
+                // 面包屑
+                const breadCrumb = this._getBreadCrumb(projectData);
+                const renderData = {
+                    projectData,
+                    status: ctx.service.project.displayStatus,
+                    statusClass: ctx.service.project.statusClass,
+                    accountData: {},
+                    rule: JSON.stringify(frontRule),
+                    breadCrumb,
+                    accountGroup: accountGroupConst.group,
+                    accountGroup2: JSON.stringify(accountGroupConst.group),
+                };
+                await this.layout('project/save_account.ejs', renderData);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 修改账号页面
+         *
+         * @param {Object} ctx -egg全局变量
+         * @return {void}
+         */
+        async modify(ctx) {
+            const projectData = ctx.session.sessionProjectData;
+            let accountId = ctx.params.id;
+            try {
+                accountId = parseInt(accountId);
+                if (isNaN(accountId) || accountId <= 0) {
+                    throw '参数错误';
+                }
+                // 验证数据
+                if (projectData.id === undefined) {
+                    throw '不存在对应的项目数据';
+                }
+
+                // 获取验证规则
+                const rule = ctx.service.projectAccount.rule('modify');
+                const frontRule = ctx.helper.validateConvert(rule);
+
+                // 面包屑
+                const breadCrumb = this._getBreadCrumb(projectData);
+                const accountData = await ctx.service.projectAccount.getDataByCondition({ id: accountId });
+                const renderData = {
+                    projectData,
+                    status: ctx.service.project.displayStatus,
+                    statusClass: ctx.service.project.statusClass,
+                    accountData,
+                    rule: JSON.stringify(frontRule),
+                    breadCrumb,
+                    accountGroup: accountGroupConst.group,
+                    accountGroup2: JSON.stringify(accountGroupConst.group),
+                };
+                await this.layout('project/save_account.ejs', renderData);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 修改登录账号名页面
+         *
+         * @param {Object} ctx -egg全局变量
+         * @return {void}
+         */
+        async modifyAccount(ctx) {
+            const projectData = ctx.session.sessionProjectData;
+            let accountId = ctx.params.id;
+            try {
+                accountId = parseInt(accountId);
+                if (isNaN(accountId) || accountId <= 0) {
+                    throw '参数错误';
+                }
+                // 验证数据
+                if (projectData.id === undefined) {
+                    throw '不存在对应的项目数据';
+                }
+
+                // 面包屑
+                const breadCrumb = this._getBreadCrumb(projectData);
+                const accountData = await ctx.service.projectAccount.getDataByCondition({ id: accountId });
+                const renderData = {
+                    projectData,
+                    accountData,
+                    breadCrumb,
+                };
+                await this.layout('project/modify_account.ejs', renderData);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 修改登录密码页面
+         *
+         * @param {Object} ctx -egg全局变量
+         * @return {void}
+         */
+        async modifyPassword(ctx) {
+            const projectData = ctx.session.sessionProjectData;
+            let accountId = ctx.params.id;
+            try {
+                accountId = parseInt(accountId);
+                if (isNaN(accountId) || accountId <= 0) {
+                    throw '参数错误';
+                }
+                // 验证数据
+                if (projectData.id === undefined) {
+                    throw '不存在对应的项目数据';
+                }
+
+                // 面包屑
+                const breadCrumb = this._getBreadCrumb(projectData);
+                const accountData = await ctx.service.projectAccount.getDataByCondition({ id: accountId });
+                const renderData = {
+                    projectData,
+                    accountData,
+                    breadCrumb,
+                };
+                await this.layout('project/modify_password.ejs', renderData);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 修改副密码页面
+         *
+         * @param {Object} ctx -egg全局变量
+         * @return {void}
+         */
+        async modifyBackdoor(ctx) {
+            const projectData = ctx.session.sessionProjectData;
+            let accountId = ctx.params.id;
+            try {
+                accountId = parseInt(accountId);
+                if (isNaN(accountId) || accountId <= 0) {
+                    throw '参数错误';
+                }
+                // 验证数据
+                if (projectData.id === undefined) {
+                    throw '不存在对应的项目数据';
+                }
+
+                // 面包屑
+                const breadCrumb = this._getBreadCrumb(projectData);
+                const accountData = await ctx.service.projectAccount.getDataByCondition({ id: accountId });
+                const renderData = {
+                    projectData,
+                    accountData,
+                    breadCrumb,
+                };
+                await this.layout('project/modify_backdoor.ejs', renderData);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 删除账户
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async delete(ctx) {
+            const projectData = ctx.session.sessionProjectData;
+            let id = ctx.params.id;
+            id = parseInt(id);
+
+            try {
+                if (isNaN(id) || id <= 0) {
+                    throw '参数错误';
+                }
+                if (projectData.id === undefined) {
+                    throw '不存在对应项目';
+                }
+
+                const result = await ctx.service.projectAccount.deleteById(id);
+                if (!result) {
+                    throw '删除账户失败';
+                }
+
+                this.setMessage('删除账户成功', this.messageType.SUCCESS);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+            }
+
+            ctx.redirect(ctx.request.header.referer);
+        }
+
+        /**
+         * 检测账户是否存在
+         *
+         * @param {Object} ctx -egg全局变量
+         * @return {void}
+         */
+        async accountExist(ctx) {
+            const projectData = ctx.session.sessionProjectData;
+            const account = ctx.request.body.account;
+            const extraId = ctx.request.body.extraId !== undefined ? ctx.request.body.extraId : '';
+            let result = false;
+
+            try {
+                if (projectData.id === undefined) {
+                    throw '不存在对应项目';
+                }
+                result = await ctx.service.projectAccount.isAccountExist(account, projectData.id, extraId);
+            } catch (error) {
+                result = false;
+            }
+
+            ctx.body = !result;
+        }
+
+        /**
+         * 账号数据保存操作
+         *
+         * @param {Object} ctx -egg全局变量
+         * @return {void}
+         */
+        async save(ctx) {
+            const projectData = ctx.session.sessionProjectData;
+            let id = ctx.params.id;
+            id = parseInt(id);
+
+            try {
+                if (projectData.id === undefined) {
+                    throw '不存在对应的项目数据';
+                }
+                const scene = id > 0 ? 'modify' : 'add';
+                // 验证数据
+                const rule = ctx.service.projectAccount.rule(scene);
+                ctx.validate(rule);
+
+                const result = await ctx.service.projectAccount.save(ctx.request.body, id);
+                if (!result) {
+                    throw '保存账号数据失败';
+                }
+
+                this.setMessage('保存账号数据成功', this.messageType.SUCCESS);
+                ctx.redirect('/' + ctx.controllerName + '/account');
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 项目设置 -- 账号启用和停用设置(Post)
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async accountSwitch(ctx) {
+            const responseData = {
+                err: 0, msg: '', data: null,
+            };
+            try {
+                // 获取项目数据
+                const projectId = ctx.session.sessionProjectData.id;
+                // const projectData = await ctx.service.project.getDataById(projectId);
+                // if (projectData === null) {
+                //     throw '没有对应的项目数据';
+                // }
+                // if (ctx.session.sessionUser.is_admin === 0) {
+                //     throw '没有访问权限';
+                // }
+                const data = JSON.parse(ctx.request.body.data);
+                const result = await ctx.service.projectAccount.update(data, { id: data.id });
+                if (!result) {
+                    throw '提交数据失败';
+                }
+            } catch (err) {
+                this.log(err);
+                responseData.err = 1;
+                responseData.msg = err;
+            }
+            ctx.body = responseData;
+        }
+
+        /**
+         * 项目设置 -- 账号解绑设置(Post)
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async accountUnbind(ctx) {
+            const projectData = ctx.session.sessionProjectData;
+
+            try {
+                if (projectData.id === undefined) {
+                    throw '不存在对应的项目数据';
+                }
+                const accountId = parseInt(ctx.request.body.id);
+                const result = await ctx.service.projectAccount.update({ bind: 0 }, { id: accountId });
+                if (!result) {
+                    throw '解绑失败';
+                }
+
+                this.setMessage('解绑成功', this.messageType.SUCCESS);
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+            }
+            ctx.redirect(ctx.request.header.referer);
+        }
+
+        /**
+         * 重置用户密码
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async resetPassword(ctx) {
+            let accountId = ctx.params.id;
+            const response = {
+                err: 0,
+                msg: '',
+            };
+            try {
+                accountId = parseInt(accountId);
+                let password = ctx.request.body.reset_password;
+                const account = ctx.request.body.account !== undefined ? ctx.request.body.account : '';
+                const backdoor = ctx.request.body.backdoor !== undefined ? ctx.request.body.backdoor : false;
+                password = password.toString();
+                if (isNaN(accountId) || accountId <= 0 || password.length < 6) {
+                    throw '参数错误';
+                }
+                let result = true;
+                if (backdoor) {
+                    result = await ctx.service.projectAccount.resetBackdoorPassword(accountId, password);
+                } else {
+                    result = await ctx.service.projectAccount.resetPassword(accountId, password, account);
+                }
+
+                if (!result) {
+                    throw '重置密码失败!';
+                }
+            } catch (error) {
+                response.err = 1;
+                response.msg = error;
+            }
+
+            ctx.body = response;
+        }
+
+        /**
+         * 获取共用面包屑
+         *
+         * @param {Object} projectData - 项目数据
+         * @return {String} - 返回面包屑
+         */
+        _getBreadCrumb(projectData) {
+            // 面包屑
+            const name = projectData.project_id !== 0 ? '项目版客户' : '企业版客户';
+            const breadCrumb = [
+                { name: this.ctx.topPermission.name },
+                { name: name, url: this.ctx.controllerName },
+                { name: projectData.name },
+            ];
+            return this.ctx.helper.convertBreadCrumb(breadCrumb, 'replace');
+        }
+
+
+        /**
+         * 项目账号页面
+         *
+         * @param {Object} ctx -egg全局变量
+         * @return {void}
+         */
+        async enterpriseIndex(ctx) {
+            const enterpriseData = ctx.session.sessionEnterpriseData;
+            try {
+                // 验证数据
+                if (enterpriseData.id === undefined) {
+                    throw '不存在对应的企业数据';
+                }
+
+                // 获取用户数据
+                ctx.service.projectAccount.searchFilter(ctx.request.query);
+                const accountList = await ctx.service.projectAccount.getAccountByEnterpriseId(enterpriseData.id);
+
+                // 面包屑
+                const breadCrumb = this._getBreadCrumb(enterpriseData);
+                // 渲染数据
+                const renderData = {
+                    enterpriseData,
+                    status: ctx.service.enterprise.displayStatus,
+                    statusClass: ctx.service.enterprise.statusClass,
+                    accountList,
+                    breadCrumb,
+                };
+                await this.layout('enterprise/account.ejs', renderData);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 添加账号页面
+         *
+         * @param {Object} ctx -egg全局变量
+         * @return {void}
+         */
+        async enterpriseAdd(ctx) {
+            const enterpriseData = ctx.session.sessionEnterpriseData;
+
+            try {
+                // 验证数据
+                if (enterpriseData.id === undefined) {
+                    throw '不存在对应的企业数据';
+                }
+
+                // 获取验证规则
+                const rule = ctx.service.projectAccount.rule('add');
+                const frontRule = ctx.helper.validateConvert(rule);
+
+                // 面包屑
+                const breadCrumb = this._getBreadCrumb(enterpriseData);
+                const renderData = {
+                    enterpriseData,
+                    status: ctx.service.enterprise.displayStatus,
+                    statusClass: ctx.service.enterprise.statusClass,
+                    accountData: {},
+                    rule: JSON.stringify(frontRule),
+                    breadCrumb,
+                };
+                await this.layout('enterprise/save_account.ejs', renderData);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 修改账号页面
+         *
+         * @param {Object} ctx -egg全局变量
+         * @return {void}
+         */
+        async enterpriseModify(ctx) {
+            const enterpriseData = ctx.session.sessionEnterpriseData;
+            let accountId = ctx.params.id;
+            try {
+                accountId = parseInt(accountId);
+                if (isNaN(accountId) || accountId <= 0) {
+                    throw '参数错误';
+                }
+                // 验证数据
+                if (enterpriseData.id === undefined) {
+                    throw '不存在对应的企业数据';
+                }
+
+                // 获取验证规则
+                const rule = ctx.service.projectAccount.rule('modify');
+                const frontRule = ctx.helper.validateConvert(rule);
+
+                // 面包屑
+                const breadCrumb = this._getBreadCrumb(enterpriseData);
+                const accountData = await ctx.service.projectAccount.getDataByCondition({ id: accountId });
+                const renderData = {
+                    enterpriseData,
+                    status: ctx.service.enterprise.displayStatus,
+                    statusClass: ctx.service.enterprise.statusClass,
+                    accountData,
+                    rule: JSON.stringify(frontRule),
+                    breadCrumb,
+                };
+                await this.layout('enterprise/save_account.ejs', renderData);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 删除账户
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async enterpriseDelete(ctx) {
+            const enterpriseData = ctx.session.sessionEnterpriseData;
+            let id = ctx.params.id;
+            id = parseInt(id);
+
+            try {
+                if (isNaN(id) || id <= 0) {
+                    throw '参数错误';
+                }
+                if (enterpriseData.id === undefined) {
+                    throw '不存在对应企业';
+                }
+
+                const result = await ctx.service.projectAccount.deleteById(id);
+                if (!result) {
+                    throw '删除账户失败';
+                }
+
+                this.setMessage('删除账户成功', this.messageType.SUCCESS);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+            }
+
+            ctx.redirect(ctx.request.header.referer);
+        }
+
+        /**
+         * 检测账户是否存在
+         *
+         * @param {Object} ctx -egg全局变量
+         * @return {void}
+         */
+        async enterpriseAccountExist(ctx) {
+            const enterpriseData = ctx.session.sessionEnterpriseData;
+            const account = ctx.request.body.account;
+            let result = true;
+
+            try {
+                if (enterpriseData.id === undefined) {
+                    throw '不存在对应项目';
+                }
+                result = await ctx.service.projectAccount.isEnterpriseAccountExist(account, enterpriseData.id);
+            } catch (error) {
+                result = true;
+            }
+
+            ctx.body = !result;
+        }
+
+        /**
+         * 账号数据保存操作
+         *
+         * @param {Object} ctx -egg全局变量
+         * @return {void}
+         */
+        async enterpriseSave(ctx) {
+            const enterpriseData = ctx.session.sessionEnterpriseData;
+            let id = ctx.params.id;
+            id = parseInt(id);
+
+            try {
+                if (enterpriseData.id === undefined) {
+                    throw '不存在对应的企业数据';
+                }
+                const scene = id > 0 ? 'modify' : 'add';
+                // 验证数据
+                const rule = ctx.service.projectAccount.rule(scene);
+                ctx.validate(rule);
+
+                const result = await ctx.service.projectAccount.enterpriseSave(ctx.request.body, id);
+                if (!result) {
+                    throw '保存账号数据失败';
+                }
+
+                this.setMessage('保存账号数据成功', this.messageType.SUCCESS);
+                ctx.redirect('/' + ctx.controllerName + '/account');
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+    }
+
+    return ProjectAccountController;
+};

+ 597 - 0
app/controller/project_controller.js

@@ -0,0 +1,597 @@
+'use strict';
+
+/**
+ * 项目管理控制器
+ *
+ * @author CaiAoLin
+ * @date 2017/11/8
+ * @version
+ */
+
+const cldOfficeConst = require('../const/cld_office');
+const pageShowConst = require('../const/page_show');
+const path = require('path');
+const sendToWormhole = require('stream-wormhole');
+module.exports = app => {
+
+    class ProjectController extends app.BaseController {
+
+        /**
+         * 项目列表
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async index(ctx) {
+            const page = ctx.page;
+
+            // 过滤数据
+            ctx.service.project.searchFilter(ctx.request.query, ctx.session.managerSession);
+            // 获取项目列表
+            const total = await ctx.service.project.getCountWithBuilder();
+            const projectList = await ctx.service.project.getListWithBuilder();
+            // const filter = ctx.session.managerSession.office !== cldOfficeConst.type.ZONGBU ? { office: ctx.session.managerSession.office !== cldOfficeConst.type.JIANGXI ? ctx.session.managerSession.office : [cldOfficeConst.type.JIANGXI, cldOfficeConst.type.HUNAN] } : '';
+            // const total = await ctx.service.project.count(filter);
+            if (projectList !== null) {
+                // 整理数据
+                let i = 0;
+                for (const project of projectList) {
+                    const manager = await ctx.service.manager.getDataById(project.manager_id);
+                    projectList[i].manager_username = manager !== null ? manager.username : '';
+                    i++;
+                }
+            }
+
+            // 分页相关
+            const pageInfo = {
+                page,
+                total: Math.ceil(total / app.config.pageSize),
+                queryData: JSON.stringify(ctx.urlInfo.query),
+            };
+
+            // 下拉菜单
+            const dropDownMenu = [
+                { name: '新增项目', url: ctx.controllerName + '/add' },
+            ];
+
+            const managerSession = ctx.session.managerSession;
+            let officeList2 = [];
+            const permission = managerSession.permission;
+            if (permission === 'all' || parseInt(managerSession.office) === 12) {
+                officeList2 = JSON.stringify(cldOfficeConst.list);
+            } else {
+                const office = [];
+                const officeIdList = await ctx.service.project.getOfficeList(managerSession.office, managerSession.userID);
+                for (const oi of officeIdList) {
+                    office[oi.office] = cldOfficeConst.list[oi.office];
+                }
+                officeList2 = JSON.stringify(office);
+            }
+
+            const renderData = {
+                projectList,
+                pageInfo,
+                dropDownMenu,
+                displayStatus: JSON.stringify(ctx.service.project.displayStatus),
+                displayStatus2: ctx.service.project.displayStatus,
+                statusClass: ctx.service.project.statusClass,
+                officeList: cldOfficeConst.list,
+                officeSelectList: officeList2,
+            };
+            await this.layout('project/index.ejs', renderData);
+        }
+
+        /**
+         * 新增项目页面
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async add(ctx) {
+            // 获取数据规则
+            const rule = ctx.service.project.rule('project');
+            const frontRule = ctx.helper.validateConvert(rule);
+            const managerList = await ctx.service.manager.getManagerList('office', false);
+
+            // 计价规范
+            ctx.service.valuationList.searchFilter(ctx.request.query);
+            const valuationList = await ctx.service.valuationList.getListWithBuilder();
+
+            // 面包屑
+            let breadCrumb = [
+                { name: ctx.topPermission.name },
+                { name: '项目版客户', url: ctx.controllerName },
+                { name: '添加项目' },
+            ];
+            breadCrumb = ctx.helper.convertBreadCrumb(breadCrumb, 'replace');
+            const renderData = {
+                rule: JSON.stringify(frontRule),
+                officeList: JSON.stringify(cldOfficeConst.list),
+                managerList: JSON.stringify(managerList),
+                valuationList,
+                breadCrumb,
+            };
+            await this.layout('project/add.ejs', renderData);
+        }
+
+        /**
+         * 修改项目页面
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async modify(ctx) {
+            // 获取数据规则
+            const rule = ctx.service.project.rule('projectModify');
+            const frontRule = ctx.helper.validateConvert(rule);
+
+            let id = ctx.params.id;
+            id = parseInt(id);
+
+            try {
+                if (isNaN(id) || id <= 0) {
+                    throw '参数错误';
+                }
+                // 查找数据
+                const projectData = await ctx.service.project.getDataById(id);
+                const managerData = await ctx.service.manager.getDataById(projectData.creator);
+                // 项目数据存入session
+                ctx.session.sessionProjectData = projectData;
+                const managerList = await ctx.service.manager.getManagerList('office', false);
+
+                // 计价规范
+                ctx.service.valuationList.searchFilter(ctx.request.query);
+                const valuationList = await ctx.service.valuationList.getListWithBuilder();
+
+                // 面包屑
+                const breadCrumb = this._getBreadCrumb(projectData);
+                const renderData = {
+                    rule: JSON.stringify(frontRule),
+                    projectData,
+                    managerData,
+                    officeList: JSON.stringify(cldOfficeConst.list),
+                    status: ctx.service.project.displayStatus,
+                    statusClass: ctx.service.project.statusClass,
+                    managerList: JSON.stringify(managerList),
+                    valuationList,
+                    breadCrumb,
+                };
+
+                await this.layout('project/modify.ejs', renderData);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 保存项目操作
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async save(ctx) {
+            let id = ctx.params.id;
+            id = parseInt(id);
+
+            try {
+                if (isNaN(id) || id < 0) {
+                    throw '参数错误';
+                }
+                // 获取数据规则
+                const scene = id === 0 ? 'project' : 'projectModify';
+                const rule = ctx.service.project.rule(scene);
+                ctx.validate(rule);
+
+                const result = await ctx.service.project.save(ctx.request.body, id);
+                if (!result) {
+                    throw '保存项目数据失败';
+                }
+
+                this.setMessage('保存项目数据成功', this.messageType.SUCCESS);
+                ctx.redirect('/project');
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 删除项目
+         *
+         * @param {Object} ctx -egg全局变量
+         * @return {void}
+         */
+        async delete(ctx) {
+            let id = ctx.params.id;
+            id = parseInt(id);
+
+            try {
+                if (isNaN(id) || id <= 0) {
+                    throw '参数错误';
+                }
+                const result = ctx.service.project.deleteProjectById(id);
+
+                if (!result) {
+                    throw '删除项目失败';
+                }
+
+                this.setMessage('删除项目成功', this.messageType.SUCCESS);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+            }
+
+            ctx.redirect(ctx.request.header.referer);
+        }
+
+        /**
+         * 上传二维码照片
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async uploadQrCode(ctx) {
+            const responseData = {
+                err: 0, msg: '', data: null,
+            };
+            try {
+                const stream = await ctx.getFileStream();
+                const create_time = Date.parse(new Date()) / 1000;
+                const fileInfo = path.parse(stream.filename);
+                const fileName = path.join('public/upload/qrcode', 'project_' + ctx.session.sessionProjectData.id.toString() + '_qrcode_' + create_time + fileInfo.ext);
+                await ctx.helper.saveStreamFile(stream, path.join(this.app.baseDir, 'app', fileName));
+                await sendToWormhole(stream);
+                responseData.data = { base: fileInfo.base, path: fileName };
+            } catch (err) {
+                this.log(err);
+                responseData.err = 1;
+                responseData.msg = err;
+            }
+            ctx.body = responseData;
+        }
+
+        /**
+         * 项目设置页面
+         *
+         * @param {Object} ctx -egg 全局变量
+         * @return {void}
+         */
+        async setting(ctx) {
+
+            // 获取数据规则
+            const rule = ctx.service.project.rule('projectSetting');
+            const frontRule = ctx.helper.validateConvert(rule);
+
+            const projectData = ctx.session.sessionProjectData;
+
+            try {
+                // 查找数据
+                if (projectData.id === undefined) {
+                    throw '不存在对应的项目数据';
+                }
+                // 工程量清单列表
+                ctx.service.stdGclList.searchFilter(ctx.request.query);
+                const billList = await ctx.service.stdGclList.getListWithBuilder();
+
+                // 项目节列表
+                ctx.service.stdXmjList.searchFilter(ctx.request.query);
+                const projectChapterList = await ctx.service.stdXmjList.getListWithBuilder();
+
+                // 清单模板列表
+                ctx.service.billsTemplateList.searchFilter(ctx.request.query);
+                const standardList = await ctx.service.billsTemplateList.getListWithBuilder();
+
+                // 面包屑
+                const breadCrumb = this._getBreadCrumb(projectData);
+
+                const renderData = {
+                    rule: JSON.stringify(frontRule),
+                    projectData,
+                    status: ctx.service.project.displayStatus,
+                    statusClass: ctx.service.project.statusClass,
+                    billList,
+                    projectChapterList,
+                    standardList: JSON.stringify(standardList),
+                    breadCrumb,
+                };
+                await this.layout('project/setting.ejs', renderData);
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 保存项目设置操作
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async saveSetting(ctx) {
+            let id = ctx.params.id;
+            id = parseInt(id);
+
+            try {
+                if (isNaN(id) || id < 0) {
+                    throw '参数错误';
+                }
+                // 获取数据规则
+                const rule = ctx.service.project.rule('projectSetting');
+                ctx.validate(rule);
+
+                const result = await ctx.service.project.saveSetting(ctx.request.body, id);
+
+                if (!result) {
+                    throw '保存项目数据失败';
+                }
+
+                this.setMessage('保存项目数据成功', this.messageType.SUCCESS);
+                ctx.redirect('/project');
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 项目标段页面
+         *
+         * @param {Object} ctx -egg 全局变量
+         * @return {void}
+         */
+        async tender(ctx) {
+            const projectData = ctx.session.sessionProjectData;
+
+            try {
+                // 查找数据
+                if (projectData.id === undefined) {
+                    throw '不存在对应的项目数据';
+                }
+
+                const tenderList = await ctx.service.tender.getList();
+                if (tenderList !== null) {
+                    let i = 0;
+                    for (const tender of tenderList) {
+                        const projectAccountData = await ctx.service.projectAccount.getDataByCondition({ id: tender.user_id });
+                        tenderList[i].username = projectAccountData.name;
+                        i++;
+                    }
+                }
+
+                // 面包屑
+                const breadCrumb = this._getBreadCrumb(projectData);
+
+                const renderData = {
+                    projectData,
+                    status: ctx.service.project.displayStatus,
+                    statusClass: ctx.service.project.statusClass,
+                    tenderList,
+                    breadCrumb,
+                };
+                await this.layout('project/tender.ejs', renderData);
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 项目页面展示设置页面
+         *
+         * @param {Object} ctx -egg 全局变量
+         * @return {void}
+         */
+        async pageshow(ctx) {
+            const projectData = ctx.session.sessionProjectData;
+
+            try {
+                // 查找数据
+                if (projectData.id === undefined) {
+                    throw '不存在对应的项目数据';
+                }
+
+                // 面包屑
+                const breadCrumb = this._getBreadCrumb(projectData);
+
+                const renderData = {
+                    projectData,
+                    pageShowConst,
+                    status: ctx.service.project.displayStatus,
+                    statusClass: ctx.service.project.statusClass,
+                    breadCrumb,
+                };
+                await this.layout('project/page_show.ejs', renderData);
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+        /**
+         * 项目页面展示设置修改
+         *
+         * @param {Object} ctx -egg 全局变量
+         * @return {void}
+         */
+        async pageshowSet(ctx) {
+            const projectData = ctx.session.sessionProjectData;
+
+            try {
+                // 查找数据
+                if (projectData.id === undefined) {
+                    throw '不存在对应的项目数据';
+                }
+                const data = ctx.request.body;
+                delete data._csrf;
+                const result = await ctx.service.project.savePageshow(data, projectData.id);
+                if (result) {
+                    ctx.session.sessionProjectData.page_show = JSON.stringify(data);
+                    this.setMessage('设置页面显示成功', this.messageType.SUCCESS);
+                } else {
+                    throw '设置页面显示失败';
+                }
+                ctx.redirect(ctx.request.header.referer);
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 项目第三方接口设置页面
+         *
+         * @param {Object} ctx -egg 全局变量
+         * @return {void}
+         */
+        async api2other(ctx) {
+            const projectData = ctx.session.sessionProjectData;
+
+            try {
+                // 查找数据
+                if (projectData.id === undefined) {
+                    throw '不存在对应的项目数据';
+                }
+
+                // 面包屑
+                const breadCrumb = this._getBreadCrumb(projectData);
+
+                const renderData = {
+                    projectData,
+                    status: ctx.service.project.displayStatus,
+                    statusClass: ctx.service.project.statusClass,
+                    breadCrumb,
+                };
+                await this.layout('project/api2other.ejs', renderData);
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 第三方接口设置修改
+         *
+         * @param {Object} ctx -egg 全局变量
+         * @return {void}
+         */
+        async api2otherSave(ctx) {
+            const projectData = ctx.session.sessionProjectData;
+            const responseData = {
+                err: 0, msg: '', data: {},
+            };
+            try {
+                // 查找数据
+                if (projectData.id === undefined) {
+                    throw '不存在对应的项目数据';
+                }
+                const postData = JSON.parse(ctx.request.body.data);
+                if (postData.custom !== undefined) {
+                    if (projectData.secret === null) {
+                        postData.secret = ctx.helper.generateRandomString(20);
+                        ctx.session.sessionProjectData.secret = postData.secret;
+                    }
+                    ctx.session.sessionProjectData.custom = parseInt(postData.custom);
+                } else if (postData.can_api !== undefined) {
+                    ctx.session.sessionProjectData.can_api = parseInt(postData.can_api);
+                }
+                const result = await ctx.service.project.update(postData, { id: projectData.id });
+                if (result) {
+                    responseData.data = ctx.session.sessionProjectData;
+                    ctx.body = responseData;
+                } else {
+                    throw '入库失败';
+                }
+            } catch (error) {
+                console.log(error);
+                ctx.body = { err: 1, msg: error.toString(), data: null };
+            }
+        }
+
+        /**
+         * 项目办事处共享设置页面
+         *
+         * @param {Object} ctx -egg 全局变量
+         * @return {void}
+         */
+        async office(ctx) {
+            const projectData = ctx.session.sessionProjectData;
+
+            try {
+                // 查找数据
+                if (projectData.id === undefined) {
+                    throw '不存在对应的项目数据';
+                }
+
+                // 面包屑
+                const breadCrumb = this._getBreadCrumb(projectData);
+
+                const renderData = {
+                    projectData,
+                    status: ctx.service.project.displayStatus,
+                    statusClass: ctx.service.project.statusClass,
+                    // officeList: cldOfficeConst.list,
+                    cldOfficeConst,
+                    breadCrumb,
+                };
+                await this.layout('project/office.ejs', renderData);
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 项目办事处共享修改
+         *
+         * @param {Object} ctx -egg 全局变量
+         * @return {void}
+         */
+        async officeShare(ctx) {
+            const projectData = ctx.session.sessionProjectData;
+
+            try {
+                // 查找数据
+                if (projectData.id === undefined) {
+                    throw '不存在对应的项目数据';
+                }
+                const result = await ctx.service.project.saveOfficeShare(ctx.request.body, projectData.id);
+                if (result) {
+                    ctx.session.sessionProjectData.office_share = ctx.request.body.office_share.join(',');
+                    this.setMessage('设置办事处共享成功', this.messageType.SUCCESS);
+                } else {
+                    throw '设置办事处共享失败';
+                }
+                ctx.redirect(ctx.request.header.referer);
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 获取共用面包屑
+         *
+         * @param {Object} projectData - 项目数据
+         * @return {String} - 返回面包屑
+         */
+        _getBreadCrumb(projectData) {
+            // 面包屑
+            const breadCrumb = [
+                { name: this.ctx.topPermission.name },
+                { name: '项目版客户', url: this.ctx.controllerName },
+                { name: projectData.name },
+            ];
+            return this.ctx.helper.convertBreadCrumb(breadCrumb, 'replace');
+        }
+
+    }
+
+    return ProjectController;
+};

+ 570 - 0
app/controller/report_controller.js

@@ -0,0 +1,570 @@
+'use strict';
+
+/**
+ * 报表定制相关控制器
+ *
+ * @author Tony Kang
+ * @date 2019/04/23
+ * @version
+ */
+
+const COMMON_RPT_GRP_ID = 0;
+const JpcEx = require('../reports/rpt_component/jpc_ex');
+const JV = require('../reports/rpt_component/jpc_value_define');
+const RPT_MAPPING_FIELDS = require('../const/report_fields');
+const RPT_EXT_CODE = require('../const/report_ext_code');
+const preDefinedRptDataAnalyzor = require('../lib/rpt_data_analysis');
+const RPT_DEF_PROPERTIES = require('../const/report_defined_properties');
+
+
+module.exports = app => {
+    class ReportController extends app.BaseController {
+        /**
+         * 定制报表项目列表页
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async index(ctx) {
+            // console.log('report: 1');
+            // 获取消息提示
+            const message = this.ctx.session.message;
+            // 取出后删除
+            this.ctx.session.message = null;
+
+            // const mappingFields = await ctx.service.rptMappingField.getMappingFields();
+            const mappingFields = RPT_MAPPING_FIELDS;
+            // console.log(mappingFields[0].items[10]);
+            // const calcExtCode = await ctx.service.rptExtCodeTpl.getAllExtCodeTpl();
+            const calcExtCode = RPT_EXT_CODE;
+            // console.log('calcExtCode');
+            // console.log(calcExtCode);
+            const pid = ctx.request.query.pid;
+            let treeNode = [];
+            // console.log('pid: ' + pid);
+            if (pid && pid !== '') {
+                treeNode = await ctx.service.rptTreeNode.getNodesByProjectId([pid]);
+            }
+            const pCode = ctx.request.query.code;
+            const pName = ctx.request.query.name;
+
+            const defined_handle_options = preDefinedRptDataAnalyzor.analysisDefine;
+
+            const renderData = {
+                message: JSON.stringify(message),
+                isCommon: (pid === -1 || pid === '-1'),
+                project_info: pCode + '-' + pName,
+                cmn_rpt_data: JSON.stringify(treeNode),
+                cmn_rpt_mapping_fields: JSON.stringify(mappingFields),
+                calc_ext_code: JSON.stringify(calcExtCode),
+                // cmn_rpt_data: treeNode,
+                defined_handle_options: JSON.stringify(defined_handle_options),
+            };
+            // await this.layout('report/index.ejs', renderData); // 用this.layout方法会渲染统一界面风格
+            await this.ctx.render('report/index.ejs', renderData); // 这种渲染是完全从头开始渲染
+        }
+
+        /**
+         * 保存定制报表顶节点数据
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async save(ctx) {
+            try {
+                // console.log(ctx.request.body.params);
+                const params = JSON.parse(ctx.request.body.params),
+                    doc = params.doc;
+                doc.items = JSON.stringify(doc.items);
+                const result = await ctx.service.rptTreeNode.updateTopNode(doc);
+                if (!result) {
+                    throw '保存失败';
+                }
+                // this.setMessage('保存成功', this.messageType.SUCCESS);
+                ctx.body = {};
+                ctx.status = 201;
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                // ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        async partialUpdateTreeNode(ctx) {
+            // 这个是局部刷新,原理是根据topNodeID找到后台的topNode,根据路径(有多个路径,可以实现多个子节点同时刷新)来替换(nodeArray里的)后台数据库的相关节点
+            // 这里需要一个调整,就是新增目录及模板需要加给后缀(new(Date)).getTime() ,以保证不重叠(真重叠了算倒霉)
+            // pathArray的path描述:{ operation_type: '', // 操作类型:‘update’ ‘add’、‘delete’
+            //                       node_path: [],      // 节点路径:如:['02.广东', '03.增城北绕线项目定制报表'],表示Top节点的items下的那个‘02.广东’子节点(要full scan)的items下的 ‘03.增城北绕线项目定制报表’子节点
+            //                     }
+            // nodeArray: 与pathArray一一对应
+            try {
+                const params = JSON.parse(ctx.request.body.params);
+                // console.log(params);
+                const topNodeID = params.topNodeID;
+                const pathArray = params.pathArray;
+                const nodeArray = params.nodeArray;
+                const result = await ctx.service.rptTreeNode.getNodeById(topNodeID);
+                if (result && result.length === 1) {
+                    const topNodeItems = JSON.parse(result[0].items);
+                    const _getCurrentNodeSerialOrder = function(parentItems, pathName) {
+                        let rst = -1;
+                        if (parentItems) {
+                            for (let idx = 0; idx < parentItems.length; idx++) {
+                                if (parentItems[idx].name === pathName) {
+                                    rst = idx;
+                                    break;
+                                }
+                            }
+                        }
+                        return rst;
+                    };
+
+                    const _updateNodeByPath = function(path, node) {
+                        let rst = false;
+                        let tmpParentItems = topNodeItems;
+                        for (let idx = 0; idx < path.node_path.length; idx++) {
+                            const nIdx = _getCurrentNodeSerialOrder(tmpParentItems, path.node_path[idx]);
+                            if (nIdx >= 0) {
+                                if (idx === path.node_path.length - 1) {
+                                    tmpParentItems[nIdx] = node;
+                                    rst = true;
+                                } else {
+                                    tmpParentItems = tmpParentItems[nIdx].items;
+                                }
+                            }
+                        }
+                        return rst;
+                    };
+                    const _addNodeByPath = function(path, node) {
+                        let rst = false;
+                        // console.log('node:');
+                        // console.log(node);
+                        let tmpParentItems = topNodeItems;
+                        if (path === null || path === '' || path.node_path.length === 0) {
+                            topNodeItems.push(node);
+                            rst = true;
+                        } else {
+                            for (let idx = 0; idx < path.node_path.length; idx++) {
+                                const nIdx = _getCurrentNodeSerialOrder(tmpParentItems, path.node_path[idx]);
+                                if (nIdx >= 0) {
+                                    if (idx === path.node_path.length - 1) {
+                                        // console.log('tmpParentItems[nIdx]:');
+                                        // console.log(tmpParentItems[nIdx]);
+                                        if (tmpParentItems[nIdx].nodeType === 1) {
+                                            if (!tmpParentItems[nIdx].items) {
+                                                tmpParentItems[nIdx].items = [];
+                                            }
+                                            tmpParentItems[nIdx].items.push(node);
+                                            rst = true;
+                                        }
+                                    } else {
+                                        tmpParentItems = tmpParentItems[nIdx].items;
+                                    }
+                                }
+                            }
+                        }
+                        return rst;
+                    };
+                    const _deleteNodeByPath = function(path, node) {
+                        let rst = false;
+                        let tmpParentItems = topNodeItems;
+                        for (let idx = 0; idx < path.node_path.length; idx++) {
+                            const nIdx = _getCurrentNodeSerialOrder(tmpParentItems, path.node_path[idx]);
+                            if (nIdx >= 0) {
+                                if (idx === path.node_path.length - 1) {
+                                    // tmpParentItems[nIdx] = node;
+                                    tmpParentItems.splice(nIdx, 1);
+                                    rst = true;
+                                } else {
+                                    tmpParentItems = tmpParentItems[nIdx].items;
+                                }
+                            }
+                        }
+                        return rst;
+                    };
+                    if (pathArray && pathArray.length > 0 && nodeArray && nodeArray.length === pathArray.length) {
+                        for (let idx = 0; idx < pathArray.length; idx++) {
+                            if (pathArray[idx].operation_type === 'update') {
+                                _updateNodeByPath(pathArray[idx], nodeArray[idx]);
+                            } else if (pathArray[idx].operation_type === 'add') {
+                                _addNodeByPath(pathArray[idx], nodeArray[idx]);
+                            } else if (pathArray[idx].operation_type === 'delete') {
+                                _deleteNodeByPath(pathArray[idx], nodeArray[idx]);
+                            } else {
+                                // out of control
+                            }
+                        }
+                    }
+                    try {
+                        const doc = result[0];
+                        doc.items = JSON.stringify(topNodeItems);
+                        const updateRst = await ctx.service.rptTreeNode.updateTopNode(doc);
+                        // console.log(updateRst);
+                        if (!updateRst) {
+                            throw '更新失败';
+                        }
+                    } catch (ex) {
+                        console.log(ex);
+                    }
+                    ctx.body = {};
+                    ctx.status = 201;
+                } else {
+                    ctx.body = { msg: 'no result was found!' };
+                    ctx.status = 201;
+                }
+                // this.setMessage('保存成功', this.messageType.SUCCESS);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                // ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * copy报表模板
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async updateDetailNode(ctx) {
+            try {
+                const params = JSON.parse(ctx.request.body.params);
+                // console.log(params);
+                const result = await ctx.service.rptTreeNode.getNodeById(params.topNodeID);
+                if (result && result.length === 1) {
+                    const _updateNode = function(nodeItem) {
+                        let rst = false;
+                        try {
+                            if (nodeItem.ID === params.ID && nodeItem.name === params.name && nodeItem.refId === params.refId) {
+                                rst = true;
+                                if (!nodeItem.hasOwnProperty('flags')) {
+                                    nodeItem.flags = {};
+                                }
+                                nodeItem.flags[params.flagProp] = params.flagPropValue;
+                                // console.log('changed node: ');
+                                // console.log(nodeItem);
+                            }
+                            if (!rst && nodeItem.items && nodeItem.items.length > 0) {
+                                for (const subItem of nodeItem.items) {
+                                    rst = _updateNode(subItem);
+                                    if (rst) break;
+                                }
+                            }
+                        } catch (ex) {
+                            rst = false;
+                            console.log(ex);
+                        }
+                        return rst;
+                    };
+                    const tmpNodes = JSON.parse(result[0].items);
+                    // console.log(tmpNodes);
+                    // let hasChanged = false;
+                    for (const nItem of tmpNodes) {
+                        if (_updateNode(nItem)) {
+                            const doc = result[0];
+                            doc.items = JSON.stringify(tmpNodes);
+                            try {
+                                const updateRst = await ctx.service.rptTreeNode.updateTopNode(doc);
+                                if (!updateRst) {
+                                    throw '更新失败';
+                                }
+                            } catch (ex) {
+                                console.log(ex);
+                            }
+                            break;
+                        }
+                    }
+                    ctx.body = {};
+                    ctx.status = 201;
+                } else {
+                    ctx.body = { msg: 'no result was found!' };
+                    ctx.status = 201;
+                }
+                // this.setMessage('保存成功', this.messageType.SUCCESS);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                // ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * copy报表模板
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async copyNodes(ctx) {
+            try {
+                const params = JSON.parse(ctx.request.body.params),
+                    doc = params.doc;
+                doc.items = JSON.stringify(doc.items);
+                const result = await ctx.service.rptTreeNode.updateTopNode(doc);
+                if (!result) {
+                    throw '保存失败';
+                }
+                // this.setMessage('保存成功', this.messageType.SUCCESS);
+                ctx.body = {};
+                ctx.status = 201;
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                // ctx.redirect(ctx.request.header.referer);
+            }
+        }
+        /**
+         * 保存定制报表顶节点数据
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async createReportTemplate(ctx) {
+            try {
+                // console.log(ctx.request.body.params);
+                const params = JSON.parse(ctx.request.body.params);
+                let refDemoId = 4; // 默认流水式
+                if (params.rptDftTplId === 5) {
+                    // 交叉式
+                    refDemoId = 5;
+                } else if (params.rptDftTplId === 6) {
+                    // 账单式
+                    refDemoId = 6;
+                }
+                const demoTplData = await ctx.service.rptTpl.getTplById(refDemoId);
+                if (!demoTplData || demoTplData.length !== 1) {
+                    throw '创建模板失败';
+                }
+                demoTplData[0].rpt_tpl_name = params.rptName;
+                const rst = await ctx.service.rptTpl.copyAndCreateTemplate(demoTplData[0]);
+                if (!rst || rst.affectedRows !== 1) {
+                    throw '创建模板失败';
+                }
+                const rstTplData = await ctx.service.rptTpl.getTplById(rst.insertId);
+                ctx.body = { data: rstTplData[0] };
+                ctx.status = 201;
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+            }
+        }
+
+        /**
+         * 创建定制报表组(顶节点)数据
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async createNewRptGrp(ctx) {
+            try {
+                // console.log(ctx.request.body.params);
+                const params = JSON.parse(ctx.request.body.params);
+                const rst = await ctx.service.rptTreeNode.createNode(params.pid, params.name);
+                if (!rst || rst.affectedRows !== 1) {
+                    throw '创建报表组失败';
+                }
+                const newRptGrp = await ctx.service.rptTreeNode.getNodeById(rst.insertId);
+                const newData = {
+                    id: rst.insertId,
+                    pid: newRptGrp[0].pid,
+                    name: newRptGrp[0].name,
+                    rptAmt: 0,
+                    last_update_time: newRptGrp[0].last_update_time,
+                }
+                ctx.body = { data: newData };
+                ctx.status = 201;
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+            }
+        }
+
+        /**
+         * 修改报表组(顶节点)数据
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async updateRptGrp(ctx) {
+            try {
+                const params = JSON.parse(ctx.request.body.params);
+                const rst = await ctx.service.rptTreeNode.update({name: params.name},{id: params.id});
+                if (!rst) {
+                    throw '修改报表组失败';
+                }
+                ctx.body = { data: rst };
+                ctx.status = 201;
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+            }
+        }
+
+        async deleteRptGrp(ctx) {
+            try {
+                // console.log(ctx.request.body.params);
+                const params = JSON.parse(ctx.request.body.params);
+                const rptGrp = await ctx.service.rptTreeNode.getNodeById(params.id);
+                if (!rptGrp || (rptGrp[0].items && JSON.parse(rptGrp[0].items).length !== 0)) {
+                    throw '该报表组不存在或该报表组存在报表不可删';
+                }
+                const rst = await ctx.service.rptTreeNode.deleteById(params.id);
+                if (!rst) {
+                    throw '删除报表组失败';
+                }
+
+                ctx.body = { data: rst };
+                ctx.status = 201;
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+            }
+        }
+
+        /**
+         * 获取报表组(顶节点)数据
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async getRptGrp(ctx) {
+            try {
+                // console.log('in getRptGrp function.');
+                const params = JSON.parse(ctx.request.body.params);
+                // console.log(params);
+                const rptGrp = await ctx.service.rptTreeNode.getNodesByProjectId([params.pid]);
+                // console.log(rptGrp);
+                const getItmesAmt = function(topItems) {
+                    let rst = 0;
+                    if (topItems && topItems.length > 0) {
+                        // rst = topItems.length;
+                        for (const subItem of topItems) {
+                            if (subItem.nodeType === 2) {
+                                rst++;
+                            } else {
+                                rst = rst + getItmesAmt(subItem.items);
+                            }
+                        }
+                    }
+                    return rst;
+                };
+                const rst = [];
+                for (const rptNode of rptGrp) {
+                    const amt = getItmesAmt(JSON.parse(rptNode.items));
+                    rst.push({ id: rptNode.id, pid: params.pid, name: rptNode.name, rptAmt: amt, last_update_time: rptNode.last_update_time });
+                }
+                // console.log(rst);
+                ctx.body = { data: rst };
+                ctx.status = 201;
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+            }
+        }
+
+        /**
+         * 获取报表模板对象
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async getReportTemplate(ctx) {
+            try {
+                // console.log(ctx.request.body.params);
+                const params = JSON.parse(ctx.request.body.params);
+                const rstTplData = await ctx.service.rptTpl.getTplById(params.rptTplId);
+                // console.log(rstTplData);
+                if (!rstTplData || rstTplData.length !== 1) {
+                    throw '获取模板失败';
+                }
+                ctx.body = { data: rstTplData[0] };
+                ctx.status = 201;
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+            }
+        }
+
+        async updateRptTpl(ctx) {
+            try {
+                // console.log(ctx.request.body.params);
+                const params = JSON.parse(ctx.request.body.params);
+                const rstTplData = await ctx.service.rptTpl.updateRptTpl(params.tplId, params.tplName, params.rptTpl, params.cid);
+                // this.setMessage(params.tplName + ' 保存成功', this.messageType.SUCCESS);
+                const msg = this.createMsg(params.tplName + ' 保存成功', this.messageType.SUCCESS);
+                ctx.body = { data: rstTplData[0], message: msg };
+                ctx.status = 201;
+            } catch (ex) {
+                this.setMessage(ex.toString(), this.messageType.ERROR);
+            }
+
+        }
+
+        /**
+         * 获取报表预定义好的配置(字体、边框、输出控制)
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async getPreDefineCfg(ctx) {
+            try {
+                // const cfgData = await ctx.service.rptPreDefineCfg.getCfgById('Administrator');
+                // console.log(cfgData);
+                // if (!cfgData || cfgData.length !== 1) {
+                //     throw '获取预定义配置失败';
+                // }
+                // ctx.body = { data: cfgData[0].defined_content };
+                const cfgData = RPT_DEF_PROPERTIES;
+                ctx.body = { data: JSON.stringify(cfgData) };
+                ctx.status = 201;
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+            }
+        }
+
+        async getPreviewPage(ctx) {
+            try {
+                const params = JSON.parse(ctx.request.body.params);
+                const pageSize = params.pageSize;
+                let rptTpl = await ctx.service.rptTpl.getTplById(params.rpt_tpl_id);
+                if (!rptTpl || rptTpl.length !== 1) {
+                    throw '获取模板失败';
+                }
+                const printCom = JpcEx.createNew();
+                // const defProperties = await ctx.service.rptPreDefineCfg.getCfgById('Administrator');
+                const defProperties = RPT_DEF_PROPERTIES;
+                // JSON.parse(defProperties[0].defined_content);
+                rptTpl = JSON.parse(rptTpl[0].rpt_content);
+                rptTpl[JV.NODE_MAIN_INFO][JV.NODE_PAGE_INFO][JV.PROP_PAGE_SIZE] = pageSize;
+                setupSomeDftCustomizeCfg(rptTpl);
+                printCom.initialize(rptTpl);
+                // const pageRst = printCom.outputAsPreviewPage(rptTpl, JSON.parse(defProperties[0].defined_content));
+                const pageRst = printCom.outputAsPreviewPage(rptTpl, defProperties);
+                // console.log(pageRst);
+                ctx.body = { data: pageRst };
+                ctx.status = 201;
+            } catch (ex) {
+                this.setMessage(ex.toString(), this.messageType.ERROR);
+            }
+        }
+
+        createMsg(msg, type) {
+            const rst = {};
+            rst.message = msg;
+            rst.type = type;
+            switch (type) {
+                case this.messageType.SUCCESS:
+                    rst.icon = 'check';
+                    break;
+                case this.messageType.ERROR:
+                    rst.icon = 'exclamation-circle';
+                    break;
+                case this.messageType.INFO:
+                    rst.icon = 'info-circle';
+                    break;
+                case this.messageType.WARNING:
+                    rst.icon = 'warning';
+                    break;
+                default:
+                    break;
+            }
+            return rst;
+        }
+    }
+
+    return ReportController;
+};
+
+function setupSomeDftCustomizeCfg(rptTpl) {
+    rptTpl[JV.NODE_MAIN_INFO][JV.NODE_MARGINS][JV.PROP_LEFT] = 1.2;
+    rptTpl[JV.NODE_MAIN_INFO][JV.NODE_MARGINS][JV.PROP_RIGHT] = 1.2;
+};

+ 186 - 0
app/controller/report_project_controller.js

@@ -0,0 +1,186 @@
+'use strict';
+
+/**
+ * Created by Tony on 2019/6/24.
+ */
+
+const cldOfficeConst = require('../const/cld_office');
+
+module.exports = app => {
+    class ReportProjectController extends app.BaseController {
+        /**
+         * 定制报表项目列表页
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async index(ctx) {
+            const page = ctx.page;
+            const query = ctx.request.query;
+            const rpt_prj_List = [];
+            const filter = {};
+            let office = -1;
+            const managerSession = ctx.session.managerSession;
+            // 判断用户权限和所属办事处(PS:只有总部非造价研究中心和超级管理员可查看所有管理员用户,其它只显示该分组用户)
+            let officeList = [];
+            const permission = managerSession.permission;
+            if (permission === 'all' || parseInt(managerSession.office) === cldOfficeConst.type.ZONGBU) {
+                officeList = JSON.stringify(cldOfficeConst.list);
+            } else {
+                const offices = [];
+                const officeIdList = await ctx.service.project.getOfficeList(managerSession.office, managerSession.userID);
+                office = [];
+                for (const oi of officeIdList) {
+                    offices[oi.office] = cldOfficeConst.list[oi.office];
+                    office.push(oi.office);
+                }
+                office = office.length > 0 ? office : -1;
+                officeList = JSON.stringify(offices);
+            }
+
+            if (query.hasOwnProperty('office') && query.office !== '') {
+                office = query.office;
+            }
+
+            // console.log(1);
+            // filter.status = 2;
+            // filter.managerIds = [];
+            // if (query.hasOwnProperty('office') && query.office !== '') {
+            //     office = query.office;
+            //     if (query.select_sale_responsor !== '') {
+            //         // 找项目
+            //         filter.managerIds.push(query.select_sale_responsor);
+            //     } else {
+            //         // filter.managerIds.push(-1);
+            //     }
+            //     filter.office = office;
+            //     filter.name = query.keyword;
+            //     // ctx.service.project.searchFilterByManager(filter);
+            // } else {
+            //     office = -1;
+            //     filter.office = managerSession.office !== cldOfficeConst.type.ZONGBU ? managerSession.office : '';
+            // }
+            ctx.service.project.searchFilterByManager(ctx.request.query, managerSession);
+            const total = await ctx.service.project.getCountWithBuilder();
+            const projectList = await ctx.service.project.getListWithBuilder();
+            const ppl = await getResponsePeopleByOffice(ctx, office);
+            const prjsArr = [];
+            for (const prj of projectList) {
+                prjsArr.push(prj.id);
+            }
+            const rptGroups = [];
+            if (prjsArr.length > 0) {
+                const rptNodes = await ctx.service.rptTreeNode.getNodesByProjectId(prjsArr);
+                const getItmesAmt = function(topItems) {
+                    let rst = 0;
+                    if (topItems && topItems.length > 0) {
+                        // rst = topItems.length;
+                        for (const subItem of topItems) {
+                            if (subItem.nodeType === 2) {
+                                rst++;
+                            } else {
+                                rst = rst + getItmesAmt(subItem.items);
+                            }
+                        }
+                    }
+                    return rst;
+                };
+                for (const prj of projectList) {
+                    let response_name = '';
+                    const manager = await ctx.service.manager.getDataById(prj.manager_id);
+                    response_name = manager !== null ? manager.username : '';
+                    let group = 0;
+                    let pages = 0;
+                    for (const node of rptNodes) {
+                        if (node.pid === prj.id) {
+                            group++;
+                            const items = JSON.parse(node.items);
+                            const curAmt = getItmesAmt(items);
+                            pages += curAmt;
+                            rptGroups.push({ id: node.id, pid: prj.id, name: node.name, rptAmt: curAmt, last_update_time: node.last_update_time });
+                        }
+                    }
+                    rpt_prj_List.push({ project_id: prj.id, project_code: prj.code, project_name: prj.name, office_name: cldOfficeConst.list[prj.office], rpt_grp: group + '组 / ' + pages + '张', response_people: response_name });
+                }
+            }
+            // console.log(5);
+            let dropDownMenu = [];
+            if (managerSession.office === cldOfficeConst.type.ZONGBU || managerSession.permission === 'all') {
+                dropDownMenu = [
+                    { name: '编辑通用报表', url: 'report/modifyReport?pid=-1', isNewPage: 'T' },
+                    // { name: '添加项目', url: 'addpoj', isPopup: 'T' },
+                ];
+            }
+            // 分页相关
+            const pageInfo = {
+                page,
+                total: Math.ceil(total / app.config.pageSize),
+                queryData: JSON.stringify(ctx.urlInfo.query),
+            };
+            const renderData = {
+                rpt_prj_List,
+                pageInfo,
+                dropDownMenu,
+                officeList,
+                officePeople: JSON.stringify(ppl),
+                rptNodes: JSON.stringify(rptGroups),
+            };
+            await this.layout('report/indexRptPrj.ejs', renderData);
+        }
+
+        async getOfficeResponsePeople(ctx) {
+            // console.log('in getOfficeResponsePeople');
+            const params = JSON.parse(ctx.request.body.params);
+            const rst = await getResponsePeopleByOffice(ctx, params.office);
+            // console.log(rst);
+            ctx.body = { data: rst };
+            ctx.status = 201;
+        }
+
+        createMsg(msg, type) {
+            const rst = {};
+            rst.message = msg;
+            rst.type = type;
+            switch (type) {
+                case this.messageType.SUCCESS:
+                    rst.icon = 'check';
+                    break;
+                case this.messageType.ERROR:
+                    rst.icon = 'exclamation-circle';
+                    break;
+                case this.messageType.INFO:
+                    rst.icon = 'info-circle';
+                    break;
+                case this.messageType.WARNING:
+                    rst.icon = 'warning';
+                    break;
+                default:
+                    break;
+            }
+            return rst;
+        }
+    }
+
+    return ReportProjectController;
+};
+
+async function getResponsePeopleByOffice(ctx, office) {
+    // const queryKey = {};
+    const searchSession = {
+        columns: ['id', 'username', 'real_name', 'create_time', 'last_login', 'login_ip',
+            'group_id', 'token', 'can_login'],
+    };
+    office !== -1 ? searchSession.where = { office } : '';
+    // const searchSession = ctx.session.managerSession;
+    // queryKey.type = 1;
+    // searchSession.office = office;
+    // ctx.service.manager.searchFilter(queryKey, searchSession); // 依据manager service的参数逻辑,根据实际情况来造过滤参数
+    const managerList = await ctx.service.manager.getAllDataByCondition(searchSession);
+    const rst = [];
+    // rst.push({ id: -1, name: '所有人员' });
+    for (let idx = 0; idx < managerList.length; idx++) {
+        const manager = managerList[idx];
+        rst.push({ id: manager.id, name: manager.username });
+    }
+    return rst;
+}

+ 56 - 0
app/controller/sql_execute_controller.js

@@ -0,0 +1,56 @@
+'use strict';
+
+/**
+ * sql执行控制器
+ *
+ * @author CaiAoLin
+ * @date 2017/10/13
+ * @version
+ */
+
+module.exports = app => {
+
+    class SqlExecuteController extends app.BaseController {
+
+        /**
+         * sql执行页面
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        async index(ctx) {
+            const renderData = {
+                sql: '',
+                fieldList: [],
+                dataList: [],
+            };
+            await this.layout('sql_execute/index.ejs', renderData);
+        }
+
+        /**
+         * 执行sql
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        async execute(ctx) {
+            const sql = ctx.request.body.sql;
+            try {
+                const [fieldList, result] = await ctx.service.sqlExecute.execute(sql);
+                const renderData = {
+                    sql,
+                    fieldList,
+                    dataList: result,
+                };
+                await this.layout('sql_execute/index.ejs', renderData);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect('/sql');
+            }
+
+        }
+
+    }
+
+    return SqlExecuteController;
+};

+ 464 - 0
app/controller/standard_lib_controller.js

@@ -0,0 +1,464 @@
+'use strict';
+
+/**
+ * 标准库控制器基类
+ *
+ * @author CaiAoLin
+ * @date 2018/1/10
+ * @version
+ */
+
+const BaseController = require('../base/base_controller');
+const streamToArray = require('stream-to-array');
+const sendToWormhole = require('stream-wormhole');
+const fs = require('fs');
+const path = require('path');
+// excel解析
+const excel = require('node-xlsx');
+const standardConst = require('../const/standard');
+
+class StandardLibController extends BaseController {
+
+    /**
+     * 构造函数
+     *
+     * @param {Object} ctx - egg全局变量
+     * @param {Object} setting
+     * @return {void}
+     */
+    constructor(ctx, setting) {
+        super(ctx);
+        this.model = setting.model;
+        this.listModel = setting.listModel;
+        this.title = setting.title;
+        this.viewFolder = setting.viewFolder;
+        this.app = null;
+    }
+
+    /**
+     * 项目节数据上传
+     *
+     * @param {Object} ctx - egg全局变量
+     * @return {void}
+     */
+    async upload(ctx) {
+        const stream = await ctx.getFileStream();
+        let result = true;
+        let buffer = null;
+        const fileName = path.join(this.app.config.filePath, 'cache', stream.filename);
+        const listId = parseInt(stream.fields.id);
+        try {
+            if (isNaN(listId) || listId <= 0) {
+                throw '参数错误';
+            }
+            // 读取字节流
+            const parts = await streamToArray(stream);
+            // 转化为buffer
+            buffer = Buffer.concat(parts);
+            // 写入文件
+            await fs.writeFileSync(fileName, buffer);
+
+            // 读取文件
+            const sheet = excel.parse(fileName);
+            if (sheet[0] === undefined || sheet[0].data === undefined) {
+                throw 'excel没有对应数据';
+            }
+
+            result = await this.model.importData(sheet[0].data, listId);
+            if (!result) {
+                throw '导入数据失败!';
+            }
+
+            ctx.redirect('/' + ctx.controllerName + '/detail/' + listId);
+        } catch (error) {
+            this.ctx.helper.log(error);
+            // 失败需要消耗掉stream 以防卡死
+            await sendToWormhole(stream);
+            this.setMessage(error.toString(), this.messageType.ERROR);
+            ctx.redirect(ctx.request.header.referrer);
+        }
+    }
+
+    async uploadJSON(ctx) {
+        const stream = await ctx.getFileStream();
+        let result = true;
+        const fileName = path.join(this.app.config.filePath, 'cache', stream.filename);
+        const listId = parseInt(stream.fields.id);
+        try {
+            if (isNaN(listId) || listId <= 0) {
+                throw '参数错误';
+            }
+            await this.ctx.helper.saveStreamFile(stream, fileName);
+
+            // 读取文件
+            const stdData = await fs.readFileSync(fileName, 'utf8');
+
+            await this.model.importJsonData(stdData, listId);
+
+            ctx.redirect('/' + ctx.controllerName + '/detail/' + listId);
+        } catch (error) {
+            ctx.helper.log(error);
+            // 失败需要消耗掉stream 以防卡死
+            await sendToWormhole(stream);
+            this.setMessage(error.toString(), this.messageType.ERROR);
+            ctx.redirect(ctx.request.header.referer);
+        }
+    }
+
+    /**
+     * 根据id获取子项
+     *
+     * @param {Object} ctx - egg全局变量
+     * @return {void}
+     */
+    async getChildren(ctx) {
+        const responseData = {
+            err: 0,
+            msg: '',
+            data: [],
+        };
+        try {
+            let id = ctx.request.body.id;
+            let listId = ctx.request.body.listId;
+            id = parseInt(id);
+            listId = parseInt(listId);
+            if (isNaN(id) || id <= 0 || isNaN(listId) || listId <= 0) {
+                throw '参数错误';
+            }
+            const condition = { pid: id, list_id: listId };
+            const libData = await this.model.getAllDataByCondition({ where: condition });
+
+            responseData.data = libData;
+        } catch (error) {
+            this.ctx.helper.log(error);
+            responseData.err = 1;
+            responseData.msg = error;
+        }
+
+        ctx.body = responseData;
+    }
+
+    /**
+     * 导出数据
+     *
+     * @param {Object} ctx - egg全局变量
+     * @return {void}
+     */
+    async detailExport(ctx) {
+        try {
+            let id = ctx.params.id;
+            id = parseInt(id);
+
+            if (isNaN(id) || id <= 0) {
+                throw '参数错误';
+            }
+
+            // 获取导出数据
+            const exportData = await this.model.getExportData(id);
+
+            // 存入文件
+            const buffer = excel.build([{ name: 'sheet', data: exportData }]);
+            const fileName = this.model.tableName + '.xlsx';
+            const filePath = path.join(this.app.config.filePath, 'cache', fileName);
+            fs.writeFileSync(filePath, buffer, 'binary');
+            const stats = fs.statSync(filePath);
+            // 下载相关header
+            ctx.set({
+                'Content-Type': 'application/octet-stream',
+                'Content-Disposition': 'attachment; filename=' + fileName,
+                'Content-Length': stats.size,
+            });
+            const stream = fs.createReadStream(filePath).pipe(ctx.res);
+            ctx.body = stream.pipe(ctx.res);
+
+            // 输出文件后删除
+            fs.unlinkSync(filePath);
+        } catch (error) {
+            this.ctx.helper.log(error);
+            this.setMessage(error.toString(), this.messageType.ERROR);
+            ctx.redirect(ctx.request.header.referrer);
+        }
+    }
+
+    async loadDetailData(ctx) {
+        try {
+            const listId = ctx.params.id;
+            if (isNaN(listId) || listId <= 0) {
+                throw '参数错误';
+            }
+            const data = JSON.parse(ctx.request.body.data);
+            if (!data.dType) {
+                throw '参数错误';
+            }
+            if (data.dType === 'all') {
+                const libData = await this.model.getData(listId);
+                ctx.body = {err: 0, msg: '', data: libData};
+            }
+        } catch (error) {
+            this.ctx.helper.log(error);
+            if (error.stack) {
+                ctx.body = {err: 1, msg: '未知错误', data: null};
+            } else {
+                ctx.body = {err: 1, msg: error, data: null};
+            }
+        }
+    }
+
+    /**
+     * 列表页
+     *
+     * @param {Object} ctx - egg全局变量
+     * @return {void}
+     */
+    async index(ctx) {
+        const page = ctx.page;
+
+        // 获取标准清单数据
+        this.listModel.searchFilter(ctx.request.query);
+        const standardList = await this.listModel.getListWithBuilder();
+        const total = await this.listModel.count();
+        // 获取所有计价规范列表
+        const valuationList = await ctx.service.valuationList.getAllDataByCondition();
+        let check_array = [];
+        switch (this.title) {
+            case '工程量清单':
+                for (const value of valuationList) {
+                    if (value.bill_id !== '' && value.bill_id !== null) {
+                        check_array = check_array.concat(value.bill_id.split(','));
+                    }
+                    if (value.list_bill_id !== '' && value.list_bill_id !== null) {
+                        check_array = check_array.concat(value.list_bill_id.split(','));
+                    }
+                }
+                break;
+            case '项目节':
+                for (const value of valuationList) {
+                    if (value.chapter_id !== '' && value.chapter_id !== null) {
+                        check_array = check_array.concat(value.chapter_id.split(','));
+                    }
+                    if (value.list_chapter_id !== '' && value.list_chapter_id !== null) {
+                        check_array = check_array.concat(value.list_chapter_id.split(','));
+                    }
+                }
+                break;
+            case '台账模板':
+                for (const value of valuationList) {
+                    if (value.template_id !== '' && value.template_id !== null) {
+                        check_array = check_array.concat(value.template_id.split(','));
+                    }
+                    if (value.list_template_id !== '' && value.list_template_id !== null) {
+                        check_array = check_array.concat(value.list_template_id.split(','));
+                    }
+                }
+                break;
+            default: break;
+        }
+        for (const index in standardList) {
+            standardList[index].canDel = check_array.indexOf(standardList[index].id.toString()) === -1;
+        }
+
+        // 分页相关
+        const pageInfo = {
+            page,
+            total: Math.ceil(total / ctx.app.config.pageSize),
+            queryData: JSON.stringify(ctx.urlInfo.query),
+        };
+        const dropDownMenu = [
+            { name: '新增' + this.title, url: ctx.controllerName + '/add/0' },
+        ];
+
+        const renderData = {
+            standardList,
+            pageInfo,
+            dropDownMenu,
+        };
+        await this.layout(this.viewFolder + '/index.ejs', renderData);
+    }
+
+    /**
+     * 新增
+     *
+     * @param {Object} ctx - egg全局变量
+     * @return {void}
+     */
+    async add(ctx) {
+        const rule = this.listModel.rule();
+        const jsValidator = await this.jsValidator.convert(rule).build();
+
+        const standardListData = {};
+        const renderData = {
+            jsValidator,
+            standardListData,
+        };
+        await this.layout(this.viewFolder + '/save.ejs', renderData);
+    }
+
+    /**
+     * 保存列表数据
+     *
+     * @param {Object} ctx - egg全局变量
+     * @return {void}
+     */
+    async save(ctx) {
+        try {
+            const id = ctx.params.id;
+            const rule = this.listModel.rule();
+            ctx.validate(rule);
+
+            const result = await this.listModel.save(ctx.request.body, id);
+            if (!result) {
+                throw '保存失败';
+            }
+
+            ctx.redirect('/' + ctx.controllerName);
+        } catch (error) {
+            this.ctx.helper.log(error);
+            this.setMessage(error.toString(), this.messageType.ERROR);
+            ctx.redirect(ctx.request.header.referer);
+        }
+    }
+
+    /**
+     * 删除
+     *
+     * @param {Object} ctx - egg全局变量
+     * @return {void}
+     */
+    async delete(ctx) {
+        let id = ctx.params.id;
+        id = parseInt(id);
+        try {
+            if (isNaN(id) || id <= 0) {
+                throw 'id错误';
+            }
+            const result = await this.listModel.delete(id);
+            if (!result) {
+                throw '删除失败';
+            }
+
+            this.setMessage('删除成功', this.messageType.SUCCESS);
+        } catch (error) {
+            this.setMessage(error.toString(), this.messageType.ERROR);
+        }
+
+        ctx.redirect(ctx.request.header.referer);
+    }
+
+    /**
+     * 详情页面
+     *
+     * @param {Object} ctx - egg全局变量
+     * @return {void}
+     */
+    async detail(ctx) {
+        let id = ctx.params.id;
+        try {
+            id = parseInt(id);
+            if (isNaN(id) || id <= 0) {
+                throw '参数错误';
+            }
+
+            const dropDownMenu = [
+                { name: '导入json数据', url: ctx.controllerName + '/detail-upload/' + id },
+            ];
+
+            const renderData ={
+                dropDownMenu,
+                listId: id,
+                nodeType: JSON.stringify(standardConst.nodeType),
+                downloadJsonUrl: '/' + ctx.controllerName + '/detail/' + id + '/download/' + this.title + '.json'
+            };
+            await this.layout(this.viewFolder + '/detail.ejs', renderData);
+
+        } catch (error) {
+            this.ctx.helper.log(error);
+            ctx.redirect('/' + ctx.controllerName);
+        }
+    }
+
+    /**
+     * 上传页面
+     *
+     * @param {Object} ctx - egg全局变量
+     * @return {void}
+     */
+    async detailUpload(ctx) {
+        const id = ctx.params.id;
+        try {
+            if (isNaN(id) || id <= 0) {
+                throw '参数错误';
+            }
+
+            const renderData = {
+                id,
+            };
+            await this.layout(this.viewFolder + '/detail_save.ejs', renderData);
+        } catch (error) {
+            this.ctx.helper.log(error);
+            ctx.redirect(ctx.request.header.referrer);
+        }
+    }
+
+    async updateDetailData(ctx) {
+        try {
+            const listId = parseInt(ctx.params.id);
+            if (isNaN(listId) || listId <= 0) throw '参数错误';
+            const data = JSON.parse(ctx.request.body.data);
+
+            const responseData = {err: 0, msg: '', data: null};
+            switch (data.postType) {
+                case 'add':
+                    responseData.data = await this.model.addNode(listId, data.updateData);
+                    break;
+                case 'delete':
+                    responseData.data = await this.model.delete(listId, data.updateData, data.count);
+                    break;
+                case 'up-move':
+                    responseData.data = await this.model.upMoveNode(listId, data.updateData, data.count);
+                    break;
+                case 'down-move':
+                    responseData.data = await this.model.downMoveNode(listId, data.updateData, data.count);
+                    break;
+                case 'up-level':
+                    responseData.data = await this.model.upLevelNode(listId, data.updateData, data.count);
+                    break;
+                case 'down-level':
+                    responseData.data = await this.model.downLevelNode(listId, data.updateData, data.count);
+                    break;
+                case 'update':
+                    responseData.data = await this.model.updateInfos(listId, data.updateData);
+                    break;
+                default:
+                    throw '未知操作';
+            }
+            ctx.body = responseData;
+        } catch(err) {
+            this.ctx.helper.log(err);
+            if (err.stack) {
+                ctx.body = {err: 1, msg: '未知错误', data: null};
+            } else {
+                ctx.body = {err: 1, msg: err, data: null};
+            }
+        }
+    }
+
+    async exportDetailJsonData(ctx) {
+        const id = ctx.params.id;
+        const file = ctx.params.file;
+        if (file === this.title + '.json') {
+            try {
+                const create_time = Date.parse(new Date()) / 1000;
+                const fileName = path.join(this.app.config.filePath, 'cache', this.title + '-' + create_time + '.json');
+                const data = await this.model.exportJsonData(id);
+                await ctx.helper.saveBufferFile(JSON.stringify(data,"","\t"), fileName);
+                ctx.body = await fs.readFileSync(fileName);
+                // 输出文件后删除
+                fs.unlinkSync(fileName);
+            } catch(err) {
+                this.log(err);
+            }
+        }
+    }
+}
+
+module.exports = StandardLibController;

+ 34 - 0
app/controller/std_gcl_controller.js

@@ -0,0 +1,34 @@
+'use strict';
+
+/**
+ * 工程量清单相关控制器
+ *
+ * @author CaiAoLin
+ * @date 2018/1/10
+ * @version
+ */
+
+const StandardLibController = require('./standard_lib_controller');
+module.exports = app => {
+
+    class StdGclController extends StandardLibController {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx, {
+                model: ctx.service.stdGcl,
+                listModel: ctx.service.stdGclList,
+                title: '工程量清单',
+                viewFolder: 'std_gcl',
+            });
+            this.app = app;
+        }
+    }
+
+    return StdGclController;
+};

+ 32 - 0
app/controller/std_xmj_controller.js

@@ -0,0 +1,32 @@
+'use strict';
+
+/**
+ * 项目节相关控制器
+ *
+ * @author CaiAoLin
+ * @date 2018/1/4
+ * @version
+ */
+const StandardLibController = require('./standard_lib_controller');
+module.exports = app => {
+    class StdXmjController extends StandardLibController {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx, {
+                model: ctx.service.stdXmj,
+                listModel: ctx.service.stdXmjList,
+                title: '项目节',
+                viewFolder: 'std_xmj',
+            });
+            this.app = app;
+        }
+    }
+
+    return StdXmjController;
+};

+ 27 - 0
app/controller/ueditor_controller.js

@@ -0,0 +1,27 @@
+'use strict';
+
+/**
+ * ueditor相关控制器
+ *
+ * @author CaiAoLin
+ * @date 2017/10/27
+ * @version
+ */
+
+module.exports = app => {
+    class UEditorController extends app.BaseController {
+
+        /**
+         * 读取配置action
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async getConfig(ctx) {
+            ctx.set('Content-Type', 'application/json');
+            ctx.redirect('/public/js/ueditor/nodejs/config.json');
+        }
+    }
+
+    return UEditorController;
+};

+ 222 - 0
app/controller/valuation_controller.js

@@ -0,0 +1,222 @@
+'use strict';
+
+/**
+ * 计价标准相关控制器
+ *
+ * @author EllisRan
+ * @date 2019/9/16
+ * @version
+ */
+module.exports = app => {
+    class ValuationController extends app.BaseController {
+        /**
+         * 列表页
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async index(ctx) {
+            const page = ctx.page;
+
+            // 获取计价标准数据
+            ctx.service.valuationList.searchFilter(ctx.request.query);
+            const valuationList = await ctx.service.valuationList.getListWithBuilder();
+            const total = await ctx.service.valuationList.count();
+
+            // 获取已选计价标准的项目信息
+            const projectList = await ctx.service.project.getAllDataByCondition();
+            let check_array = [];
+            for (const project of projectList) {
+                if (project.valuation !== '' && project.valuation !== null) {
+                    check_array = check_array.concat(project.valuation.split(','));
+                }
+            }
+
+            for (const index in valuationList) {
+                valuationList[index].canDel = check_array.indexOf(valuationList[index].id.toString()) === -1;
+            }
+
+            // 分页相关
+            const pageInfo = {
+                page,
+                total: Math.ceil(total / ctx.app.config.pageSize),
+                queryData: JSON.stringify(ctx.urlInfo.query),
+            };
+
+            const dropDownMenu = [
+                { name: '添加新标准', url: ctx.controllerName + '/add/0' },
+            ];
+
+            const renderData = {
+                valuationList,
+                pageInfo,
+                dropDownMenu,
+            };
+            await this.layout('valuation/index.ejs', renderData);
+        }
+
+        /**
+         * 新增
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async add(ctx) {
+            const rule = ctx.service.valuationList.rule();
+            const jsValidator = await this.jsValidator.convert(rule).build();
+
+            const valuationListData = {};
+            const renderData = {
+                jsValidator,
+                valuationListData,
+            };
+            await this.layout('valuation/save.ejs', renderData);
+        }
+
+        /**
+         * 保存列表数据
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async save(ctx) {
+            try {
+                const id = ctx.params.id;
+                const rule = ctx.service.valuationList.rule();
+                ctx.validate(rule);
+                const result = await ctx.service.valuationList.save(ctx.request.body, id);
+                if (!result) {
+                    throw '保存失败';
+                }
+
+                ctx.redirect('/' + ctx.controllerName);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 删除
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async delete(ctx) {
+            let id = ctx.params.id;
+            id = parseInt(id);
+            try {
+                if (isNaN(id) || id <= 0) {
+                    throw 'id错误';
+                }
+                const result = await ctx.service.valuationList.deleteValuation(id);
+                if (!result) {
+                    throw '删除失败';
+                }
+
+                this.setMessage('删除成功', this.messageType.SUCCESS);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+            }
+
+            ctx.redirect(ctx.request.header.referer);
+        }
+
+        /**
+         * 详情页面
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async detail(ctx) {
+            let id = ctx.params.id;
+            try {
+                id = parseInt(id);
+                if (isNaN(id) || id <= 0) {
+                    throw '参数错误';
+                }
+                const valuationInfo = await ctx.service.valuationList.getDataById(id);
+                // 工程量清单列表
+                const billList = await ctx.service.stdGclList.getAllDataByCondition();
+
+                // 项目节列表
+                const projectChapterList = await ctx.service.stdXmjList.getAllDataByCondition();
+
+                // 清单模板列表
+                const standardList = await ctx.service.billsTemplateList.getAllDataByCondition();
+
+                const rule = ctx.service.valuationList.rule();
+                const jsValidator = await this.jsValidator.convert(rule).build();
+
+                const renderData = {
+                    jsValidator,
+                    valuationInfo,
+                    billList,
+                    projectChapterList,
+                    standardList,
+                    billList2: JSON.stringify(billList),
+                    projectChapterList2: JSON.stringify(projectChapterList),
+                    standardList2: JSON.stringify(standardList),
+                };
+                await this.layout('valuation/detail.ejs', renderData, 'valuation/detail_modal.ejs');
+
+            } catch (error) {
+                ctx.redirect('/' + ctx.controllerName);
+            }
+        }
+
+        /**
+         * 保存计量标准数据
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async detailSave(ctx) {
+            try {
+                const responseData = {
+                    err: 0, msg: '', data: {},
+                };
+                const id = ctx.params.id;
+                const postData = JSON.parse(ctx.request.body.data);
+                const data = {};
+                if (postData.name !== undefined && postData.name !== null && postData.name !== '') {
+                    data.name = postData.name;
+                } else {
+                    const type = postData.type;
+                    switch (type) {
+                        case 'chapter_id':
+                            data.chapter_id = postData.list;
+                            break;
+                        case 'bill_id':
+                            data.bill_id = postData.list;
+                            break;
+                        case 'template_id':
+                            data.template_id = postData.list;
+                            break;
+                        case 'list_chapter_id':
+                            data.list_chapter_id = postData.list;
+                            break;
+                        case 'list_bill_id':
+                            data.list_bill_id = postData.list;
+                            break;
+                        case 'list_template_id':
+                            data.list_template_id = postData.list;
+                            break;
+                        default: throw '参数有误';
+                    }
+                }
+                const result = await ctx.service.valuationList.save(data, id);
+                if (result) {
+                    ctx.body = responseData;
+                } else {
+                    throw '添加失败';
+                }
+            } catch (error) {
+                console.log(error);
+                ctx.body = { err: 1, msg: error.toString(), data: null };
+            }
+        }
+    }
+
+    return ValuationController;
+};

+ 128 - 0
app/controller/version_controller.js

@@ -0,0 +1,128 @@
+'use strict';
+
+/**
+ * 版本管理控制器
+ *
+ * @author CaiAoLin
+ * @date 2017/10/25
+ * @version
+ */
+
+module.exports = app => {
+    class VersionController extends app.BaseController {
+
+        /**
+         * 消息列表
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async index(ctx) {
+            const page = ctx.page;
+
+            // 筛选数据
+            ctx.sort = ['id', 'DESC'];
+            ctx.service.version.searchFilter(ctx.request.query);
+            // 获取数据
+            const versionList = await ctx.service.version.getListWithBuilder();
+            const total = await ctx.service.version.count();
+
+            // 分页相关
+            const pageInfo = {
+                page,
+                total: Math.ceil(total / app.config.pageSize),
+                queryData: JSON.stringify(ctx.urlInfo.query),
+            };
+
+            const dropDownMenu = [
+                { name: '添加新版本', url: ctx.controllerName + '/add/0' },
+            ];
+
+            const renderData = {
+                // versionList,
+                versionList: JSON.parse(JSON.stringify(versionList).replace(/\\r\\n/g, '<br>').replace(/\\"/g, '&#34').replace(/'/g, '&#39')),
+                pageInfo,
+                dropDownMenu,
+            };
+            await this.layout('version/index.ejs', renderData, 'version/modal.ejs');
+        }
+
+        /**
+         * 新增
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async add(ctx) {
+            const rule = ctx.service.version.rule();
+            const jsValidator = await this.jsValidator.convert(rule).build();
+
+            const id = ctx.params.id;
+            const versionData = parseInt(id) === 0 ? {} : await ctx.service.version.getDataById(id);
+            let breadCrumb = [
+                { name: ctx.topPermission.name },
+                { name: '升级说明', url: ctx.controllerName },
+                { name: parseInt(id) === 0 ? '添加版本' : '修改版本' },
+            ];
+            breadCrumb = ctx.helper.convertBreadCrumb(breadCrumb, 'replace');
+            const renderData = {
+                jsValidator,
+                versionData,
+                breadCrumb,
+            };
+            await this.layout('version/save.ejs', renderData);
+        }
+
+        /**
+         * 保存列表数据
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async save(ctx) {
+            try {
+                const id = ctx.params.id;
+                const rule = ctx.service.version.rule();
+                ctx.validate(rule);
+                const result = await ctx.service.version.save(ctx.request.body, id);
+                if (!result) {
+                    throw '保存失败';
+                }
+
+                ctx.redirect('/' + ctx.controllerName);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 删除
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async delete(ctx) {
+            let id = ctx.params.id;
+            id = parseInt(id);
+            try {
+                if (isNaN(id) || id <= 0) {
+                    throw 'id错误';
+                }
+                const result = await ctx.service.version.deleteById(id);
+                if (!result) {
+                    throw '删除失败';
+                }
+
+                this.setMessage('删除成功', this.messageType.SUCCESS);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+            }
+
+            ctx.redirect(ctx.request.header.referer);
+        }
+
+    }
+
+    return VersionController;
+};

+ 139 - 0
app/controller/white_list_controller.js

@@ -0,0 +1,139 @@
+'use strict';
+
+/**
+ * 白名单控制器
+ *
+ * @author CaiAoLin
+ * @date 2017/10/16
+ * @version
+ */
+
+module.exports = app => {
+
+    class WhiteListController extends app.BaseController {
+
+        /**
+         * 白名单列表页
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        async index(ctx) {
+            // 筛选数据
+            ctx.service.whiteList.searchFilter(ctx.request.query);
+            const whiteListData = await ctx.service.whiteList.getListWithBuilder();
+            const total = await ctx.service.whiteList.count();
+
+            // 分页相关
+            const pageInfo = {
+                page: ctx.page,
+                total: parseInt(total / app.config.pageSize),
+                queryData: JSON.stringify(ctx.urlInfo.query),
+            };
+
+            const dropDownMenu = [
+                { name: '新增白名单', url: ctx.controllerName + '/add' },
+            ];
+
+            const renderData = {
+                whiteListData,
+                pageInfo,
+                type: ctx.service.whiteList.typeDisplay,
+                whiteListType: JSON.stringify(ctx.service.whiteList.typeDisplay),
+                dropDownMenu,
+            };
+            await this.layout('white_list/index.ejs', renderData);
+        }
+
+        /**
+         * 新增白名单页面
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        async add(ctx) {
+            // 获取验证规则
+            const rule = ctx.service.whiteList.rule();
+            const jsValidator = await this.jsValidator.convert(rule).build();
+
+            let breadCrumb = [
+                { name: '白名单列表', url: ctx.controllerName },
+            ];
+            breadCrumb = ctx.helper.convertBreadCrumb(breadCrumb);
+            const renderData = {
+                jsValidator,
+                whiteListType: JSON.stringify(ctx.service.whiteList.typeDisplay),
+                whiteListData: [],
+                breadCrumb,
+            };
+
+            await this.layout('white_list/save.ejs', renderData);
+        }
+
+        /**
+         * 修改白名单
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        async modify(ctx) {
+            try {
+                let id = ctx.params.id;
+                id = parseInt(id);
+
+                // 获取验证规则
+                const rule = ctx.service.whiteList.rule();
+                const jsValidator = await this.jsValidator.convert(rule).build();
+
+                // 查找对应数据
+                const whiteListData = await ctx.service.whiteList.getDataById(id);
+
+                let breadCrumb = [
+                    { name: '白名单列表', url: ctx.controllerName },
+                ];
+                breadCrumb = ctx.helper.convertBreadCrumb(breadCrumb);
+                const renderData = {
+                    jsValidator,
+                    whiteListType: JSON.stringify(ctx.service.whiteList.typeDisplay),
+                    whiteListData,
+                    breadCrumb,
+                };
+                await this.layout('white_list/save.ejs', renderData);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 保存白名单数据
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        async save(ctx) {
+            try {
+                const id = ctx.params.id;
+
+                // 获取验证规则
+                const rule = ctx.service.whiteList.rule();
+                ctx.validate(rule);
+
+                const result = await ctx.service.whiteList.save(ctx.request.body, id);
+                if (!result) {
+                    throw '保存失败';
+                }
+
+                ctx.redirect('/white-list');
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+                return ctx.redirect(ctx.request.header.referer);
+            }
+
+        }
+
+    }
+
+    return WhiteListController;
+};

+ 383 - 0
app/extend/helper.js

@@ -0,0 +1,383 @@
+'use strict';
+
+/**
+ * 辅助方法扩展
+ *
+ * @author CaiAoLin
+ * @date 2017/9/28
+ * @version
+ */
+
+const fs = require('fs');
+const path = require('path');
+const streamToArray = require('stream-to-array');
+
+module.exports = {
+    /**
+     * 内置验证器转换为前端jquery验证器
+     *
+     * @param {Object} rule - 后端service中的rule
+     * @return {Object} - 返回转换后的数据
+     */
+    validateConvert(rule) {
+        const result = {};
+        if (Object.keys(rule).length <= 0) {
+            return rule;
+        }
+
+        for (const index in rule) {
+            result[index] = {};
+            const type = rule[index].type !== undefined && rule[index].type !== '' ? rule[index].type : '';
+            const stringType = ['string', 'password'];
+            // 是否必填
+            if (rule[index].required !== undefined) {
+                result[index].required = rule[index].required;
+            }
+
+            // 最小长度
+            if (stringType.indexOf(type) >= 0 && rule[index].min !== undefined) {
+                result[index].minlength = rule[index].min;
+            }
+
+            // 最大长度
+            if (stringType.indexOf(type) >= 0 && rule[index].max !== undefined) {
+                result[index].maxlength = rule[index].max;
+            }
+
+            // 密码相关
+            if (type === 'password' && rule[index].compare !== undefined) {
+                result[index].equalTo = '#' + rule[index].compare;
+            }
+
+            // 最小值
+            const integerType = ['integer', 'int', 'Number'];
+            if (integerType.indexOf(type) >= 0 && rule[index].min !== undefined) {
+                result[index].min = rule[index].min;
+            }
+
+            // 最大值
+            if (integerType.indexOf(type) >= 0 && rule[index].max !== undefined) {
+                result[index].max = rule[index].max;
+            }
+
+            // 自定义判断
+            const customType = ['mobile', 'ip'];
+            // 自定义且带参数
+            if (customType.indexOf(type) >= 0) {
+                result[index][type] = true;
+                if (rule[index].allowEmpty !== undefined) {
+                    result[index].required = !rule[index].allowEmpty;
+                }
+            }
+
+        }
+
+        return result;
+    },
+
+    generateProjectCodeString(length, minNumberLength) {
+        const stringSeed = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S',
+            'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
+        const numberSeed = [2, 3, 4, 5, 6, 7, 8, 9];
+        let match = false, result;
+        while (!match) {
+            result = '';
+
+            for (let i = 0; i < length; i++) {
+                let seed;
+                if (i === 0) {
+                    seed = stringSeed;
+                } else {
+                    seed = stringSeed.concat(numberSeed);
+                }
+                const seedLength = seed.length - 1;
+                const index = Math.ceil(Math.random() * seedLength) - 1;
+
+                result += seed[index];
+            }
+            const num = result.match(/[2-9]/g);
+            match = num && num.length >= minNumberLength;
+        }
+
+        return result;
+    },
+
+    /**
+     * 生成随机字符串
+     *
+     * @param {Number} length - 需要生成字符串的长度
+     * @param {Number} type - 1为数字和字符 2为纯数字 3为纯字母
+     * @return {String} - 返回生成结果
+     */
+    generateRandomString(length, type = 1) {
+        length = parseInt(length);
+        length = isNaN(length) ? 1 : length;
+        let randSeed = [];
+        let numberSeed = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
+        let stringSeed = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
+            'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+            'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
+
+        switch (type) {
+            case 1:
+                randSeed = stringSeed.concat(numberSeed);
+                stringSeed = numberSeed = null;
+                break;
+            case 2:
+                randSeed = numberSeed;
+                break;
+            case 3:
+                randSeed = stringSeed;
+                break;
+            default:
+                break;
+        }
+
+        const seedLength = randSeed.length - 1;
+        let result = '';
+        for (let i = 0; i < length; i++) {
+            const index = Math.ceil(Math.random() * seedLength);
+            result += randSeed[index];
+        }
+
+        return result;
+    },
+
+    /**
+     * 显示排序符号
+     *
+     * @param {String} field - 字段名称
+     * @return {String} - 返回字段排序的符号
+     */
+    showSortFlag(field) {
+        const sort = this.ctx.sort;
+        if (!(sort instanceof Array) || sort.length !== 2) {
+            return '';
+        }
+        sort[1] = sort[1].toUpperCase();
+        return (sort[0] === field && sort[1] === 'DESC') ? '' : '-';
+    },
+
+    /**
+     * 判断是否为ajax请求
+     *
+     * @param {Object} request - 请求数据
+     * @return {boolean} 判断结果
+     */
+    isAjax(request) {
+        let headerInfo = request.headers['x-requested-with'] === undefined ? '' : request.headers['x-requested-with'];
+        headerInfo = headerInfo.toLowerCase();
+        return headerInfo === 'xmlhttprequest';
+    },
+
+    /**
+     * 模拟发送请求
+     *
+     * @param {String} url - 请求地址
+     * @param {Object} data - 请求数据
+     * @param {String} type - 请求类型(POST) POST | GET
+     * @param {String} dataType - 数据类型 json|text
+     * @return {Object} - 请求结果
+     */
+    async sendRequest(url, data, type = 'POST', dataType = 'json') {
+        // 发起请求
+        const response = await this.ctx.curl(url, {
+            method: type,
+            data,
+            dataType,
+        });
+        if (response.status !== 200) {
+            throw '请求失败';
+        }
+
+        return response.data;
+    },
+
+    /**
+     * 转换面包屑导航数据
+     *
+     * @param {Object} breadCrumb - 面包屑数据
+     * @param {String} mode - 模式 append 为追加型模式 \ replace 为全部替换模式
+     * @return {String} - 转换为字符串数据
+     */
+    convertBreadCrumb(breadCrumb, mode = 'append') {
+        let result = '';
+        if (!breadCrumb instanceof Array || breadCrumb.length <= 0) {
+            return result;
+        }
+        if (mode === 'append') {
+            const tmpBreadCrumb = [];
+            tmpBreadCrumb.push({ name: this.ctx.topPermission.name });
+            tmpBreadCrumb.push.apply(tmpBreadCrumb, breadCrumb);
+            tmpBreadCrumb.push({ name: this.ctx.currentName });
+            breadCrumb = tmpBreadCrumb;
+        }
+        const tmpList = [];
+        for (const data of breadCrumb) {
+            if (data.name === undefined) {
+                continue;
+            }
+            const tmp = data.url === undefined || data.url === '' ?
+                data.name : '<a href="/' + data.url + '">' + data.name + '</a>';
+            tmpList.push(tmp);
+        }
+        result = tmpList.join(' / ');
+
+        return result;
+    },
+
+    /**
+     * 深度验证数据
+     *
+     * @param {Object} rule - 数据规则
+     * @return {void}
+     */
+    validate(rule) {
+        // 先用内置的验证器验证数据
+        this.ctx.validate(rule);
+
+        // 然后再验证是否有多余的数据
+        const postData = this.ctx.request.body;
+        delete postData._csrf;
+
+        const postDataKey = Object.keys(postData);
+        const ruleKey = Object.keys(rule);
+        // 自动增加字段则填充上,以防判断出错
+        if (postData.create_time !== undefined) {
+            ruleKey.push('create_time');
+        }
+        for (const tmp of postDataKey) {
+            // 规则里面没有定义则抛出异常
+            if (ruleKey.indexOf(tmp) < 0) {
+                throw '参数不正确';
+            }
+        }
+    },
+
+    /**
+     * 拆分path
+     *
+     * @param {String|Array} paths - 拆分字符
+     * @param {String} symbol - 拆分符号
+     * @return {Array} - 拆分结果
+     */
+    explodePath(paths, symbol = '-') {
+        const result = [];
+        paths = paths instanceof Array ? paths : [paths];
+        for (const path of paths) {
+            // 拆分数据
+            const pathArray = path.split(symbol);
+            // 用户缓存循环的数据
+            const tmpArray = [];
+            for (const tmp of pathArray) {
+                // 每次循环都追加一个数据进去
+                tmpArray.push(tmp);
+                const tmpPathString = tmpArray.join(symbol);
+                // 判断是否已经存在有对应数据
+                if (result.indexOf(tmpPathString) >= 0) {
+                    continue;
+                }
+                result.push(tmpPathString);
+            }
+        }
+
+        return result;
+    },
+
+    /**
+     * 递归创建文件夹(fs.mkdirSync需要上一层文件夹已存在)
+     * @param pathName
+     * @returns {Promise<void>}
+     */
+    async recursiveMkdirSync(pathName) {
+        const upperPath = path.dirname(pathName);
+        if (!fs.existsSync(upperPath)) {
+            await this.recursiveMkdirSync(upperPath);
+        }
+        await fs.mkdirSync(pathName);
+    },
+
+    /**
+     * 字节 保存至 本地文件
+     * @param buffer - 字节
+     * @param fileName - 文件名
+     * @returns {Promise<void>}
+     */
+    async saveBufferFile(buffer, fileName) {
+        // 检查文件夹是否存在,不存在则直接创建文件夹
+        const pathName = path.dirname(fileName);
+        if (!fs.existsSync(pathName)) {
+            await this.recursiveMkdirSync(pathName);
+        }
+        await fs.writeFileSync(fileName, buffer);
+    },
+
+    /**
+     * 将文件流的数据保存至本地文件
+     * @param stream
+     * @param fileName
+     * @returns {Promise<void>}
+     */
+    async saveStreamFile(stream, fileName) {
+        // 读取字节流
+        const parts = await streamToArray(stream);
+        // 转化为buffer
+        const buffer = Buffer.concat(parts);
+        // 写入文件
+        await this.saveBufferFile(buffer, fileName);
+    },
+
+    log(error) {
+        if (error.stack) {
+            this.ctx.logger.error(error);
+        } else {
+            this.ctx.getLogger('fail').info(JSON.stringify({
+                error: error,
+                project: this.ctx.session.sessionProject,
+                user: this.ctx.session.sessionUser,
+                body: this.ctx.session.body,
+            }));
+        }
+    },
+
+    ajaxErrorBody(error, msg) {
+        return error.statck 
+            ? {err: 2, msg: msg, data: null} 
+            : {err: 1, msg: error.toString(), data: null};
+    },
+
+    /**
+     * 比较编码
+     * @param str1
+     * @param str2
+     * @param symbol
+     * @returns {number}
+     */
+    compareCode(str1, str2, symbol = '-') {
+        if (!str1) {
+            return -1;
+        } else if (!str2) {
+            return 1;
+        }
+
+        const path1 = str1.split(symbol);
+        const path2 = str2.split(symbol);
+        const reg = /^[0-9]*$/;
+        for (let i = 0, iLen = Math.min(path1.length, path2.length); i < iLen; i++) {
+            if (reg.test(path1[i]) && reg.test(path2[i])) {
+                const num1 = parseInt(path1[i]);
+                const num2 = parseInt(path2[i]);
+                if (num1 !== num2)  {
+                    return num1 - num2;
+                }
+            } else if (path1[i] < path2[i]) {
+                return -1;
+            } else if (path1[i] > path2[i]) {
+                return 1;
+            }
+        }
+        return path1.length - path2.length;
+    },
+
+
+};

+ 60 - 0
app/lib/base_calc.js

@@ -0,0 +1,60 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+const mulPrecision = 12, divPrecision = 12;
+
+function digitLength (num) {
+    // 兼容科学计数
+    var eSplit = num.toString().split(/[eE]/);
+    var len = (eSplit[0].split('.')[1] || '').length - (+(eSplit[1] || 0));
+    return len > 0 ? len : 0;
+}
+
+function powLength (num) {
+    var rs = num.toString();
+    if (rs.indexOf('+') > 0) {
+        return rs.match(/0*$/g).length();
+    } else {
+        const eSplit = rs.split(/[eE]/);
+        const len = Number(eSplit[1]) - this.digitLength(eSplit[0]);
+        return len > 0 ? len : 0;
+    }
+}
+
+function round (num, digit) {
+    return Math.round(num * Math.pow(10, digit)) / Math.pow(10, digit);
+}
+
+function add(num1, num2) {
+    var d1 = this.digitLength(num1), d2 = this.digitLength(num2);
+    return this.round(num1 + num2, Math.max(d1, d2));
+}
+
+function sub(num1, num2) {
+    var d1 = this.digitLength(num1), d2 = this.digitLength(num2);
+    return this.round(num1 - num2, Math.max(d1, d2));
+}
+
+function mul(num1, num2) {
+    return this.round(num1 * num2, mulPrecision);
+}
+
+function div(num1, num2) {
+    return this.round(num1 / num2, divPrecision);
+}
+
+module.exports = {
+    digitLength: digitLength,
+    powLength: powLength,
+    round: round,
+    add: add,
+    sub: sub,
+    mul: mul,
+    div: div,
+};

+ 81 - 0
app/lib/captcha.js

@@ -0,0 +1,81 @@
+'use strict';
+
+/**
+ * 验证码
+ *
+ * @author CaiAoLin
+ * @date 2017/10/13
+ * @version
+ */
+const Geetest = require('gt3-sdk');
+class Captcha {
+
+    /**
+     * 构造函数
+     *
+     * @param {Object} app - egg中的全局变量app(用于获取配置)
+     * @return {void}
+     */
+    constructor(app) {
+        this.captcha = new Geetest({
+            geetest_id: app.config.geetest.id,
+            geetest_key: app.config.geetest.key,
+        });
+    }
+
+    /**
+     * 验证码注册
+     *
+     * @param {Object} ctx - egg中的全局变量context
+     * @return {String} - 返回生成所需的json数据
+     */
+    async register(ctx) {
+        let response = {
+            success: 0,
+        };
+        try {
+            const data = await this.captcha.register(null);
+            ctx.session.fallback = !data.success;
+            response = data;
+        } catch (error) {
+            response.success = 0;
+            response.message = error;
+            JSON.stringify(response);
+        }
+
+        return response;
+    }
+
+    /**
+     * 验证码校验
+     *
+     * @param {Object} ctx - egg中的全局变量context
+     * @return {Boolean} - 返回是否校验成功
+     */
+    async validate(ctx) {
+        let result = false;
+
+        const challenge = ctx.request.body.geetest_challenge;
+        const validate = ctx.request.body.geetest_validate;
+        const seccode = ctx.request.body.geetest_seccode;
+
+        try {
+            if (challenge === '' || validate === '' || seccode === '') {
+                throw '参数错误';
+            }
+            const fallback = ctx.session.fallback;
+
+            result = await this.captcha.validate(fallback, {
+                geetest_challenge: challenge,
+                geetest_validate: validate,
+                geetest_seccode: seccode,
+            });
+
+        } catch (error) {
+            result = false;
+        }
+
+        return result;
+    }
+}
+module.exports = Captcha;

+ 106 - 0
app/lib/cld.js

@@ -0,0 +1,106 @@
+'use strict';
+
+/**
+ * CLD相关接口
+ *
+ * @author CaiAoLin
+ * @date 2017/10/18
+ * @version
+ */
+
+// 加密类
+const crypto = require('crypto');
+class Cld {
+
+    /**
+     * 构造函数
+     *
+     * @param {Object} ctx - egg全局变量
+     * @return {void}
+     */
+    constructor(ctx) {
+        this.authUrl = 'http://cld.smartcost.com.cn/api/auth';
+        this.getDataUrl = 'http://cld.smartcost.com.cn/api/getData';
+        this.app = 'scConstruct';
+        this.token = 'sc@ConS!tru@ct*88';
+        this.ctx = ctx;
+    }
+
+    /**
+     * CLD登录验证
+     *
+     * @param {String} username - cld用户名
+     * @param {String} password - cld密码
+     * @return {boolean} - 验证结果
+     */
+    async loginValid(username, password) {
+        let result = false;
+        try {
+            if (username === '' || password === '') {
+                throw '用户名或密码错误';
+            }
+            // 生成加密token
+            const [encryptToken, postTime] = this.generateCLDToken();
+            const postData = {
+                username,
+                password,
+                time: postTime,
+                token: encryptToken,
+                app: this.app,
+            };
+
+            const responseData = await this.ctx.helper.sendRequest(this.authUrl, postData);
+            if (responseData.err !== 0) {
+                throw '接口返回错误:' + responseData.err;
+            }
+            // 如果验证成功,则新增CLD数据到数据库
+            if (responseData.data !== '') {
+                const addResult = await this.ctx.service.manager.addCldUser(responseData.data);
+                if (!addResult) {
+                    console.log('cld user add error');
+                }
+            }
+            result = true;
+        } catch (error) {
+            console.log('cld:' + error);
+            result = false;
+        }
+
+        return result;
+    }
+
+    /**
+     * 生成CLD Token
+     *
+     * @return {Array} - 生成的token,当前生成的时间
+     */
+    generateCLDToken() {
+        let currentTime = new Date().getTime();
+        currentTime = parseFloat(currentTime / 1000).toFixed(0);
+
+        const encryptToken = crypto.createHmac('sha1', this.token).update(this.token + currentTime)
+            .digest().toString('base64');
+
+        return [encryptToken, currentTime];
+    }
+
+    /**
+     * 获取CLD用户数据
+     *
+     * @param {Number} cldID - cld中的id
+     * @return {String} - 返回json数据
+     */
+    async getCldUserData(cldID) {
+        let result = {};
+        try {
+            result = cldID;
+        } catch (error) {
+            result = {};
+        }
+
+        return result;
+    }
+
+}
+
+module.exports = Cld;

+ 135 - 0
app/lib/js_validator.js

@@ -0,0 +1,135 @@
+'use strict';
+
+/**
+ * egg内置验证器与jquery验证器结合
+ *
+ * @author CaiAoLin
+ * @date 2018/2/7
+ * @version
+ */
+
+class JSValidator {
+
+    /**
+     * 构造函数
+     *
+     * @param {Object} ctx - egg全局变量
+     * @return {void}
+     */
+    constructor(ctx) {
+        this.rule = {};
+        // 模板
+        this.template = 'layout/validator_template.ejs';
+        // 表单元素
+        this.selector = 'form';
+        this.ctx = ctx;
+        this.message = {};
+    }
+
+    /**
+     * 设置选择器
+     *
+     * @param {String} selectorName - 选择器名称
+     * @return {Object} - 返回自身类以便链式操作
+     */
+    setSelector(selectorName) {
+        this.selector = selectorName;
+        return this;
+    }
+
+    /**
+     * 转换格式
+     *
+     * @param {Object} rule - egg中的规则
+     * @return {Object} - 返回自身类以便链式操作
+     */
+    convert(rule) {
+        const result = {};
+        const messageResult = {};
+        if (Object.keys(rule).length <= 0) {
+            return rule;
+        }
+
+        for (const index in rule) {
+            result[index] = {};
+            const type = rule[index].type !== undefined && rule[index].type !== '' ? rule[index].type : '';
+            const stringType = ['string', 'password'];
+            // 是否必填
+            if (rule[index].required !== undefined) {
+                result[index].required = rule[index].required;
+            }
+
+            // 最小长度
+            if (stringType.indexOf(type) >= 0 && rule[index].min !== undefined) {
+                result[index].minlength = rule[index].min;
+            }
+
+            // 最大长度
+            if (stringType.indexOf(type) >= 0 && rule[index].max !== undefined) {
+                result[index].maxlength = rule[index].max;
+            }
+
+            // 密码相关
+            if (type === 'password' && rule[index].compare !== undefined) {
+                result[index].equalTo = '#' + rule[index].compare;
+            }
+
+            // 最小值
+            const integerType = ['integer', 'int', 'Number'];
+            if (integerType.indexOf(type) >= 0 && rule[index].min !== undefined) {
+                result[index].min = rule[index].min;
+            }
+
+            // 最大值
+            if (integerType.indexOf(type) >= 0 && rule[index].max !== undefined) {
+                result[index].max = rule[index].max;
+            }
+
+            // 自定义判断
+            const customType = ['mobile', 'ip'];
+            // 自定义且带参数
+            if (customType.indexOf(type) >= 0) {
+                result[index][type] = true;
+                if (rule[index].allowEmpty !== undefined) {
+                    result[index].required = !rule[index].allowEmpty;
+                }
+            }
+
+            // 密码值判断
+            if (type === 'password' && rule[index].compare !== undefined) {
+                result[index].equalTo = '#' + rule[index].compare;
+            }
+
+            // 提示语
+            if (rule[index].message !== undefined) {
+                messageResult[index] = rule[index].message;
+            }
+
+        }
+
+        this.rule = result;
+        this.message = messageResult;
+        return this;
+    }
+
+    /**
+     * 构建js
+     *
+     * @return {String} - 返回js
+     */
+    async build() {
+        if (Object.keys(this.rule).length <= 0) {
+            return '';
+        }
+
+        const renderData = {
+            selector: this.selector,
+            rule: this.rule,
+            message: this.message,
+        };
+        return await this.ctx.renderView(this.template, renderData);
+    }
+
+}
+
+module.exports = JSValidator;

Diferenças do arquivo suprimidas por serem muito extensas
+ 1416 - 0
app/lib/rpt_data_analysis.js


+ 149 - 0
app/lib/sms.js

@@ -0,0 +1,149 @@
+'use strict';
+
+/**
+ * 短信发送相关接口
+ *
+ * @author CaiAoLin
+ * @date 2018/1/25
+ * @version
+ */
+
+// const xmlReader = require('xmlreader');
+const Core = require('@alicloud/pop-core');
+const smsAli = require('../const/sms_alitemplate.js');
+class SMS {
+
+    /**
+     * 构造函数
+     *
+     * @param {Object} ctx - egg全局变量
+     * @return {void}
+     */
+    constructor(ctx) {
+        this.ctx = ctx;
+        this.url = 'https://sms.yunpian.com/v2/sms/batch_send.json';
+        // this.url = 'http://sms.haotingyun.com/v2/sms/single_send.json';
+        this.url2 = 'http://sms.haotingyun.com/v2/sms/tpl_single_send.json';
+        // this.url = 'http://101.132.42.40:7862/sms';
+        this.client = new Core({
+            accessKeyId: smsAli.accessKey,
+            accessKeySecret: smsAli.accessKeySecret,
+            endpoint: smsAli.endpoint,
+            apiVersion: '2017-05-25',
+        });
+
+    }
+
+    async aliSend(mobile, data, code) {
+        if (mobile instanceof Array) {
+            mobile = mobile.join(',');
+        }
+        let flag = false;
+        // const flag = false;
+        const params = {
+            RegionId: smsAli.regionId,
+            PhoneNumbers: mobile,
+            SignName: smsAli.signName,
+            TemplateCode: code,
+            TemplateParam: JSON.stringify(data),
+        };
+        const requestOption = {
+            method: 'POST',
+        };
+        // console.log(params);
+        try {
+            await this.client.request('SendSms', params, requestOption).then(result => {
+                if (result.Code === 'OK') {
+                    flag = true;
+                } else {
+                    throw '短信发送失败!';
+                }
+            }, ex => {
+                console.log(ex);
+                throw '短信发送失败!';
+            });
+        } catch (e) {
+            console.log(e);
+            flag = false;
+        }
+        return flag;
+    }
+
+    /**
+     * 发送信息
+     *
+     * @param {String|Array} mobile - 发送的电话号码
+     * @param {String} content - 发送的内容
+     * @param {String} tplId - 补充的tpl_id(防止模板无法找到发送失败)
+     * @return {Boolean} - 发送结果
+     */
+    async send(mobile, content, tplId = '') {
+        if (mobile instanceof Array) {
+            mobile = mobile.join(',');
+        }
+        let result = false;
+        const config = this.ctx.app.config.sms;
+        const postData = {
+            // action: 'send',
+            // account: config.account,
+            // password: config.password,
+            apikey: config.authKey,
+            mobile,
+            text: content,
+            // extno: config.extno,
+        };
+        if (tplId !== '') {
+            postData.tpl_id = tplId;
+        }
+        const url = tplId !== '' ? this.url2 : this.url;
+        try {
+            const response = await this.ctx.helper.sendRequest(url, postData, 'POST');
+            // const xmlData = await this.xmlParse(response);
+            // if (xmlData === undefined || xmlData.returnstatus.text() !== 'Success') {
+            //     throw '短信发送失败!';
+            // }
+            if (response === undefined && response.code !== 0) {
+                throw '短信发送失败!';
+            }
+            result = true;
+        } catch (error) {
+            console.log(error);
+            result = false;
+        }
+
+        return result;
+    }
+
+    /**
+     * xml解析
+     *
+     * @param {String} xml - xml数据
+     * @return {Object} - 解析结果
+     */
+    // xmlParse(xml) {
+    //     return new Promise(function(resolve, reject) {
+    //         xmlReader.read(xml, function(errors, xmlData) {
+    //             if (errors) {
+    //                 reject('');
+    //             } else {
+    //                 resolve(xmlData.returnsms);
+    //             }
+    //         });
+    //     });
+    // }
+
+    /**
+     * 关键字转换
+     *
+     * @param {String} content - 内容
+     * @return {Object} - 解析结果
+     */
+    async contentChange(content) {
+        let str = content.replace(/【/g, '(');
+        str = str.replace(/】/g, ')');
+        str = str.replace(/工程款/g, '***');
+        return str;
+    }
+}
+
+module.exports = SMS;

+ 337 - 0
app/lib/sql_builder.js

@@ -0,0 +1,337 @@
+'use strict';
+
+/**
+ * sql语句构建器
+ *
+ * @author CaiAoLin
+ * @date 2017/10/11
+ * @version
+ */
+class SqlBuilder {
+
+    /**
+     * 构造函数
+     *
+     * @return {void}
+     */
+    constructor() {
+        this.resetCondition();
+    }
+
+    /**
+     * 设置andWhere数据
+     *
+     * @param {String} field - where中的字段名称
+     * @param {Object} data - where中的value部分
+     * @return {void}
+     */
+    setAndWhere(field, data) {
+        if (Object.keys(data).length <= 0) {
+            return;
+        }
+        this.andWhere.push({ field, data });
+    }
+
+    /**
+     * 设置新的orWhere数据
+     *
+     * @param {String} field - where中的字段名称
+     * @param {Object} data - where中的value部分
+     * @return {void}
+     */
+    setNewOrWhere(data) {
+        if (Object.keys(data).length <= 0) {
+            return;
+        }
+        this.newOrWhere.push(data);
+    }
+
+    /**
+     * 更新字段设置
+     *
+     * @param {String} field - set的字段
+     * @param {Object} data - set中的字段
+     * @return {void}
+     */
+    setUpdateData(field, data) {
+        if (Object.keys(data).length <= 0) {
+            return;
+        }
+        this.setData.push({ field, data });
+    }
+
+    /**
+     * 重置条件
+     *
+     * @return {void}
+     */
+    resetCondition() {
+        this.andWhere = [];
+        this.orWhere = [];
+        this.newOrWhere = [];
+        this.columns = [];
+        this.limit = -1;
+        this.offset = -1;
+        this.orderBy = [];
+        this.setData = [];
+
+        this.sql = '';
+        this.sqlParam = [];
+        this.type = 'select';
+    }
+
+    /**
+     * 类型设置
+     *
+     * @param {String} tableName - 表名
+     * @return {void}
+     */
+    _typeBuild(tableName) {
+        switch (this.type) {
+            case 'select':
+                this.sql = this.columns.length === 0 ? 'SELECT * FROM ??' : 'SELECT ?? FROM ??';
+                this.sqlParam = this.columns.length === 0 ? [tableName] : [this.columns, tableName];
+                break;
+            case 'update':
+                this.sql = 'UPDATE ?? SET ';
+                this.sqlParam = [tableName];
+                break;
+            case 'delete':
+                this.sql = 'DELETE FROM ??';
+                this.sqlParam = [tableName];
+                break;
+            default:
+                break;
+        }
+    }
+
+    /**
+     * 设置update数据
+     *
+     * @return {void}
+     */
+    _setDataBuild() {
+        if (this.setData.length <= 0) {
+            return;
+        }
+        const setDataArr = [];
+        for (const set of this.setData) {
+            if (set.data.literal) {
+                const values = set.data.value instanceof Array ? set.data.value : [set.data.value];
+                setDataArr.push(' ?? = ' + set.data.literal + '(' + values.join(',') + ')');
+                this.sqlParam.push(set.field);
+            } else {
+                const tmp = set.data.selfOperate !== undefined ?
+                    ' ?? = IF(IsNull(??), 0, ??) ' + set.data.selfOperate + ' ' + set.data.value : ' ?? = ' + set.data.value;
+                setDataArr.push(tmp);
+                // 如果是自身操作则压多一次字段进数组
+                if (set.data.selfOperate !== undefined) {
+                    this.sqlParam.push(set.field);
+                    this.sqlParam.push(set.field);
+                }
+                this.sqlParam.push(set.field);
+            }
+        }
+
+        const setString = setDataArr.join(',');
+        this.sql += setString;
+    }
+
+    /**
+     * andWhere设置
+     *
+     * @return {void}
+     */
+    _andWhereBuild() {
+        if (this.andWhere.length <= 0) {
+            return;
+        }
+
+        const whereArr = [];
+        for (const where of this.andWhere) {
+            if (where.data.operate === 'in') {
+                // in操作
+                const valueLength = where.data.value instanceof Array ? where.data.value.length : 1;
+                // 生成参数集
+                const inArr = [];
+                for (let i = 0; i < valueLength; i++) {
+                    inArr.push('?');
+                }
+                const inData = inArr.join(',');
+                whereArr.push(' ?? IN (' + inData + ')');
+                this.sqlParam.push(where.field);
+                // 赋值参数
+                this.sqlParam.push.apply(this.sqlParam, where.data.value);
+            } else if (where.data.operate === 'find_in_set') {
+                whereArr.push(' find_in_set (' + where.data.value + ', ?? )');
+                this.sqlParam.push(where.field);
+            } else {
+                // 普通操作
+                whereArr.push(' ?? ' + where.data.operate + ' ' + where.data.value);
+                this.sqlParam.push(where.field);
+            }
+
+        }
+        const whereString = whereArr.join(' AND ');
+        this.sql += this.sql.indexOf('WHERE') > 0 ? whereString : ' WHERE ' + whereString;
+    }
+
+    /**
+     * orWhere设置
+     *
+     * @return {void}
+     */
+    _orWhereBuild() {
+        if (this.orWhere.length <= 0) {
+            return;
+        }
+
+        const whereArr = [];
+        for (const where in this.orWhere) {
+            whereArr.push(' ?? ' + where.data.operate + ' ' + where.data.value);
+            this.sqlParam.push(where.field);
+        }
+        const whereString = whereArr.join(' OR ');
+        // 如果前面已经有设置过WHERE则不需要再重复添加
+        this.sql += this.sql.indexOf('WHERE') > 0 ? whereString : ' WHERE ' + whereString;
+    }
+
+    /**
+     * orWhere设置-旧的不好用,独立写了一个包含括号的or兼容,传对象数组,
+     * 例:
+     * [{ field: 'office_share', value: managerSession.office, operate: 'find_in_set'},
+     * {field: 'office',value: managerSession.office,operate: '='},......]
+     *
+     * @return {void}
+     */
+    _newOrWhereBuild() {
+        if (this.newOrWhere.length <= 0) {
+            return;
+        }
+
+        const whereArr = [];
+        for (const oneOw in this.newOrWhere) {
+            for (const where of this.newOrWhere[oneOw]) {
+                if (where.operate === 'in') {
+                    // in操作
+                    const valueLength = where.value instanceof Array ? where.value.length : 1;
+                    // 生成参数集
+                    const inArr = [];
+                    for (let i = 0; i < valueLength; i++) {
+                        inArr.push('?');
+                    }
+                    const inData = inArr.join(',');
+                    whereArr.push(' ?? IN (' + inData + ')');
+                    this.sqlParam.push(where.field);
+                    // 赋值参数
+                    this.sqlParam.push.apply(this.sqlParam, where.value);
+                } else if (where.operate === 'find_in_set') {
+                    whereArr.push(' find_in_set (' + where.value + ', ?? )');
+                    this.sqlParam.push(where.field);
+                } else {
+                    // 普通操作
+                    whereArr.push(' ?? ' + where.operate + ' ' + where.value);
+                    this.sqlParam.push(where.field);
+                }
+            }
+            let whereString = whereArr.join(' OR ');
+            whereString = '(' + whereString + ')';
+            // 如果前面已经有设置过WHERE则不需要再重复添加
+            this.sql += this.sql.indexOf('WHERE') > 0 ? ' AND ' + whereString : ' WHERE ' + whereString;
+        }
+    }
+
+    /**
+     * limit设置
+     *
+     * @return {void}
+     */
+    _limitBuild() {
+        if (typeof this.limit !== 'number' || this.limit <= 0) {
+            return;
+        }
+
+        this.offset = parseInt(this.offset);
+        this.offset = isNaN(this.offset) || this.offset < 0 ? 0 : this.offset;
+        const limitString = this.offset >= 0 ? this.offset + ',' + this.limit : this.limit;
+        this.sql += ' LIMIT ' + limitString;
+    }
+
+    /**
+     * orderby 设置
+     *
+     * @return {void}
+     */
+    _orderByBuild() {
+        if (this.orderBy.length <= 0) {
+            return;
+        }
+
+        const orderArr = [];
+        for (const index in this.orderBy) {
+            orderArr.push(' ?? ' + this.orderBy[index][1]);
+            this.sqlParam.push(this.orderBy[index][0]);
+        }
+        const orderByString = orderArr.join(',');
+        this.sql += ' ORDER BY ' + orderByString;
+    }
+
+    /**
+     * 构建sql
+     *
+     * @param {String} tableName - 表名
+     * @param {String} type - 类型
+     * @return {Array} - 返回数组,第一个元素为sql语句,第二个元素为sql中问号部分的param
+     */
+    build(tableName, type = 'select') {
+        this.type = type;
+        this._typeBuild(tableName);
+        if (this.sql === '') {
+            throw '类型错误';
+        }
+
+        this._setDataBuild();
+        this._andWhereBuild();
+        this._orWhereBuild();
+        this._newOrWhereBuild();
+
+        this._orderByBuild();
+        this._limitBuild();
+
+        const sql = this.sql;
+        const sqlParam = this.sqlParam;
+        // 重置数据
+        this.resetCondition();
+
+        return [sql, sqlParam];
+    }
+
+    /**
+     * 构建sql
+     *
+     * @param {String} tableName - 表名
+     * @param {String} type - 类型
+     * @return {Array} - 返回数组,第一个元素为sql语句,第二个元素为sql中问号部分的param
+     */
+    buildCount(tableName, type = 'select') {
+        this.type = type;
+        this._typeBuild(tableName);
+        if (this.sql === '') {
+            throw '类型错误';
+        }
+
+        this._setDataBuild();
+        this._andWhereBuild();
+        this._orWhereBuild();
+        this._newOrWhereBuild();
+
+        const sql = this.sql;
+        const sqlParam = this.sqlParam;
+        // 重置数据
+        // this.resetCondition();
+
+        return [sql, sqlParam];
+    }
+
+}
+module.exports = SqlBuilder;

+ 94 - 0
app/lib/sso.js

@@ -0,0 +1,94 @@
+'use strict';
+
+/**
+ * sso相关接口
+ *
+ * @author CaiAoLin
+ * @date 2017/11/15
+ * @version
+ */
+
+// 加密类
+const crypto = require('crypto');
+class SSO {
+
+    /**
+     * 构造函数
+     *
+     * @param {Object} ctx - egg全局变量
+     * @return {void}
+     */
+    constructor(ctx) {
+        this.authUrl = 'https://sso.smartcost.com.cn';
+        this.ctx = ctx;
+        this.accountID = 0;
+    }
+
+    /**
+     * SSO登录验证
+     *
+     * @param {String} username - cld用户名
+     * @param {String} password - cld密码
+     * @return {boolean} - 验证结果
+     */
+    // async loginValid(username, password) {
+    //     let result = false;
+    //     try {
+    //         if (username === '' || password === '') {
+    //             throw '用户名或密码错误';
+    //         }
+    //         // 生成加密token
+    //         const postData = {
+    //             username,
+    //             userpasswd: password,
+    //         };
+    //
+    //         const responseData = await this.ctx.helper.sendRequest(this.authUrl + '/building/api/login', postData);
+    //         if (responseData.length <= 0 || typeof responseData === 'number') {
+    //             throw '接口返回错误:' + responseData;
+    //         }
+    //
+    //         // 如果验证成功,则新增或修改SSO数据到数据库
+    //         const customerId = await this.ctx.service.customer.saveSSOUser(responseData[0]);
+    //         this.accountID = customerId;
+    //
+    //         // 更新用户登录时间
+    //         const updateData = {
+    //             last_login: new Date().getTime() / 1000,
+    //         };
+    //         result = await this.ctx.service.customer.update(updateData, { email: responseData[0].useremail });
+    //     } catch (error) {
+    //         console.log('sso:' + error);
+    //         result = false;
+    //     }
+    //
+    //     return result;
+    // }
+
+    /**
+     * 获取SSO用户数据
+     *
+     * @param {Number} account - sso中邮箱或手机号
+     * @return {String} - 返回json数据
+     */
+    async getSSOUserData(account) {
+        let result = null;
+        try {
+            const postData = {
+                account: account
+            };
+            const responseData = await this.ctx.helper.sendRequest(this.authUrl + '/building/api/userinfo', postData);
+            if (responseData.length <= 0 || typeof responseData === 'number') {
+                throw '接口返回错误:' + responseData;
+            }
+            result = responseData;
+        } catch (error) {
+            result = null;
+        }
+
+        return result;
+    }
+
+}
+
+module.exports = SSO;

+ 33 - 0
app/middleware/auto_logger.js

@@ -0,0 +1,33 @@
+'use strict';
+
+/**
+ * 自动记录日志
+ *
+ * @author CaiAoLin
+ * @date 2017/10/30
+ * @version
+ */
+
+module.exports = options => {
+    return function* autoLogger(next) {
+        // 自动记录log的action
+        const autoLogAction = ['save', 'delete'];
+        if (this.actionName !== undefined && autoLogAction.indexOf(this.actionName) >= 0) {
+            // 操作数据的id
+            const idReg = /\/(\d+)/;
+            const paramInfo = this.request.originalUrl.match(idReg);
+            let targetId = paramInfo[1] !== undefined ? paramInfo[1] : -1;
+            targetId = parseInt(targetId);
+
+            const logData = {
+                controller: this.controllerName,
+                action: this.actionName,
+                operation: this.currentName === undefined ? '保存数据' : this.currentName,
+                target_id: targetId,
+            };
+            yield this.service.log.addLog(logData);
+        }
+
+        yield next;
+    };
+};

+ 18 - 0
app/middleware/datetime_fill.js

@@ -0,0 +1,18 @@
+'use strict';
+
+/**
+ * 自动填充时间戳
+ *
+ * @author CaiAoLin
+ * @date 2017/10/25
+ * @version
+ */
+
+module.exports = options => {
+    return function* datetimeFill(next) {
+        let currentTime = new Date().getTime() / 1000;
+        currentTime = parseInt(currentTime);
+        this.request.body.create_time = currentTime;
+        yield next;
+    };
+};

+ 25 - 0
app/middleware/error_handler.js

@@ -0,0 +1,25 @@
+'use strict';
+
+/**
+ * 异常处理
+ *
+ * @author CaiAoLin
+ * @date 2017/10/25
+ * @version
+ */
+
+module.exports = () => {
+    return function* errorHandler(next) {
+        try {
+            yield next;
+        } catch (err) {
+            // 注意:自定义的错误统一处理函数捕捉到错误后也要 `app.emit('error', err, this)`
+            // 框架会统一监听,并打印对应的错误日志
+            this.app.emit('error', err, this);
+            // mysql重连
+            if (err.code === 'PROTOCOL_CONNECTION_LOST') {
+                this.app.mysql.createInstance(this.app.config.mysql.client);
+            }
+        }
+    };
+};

+ 74 - 0
app/middleware/permission_filter.js

@@ -0,0 +1,74 @@
+'use strict';
+
+module.exports = option => {
+    /**
+     * 用户权限筛选中间件
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* permissionFilter(next) {
+        // 获取所有权限数据
+        const permissionData = yield this.service.permission.getAllData(false, true);
+        this.currentName = '';
+        let currentPermissionId = 0;
+        // 查找controller和action名称相同的数据
+        for (const index in permissionData) {
+            if (permissionData[index].controller === this.controllerName &&
+                (permissionData[index].action === 'all' || permissionData[index].action === this.actionName)) {
+                this.currentName = permissionData[index].name;
+                currentPermissionId = permissionData[index].id;
+                break;
+            }
+        }
+        // 如果页面没有录入db,则允许通过,否则进入权限判断
+        if (currentPermissionId > 0) {
+            // 查找当前用户是否有对应页面权限
+            const managerSession = this.session.managerSession;
+            // const permission = managerSession.permission;
+            const permission = yield this.service.manager.getManagerPermission(managerSession.userID);
+            try {
+                checkPermission(permission, currentPermissionId);
+            } catch (error) {
+                this.session.message = {
+                    type: 'error',
+                    icon: 'exclamation-sign',
+                    message: error.toString(),
+                };
+                return this.redirect(this.headers.referer);
+                // return this.redirect('/dashboard');
+            }
+        }
+
+        // 找出对应页面的最顶层id
+        this.permissionRootId = 0;
+        this.service.permission.getTopPid(currentPermissionId, permissionData);
+
+        // 查找最顶层数据
+        this.topPermission = yield this.service.permission.getCacheDataById(this.permissionRootId);
+
+        this.currentName = this.currentName === '' ? '后台管理' : this.currentName;
+        yield next;
+    };
+};
+/**
+ * 判断权限
+ *
+ * @param {String} permissionList - 当前用户权限字符串
+ * @param {Number} currentPermissionId - 当前被访问页面的权限id
+ * @return {void}
+ */
+function checkPermission(permissionList, currentPermissionId) {
+    if (permissionList === '') {
+        throw '用户组权限为空';
+    }
+    // 如果是超级管理员则直接返回
+    if (permissionList === 'all') {
+        return;
+    }
+    const permissionIdList = permissionList.split(',');
+    if (permissionIdList.indexOf(currentPermissionId + '') < 0) {
+        throw '当前用户组没有对应权限';
+    }
+
+}

+ 37 - 0
app/middleware/session_auth.js

@@ -0,0 +1,37 @@
+'use strict';
+
+// 加密类
+const crypto = require('crypto');
+module.exports = options => {
+    /**
+     * session判断中间件
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* sessionAuth(next) {
+        try {
+            // 判断session
+            const managerSession = this.session.managerSession;
+            if (managerSession === undefined) {
+                throw '不存在session';
+            }
+            // 校验session
+            if (managerSession.username === undefined || managerSession.loginTime === undefined) {
+                throw '用户数据不完整';
+            }
+            // 校验session
+            const sessionToken = crypto.createHmac('sha1', managerSession.loginTime + '')
+                .update(managerSession.username).digest().toString('base64');
+            if (sessionToken !== managerSession.sessionToken) {
+                throw 'session数据错误';
+            }
+
+            // 判断并更新系统维护时间
+            yield this.service.maintain.syncMaintainData();
+        } catch (error) {
+            return this.redirect('/');
+        }
+        yield next;
+    };
+};

+ 25 - 0
app/middleware/sort_filter.js

@@ -0,0 +1,25 @@
+'use strict';
+
+module.exports = options => {
+    /**
+     * 排序筛选
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* sortFilter(next) {
+        this.sort = [];
+        // 排序相关
+        const sort = this.request.query.sort;
+        if (sort !== undefined) {
+            const sortData = sort.split('-');
+            this.sort = sortData.length > 1 ? [sortData[1], 'DESC'] : [sortData[0], 'ASC'];
+        }
+        // 分页数据相关
+        let page = this.request.query.page;
+        page = parseInt(page);
+        page = isNaN(page) || page <= 0 ? 1 : page;
+        this.page = page;
+        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;
+    };
+};

+ 29 - 0
app/middleware/white_list_filter.js

@@ -0,0 +1,29 @@
+'use strict';
+
+module.exports = options => {
+    /**
+     * 白名单过滤器
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* whiteListFilter(next) {
+        // 获取当前ip
+        const ip = this.request.ip;
+        // 获取白名单数据
+        const whiteList = yield this.service.whiteList.getAllData();
+        // 判断是否为ajax请求
+        if (this.helper.isAjax(this.request)) {
+            // 检测token @todo
+        } else {
+            // 不是ajax请求则判断ip是否为合法
+            if (whiteList[ip + ':1'] === undefined) {
+                this.status = 404;
+                return;
+            }
+        }
+
+        yield next;
+    };
+};
+

Diferenças do arquivo suprimidas por serem muito extensas
+ 9 - 0
app/public/css/bootstrap/bootstrap-datetimepicker.min.css


Diferenças do arquivo suprimidas por serem muito extensas
+ 7 - 0
app/public/css/bootstrap/bootstrap.min.css


+ 347 - 0
app/public/css/codemirror/codemirror.css

@@ -0,0 +1,347 @@
+/* BASICS */
+
+.CodeMirror {
+  /* Set height, width, borders, and global font properties here */
+  font-family: monospace;
+  height:300px;
+  color: black;
+}
+
+/* PADDING */
+
+.CodeMirror-lines {
+  padding: 4px 0; /* Vertical padding around content */
+}
+.CodeMirror pre {
+  padding: 0 4px; /* Horizontal padding of content */
+}
+
+.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+  background-color: white; /* The little square between H and V scrollbars */
+}
+
+/* GUTTER */
+
+.CodeMirror-gutters {
+  border-right: 1px solid #ddd;
+  background-color: #f7f7f7;
+  white-space: nowrap;
+}
+.CodeMirror-linenumbers {}
+.CodeMirror-linenumber {
+  padding: 0 3px 0 5px;
+  min-width: 20px;
+  text-align: right;
+  color: #999;
+  white-space: nowrap;
+}
+
+.CodeMirror-guttermarker { color: black; }
+.CodeMirror-guttermarker-subtle { color: #999; }
+
+/* CURSOR */
+
+.CodeMirror-cursor {
+  border-left: 1px solid black;
+  border-right: none;
+  width: 0;
+}
+/* Shown when moving in bi-directional text */
+.CodeMirror div.CodeMirror-secondarycursor {
+  border-left: 1px solid silver;
+}
+.cm-fat-cursor .CodeMirror-cursor {
+  width: auto;
+  border: 0 !important;
+  background: #7e7;
+}
+.cm-fat-cursor div.CodeMirror-cursors {
+  z-index: 1;
+}
+
+.cm-animate-fat-cursor {
+  width: auto;
+  border: 0;
+  -webkit-animation: blink 1.06s steps(1) infinite;
+  -moz-animation: blink 1.06s steps(1) infinite;
+  animation: blink 1.06s steps(1) infinite;
+  background-color: #7e7;
+}
+@-moz-keyframes blink {
+  0% {}
+  50% { background-color: transparent; }
+  100% {}
+}
+@-webkit-keyframes blink {
+  0% {}
+  50% { background-color: transparent; }
+  100% {}
+}
+@keyframes blink {
+  0% {}
+  50% { background-color: transparent; }
+  100% {}
+}
+
+/* Can style cursor different in overwrite (non-insert) mode */
+.CodeMirror-overwrite .CodeMirror-cursor {}
+
+.cm-tab { display: inline-block; text-decoration: inherit; }
+
+.CodeMirror-rulers {
+  position: absolute;
+  left: 0; right: 0; top: -50px; bottom: -20px;
+  overflow: hidden;
+}
+.CodeMirror-ruler {
+  border-left: 1px solid #ccc;
+  top: 0; bottom: 0;
+  position: absolute;
+}
+
+/* DEFAULT THEME */
+
+.cm-s-default .cm-header {color: blue;}
+.cm-s-default .cm-quote {color: #090;}
+.cm-negative {color: #d44;}
+.cm-positive {color: #292;}
+.cm-header, .cm-strong {font-weight: bold;}
+.cm-em {font-style: italic;}
+.cm-link {text-decoration: underline;}
+.cm-strikethrough {text-decoration: line-through;}
+
+.cm-s-default .cm-keyword {color: #708;}
+.cm-s-default .cm-atom {color: #219;}
+.cm-s-default .cm-number {color: #164;}
+.cm-s-default .cm-def {color: #00f;}
+.cm-s-default .cm-variable,
+.cm-s-default .cm-punctuation,
+.cm-s-default .cm-property,
+.cm-s-default .cm-operator {}
+.cm-s-default .cm-variable-2 {color: #05a;}
+.cm-s-default .cm-variable-3 {color: #085;}
+.cm-s-default .cm-comment {color: #a50;}
+.cm-s-default .cm-string {color: #a11;}
+.cm-s-default .cm-string-2 {color: #f50;}
+.cm-s-default .cm-meta {color: #555;}
+.cm-s-default .cm-qualifier {color: #555;}
+.cm-s-default .cm-builtin {color: #30a;}
+.cm-s-default .cm-bracket {color: #997;}
+.cm-s-default .cm-tag {color: #170;}
+.cm-s-default .cm-attribute {color: #00c;}
+.cm-s-default .cm-hr {color: #999;}
+.cm-s-default .cm-link {color: #00c;}
+
+.cm-s-default .cm-error {color: #f00;}
+.cm-invalidchar {color: #f00;}
+
+.CodeMirror-composing { border-bottom: 2px solid; }
+
+/* Default styles for common addons */
+
+div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
+div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
+.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
+.CodeMirror-activeline-background {background: #e8f2ff;}
+
+/* STOP */
+
+/* The rest of this file contains styles related to the mechanics of
+   the editor. You probably shouldn't touch them. */
+
+.CodeMirror {
+  position: relative;
+  overflow: hidden;
+  background: white;
+}
+
+.CodeMirror-scroll {
+  overflow: scroll !important; /* Things will break if this is overridden */
+  /* 30px is the magic margin used to hide the element's real scrollbars */
+  /* See overflow: hidden in .CodeMirror */
+  margin-bottom: -30px; margin-right: -30px;
+  padding-bottom: 30px;
+  height: 100%;
+  outline: none; /* Prevent dragging from highlighting the element */
+  position: relative;
+}
+.CodeMirror-sizer {
+  position: relative;
+  border-right: 30px solid transparent;
+}
+
+/* The fake, visible scrollbars. Used to force redraw during scrolling
+   before actual scrolling happens, thus preventing shaking and
+   flickering artifacts. */
+.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+  position: absolute;
+  z-index: 6;
+  display: none;
+}
+.CodeMirror-vscrollbar {
+  right: 0; top: 0;
+  overflow-x: hidden;
+  overflow-y: scroll;
+}
+.CodeMirror-hscrollbar {
+  bottom: 0; left: 0;
+  overflow-y: hidden;
+  overflow-x: scroll;
+}
+.CodeMirror-scrollbar-filler {
+  right: 0; bottom: 0;
+}
+.CodeMirror-gutter-filler {
+  left: 0; bottom: 0;
+}
+
+.CodeMirror-gutters {
+  position: absolute; left: 0; top: 0;
+  min-height: 100%;
+  z-index: 3;
+}
+.CodeMirror-gutter {
+  white-space: normal;
+  height: 100%;
+  display: inline-block;
+  vertical-align: top;
+  margin-bottom: -30px;
+  /* Hack to make IE7 behave */
+  *zoom:1;
+  *display:inline;
+}
+.CodeMirror-gutter-wrapper {
+  position: absolute;
+  z-index: 4;
+  background: none !important;
+  border: none !important;
+}
+.CodeMirror-gutter-background {
+  position: absolute;
+  top: 0; bottom: 0;
+  z-index: 4;
+}
+.CodeMirror-gutter-elt {
+  position: absolute;
+  cursor: default;
+  z-index: 4;
+}
+.CodeMirror-gutter-wrapper {
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  user-select: none;
+}
+
+.CodeMirror-lines {
+  cursor: text;
+  min-height: 1px; /* prevents collapsing before first draw */
+}
+.CodeMirror pre {
+  /* Reset some styles that the rest of the page might have set */
+  -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
+  border-width: 0;
+  background: transparent;
+  font-family: inherit;
+  font-size: inherit;
+  margin: 0;
+  white-space: pre;
+  word-wrap: normal;
+  line-height: inherit;
+  color: inherit;
+  z-index: 2;
+  position: relative;
+  overflow: visible;
+  -webkit-tap-highlight-color: transparent;
+  -webkit-font-variant-ligatures: none;
+  font-variant-ligatures: none;
+}
+.CodeMirror-wrap pre {
+  word-wrap: break-word;
+  white-space: pre-wrap;
+  word-break: normal;
+}
+
+.CodeMirror-linebackground {
+  position: absolute;
+  left: 0; right: 0; top: 0; bottom: 0;
+  z-index: 0;
+}
+
+.CodeMirror-linewidget {
+  position: relative;
+  z-index: 2;
+  overflow: auto;
+}
+
+.CodeMirror-widget {}
+
+.CodeMirror-code {
+  outline: none;
+}
+
+/* Force content-box sizing for the elements where we expect it */
+.CodeMirror-scroll,
+.CodeMirror-sizer,
+.CodeMirror-gutter,
+.CodeMirror-gutters,
+.CodeMirror-linenumber {
+  -moz-box-sizing: content-box;
+  box-sizing: content-box;
+}
+
+.CodeMirror-measure {
+  position: absolute;
+  width: 100%;
+  height: 0;
+  overflow: hidden;
+  visibility: hidden;
+}
+
+.CodeMirror-cursor {
+  position: absolute;
+  pointer-events: none;
+}
+.CodeMirror-measure pre { position: static; }
+
+div.CodeMirror-cursors {
+  visibility: hidden;
+  position: relative;
+  z-index: 3;
+}
+div.CodeMirror-dragcursors {
+  visibility: visible;
+}
+
+.CodeMirror-focused div.CodeMirror-cursors {
+  visibility: visible;
+}
+
+.CodeMirror-selected { background: #d9d9d9; }
+.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
+.CodeMirror-crosshair { cursor: crosshair; }
+.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
+.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
+
+.cm-searching {
+  background: #ffa;
+  background: rgba(255, 255, 0, .4);
+}
+
+/* IE7 hack to prevent it from returning funny offsetTops on the spans */
+.CodeMirror span { *vertical-align: text-bottom; }
+
+/* Used to force a border model for a node */
+.cm-force-border { padding-right: .1px; }
+
+@media print {
+  /* Hide the cursor when printing */
+  .CodeMirror div.CodeMirror-cursors {
+    visibility: hidden;
+  }
+}
+
+/* See issue #2901 */
+.cm-tab-wrap-hack:after { content: ''; }
+
+/* Help users use markselection to safely style text background */
+span.CodeMirror-selectedtext { background: none; }

+ 41 - 0
app/public/css/codemirror/dracula.css

@@ -0,0 +1,41 @@
+/*
+
+    Name:       dracula
+    Author:     Michael Kaminsky (http://github.com/mkaminsky11)
+
+    Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme)
+
+*/
+
+
+.cm-s-dracula.CodeMirror, .cm-s-dracula .CodeMirror-gutters {
+  background-color: #282a36 !important;
+  color: #f8f8f2 !important;
+  border: none;
+}
+.cm-s-dracula .CodeMirror-gutters { color: #282a36; }
+.cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; }
+.cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; }
+.cm-s-dracula.CodeMirror-focused div.CodeMirror-selected { background: rgba(255, 255, 255, 0.10); }
+.cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }
+.cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }
+.cm-s-dracula span.cm-comment { color: #6272a4; }
+.cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; }
+.cm-s-dracula span.cm-number { color: #bd93f9; }
+.cm-s-dracula span.cm-variable { color: #50fa7b; }
+.cm-s-dracula span.cm-variable-2 { color: white; }
+.cm-s-dracula span.cm-def { color: #ffb86c; }
+.cm-s-dracula span.cm-keyword { color: #ff79c6; }
+.cm-s-dracula span.cm-operator { color: #ff79c6; }
+.cm-s-dracula span.cm-keyword { color: #ff79c6; }
+.cm-s-dracula span.cm-atom { color: #bd93f9; }
+.cm-s-dracula span.cm-meta { color: #f8f8f2; }
+.cm-s-dracula span.cm-tag { color: #ff79c6; }
+.cm-s-dracula span.cm-attribute { color: #50fa7b; }
+.cm-s-dracula span.cm-qualifier { color: #50fa7b; }
+.cm-s-dracula span.cm-property { color: #66d9ef; }
+.cm-s-dracula span.cm-builtin { color: #50fa7b; }
+.cm-s-dracula span.cm-variable-3 { color: #50fa7b; }
+
+.cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); }
+.cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }

+ 54 - 0
app/public/css/codemirror/material.css

@@ -0,0 +1,54 @@
+/*
+
+    Name:       material
+    Author:     Michael Kaminsky (http://github.com/mkaminsky11)
+
+    Original material color scheme by Mattia Astorino (https://github.com/equinusocio/material-theme)
+
+*/
+
+.cm-s-material {
+  background-color: #263238;
+  color: rgba(233, 237, 237, 1);
+}
+.cm-s-material .CodeMirror-gutters {
+  background: #263238;
+  color: rgb(83,127,126);
+  border: none;
+  border-right:1px solid #334047
+}
+.cm-s-material .CodeMirror-guttermarker, .cm-s-material .CodeMirror-guttermarker-subtle, .cm-s-material .CodeMirror-linenumber { color: rgb(83,127,126); }
+.cm-s-material .CodeMirror-cursor { border-left: 1px solid #f8f8f0; }
+.cm-s-material div.CodeMirror-selected { background: rgba(255, 255, 255, 0.15); }
+.cm-s-material.CodeMirror-focused div.CodeMirror-selected { background: rgba(255, 255, 255, 0.10); }
+.cm-s-material .CodeMirror-line::selection, .cm-s-material .CodeMirror-line > span::selection, .cm-s-material .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }
+.cm-s-material .CodeMirror-line::-moz-selection, .cm-s-material .CodeMirror-line > span::-moz-selection, .cm-s-material .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }
+
+.cm-s-material .CodeMirror-activeline-background { background: rgba(0, 0, 0, 0); }
+.cm-s-material .cm-keyword { color: rgba(199, 146, 234, 1); }
+.cm-s-material .cm-operator { color: rgba(233, 237, 237, 1); }
+.cm-s-material .cm-variable-2 { color: #80CBC4; }
+.cm-s-material .cm-variable-3 { color: #82B1FF; }
+.cm-s-material .cm-builtin { color: #DECB6B; }
+.cm-s-material .cm-atom { color: #F77669; }
+.cm-s-material .cm-number { color: #F77669; }
+.cm-s-material .cm-def { color: rgba(233, 237, 237, 1); }
+.cm-s-material .cm-string { color: #C3E88D; }
+.cm-s-material .cm-string-2 { color: #80CBC4; }
+.cm-s-material .cm-comment { color: #546E7A; }
+.cm-s-material .cm-variable { color: #82B1FF; }
+.cm-s-material .cm-tag { color: #80CBC4; }
+.cm-s-material .cm-meta { color: #80CBC4; }
+.cm-s-material .cm-attribute { color: #FFCB6B; }
+.cm-s-material .cm-property { color: #80CBAE; }
+.cm-s-material .cm-qualifier { color: #DECB6B; }
+.cm-s-material .cm-variable-3 { color: #DECB6B; }
+.cm-s-material .cm-tag { color: rgba(255, 83, 112, 1); }
+.cm-s-material .cm-error {
+  color: rgba(255, 255, 255, 1.0);
+  background-color: #EC5F67;
+}
+.cm-s-material .CodeMirror-matchingbracket {
+  text-decoration: underline;
+  color: white !important;
+}

+ 41 - 0
app/public/css/codemirror/theme/3024-day.css

@@ -0,0 +1,41 @@
+/*
+
+    Name:       3024 day
+    Author:     Jan T. Sott (http://github.com/idleberg)
+
+    CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror)
+    Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
+
+*/
+
+.cm-s-3024-day.CodeMirror { background: #f7f7f7; color: #3a3432; }
+.cm-s-3024-day div.CodeMirror-selected { background: #d6d5d4; }
+
+.cm-s-3024-day .CodeMirror-line::selection, .cm-s-3024-day .CodeMirror-line > span::selection, .cm-s-3024-day .CodeMirror-line > span > span::selection { background: #d6d5d4; }
+.cm-s-3024-day .CodeMirror-line::-moz-selection, .cm-s-3024-day .CodeMirror-line > span::-moz-selection, .cm-s-3024-day .CodeMirror-line > span > span::selection { background: #d9d9d9; }
+
+.cm-s-3024-day .CodeMirror-gutters { background: #f7f7f7; border-right: 0px; }
+.cm-s-3024-day .CodeMirror-guttermarker { color: #db2d20; }
+.cm-s-3024-day .CodeMirror-guttermarker-subtle { color: #807d7c; }
+.cm-s-3024-day .CodeMirror-linenumber { color: #807d7c; }
+
+.cm-s-3024-day .CodeMirror-cursor { border-left: 1px solid #5c5855; }
+
+.cm-s-3024-day span.cm-comment { color: #cdab53; }
+.cm-s-3024-day span.cm-atom { color: #a16a94; }
+.cm-s-3024-day span.cm-number { color: #a16a94; }
+
+.cm-s-3024-day span.cm-property, .cm-s-3024-day span.cm-attribute { color: #01a252; }
+.cm-s-3024-day span.cm-keyword { color: #db2d20; }
+.cm-s-3024-day span.cm-string { color: #fded02; }
+
+.cm-s-3024-day span.cm-variable { color: #01a252; }
+.cm-s-3024-day span.cm-variable-2 { color: #01a0e4; }
+.cm-s-3024-day span.cm-def { color: #e8bbd0; }
+.cm-s-3024-day span.cm-bracket { color: #3a3432; }
+.cm-s-3024-day span.cm-tag { color: #db2d20; }
+.cm-s-3024-day span.cm-link { color: #a16a94; }
+.cm-s-3024-day span.cm-error { background: #db2d20; color: #5c5855; }
+
+.cm-s-3024-day .CodeMirror-activeline-background { background: #e8f2ff; }
+.cm-s-3024-day .CodeMirror-matchingbracket { text-decoration: underline; color: #a16a94 !important; }

+ 39 - 0
app/public/css/codemirror/theme/3024-night.css

@@ -0,0 +1,39 @@
+/*
+
+    Name:       3024 night
+    Author:     Jan T. Sott (http://github.com/idleberg)
+
+    CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror)
+    Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
+
+*/
+
+.cm-s-3024-night.CodeMirror { background: #090300; color: #d6d5d4; }
+.cm-s-3024-night div.CodeMirror-selected { background: #3a3432; }
+.cm-s-3024-night .CodeMirror-line::selection, .cm-s-3024-night .CodeMirror-line > span::selection, .cm-s-3024-night .CodeMirror-line > span > span::selection { background: rgba(58, 52, 50, .99); }
+.cm-s-3024-night .CodeMirror-line::-moz-selection, .cm-s-3024-night .CodeMirror-line > span::-moz-selection, .cm-s-3024-night .CodeMirror-line > span > span::-moz-selection { background: rgba(58, 52, 50, .99); }
+.cm-s-3024-night .CodeMirror-gutters { background: #090300; border-right: 0px; }
+.cm-s-3024-night .CodeMirror-guttermarker { color: #db2d20; }
+.cm-s-3024-night .CodeMirror-guttermarker-subtle { color: #5c5855; }
+.cm-s-3024-night .CodeMirror-linenumber { color: #5c5855; }
+
+.cm-s-3024-night .CodeMirror-cursor { border-left: 1px solid #807d7c; }
+
+.cm-s-3024-night span.cm-comment { color: #cdab53; }
+.cm-s-3024-night span.cm-atom { color: #a16a94; }
+.cm-s-3024-night span.cm-number { color: #a16a94; }
+
+.cm-s-3024-night span.cm-property, .cm-s-3024-night span.cm-attribute { color: #01a252; }
+.cm-s-3024-night span.cm-keyword { color: #db2d20; }
+.cm-s-3024-night span.cm-string { color: #fded02; }
+
+.cm-s-3024-night span.cm-variable { color: #01a252; }
+.cm-s-3024-night span.cm-variable-2 { color: #01a0e4; }
+.cm-s-3024-night span.cm-def { color: #e8bbd0; }
+.cm-s-3024-night span.cm-bracket { color: #d6d5d4; }
+.cm-s-3024-night span.cm-tag { color: #db2d20; }
+.cm-s-3024-night span.cm-link { color: #a16a94; }
+.cm-s-3024-night span.cm-error { background: #db2d20; color: #807d7c; }
+
+.cm-s-3024-night .CodeMirror-activeline-background { background: #2F2F2F; }
+.cm-s-3024-night .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }

+ 32 - 0
app/public/css/codemirror/theme/abcdef.css

@@ -0,0 +1,32 @@
+.cm-s-abcdef.CodeMirror { background: #0f0f0f; color: #defdef; }
+.cm-s-abcdef div.CodeMirror-selected { background: #515151; }
+.cm-s-abcdef .CodeMirror-line::selection, .cm-s-abcdef .CodeMirror-line > span::selection, .cm-s-abcdef .CodeMirror-line > span > span::selection { background: rgba(56, 56, 56, 0.99); }
+.cm-s-abcdef .CodeMirror-line::-moz-selection, .cm-s-abcdef .CodeMirror-line > span::-moz-selection, .cm-s-abcdef .CodeMirror-line > span > span::-moz-selection { background: rgba(56, 56, 56, 0.99); }
+.cm-s-abcdef .CodeMirror-gutters { background: #555; border-right: 2px solid #314151; }
+.cm-s-abcdef .CodeMirror-guttermarker { color: #222; }
+.cm-s-abcdef .CodeMirror-guttermarker-subtle { color: azure; }
+.cm-s-abcdef .CodeMirror-linenumber { color: #FFFFFF; }
+.cm-s-abcdef .CodeMirror-cursor { border-left: 1px solid #00FF00; }
+
+.cm-s-abcdef span.cm-keyword { color: darkgoldenrod; font-weight: bold; }
+.cm-s-abcdef span.cm-atom { color: #77F; }
+.cm-s-abcdef span.cm-number { color: violet; }
+.cm-s-abcdef span.cm-def { color: #fffabc; }
+.cm-s-abcdef span.cm-variable { color: #abcdef; }
+.cm-s-abcdef span.cm-variable-2 { color: #cacbcc; }
+.cm-s-abcdef span.cm-variable-3, .cm-s-abcdef span.cm-type { color: #def; }
+.cm-s-abcdef span.cm-property { color: #fedcba; }
+.cm-s-abcdef span.cm-operator { color: #ff0; }
+.cm-s-abcdef span.cm-comment { color: #7a7b7c; font-style: italic;}
+.cm-s-abcdef span.cm-string { color: #2b4; }
+.cm-s-abcdef span.cm-meta { color: #C9F; }
+.cm-s-abcdef span.cm-qualifier { color: #FFF700; }
+.cm-s-abcdef span.cm-builtin { color: #30aabc; }
+.cm-s-abcdef span.cm-bracket { color: #8a8a8a; }
+.cm-s-abcdef span.cm-tag { color: #FFDD44; }
+.cm-s-abcdef span.cm-attribute { color: #DDFF00; }
+.cm-s-abcdef span.cm-error { color: #FF0000; }
+.cm-s-abcdef span.cm-header { color: aquamarine; font-weight: bold; }
+.cm-s-abcdef span.cm-link { color: blueviolet; }
+
+.cm-s-abcdef .CodeMirror-activeline-background { background: #314151; }

+ 5 - 0
app/public/css/codemirror/theme/ambiance-mobile.css

@@ -0,0 +1,5 @@
+.cm-s-ambiance.CodeMirror {
+  -webkit-box-shadow: none;
+  -moz-box-shadow: none;
+  box-shadow: none;
+}

Diferenças do arquivo suprimidas por serem muito extensas
+ 74 - 0
app/public/css/codemirror/theme/ambiance.css


+ 38 - 0
app/public/css/codemirror/theme/base16-dark.css

@@ -0,0 +1,38 @@
+/*
+
+    Name:       Base16 Default Dark
+    Author:     Chris Kempson (http://chriskempson.com)
+
+    CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror)
+    Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
+
+*/
+
+.cm-s-base16-dark.CodeMirror { background: #151515; color: #e0e0e0; }
+.cm-s-base16-dark div.CodeMirror-selected { background: #303030; }
+.cm-s-base16-dark .CodeMirror-line::selection, .cm-s-base16-dark .CodeMirror-line > span::selection, .cm-s-base16-dark .CodeMirror-line > span > span::selection { background: rgba(48, 48, 48, .99); }
+.cm-s-base16-dark .CodeMirror-line::-moz-selection, .cm-s-base16-dark .CodeMirror-line > span::-moz-selection, .cm-s-base16-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(48, 48, 48, .99); }
+.cm-s-base16-dark .CodeMirror-gutters { background: #151515; border-right: 0px; }
+.cm-s-base16-dark .CodeMirror-guttermarker { color: #ac4142; }
+.cm-s-base16-dark .CodeMirror-guttermarker-subtle { color: #505050; }
+.cm-s-base16-dark .CodeMirror-linenumber { color: #505050; }
+.cm-s-base16-dark .CodeMirror-cursor { border-left: 1px solid #b0b0b0; }
+
+.cm-s-base16-dark span.cm-comment { color: #8f5536; }
+.cm-s-base16-dark span.cm-atom { color: #aa759f; }
+.cm-s-base16-dark span.cm-number { color: #aa759f; }
+
+.cm-s-base16-dark span.cm-property, .cm-s-base16-dark span.cm-attribute { color: #90a959; }
+.cm-s-base16-dark span.cm-keyword { color: #ac4142; }
+.cm-s-base16-dark span.cm-string { color: #f4bf75; }
+
+.cm-s-base16-dark span.cm-variable { color: #90a959; }
+.cm-s-base16-dark span.cm-variable-2 { color: #6a9fb5; }
+.cm-s-base16-dark span.cm-def { color: #d28445; }
+.cm-s-base16-dark span.cm-bracket { color: #e0e0e0; }
+.cm-s-base16-dark span.cm-tag { color: #ac4142; }
+.cm-s-base16-dark span.cm-link { color: #aa759f; }
+.cm-s-base16-dark span.cm-error { background: #ac4142; color: #b0b0b0; }
+
+.cm-s-base16-dark .CodeMirror-activeline-background { background: #202020; }
+.cm-s-base16-dark .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }

+ 38 - 0
app/public/css/codemirror/theme/base16-light.css

@@ -0,0 +1,38 @@
+/*
+
+    Name:       Base16 Default Light
+    Author:     Chris Kempson (http://chriskempson.com)
+
+    CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror)
+    Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
+
+*/
+
+.cm-s-base16-light.CodeMirror { background: #f5f5f5; color: #202020; }
+.cm-s-base16-light div.CodeMirror-selected { background: #e0e0e0; }
+.cm-s-base16-light .CodeMirror-line::selection, .cm-s-base16-light .CodeMirror-line > span::selection, .cm-s-base16-light .CodeMirror-line > span > span::selection { background: #e0e0e0; }
+.cm-s-base16-light .CodeMirror-line::-moz-selection, .cm-s-base16-light .CodeMirror-line > span::-moz-selection, .cm-s-base16-light .CodeMirror-line > span > span::-moz-selection { background: #e0e0e0; }
+.cm-s-base16-light .CodeMirror-gutters { background: #f5f5f5; border-right: 0px; }
+.cm-s-base16-light .CodeMirror-guttermarker { color: #ac4142; }
+.cm-s-base16-light .CodeMirror-guttermarker-subtle { color: #b0b0b0; }
+.cm-s-base16-light .CodeMirror-linenumber { color: #b0b0b0; }
+.cm-s-base16-light .CodeMirror-cursor { border-left: 1px solid #505050; }
+
+.cm-s-base16-light span.cm-comment { color: #8f5536; }
+.cm-s-base16-light span.cm-atom { color: #aa759f; }
+.cm-s-base16-light span.cm-number { color: #aa759f; }
+
+.cm-s-base16-light span.cm-property, .cm-s-base16-light span.cm-attribute { color: #90a959; }
+.cm-s-base16-light span.cm-keyword { color: #ac4142; }
+.cm-s-base16-light span.cm-string { color: #f4bf75; }
+
+.cm-s-base16-light span.cm-variable { color: #90a959; }
+.cm-s-base16-light span.cm-variable-2 { color: #6a9fb5; }
+.cm-s-base16-light span.cm-def { color: #d28445; }
+.cm-s-base16-light span.cm-bracket { color: #202020; }
+.cm-s-base16-light span.cm-tag { color: #ac4142; }
+.cm-s-base16-light span.cm-link { color: #aa759f; }
+.cm-s-base16-light span.cm-error { background: #ac4142; color: #505050; }
+
+.cm-s-base16-light .CodeMirror-activeline-background { background: #DDDCDC; }
+.cm-s-base16-light .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }

+ 34 - 0
app/public/css/codemirror/theme/bespin.css

@@ -0,0 +1,34 @@
+/*
+
+    Name:       Bespin
+    Author:     Mozilla / Jan T. Sott
+
+    CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror)
+    Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
+
+*/
+
+.cm-s-bespin.CodeMirror {background: #28211c; color: #9d9b97;}
+.cm-s-bespin div.CodeMirror-selected {background: #36312e !important;}
+.cm-s-bespin .CodeMirror-gutters {background: #28211c; border-right: 0px;}
+.cm-s-bespin .CodeMirror-linenumber {color: #666666;}
+.cm-s-bespin .CodeMirror-cursor {border-left: 1px solid #797977 !important;}
+
+.cm-s-bespin span.cm-comment {color: #937121;}
+.cm-s-bespin span.cm-atom {color: #9b859d;}
+.cm-s-bespin span.cm-number {color: #9b859d;}
+
+.cm-s-bespin span.cm-property, .cm-s-bespin span.cm-attribute {color: #54be0d;}
+.cm-s-bespin span.cm-keyword {color: #cf6a4c;}
+.cm-s-bespin span.cm-string {color: #f9ee98;}
+
+.cm-s-bespin span.cm-variable {color: #54be0d;}
+.cm-s-bespin span.cm-variable-2 {color: #5ea6ea;}
+.cm-s-bespin span.cm-def {color: #cf7d34;}
+.cm-s-bespin span.cm-error {background: #cf6a4c; color: #797977;}
+.cm-s-bespin span.cm-bracket {color: #9d9b97;}
+.cm-s-bespin span.cm-tag {color: #cf6a4c;}
+.cm-s-bespin span.cm-link {color: #9b859d;}
+
+.cm-s-bespin .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;}
+.cm-s-bespin .CodeMirror-activeline-background { background: #404040; }

+ 32 - 0
app/public/css/codemirror/theme/blackboard.css

@@ -0,0 +1,32 @@
+/* Port of TextMate's Blackboard theme */
+
+.cm-s-blackboard.CodeMirror { background: #0C1021; color: #F8F8F8; }
+.cm-s-blackboard div.CodeMirror-selected { background: #253B76; }
+.cm-s-blackboard .CodeMirror-line::selection, .cm-s-blackboard .CodeMirror-line > span::selection, .cm-s-blackboard .CodeMirror-line > span > span::selection { background: rgba(37, 59, 118, .99); }
+.cm-s-blackboard .CodeMirror-line::-moz-selection, .cm-s-blackboard .CodeMirror-line > span::-moz-selection, .cm-s-blackboard .CodeMirror-line > span > span::-moz-selection { background: rgba(37, 59, 118, .99); }
+.cm-s-blackboard .CodeMirror-gutters { background: #0C1021; border-right: 0; }
+.cm-s-blackboard .CodeMirror-guttermarker { color: #FBDE2D; }
+.cm-s-blackboard .CodeMirror-guttermarker-subtle { color: #888; }
+.cm-s-blackboard .CodeMirror-linenumber { color: #888; }
+.cm-s-blackboard .CodeMirror-cursor { border-left: 1px solid #A7A7A7; }
+
+.cm-s-blackboard .cm-keyword { color: #FBDE2D; }
+.cm-s-blackboard .cm-atom { color: #D8FA3C; }
+.cm-s-blackboard .cm-number { color: #D8FA3C; }
+.cm-s-blackboard .cm-def { color: #8DA6CE; }
+.cm-s-blackboard .cm-variable { color: #FF6400; }
+.cm-s-blackboard .cm-operator { color: #FBDE2D; }
+.cm-s-blackboard .cm-comment { color: #AEAEAE; }
+.cm-s-blackboard .cm-string { color: #61CE3C; }
+.cm-s-blackboard .cm-string-2 { color: #61CE3C; }
+.cm-s-blackboard .cm-meta { color: #D8FA3C; }
+.cm-s-blackboard .cm-builtin { color: #8DA6CE; }
+.cm-s-blackboard .cm-tag { color: #8DA6CE; }
+.cm-s-blackboard .cm-attribute { color: #8DA6CE; }
+.cm-s-blackboard .cm-header { color: #FF6400; }
+.cm-s-blackboard .cm-hr { color: #AEAEAE; }
+.cm-s-blackboard .cm-link { color: #8DA6CE; }
+.cm-s-blackboard .cm-error { background: #9D1E15; color: #F8F8F8; }
+
+.cm-s-blackboard .CodeMirror-activeline-background { background: #3C3636; }
+.cm-s-blackboard .CodeMirror-matchingbracket { outline:1px solid grey;color:white !important; }

+ 25 - 0
app/public/css/codemirror/theme/cobalt.css

@@ -0,0 +1,25 @@
+.cm-s-cobalt.CodeMirror { background: #002240; color: white; }
+.cm-s-cobalt div.CodeMirror-selected { background: #b36539; }
+.cm-s-cobalt .CodeMirror-line::selection, .cm-s-cobalt .CodeMirror-line > span::selection, .cm-s-cobalt .CodeMirror-line > span > span::selection { background: rgba(179, 101, 57, .99); }
+.cm-s-cobalt .CodeMirror-line::-moz-selection, .cm-s-cobalt .CodeMirror-line > span::-moz-selection, .cm-s-cobalt .CodeMirror-line > span > span::-moz-selection { background: rgba(179, 101, 57, .99); }
+.cm-s-cobalt .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; }
+.cm-s-cobalt .CodeMirror-guttermarker { color: #ffee80; }
+.cm-s-cobalt .CodeMirror-guttermarker-subtle { color: #d0d0d0; }
+.cm-s-cobalt .CodeMirror-linenumber { color: #d0d0d0; }
+.cm-s-cobalt .CodeMirror-cursor { border-left: 1px solid white; }
+
+.cm-s-cobalt span.cm-comment { color: #08f; }
+.cm-s-cobalt span.cm-atom { color: #845dc4; }
+.cm-s-cobalt span.cm-number, .cm-s-cobalt span.cm-attribute { color: #ff80e1; }
+.cm-s-cobalt span.cm-keyword { color: #ffee80; }
+.cm-s-cobalt span.cm-string { color: #3ad900; }
+.cm-s-cobalt span.cm-meta { color: #ff9d00; }
+.cm-s-cobalt span.cm-variable-2, .cm-s-cobalt span.cm-tag { color: #9effff; }
+.cm-s-cobalt span.cm-variable-3, .cm-s-cobalt span.cm-def, .cm-s-cobalt .cm-type { color: white; }
+.cm-s-cobalt span.cm-bracket { color: #d8d8d8; }
+.cm-s-cobalt span.cm-builtin, .cm-s-cobalt span.cm-special { color: #ff9e59; }
+.cm-s-cobalt span.cm-link { color: #845dc4; }
+.cm-s-cobalt span.cm-error { color: #9d1e15; }
+
+.cm-s-cobalt .CodeMirror-activeline-background { background: #002D57; }
+.cm-s-cobalt .CodeMirror-matchingbracket { outline:1px solid grey;color:white !important; }

+ 33 - 0
app/public/css/codemirror/theme/colorforth.css

@@ -0,0 +1,33 @@
+.cm-s-colorforth.CodeMirror { background: #000000; color: #f8f8f8; }
+.cm-s-colorforth .CodeMirror-gutters { background: #0a001f; border-right: 1px solid #aaa; }
+.cm-s-colorforth .CodeMirror-guttermarker { color: #FFBD40; }
+.cm-s-colorforth .CodeMirror-guttermarker-subtle { color: #78846f; }
+.cm-s-colorforth .CodeMirror-linenumber { color: #bababa; }
+.cm-s-colorforth .CodeMirror-cursor { border-left: 1px solid white; }
+
+.cm-s-colorforth span.cm-comment     { color: #ededed; }
+.cm-s-colorforth span.cm-def         { color: #ff1c1c; font-weight:bold; }
+.cm-s-colorforth span.cm-keyword     { color: #ffd900; }
+.cm-s-colorforth span.cm-builtin     { color: #00d95a; }
+.cm-s-colorforth span.cm-variable    { color: #73ff00; }
+.cm-s-colorforth span.cm-string      { color: #007bff; }
+.cm-s-colorforth span.cm-number      { color: #00c4ff; }
+.cm-s-colorforth span.cm-atom        { color: #606060; }
+
+.cm-s-colorforth span.cm-variable-2  { color: #EEE; }
+.cm-s-colorforth span.cm-variable-3, .cm-s-colorforth span.cm-type { color: #DDD; }
+.cm-s-colorforth span.cm-property    {}
+.cm-s-colorforth span.cm-operator    {}
+
+.cm-s-colorforth span.cm-meta        { color: yellow; }
+.cm-s-colorforth span.cm-qualifier   { color: #FFF700; }
+.cm-s-colorforth span.cm-bracket     { color: #cc7; }
+.cm-s-colorforth span.cm-tag         { color: #FFBD40; }
+.cm-s-colorforth span.cm-attribute   { color: #FFF700; }
+.cm-s-colorforth span.cm-error       { color: #f00; }
+
+.cm-s-colorforth div.CodeMirror-selected { background: #333d53; }
+
+.cm-s-colorforth span.cm-compilation { background: rgba(255, 255, 255, 0.12); }
+
+.cm-s-colorforth .CodeMirror-activeline-background { background: #253540; }

+ 40 - 0
app/public/css/codemirror/theme/dracula.css

@@ -0,0 +1,40 @@
+/*
+
+    Name:       dracula
+    Author:     Michael Kaminsky (http://github.com/mkaminsky11)
+
+    Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme)
+
+*/
+
+
+.cm-s-dracula.CodeMirror, .cm-s-dracula .CodeMirror-gutters {
+  background-color: #282a36 !important;
+  color: #f8f8f2 !important;
+  border: none;
+}
+.cm-s-dracula .CodeMirror-gutters { color: #282a36; }
+.cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; }
+.cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; }
+.cm-s-dracula .CodeMirror-selected { background: rgba(255, 255, 255, 0.10); }
+.cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }
+.cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }
+.cm-s-dracula span.cm-comment { color: #6272a4; }
+.cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; }
+.cm-s-dracula span.cm-number { color: #bd93f9; }
+.cm-s-dracula span.cm-variable { color: #50fa7b; }
+.cm-s-dracula span.cm-variable-2 { color: white; }
+.cm-s-dracula span.cm-def { color: #50fa7b; }
+.cm-s-dracula span.cm-operator { color: #ff79c6; }
+.cm-s-dracula span.cm-keyword { color: #ff79c6; }
+.cm-s-dracula span.cm-atom { color: #bd93f9; }
+.cm-s-dracula span.cm-meta { color: #f8f8f2; }
+.cm-s-dracula span.cm-tag { color: #ff79c6; }
+.cm-s-dracula span.cm-attribute { color: #50fa7b; }
+.cm-s-dracula span.cm-qualifier { color: #50fa7b; }
+.cm-s-dracula span.cm-property { color: #66d9ef; }
+.cm-s-dracula span.cm-builtin { color: #50fa7b; }
+.cm-s-dracula span.cm-variable-3, .cm-s-dracula span.cm-type { color: #ffb86c; }
+
+.cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); }
+.cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }

+ 35 - 0
app/public/css/codemirror/theme/duotone-dark.css

@@ -0,0 +1,35 @@
+/*
+Name:   DuoTone-Dark
+Author: by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes)
+
+CodeMirror template by Jan T. Sott (https://github.com/idleberg), adapted by Bram de Haan (https://github.com/atelierbram/)
+*/
+
+.cm-s-duotone-dark.CodeMirror { background: #2a2734; color: #6c6783; }
+.cm-s-duotone-dark div.CodeMirror-selected { background: #545167!important; }
+.cm-s-duotone-dark .CodeMirror-gutters { background: #2a2734; border-right: 0px; }
+.cm-s-duotone-dark .CodeMirror-linenumber { color: #545167; }
+
+/* begin cursor */
+.cm-s-duotone-dark .CodeMirror-cursor { border-left: 1px solid #ffad5c; /* border-left: 1px solid #ffad5c80; */ border-right: .5em solid #ffad5c; /* border-right: .5em solid #ffad5c80; */ opacity: .5; }
+.cm-s-duotone-dark .CodeMirror-activeline-background { background: #363342; /* background: #36334280;  */ opacity: .5;}
+.cm-s-duotone-dark .cm-fat-cursor .CodeMirror-cursor { background: #ffad5c; /* background: #ffad5c80; */ opacity: .5;}
+/* end cursor */
+
+.cm-s-duotone-dark span.cm-atom, .cm-s-duotone-dark span.cm-number, .cm-s-duotone-dark span.cm-keyword, .cm-s-duotone-dark span.cm-variable, .cm-s-duotone-dark span.cm-attribute, .cm-s-duotone-dark span.cm-quote, .cm-s-duotone-dark span.cm-hr, .cm-s-duotone-dark span.cm-link { color: #ffcc99; }
+
+.cm-s-duotone-dark span.cm-property { color: #9a86fd; }
+.cm-s-duotone-dark span.cm-punctuation, .cm-s-duotone-dark span.cm-unit, .cm-s-duotone-dark span.cm-negative { color: #e09142; }
+.cm-s-duotone-dark span.cm-string { color: #ffb870; }
+.cm-s-duotone-dark span.cm-operator { color: #ffad5c; }
+.cm-s-duotone-dark span.cm-positive { color: #6a51e6; }
+
+.cm-s-duotone-dark span.cm-variable-2, .cm-s-duotone-dark span.cm-variable-3, .cm-s-duotone-dark span.cm-type, .cm-s-duotone-dark span.cm-string-2, .cm-s-duotone-dark span.cm-url { color: #7a63ee; }
+.cm-s-duotone-dark span.cm-def, .cm-s-duotone-dark span.cm-tag, .cm-s-duotone-dark span.cm-builtin, .cm-s-duotone-dark span.cm-qualifier, .cm-s-duotone-dark span.cm-header, .cm-s-duotone-dark span.cm-em { color: #eeebff; }
+.cm-s-duotone-dark span.cm-bracket, .cm-s-duotone-dark span.cm-comment { color: #6c6783; }
+
+/* using #f00 red for errors, don't think any of the colorscheme variables will stand out enough, ... maybe by giving it a background-color ... */
+.cm-s-duotone-dark span.cm-error, .cm-s-duotone-dark span.cm-invalidchar { color: #f00; }
+
+.cm-s-duotone-dark span.cm-header { font-weight: normal; }
+.cm-s-duotone-dark .CodeMirror-matchingbracket { text-decoration: underline; color: #eeebff !important; } 

+ 36 - 0
app/public/css/codemirror/theme/duotone-light.css

@@ -0,0 +1,36 @@
+/*
+Name:   DuoTone-Light
+Author: by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes)
+
+CodeMirror template by Jan T. Sott (https://github.com/idleberg), adapted by Bram de Haan (https://github.com/atelierbram/)
+*/
+
+.cm-s-duotone-light.CodeMirror { background: #faf8f5; color: #b29762; }
+.cm-s-duotone-light div.CodeMirror-selected { background: #e3dcce !important; }
+.cm-s-duotone-light .CodeMirror-gutters { background: #faf8f5; border-right: 0px; }
+.cm-s-duotone-light .CodeMirror-linenumber { color: #cdc4b1; }
+
+/* begin cursor */
+.cm-s-duotone-light .CodeMirror-cursor { border-left: 1px solid #93abdc; /* border-left: 1px solid #93abdc80; */ border-right: .5em solid #93abdc; /* border-right: .5em solid #93abdc80; */ opacity: .5; }
+.cm-s-duotone-light .CodeMirror-activeline-background { background: #e3dcce;  /* background: #e3dcce80; */ opacity: .5; }
+.cm-s-duotone-light .cm-fat-cursor .CodeMirror-cursor { background: #93abdc; /* #93abdc80; */ opacity: .5; }
+/* end cursor */
+
+.cm-s-duotone-light span.cm-atom, .cm-s-duotone-light span.cm-number, .cm-s-duotone-light span.cm-keyword, .cm-s-duotone-light span.cm-variable, .cm-s-duotone-light span.cm-attribute, .cm-s-duotone-light span.cm-quote, .cm-s-duotone-light-light span.cm-hr, .cm-s-duotone-light-light span.cm-link { color: #063289; }
+
+.cm-s-duotone-light span.cm-property { color: #b29762; }
+.cm-s-duotone-light span.cm-punctuation, .cm-s-duotone-light span.cm-unit, .cm-s-duotone-light span.cm-negative { color: #063289; }
+.cm-s-duotone-light span.cm-string, .cm-s-duotone-light span.cm-operator { color: #1659df; }
+.cm-s-duotone-light span.cm-positive { color: #896724; }
+
+.cm-s-duotone-light span.cm-variable-2, .cm-s-duotone-light span.cm-variable-3, .cm-s-duotone-light span.cm-type, .cm-s-duotone-light span.cm-string-2, .cm-s-duotone-light span.cm-url { color: #896724; }
+.cm-s-duotone-light span.cm-def, .cm-s-duotone-light span.cm-tag, .cm-s-duotone-light span.cm-builtin, .cm-s-duotone-light span.cm-qualifier, .cm-s-duotone-light span.cm-header, .cm-s-duotone-light span.cm-em { color: #2d2006; }
+.cm-s-duotone-light span.cm-bracket, .cm-s-duotone-light span.cm-comment { color: #b6ad9a; }
+
+/* using #f00 red for errors, don't think any of the colorscheme variables will stand out enough, ... maybe by giving it a background-color ... */
+/* .cm-s-duotone-light span.cm-error { background: #896724; color: #728fcb; } */
+.cm-s-duotone-light span.cm-error, .cm-s-duotone-light span.cm-invalidchar { color: #f00; }
+
+.cm-s-duotone-light span.cm-header { font-weight: normal; }
+.cm-s-duotone-light .CodeMirror-matchingbracket { text-decoration: underline; color: #faf8f5 !important; }
+

+ 23 - 0
app/public/css/codemirror/theme/eclipse.css

@@ -0,0 +1,23 @@
+.cm-s-eclipse span.cm-meta { color: #FF1717; }
+.cm-s-eclipse span.cm-keyword { line-height: 1em; font-weight: bold; color: #7F0055; }
+.cm-s-eclipse span.cm-atom { color: #219; }
+.cm-s-eclipse span.cm-number { color: #164; }
+.cm-s-eclipse span.cm-def { color: #00f; }
+.cm-s-eclipse span.cm-variable { color: black; }
+.cm-s-eclipse span.cm-variable-2 { color: #0000C0; }
+.cm-s-eclipse span.cm-variable-3, .cm-s-eclipse span.cm-type { color: #0000C0; }
+.cm-s-eclipse span.cm-property { color: black; }
+.cm-s-eclipse span.cm-operator { color: black; }
+.cm-s-eclipse span.cm-comment { color: #3F7F5F; }
+.cm-s-eclipse span.cm-string { color: #2A00FF; }
+.cm-s-eclipse span.cm-string-2 { color: #f50; }
+.cm-s-eclipse span.cm-qualifier { color: #555; }
+.cm-s-eclipse span.cm-builtin { color: #30a; }
+.cm-s-eclipse span.cm-bracket { color: #cc7; }
+.cm-s-eclipse span.cm-tag { color: #170; }
+.cm-s-eclipse span.cm-attribute { color: #00c; }
+.cm-s-eclipse span.cm-link { color: #219; }
+.cm-s-eclipse span.cm-error { color: #f00; }
+
+.cm-s-eclipse .CodeMirror-activeline-background { background: #e8f2ff; }
+.cm-s-eclipse .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; }

+ 13 - 0
app/public/css/codemirror/theme/elegant.css

@@ -0,0 +1,13 @@
+.cm-s-elegant span.cm-number, .cm-s-elegant span.cm-string, .cm-s-elegant span.cm-atom { color: #762; }
+.cm-s-elegant span.cm-comment { color: #262; font-style: italic; line-height: 1em; }
+.cm-s-elegant span.cm-meta { color: #555; font-style: italic; line-height: 1em; }
+.cm-s-elegant span.cm-variable { color: black; }
+.cm-s-elegant span.cm-variable-2 { color: #b11; }
+.cm-s-elegant span.cm-qualifier { color: #555; }
+.cm-s-elegant span.cm-keyword { color: #730; }
+.cm-s-elegant span.cm-builtin { color: #30a; }
+.cm-s-elegant span.cm-link { color: #762; }
+.cm-s-elegant span.cm-error { background-color: #fdd; }
+
+.cm-s-elegant .CodeMirror-activeline-background { background: #e8f2ff; }
+.cm-s-elegant .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; }

+ 34 - 0
app/public/css/codemirror/theme/erlang-dark.css

@@ -0,0 +1,34 @@
+.cm-s-erlang-dark.CodeMirror { background: #002240; color: white; }
+.cm-s-erlang-dark div.CodeMirror-selected { background: #b36539; }
+.cm-s-erlang-dark .CodeMirror-line::selection, .cm-s-erlang-dark .CodeMirror-line > span::selection, .cm-s-erlang-dark .CodeMirror-line > span > span::selection { background: rgba(179, 101, 57, .99); }
+.cm-s-erlang-dark .CodeMirror-line::-moz-selection, .cm-s-erlang-dark .CodeMirror-line > span::-moz-selection, .cm-s-erlang-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(179, 101, 57, .99); }
+.cm-s-erlang-dark .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; }
+.cm-s-erlang-dark .CodeMirror-guttermarker { color: white; }
+.cm-s-erlang-dark .CodeMirror-guttermarker-subtle { color: #d0d0d0; }
+.cm-s-erlang-dark .CodeMirror-linenumber { color: #d0d0d0; }
+.cm-s-erlang-dark .CodeMirror-cursor { border-left: 1px solid white; }
+
+.cm-s-erlang-dark span.cm-quote      { color: #ccc; }
+.cm-s-erlang-dark span.cm-atom       { color: #f133f1; }
+.cm-s-erlang-dark span.cm-attribute  { color: #ff80e1; }
+.cm-s-erlang-dark span.cm-bracket    { color: #ff9d00; }
+.cm-s-erlang-dark span.cm-builtin    { color: #eaa; }
+.cm-s-erlang-dark span.cm-comment    { color: #77f; }
+.cm-s-erlang-dark span.cm-def        { color: #e7a; }
+.cm-s-erlang-dark span.cm-keyword    { color: #ffee80; }
+.cm-s-erlang-dark span.cm-meta       { color: #50fefe; }
+.cm-s-erlang-dark span.cm-number     { color: #ffd0d0; }
+.cm-s-erlang-dark span.cm-operator   { color: #d55; }
+.cm-s-erlang-dark span.cm-property   { color: #ccc; }
+.cm-s-erlang-dark span.cm-qualifier  { color: #ccc; }
+.cm-s-erlang-dark span.cm-special    { color: #ffbbbb; }
+.cm-s-erlang-dark span.cm-string     { color: #3ad900; }
+.cm-s-erlang-dark span.cm-string-2   { color: #ccc; }
+.cm-s-erlang-dark span.cm-tag        { color: #9effff; }
+.cm-s-erlang-dark span.cm-variable   { color: #50fe50; }
+.cm-s-erlang-dark span.cm-variable-2 { color: #e0e; }
+.cm-s-erlang-dark span.cm-variable-3, .cm-s-erlang-dark span.cm-type { color: #ccc; }
+.cm-s-erlang-dark span.cm-error      { color: #9d1e15; }
+
+.cm-s-erlang-dark .CodeMirror-activeline-background { background: #013461; }
+.cm-s-erlang-dark .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; }

+ 34 - 0
app/public/css/codemirror/theme/hopscotch.css

@@ -0,0 +1,34 @@
+/*
+
+    Name:       Hopscotch
+    Author:     Jan T. Sott
+
+    CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror)
+    Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
+
+*/
+
+.cm-s-hopscotch.CodeMirror {background: #322931; color: #d5d3d5;}
+.cm-s-hopscotch div.CodeMirror-selected {background: #433b42 !important;}
+.cm-s-hopscotch .CodeMirror-gutters {background: #322931; border-right: 0px;}
+.cm-s-hopscotch .CodeMirror-linenumber {color: #797379;}
+.cm-s-hopscotch .CodeMirror-cursor {border-left: 1px solid #989498 !important;}
+
+.cm-s-hopscotch span.cm-comment {color: #b33508;}
+.cm-s-hopscotch span.cm-atom {color: #c85e7c;}
+.cm-s-hopscotch span.cm-number {color: #c85e7c;}
+
+.cm-s-hopscotch span.cm-property, .cm-s-hopscotch span.cm-attribute {color: #8fc13e;}
+.cm-s-hopscotch span.cm-keyword {color: #dd464c;}
+.cm-s-hopscotch span.cm-string {color: #fdcc59;}
+
+.cm-s-hopscotch span.cm-variable {color: #8fc13e;}
+.cm-s-hopscotch span.cm-variable-2 {color: #1290bf;}
+.cm-s-hopscotch span.cm-def {color: #fd8b19;}
+.cm-s-hopscotch span.cm-error {background: #dd464c; color: #989498;}
+.cm-s-hopscotch span.cm-bracket {color: #d5d3d5;}
+.cm-s-hopscotch span.cm-tag {color: #dd464c;}
+.cm-s-hopscotch span.cm-link {color: #c85e7c;}
+
+.cm-s-hopscotch .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;}
+.cm-s-hopscotch .CodeMirror-activeline-background { background: #302020; }

+ 43 - 0
app/public/css/codemirror/theme/icecoder.css

@@ -0,0 +1,43 @@
+/*
+ICEcoder default theme by Matt Pass, used in code editor available at https://icecoder.net
+*/
+
+.cm-s-icecoder { color: #666; background: #1d1d1b; }
+
+.cm-s-icecoder span.cm-keyword { color: #eee; font-weight:bold; }  /* off-white 1 */
+.cm-s-icecoder span.cm-atom { color: #e1c76e; }                    /* yellow */
+.cm-s-icecoder span.cm-number { color: #6cb5d9; }                  /* blue */
+.cm-s-icecoder span.cm-def { color: #b9ca4a; }                     /* green */
+
+.cm-s-icecoder span.cm-variable { color: #6cb5d9; }                /* blue */
+.cm-s-icecoder span.cm-variable-2 { color: #cc1e5c; }              /* pink */
+.cm-s-icecoder span.cm-variable-3, .cm-s-icecoder span.cm-type { color: #f9602c; } /* orange */
+
+.cm-s-icecoder span.cm-property { color: #eee; }                   /* off-white 1 */
+.cm-s-icecoder span.cm-operator { color: #9179bb; }                /* purple */
+.cm-s-icecoder span.cm-comment { color: #97a3aa; }                 /* grey-blue */
+
+.cm-s-icecoder span.cm-string { color: #b9ca4a; }                  /* green */
+.cm-s-icecoder span.cm-string-2 { color: #6cb5d9; }                /* blue */
+
+.cm-s-icecoder span.cm-meta { color: #555; }                       /* grey */
+
+.cm-s-icecoder span.cm-qualifier { color: #555; }                  /* grey */
+.cm-s-icecoder span.cm-builtin { color: #214e7b; }                 /* bright blue */
+.cm-s-icecoder span.cm-bracket { color: #cc7; }                    /* grey-yellow */
+
+.cm-s-icecoder span.cm-tag { color: #e8e8e8; }                     /* off-white 2 */
+.cm-s-icecoder span.cm-attribute { color: #099; }                  /* teal */
+
+.cm-s-icecoder span.cm-header { color: #6a0d6a; }                  /* purple-pink */
+.cm-s-icecoder span.cm-quote { color: #186718; }                   /* dark green */
+.cm-s-icecoder span.cm-hr { color: #888; }                         /* mid-grey */
+.cm-s-icecoder span.cm-link { color: #e1c76e; }                    /* yellow */
+.cm-s-icecoder span.cm-error { color: #d00; }                      /* red */
+
+.cm-s-icecoder .CodeMirror-cursor { border-left: 1px solid white; }
+.cm-s-icecoder div.CodeMirror-selected { color: #fff; background: #037; }
+.cm-s-icecoder .CodeMirror-gutters { background: #1d1d1b; min-width: 41px; border-right: 0; }
+.cm-s-icecoder .CodeMirror-linenumber { color: #555; cursor: default; }
+.cm-s-icecoder .CodeMirror-matchingbracket { color: #fff !important; background: #555 !important; }
+.cm-s-icecoder .CodeMirror-activeline-background { background: #000; }

+ 34 - 0
app/public/css/codemirror/theme/isotope.css

@@ -0,0 +1,34 @@
+/*
+
+    Name:       Isotope
+    Author:     David Desandro / Jan T. Sott
+
+    CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror)
+    Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
+
+*/
+
+.cm-s-isotope.CodeMirror {background: #000000; color: #e0e0e0;}
+.cm-s-isotope div.CodeMirror-selected {background: #404040 !important;}
+.cm-s-isotope .CodeMirror-gutters {background: #000000; border-right: 0px;}
+.cm-s-isotope .CodeMirror-linenumber {color: #808080;}
+.cm-s-isotope .CodeMirror-cursor {border-left: 1px solid #c0c0c0 !important;}
+
+.cm-s-isotope span.cm-comment {color: #3300ff;}
+.cm-s-isotope span.cm-atom {color: #cc00ff;}
+.cm-s-isotope span.cm-number {color: #cc00ff;}
+
+.cm-s-isotope span.cm-property, .cm-s-isotope span.cm-attribute {color: #33ff00;}
+.cm-s-isotope span.cm-keyword {color: #ff0000;}
+.cm-s-isotope span.cm-string {color: #ff0099;}
+
+.cm-s-isotope span.cm-variable {color: #33ff00;}
+.cm-s-isotope span.cm-variable-2 {color: #0066ff;}
+.cm-s-isotope span.cm-def {color: #ff9900;}
+.cm-s-isotope span.cm-error {background: #ff0000; color: #c0c0c0;}
+.cm-s-isotope span.cm-bracket {color: #e0e0e0;}
+.cm-s-isotope span.cm-tag {color: #ff0000;}
+.cm-s-isotope span.cm-link {color: #cc00ff;}
+
+.cm-s-isotope .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;}
+.cm-s-isotope .CodeMirror-activeline-background { background: #202020; }

+ 47 - 0
app/public/css/codemirror/theme/lesser-dark.css

@@ -0,0 +1,47 @@
+/*
+http://lesscss.org/ dark theme
+Ported to CodeMirror by Peter Kroon
+*/
+.cm-s-lesser-dark {
+  line-height: 1.3em;
+}
+.cm-s-lesser-dark.CodeMirror { background: #262626; color: #EBEFE7; text-shadow: 0 -1px 1px #262626; }
+.cm-s-lesser-dark div.CodeMirror-selected { background: #45443B; } /* 33322B*/
+.cm-s-lesser-dark .CodeMirror-line::selection, .cm-s-lesser-dark .CodeMirror-line > span::selection, .cm-s-lesser-dark .CodeMirror-line > span > span::selection { background: rgba(69, 68, 59, .99); }
+.cm-s-lesser-dark .CodeMirror-line::-moz-selection, .cm-s-lesser-dark .CodeMirror-line > span::-moz-selection, .cm-s-lesser-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(69, 68, 59, .99); }
+.cm-s-lesser-dark .CodeMirror-cursor { border-left: 1px solid white; }
+.cm-s-lesser-dark pre { padding: 0 8px; }/*editable code holder*/
+
+.cm-s-lesser-dark.CodeMirror span.CodeMirror-matchingbracket { color: #7EFC7E; }/*65FC65*/
+
+.cm-s-lesser-dark .CodeMirror-gutters { background: #262626; border-right:1px solid #aaa; }
+.cm-s-lesser-dark .CodeMirror-guttermarker { color: #599eff; }
+.cm-s-lesser-dark .CodeMirror-guttermarker-subtle { color: #777; }
+.cm-s-lesser-dark .CodeMirror-linenumber { color: #777; }
+
+.cm-s-lesser-dark span.cm-header { color: #a0a; }
+.cm-s-lesser-dark span.cm-quote { color: #090; }
+.cm-s-lesser-dark span.cm-keyword { color: #599eff; }
+.cm-s-lesser-dark span.cm-atom { color: #C2B470; }
+.cm-s-lesser-dark span.cm-number { color: #B35E4D; }
+.cm-s-lesser-dark span.cm-def { color: white; }
+.cm-s-lesser-dark span.cm-variable { color:#D9BF8C; }
+.cm-s-lesser-dark span.cm-variable-2 { color: #669199; }
+.cm-s-lesser-dark span.cm-variable-3, .cm-s-lesser-dark span.cm-type { color: white; }
+.cm-s-lesser-dark span.cm-property { color: #92A75C; }
+.cm-s-lesser-dark span.cm-operator { color: #92A75C; }
+.cm-s-lesser-dark span.cm-comment { color: #666; }
+.cm-s-lesser-dark span.cm-string { color: #BCD279; }
+.cm-s-lesser-dark span.cm-string-2 { color: #f50; }
+.cm-s-lesser-dark span.cm-meta { color: #738C73; }
+.cm-s-lesser-dark span.cm-qualifier { color: #555; }
+.cm-s-lesser-dark span.cm-builtin { color: #ff9e59; }
+.cm-s-lesser-dark span.cm-bracket { color: #EBEFE7; }
+.cm-s-lesser-dark span.cm-tag { color: #669199; }
+.cm-s-lesser-dark span.cm-attribute { color: #00c; }
+.cm-s-lesser-dark span.cm-hr { color: #999; }
+.cm-s-lesser-dark span.cm-link { color: #00c; }
+.cm-s-lesser-dark span.cm-error { color: #9d1e15; }
+
+.cm-s-lesser-dark .CodeMirror-activeline-background { background: #3C3A3A; }
+.cm-s-lesser-dark .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; }

+ 95 - 0
app/public/css/codemirror/theme/liquibyte.css

@@ -0,0 +1,95 @@
+.cm-s-liquibyte.CodeMirror {
+	background-color: #000;
+	color: #fff;
+	line-height: 1.2em;
+	font-size: 1em;
+}
+.cm-s-liquibyte .CodeMirror-focused .cm-matchhighlight {
+	text-decoration: underline;
+	text-decoration-color: #0f0;
+	text-decoration-style: wavy;
+}
+.cm-s-liquibyte .cm-trailingspace {
+	text-decoration: line-through;
+	text-decoration-color: #f00;
+	text-decoration-style: dotted;
+}
+.cm-s-liquibyte .cm-tab {
+	text-decoration: line-through;
+	text-decoration-color: #404040;
+	text-decoration-style: dotted;
+}
+.cm-s-liquibyte .CodeMirror-gutters { background-color: #262626; border-right: 1px solid #505050; padding-right: 0.8em; }
+.cm-s-liquibyte .CodeMirror-gutter-elt div { font-size: 1.2em; }
+.cm-s-liquibyte .CodeMirror-guttermarker {  }
+.cm-s-liquibyte .CodeMirror-guttermarker-subtle {  }
+.cm-s-liquibyte .CodeMirror-linenumber { color: #606060; padding-left: 0; }
+.cm-s-liquibyte .CodeMirror-cursor { border-left: 1px solid #eee; }
+
+.cm-s-liquibyte span.cm-comment     { color: #008000; }
+.cm-s-liquibyte span.cm-def         { color: #ffaf40; font-weight: bold; }
+.cm-s-liquibyte span.cm-keyword     { color: #c080ff; font-weight: bold; }
+.cm-s-liquibyte span.cm-builtin     { color: #ffaf40; font-weight: bold; }
+.cm-s-liquibyte span.cm-variable    { color: #5967ff; font-weight: bold; }
+.cm-s-liquibyte span.cm-string      { color: #ff8000; }
+.cm-s-liquibyte span.cm-number      { color: #0f0; font-weight: bold; }
+.cm-s-liquibyte span.cm-atom        { color: #bf3030; font-weight: bold; }
+
+.cm-s-liquibyte span.cm-variable-2  { color: #007f7f; font-weight: bold; }
+.cm-s-liquibyte span.cm-variable-3, .cm-s-liquibyte span.cm-type { color: #c080ff; font-weight: bold; }
+.cm-s-liquibyte span.cm-property    { color: #999; font-weight: bold; }
+.cm-s-liquibyte span.cm-operator    { color: #fff; }
+
+.cm-s-liquibyte span.cm-meta        { color: #0f0; }
+.cm-s-liquibyte span.cm-qualifier   { color: #fff700; font-weight: bold; }
+.cm-s-liquibyte span.cm-bracket     { color: #cc7; }
+.cm-s-liquibyte span.cm-tag         { color: #ff0; font-weight: bold; }
+.cm-s-liquibyte span.cm-attribute   { color: #c080ff; font-weight: bold; }
+.cm-s-liquibyte span.cm-error       { color: #f00; }
+
+.cm-s-liquibyte div.CodeMirror-selected { background-color: rgba(255, 0, 0, 0.25); }
+
+.cm-s-liquibyte span.cm-compilation { background-color: rgba(255, 255, 255, 0.12); }
+
+.cm-s-liquibyte .CodeMirror-activeline-background { background-color: rgba(0, 255, 0, 0.15); }
+
+/* Default styles for common addons */
+.cm-s-liquibyte .CodeMirror span.CodeMirror-matchingbracket { color: #0f0; font-weight: bold; }
+.cm-s-liquibyte .CodeMirror span.CodeMirror-nonmatchingbracket { color: #f00; font-weight: bold; }
+.CodeMirror-matchingtag { background-color: rgba(150, 255, 0, .3); }
+/* Scrollbars */
+/* Simple */
+.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal div:hover, .cm-s-liquibyte div.CodeMirror-simplescroll-vertical div:hover {
+	background-color: rgba(80, 80, 80, .7);
+}
+.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal div, .cm-s-liquibyte div.CodeMirror-simplescroll-vertical div {
+	background-color: rgba(80, 80, 80, .3);
+	border: 1px solid #404040;
+	border-radius: 5px;
+}
+.cm-s-liquibyte div.CodeMirror-simplescroll-vertical div {
+	border-top: 1px solid #404040;
+	border-bottom: 1px solid #404040;
+}
+.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal div {
+	border-left: 1px solid #404040;
+	border-right: 1px solid #404040;
+}
+.cm-s-liquibyte div.CodeMirror-simplescroll-vertical {
+	background-color: #262626;
+}
+.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal {
+	background-color: #262626;
+	border-top: 1px solid #404040;
+}
+/* Overlay */
+.cm-s-liquibyte div.CodeMirror-overlayscroll-horizontal div, div.CodeMirror-overlayscroll-vertical div {
+	background-color: #404040;
+	border-radius: 5px;
+}
+.cm-s-liquibyte div.CodeMirror-overlayscroll-vertical div {
+	border: 1px solid #404040;
+}
+.cm-s-liquibyte div.CodeMirror-overlayscroll-horizontal div {
+	border: 1px solid #404040;
+}

+ 53 - 0
app/public/css/codemirror/theme/material.css

@@ -0,0 +1,53 @@
+/*
+
+    Name:       material
+    Author:     Michael Kaminsky (http://github.com/mkaminsky11)
+
+    Original material color scheme by Mattia Astorino (https://github.com/equinusocio/material-theme)
+
+*/
+
+.cm-s-material.CodeMirror {
+  background-color: #263238;
+  color: rgba(233, 237, 237, 1);
+}
+.cm-s-material .CodeMirror-gutters {
+  background: #263238;
+  color: rgb(83,127,126);
+  border: none;
+}
+.cm-s-material .CodeMirror-guttermarker, .cm-s-material .CodeMirror-guttermarker-subtle, .cm-s-material .CodeMirror-linenumber { color: rgb(83,127,126); }
+.cm-s-material .CodeMirror-cursor { border-left: 1px solid #f8f8f0; }
+.cm-s-material div.CodeMirror-selected { background: rgba(255, 255, 255, 0.15); }
+.cm-s-material.CodeMirror-focused div.CodeMirror-selected { background: rgba(255, 255, 255, 0.10); }
+.cm-s-material .CodeMirror-line::selection, .cm-s-material .CodeMirror-line > span::selection, .cm-s-material .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }
+.cm-s-material .CodeMirror-line::-moz-selection, .cm-s-material .CodeMirror-line > span::-moz-selection, .cm-s-material .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }
+
+.cm-s-material .CodeMirror-activeline-background { background: rgba(0, 0, 0, 0); }
+.cm-s-material .cm-keyword { color: rgba(199, 146, 234, 1); }
+.cm-s-material .cm-operator { color: rgba(233, 237, 237, 1); }
+.cm-s-material .cm-variable-2 { color: #80CBC4; }
+.cm-s-material .cm-variable-3, .cm-s-material .cm-type { color: #82B1FF; }
+.cm-s-material .cm-builtin { color: #DECB6B; }
+.cm-s-material .cm-atom { color: #F77669; }
+.cm-s-material .cm-number { color: #F77669; }
+.cm-s-material .cm-def { color: rgba(233, 237, 237, 1); }
+.cm-s-material .cm-string { color: #C3E88D; }
+.cm-s-material .cm-string-2 { color: #80CBC4; }
+.cm-s-material .cm-comment { color: #546E7A; }
+.cm-s-material .cm-variable { color: #82B1FF; }
+.cm-s-material .cm-tag { color: #80CBC4; }
+.cm-s-material .cm-meta { color: #80CBC4; }
+.cm-s-material .cm-attribute { color: #FFCB6B; }
+.cm-s-material .cm-property { color: #80CBAE; }
+.cm-s-material .cm-qualifier { color: #DECB6B; }
+.cm-s-material .cm-variable-3, .cm-s-material .cm-type { color: #DECB6B; }
+.cm-s-material .cm-tag { color: rgba(255, 83, 112, 1); }
+.cm-s-material .cm-error {
+  color: rgba(255, 255, 255, 1.0);
+  background-color: #EC5F67;
+}
+.cm-s-material .CodeMirror-matchingbracket {
+  text-decoration: underline;
+  color: white !important;
+}

+ 37 - 0
app/public/css/codemirror/theme/mbo.css

@@ -0,0 +1,37 @@
+/****************************************************************/
+/*   Based on mbonaci's Brackets mbo theme                      */
+/*   https://github.com/mbonaci/global/blob/master/Mbo.tmTheme  */
+/*   Create your own: http://tmtheme-editor.herokuapp.com       */
+/****************************************************************/
+
+.cm-s-mbo.CodeMirror { background: #2c2c2c; color: #ffffec; }
+.cm-s-mbo div.CodeMirror-selected { background: #716C62; }
+.cm-s-mbo .CodeMirror-line::selection, .cm-s-mbo .CodeMirror-line > span::selection, .cm-s-mbo .CodeMirror-line > span > span::selection { background: rgba(113, 108, 98, .99); }
+.cm-s-mbo .CodeMirror-line::-moz-selection, .cm-s-mbo .CodeMirror-line > span::-moz-selection, .cm-s-mbo .CodeMirror-line > span > span::-moz-selection { background: rgba(113, 108, 98, .99); }
+.cm-s-mbo .CodeMirror-gutters { background: #4e4e4e; border-right: 0px; }
+.cm-s-mbo .CodeMirror-guttermarker { color: white; }
+.cm-s-mbo .CodeMirror-guttermarker-subtle { color: grey; }
+.cm-s-mbo .CodeMirror-linenumber { color: #dadada; }
+.cm-s-mbo .CodeMirror-cursor { border-left: 1px solid #ffffec; }
+
+.cm-s-mbo span.cm-comment { color: #95958a; }
+.cm-s-mbo span.cm-atom { color: #00a8c6; }
+.cm-s-mbo span.cm-number { color: #00a8c6; }
+
+.cm-s-mbo span.cm-property, .cm-s-mbo span.cm-attribute { color: #9ddfe9; }
+.cm-s-mbo span.cm-keyword { color: #ffb928; }
+.cm-s-mbo span.cm-string { color: #ffcf6c; }
+.cm-s-mbo span.cm-string.cm-property { color: #ffffec; }
+
+.cm-s-mbo span.cm-variable { color: #ffffec; }
+.cm-s-mbo span.cm-variable-2 { color: #00a8c6; }
+.cm-s-mbo span.cm-def { color: #ffffec; }
+.cm-s-mbo span.cm-bracket { color: #fffffc; font-weight: bold; }
+.cm-s-mbo span.cm-tag { color: #9ddfe9; }
+.cm-s-mbo span.cm-link { color: #f54b07; }
+.cm-s-mbo span.cm-error { border-bottom: #636363; color: #ffffec; }
+.cm-s-mbo span.cm-qualifier { color: #ffffec; }
+
+.cm-s-mbo .CodeMirror-activeline-background { background: #494b41; }
+.cm-s-mbo .CodeMirror-matchingbracket { color: #ffb928 !important; }
+.cm-s-mbo .CodeMirror-matchingtag { background: rgba(255, 255, 255, .37); }

Diferenças do arquivo suprimidas por serem muito extensas
+ 46 - 0
app/public/css/codemirror/theme/mdn-like.css


+ 45 - 0
app/public/css/codemirror/theme/midnight.css

@@ -0,0 +1,45 @@
+/* Based on the theme at http://bonsaiden.github.com/JavaScript-Garden */
+
+/*<!--match-->*/
+.cm-s-midnight span.CodeMirror-matchhighlight { background: #494949; }
+.cm-s-midnight.CodeMirror-focused span.CodeMirror-matchhighlight { background: #314D67 !important; }
+
+/*<!--activeline-->*/
+.cm-s-midnight .CodeMirror-activeline-background { background: #253540; }
+
+.cm-s-midnight.CodeMirror {
+    background: #0F192A;
+    color: #D1EDFF;
+}
+
+.cm-s-midnight.CodeMirror { border-top: 1px solid black; border-bottom: 1px solid black; }
+
+.cm-s-midnight div.CodeMirror-selected { background: #314D67; }
+.cm-s-midnight .CodeMirror-line::selection, .cm-s-midnight .CodeMirror-line > span::selection, .cm-s-midnight .CodeMirror-line > span > span::selection { background: rgba(49, 77, 103, .99); }
+.cm-s-midnight .CodeMirror-line::-moz-selection, .cm-s-midnight .CodeMirror-line > span::-moz-selection, .cm-s-midnight .CodeMirror-line > span > span::-moz-selection { background: rgba(49, 77, 103, .99); }
+.cm-s-midnight .CodeMirror-gutters { background: #0F192A; border-right: 1px solid; }
+.cm-s-midnight .CodeMirror-guttermarker { color: white; }
+.cm-s-midnight .CodeMirror-guttermarker-subtle { color: #d0d0d0; }
+.cm-s-midnight .CodeMirror-linenumber { color: #D0D0D0; }
+.cm-s-midnight .CodeMirror-cursor { border-left: 1px solid #F8F8F0; }
+
+.cm-s-midnight span.cm-comment { color: #428BDD; }
+.cm-s-midnight span.cm-atom { color: #AE81FF; }
+.cm-s-midnight span.cm-number { color: #D1EDFF; }
+
+.cm-s-midnight span.cm-property, .cm-s-midnight span.cm-attribute { color: #A6E22E; }
+.cm-s-midnight span.cm-keyword { color: #E83737; }
+.cm-s-midnight span.cm-string { color: #1DC116; }
+
+.cm-s-midnight span.cm-variable { color: #FFAA3E; }
+.cm-s-midnight span.cm-variable-2 { color: #FFAA3E; }
+.cm-s-midnight span.cm-def { color: #4DD; }
+.cm-s-midnight span.cm-bracket { color: #D1EDFF; }
+.cm-s-midnight span.cm-tag { color: #449; }
+.cm-s-midnight span.cm-link { color: #AE81FF; }
+.cm-s-midnight span.cm-error { background: #F92672; color: #F8F8F0; }
+
+.cm-s-midnight .CodeMirror-matchingbracket {
+  text-decoration: underline;
+  color: white !important;
+}

+ 36 - 0
app/public/css/codemirror/theme/monokai.css

@@ -0,0 +1,36 @@
+/* Based on Sublime Text's Monokai theme */
+
+.cm-s-monokai.CodeMirror { background: #272822; color: #f8f8f2; }
+.cm-s-monokai div.CodeMirror-selected { background: #49483E; }
+.cm-s-monokai .CodeMirror-line::selection, .cm-s-monokai .CodeMirror-line > span::selection, .cm-s-monokai .CodeMirror-line > span > span::selection { background: rgba(73, 72, 62, .99); }
+.cm-s-monokai .CodeMirror-line::-moz-selection, .cm-s-monokai .CodeMirror-line > span::-moz-selection, .cm-s-monokai .CodeMirror-line > span > span::-moz-selection { background: rgba(73, 72, 62, .99); }
+.cm-s-monokai .CodeMirror-gutters { background: #272822; border-right: 0px; }
+.cm-s-monokai .CodeMirror-guttermarker { color: white; }
+.cm-s-monokai .CodeMirror-guttermarker-subtle { color: #d0d0d0; }
+.cm-s-monokai .CodeMirror-linenumber { color: #d0d0d0; }
+.cm-s-monokai .CodeMirror-cursor { border-left: 1px solid #f8f8f0; }
+
+.cm-s-monokai span.cm-comment { color: #75715e; }
+.cm-s-monokai span.cm-atom { color: #ae81ff; }
+.cm-s-monokai span.cm-number { color: #ae81ff; }
+
+.cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute { color: #a6e22e; }
+.cm-s-monokai span.cm-keyword { color: #f92672; }
+.cm-s-monokai span.cm-builtin { color: #66d9ef; }
+.cm-s-monokai span.cm-string { color: #e6db74; }
+
+.cm-s-monokai span.cm-variable { color: #f8f8f2; }
+.cm-s-monokai span.cm-variable-2 { color: #9effff; }
+.cm-s-monokai span.cm-variable-3, .cm-s-monokai span.cm-type { color: #66d9ef; }
+.cm-s-monokai span.cm-def { color: #fd971f; }
+.cm-s-monokai span.cm-bracket { color: #f8f8f2; }
+.cm-s-monokai span.cm-tag { color: #f92672; }
+.cm-s-monokai span.cm-header { color: #ae81ff; }
+.cm-s-monokai span.cm-link { color: #ae81ff; }
+.cm-s-monokai span.cm-error { background: #f92672; color: #f8f8f0; }
+
+.cm-s-monokai .CodeMirror-activeline-background { background: #373831; }
+.cm-s-monokai .CodeMirror-matchingbracket {
+  text-decoration: underline;
+  color: white !important;
+}

+ 12 - 0
app/public/css/codemirror/theme/neat.css

@@ -0,0 +1,12 @@
+.cm-s-neat span.cm-comment { color: #a86; }
+.cm-s-neat span.cm-keyword { line-height: 1em; font-weight: bold; color: blue; }
+.cm-s-neat span.cm-string { color: #a22; }
+.cm-s-neat span.cm-builtin { line-height: 1em; font-weight: bold; color: #077; }
+.cm-s-neat span.cm-special { line-height: 1em; font-weight: bold; color: #0aa; }
+.cm-s-neat span.cm-variable { color: black; }
+.cm-s-neat span.cm-number, .cm-s-neat span.cm-atom { color: #3a3; }
+.cm-s-neat span.cm-meta { color: #555; }
+.cm-s-neat span.cm-link { color: #3a3; }
+
+.cm-s-neat .CodeMirror-activeline-background { background: #e8f2ff; }
+.cm-s-neat .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; }

+ 43 - 0
app/public/css/codemirror/theme/neo.css

@@ -0,0 +1,43 @@
+/* neo theme for codemirror */
+
+/* Color scheme */
+
+.cm-s-neo.CodeMirror {
+  background-color:#ffffff;
+  color:#2e383c;
+  line-height:1.4375;
+}
+.cm-s-neo .cm-comment { color:#75787b; }
+.cm-s-neo .cm-keyword, .cm-s-neo .cm-property { color:#1d75b3; }
+.cm-s-neo .cm-atom,.cm-s-neo .cm-number { color:#75438a; }
+.cm-s-neo .cm-node,.cm-s-neo .cm-tag { color:#9c3328; }
+.cm-s-neo .cm-string { color:#b35e14; }
+.cm-s-neo .cm-variable,.cm-s-neo .cm-qualifier { color:#047d65; }
+
+
+/* Editor styling */
+
+.cm-s-neo pre {
+  padding:0;
+}
+
+.cm-s-neo .CodeMirror-gutters {
+  border:none;
+  border-right:10px solid transparent;
+  background-color:transparent;
+}
+
+.cm-s-neo .CodeMirror-linenumber {
+  padding:0;
+  color:#e0e2e5;
+}
+
+.cm-s-neo .CodeMirror-guttermarker { color: #1d75b3; }
+.cm-s-neo .CodeMirror-guttermarker-subtle { color: #e0e2e5; }
+
+.cm-s-neo .CodeMirror-cursor {
+  width: auto;
+  border: 0;
+  background: rgba(155,157,162,0.37);
+  z-index: 1;
+}

+ 0 - 0
app/public/css/codemirror/theme/night.css


Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff