فهرست منبع

feat: 授权跳转项目管理

lanjianrong 3 سال پیش
والد
کامیت
1a3b031c92

+ 3 - 0
app/base/base_controller.js

@@ -36,6 +36,9 @@ class BaseController extends Controller {
             }
         }
         menuList.datacollect.display = ctx.session && ctx.session.sessionProject ? ctx.session.sessionProject.showDataCollect : false;
+        if (ctx.session && ctx.session.sessionProject && ctx.session.sessionProject.page_show && ctx.session.sessionProject.page_show.openManagement) {
+            menuList.management.display = true;
+        }
         // 菜单列表
         ctx.menuList = menuList;
         ctx.showProject = false;

+ 1 - 0
app/const/page_show.js

@@ -37,6 +37,7 @@ const defaultSetting = {
     openMaterialTax: 0,
     addDataCollect: 1,
     closeWapYfSf: 0,
+    openManagement: 0,
 };
 
 

+ 3 - 0
app/const/sign.js

@@ -13,7 +13,10 @@ const path = {
     api: 'http://106.52.243.222:9091/eseal',
 };
 
+/** 请求项目管理token所需秘钥 */
+const managementApiSecretKey = 'JL_CM_lksjdofuosdjflj01231209uljsf90@@#(lnm8';
 
 module.exports = {
     path,
+    managementApiSecretKey,
 };

+ 114 - 3
app/controller/login_controller.js

@@ -10,7 +10,7 @@
 const URL = require('url');
 const maintainConst = require('../const/maintain');
 const OAuth = require('co-wechat-oauth');
-
+const office = require('../const/cld_office')
 module.exports = app => {
 
     class LoginController extends app.BaseController {
@@ -154,7 +154,6 @@ module.exports = app => {
          */
         async login(ctx) {
             let loginType = ctx.request.body.type;
-
             try {
                 loginType = parseInt(loginType);
                 const result = await ctx.service.projectAccount.accountLogin(ctx.request.body, loginType);
@@ -279,7 +278,6 @@ module.exports = app => {
         async port(ctx) {
             // 获取系统维护信息
             const maintainData = await ctx.service.maintain.getDataById(1);
-
             if (!ctx.app.config.is_debug) {
                 await ctx.service.maintain.syncMaintainData();
             }
@@ -369,6 +367,119 @@ module.exports = app => {
                 ctx.redirect('/login');
             }
         }
+
+        /**
+         * (项目管理)获取账号
+         * @param {Object} ctx - egg全局变量
+         */
+        async account(ctx) {
+            const data = ctx.data;
+            const response = {
+                code: 0,
+                data: 0,
+                msg: '',
+            };
+            try {
+                const project = await ctx.service.project.getProjectByCode(data.code);
+                if (!project) throw '未找到该项目';
+                const pa = await ctx.service.projectAccount.getAccountInfoByAccountWithPid(data.account, project.id);
+                if (!pa ) throw '该账号不存在,请联系管理员';
+                const manager = await ctx.service.manager.getDataById(project.manager_id)
+                const { is_admin: isAdmin, account_group: accountGroup, ...otherObj} = pa
+                response.data = {
+                  account: { ...otherObj, accountGroup, isAdmin},
+                  project: {
+                    code: project.code,
+                    name: project.name,
+                    categoryId: project.manager_office,
+                    category: office.list[project.manager_office],
+                    staff: manager.real_name
+                  }
+                }
+            } catch (error) {
+                console.log(error);
+                response.code = -1;
+                response.msg = error.toString();
+            }
+            ctx.body = response;
+        }
+
+        /**
+         * (项目管理)获取项目
+         * @param {Object} ctx - egg全局变量
+         */
+        async project(ctx) {
+            const data = ctx.data;
+            const response = {
+                code: 0,
+                data: 0,
+                msg: '',
+            };
+            try {
+                const project = await ctx.service.project.getProjectByCode(data.code);
+                if (!project) throw '未找到该项目';
+                response.data = project;
+            } catch (error) {
+                response.code = -1;
+                response.msg = error.toString();
+            }
+            ctx.body = response;
+        }
+
+        /**
+         * (项目管理)校验项目
+         * @param {Object} ctx - egg全局变量
+         */
+        async vertifyProject(ctx) {
+            const response = {
+                code: 0,
+                data: 0,
+                msg: '',
+            };
+            try {
+              const code = ctx.session.sessionProject.code
+              const account = ctx.session.sessionUser.account
+              if (!code || !account) {
+                throw new Error('参数错误')
+              }
+                const result = await ctx.service.project.verifyManagementProject(ctx.helper.createJWT({ code }));
+                const token = ctx.helper.createJWT({ code, account })
+                const redirect = `${app.config.env === 'local' ? 'http://pmqa.smartcost.com.cn/auth' : 'http://pm.6jlzf.cn/auth'}?token=${token}`
+                response.data = { ...result, is_admin: ctx.session.sessionUser.account === ctx.session.sessionProject.userAccount, redirect};
+            } catch (error) {
+                response.code = -1;
+                response.msg = error.toString();
+            }
+            ctx.body = response;
+        }
+
+        /**
+         * (项目管理)新增项目
+         * @param {Object} ctx - egg全局变量
+         */
+        async addProject(ctx) {
+          const response = {
+              code: 0,
+              data: {},
+              msg: '',
+          };
+          try {
+              const result = await ctx.service.project.addProjectFromManagement()
+              if (!result) {
+                throw new Error('请求失败')
+              }
+              const { code = -1, data = {}, msg = '请求失败' } = result
+              if ( code === 0 ) {
+                response.data = data
+              } else {
+                throw new Error(msg)
+              }
+          } catch (error) {
+              response.code = -1;
+              response.msg = error.toString();
+          }
+          ctx.body = response;
+      }
     }
 
     return LoginController;

+ 11 - 1
app/extend/helper.js

@@ -23,7 +23,8 @@ const UAParser = require('ua-parser-js');
 const math = require('mathjs');
 const syncApiConst = require('../const/sync_api');
 const crypto = require('crypto');
-
+const jwt = require('jsonwebtoken');
+const sign = require('../const/sign')
 module.exports = {
     _,
 
@@ -1465,4 +1466,13 @@ module.exports = {
         const endMonth = moment(month).endOf('month').format('YYYY-MM-DD HH:mm:ss');
         return [startMonth, endMonth];
     },
+
+    /**
+     * 创建json web token
+     * @param {Object} data - 签名数据
+     * @return {String} token
+     */
+    createJWT(data) {
+        return jwt.sign({ data }, sign.managementApiSecretKey, { expiresIn: '7d' });
+    },
 };

+ 56 - 0
app/middleware/api3management_check.js

@@ -0,0 +1,56 @@
+'use strict';
+
+/**
+ *
+ * @author LanJianRong
+ * @date  2021-12-27
+ * @version
+ */
+const maintainConst = require('../const/maintain');
+const sign = require('../const/sign');
+const jwt = require('jsonwebtoken');
+module.exports = options => {
+    return function* api3managementCheck(next) {
+        try {
+            // 获取系统维护信息
+            const maintainData = yield this.service.maintain.getDataById(1);
+            if (maintainData.status === maintainConst.status.ongoing) {
+                throw '系统维护中~';
+            }
+            const token = this.query.auth;
+            if (!token) {
+                throw '参数有误';
+            }
+            try {
+                const decoded = jwt.verify(token, sign.managementApiSecretKey);
+                if (!decoded.data) throw '参数有误';
+                this.data = decoded.data;
+            } catch (error) {
+                throw error;
+            }
+            // const data = yield this.service.project.getProjectByCode(code.toString().trim());
+            // if (data === null) {
+            //     throw '不存在项目数据';
+            // }
+            // if (data.custom === 0) {
+            //     throw '无法通过接口登录本系统';
+            // }
+            // if (data.custom === 1 && data.can_api === 0) {
+            //     throw '接口已关闭,无法使用';
+            // }
+            // const encryptSign = crypto.createHash('md5').update(data.code + data.secret + time.toString()).digest('hex').toString();
+            // if (encryptSign !== sign) {
+            //     throw '参数验证失败';
+            // }
+            // this.projectData = data;
+            yield next;
+        } catch (err) {
+            this.body = {
+                code: -1,
+                msg: err.toString(),
+                data: null,
+            };
+            return;
+        }
+    };
+};

+ 61 - 5
app/public/js/global.js

@@ -116,6 +116,62 @@ $(function(){
             $(this).attr('href', $(this).attr('href') + '?sort=' + orders[0] + '&order=' + orders[1]);
         }
     });
+
+    $('#nav_management').click(function(e) {
+      e.preventDefault()
+      showWaitingView();
+      $.ajax({
+        type: 'GET',
+        url: '/management/proxy/project/vertify',
+        dataType: 'json',
+        cache: false,
+        timeout: 60000,
+        success: function ({code = -1, data: { exist, is_admin, redirect } = { exist: 0, is_admin: false}}) {
+          console.log(redirect);
+          if (code === 0) {
+              if (!exist && !is_admin) {
+                toastr.error('「项目管理」系统不存在当前项目,请联系管理员进行处理');
+              } else if(!exist && is_admin) {
+                $('#add-management').modal('show')
+              } else {
+                redirect && window.open(redirect)
+              }
+            }
+            closeWaitingView()
+          },
+          error: function(jqXHR, textStatus, errorThrown){
+            toastr.error('error: ' + textStatus + " " + errorThrown);
+            closeWaitingView();
+          }
+        })
+      });
+    $('#add-management .btn-primary').click(function() {
+      
+      $('#add-management').modal('hide')
+      $('#process-management').modal('show')
+      $.ajax({
+        tpye: 'post',
+        url: '/management/proxy/project/add',
+        dataType: 'json',
+        cache: false,
+        timeout: 60000,
+        // beforeSend: function(xhr) {
+        //   let csrfToken = Cookies.get('csrfToken_j');
+        //   xhr.setRequestHeader('x-csrf-token', csrfToken);
+        // },
+        success: function ({code = -1, data = {}}) {
+          if (code === 0) {
+            timer = setTimeout(() => {
+              $('#process-management .process-child').css('width', '100%')
+              setTimeout(() => {
+                const { token } = data
+                token && window.open(`http://localhost:3000/auth?token=${token}`)
+              }, 150);
+            }, 500);
+          }
+        }
+      })
+    })
 });
 
 function checkShowLast (count) {
@@ -371,15 +427,15 @@ const postCompressFile = function (url, data, successCallback, errorCallBack) {
       url: url,
       data: {'data': JSON.stringify(data)},
       // dataType: 'blob',
-      cache: false,
+      // cache: false,
       timeout: 180000,
       xhrFields: {
         responseType: 'blob'
       },
-      beforeSend: function(xhr) {
-          let csrfToken = Cookies.get('csrfToken_j');
-          xhr.setRequestHeader('x-csrf-token', csrfToken);
-      },
+      // beforeSend: function(xhr) {
+      //     let csrfToken = Cookies.get('csrfToken_j');
+      //     xhr.setRequestHeader('x-csrf-token', csrfToken);
+      // },
       success: function(result){
           // if (result.err === 0) {
           //     if (successCallback) {

+ 7 - 0
app/router.js

@@ -19,6 +19,8 @@ module.exports = app => {
     const materialCheck = app.middlewares.materialCheck();
     // 第三方接口认证判断中间件
     const api2otherCheck = app.middlewares.api2otherCheck();
+    // 项目管理接口认证中间件
+    const api3managementCheck = app.middlewares.api3managementCheck();
     // 微信验证登录中间件
     const wechatAuth = app.middlewares.wechatAuth();
     // 预付款中间件
@@ -43,6 +45,11 @@ module.exports = app => {
     app.post('/sign/save', 'signController.save');
     app.post('/reset/password', 'loginController.resetPassword');
 
+    // 项目管理对计量接口相关
+    app.get('/management/account', api3managementCheck, 'loginController.account');
+    app.get('/management/project', api3managementCheck, 'loginController.project');
+    app.get('/management/proxy/project/vertify', sessionAuth, 'loginController.vertifyProject');
+    app.get('/management/proxy/project/add', sessionAuth, 'loginController.addProject');
     // 用户信息初始化相关
     app.get('/boot', sessionAuth, 'bootController.index');
     app.post('/boot', sessionAuth, 'bootController.boot');

+ 1 - 0
app/service/manager.js

@@ -22,6 +22,7 @@ module.exports = app => {
             super(ctx);
             this.tableName = 'manager';
         }
+
     }
 
     return Manager;

+ 52 - 0
app/service/project.js

@@ -191,6 +191,58 @@ module.exports = app => {
             });
             return result.affectedRows === 1;
         }
+
+        /**
+         * 校验项目是否存在(项目管理)
+         * @param {String} token - token
+         * @return {Promise} Promise
+         */
+        verifyManagementProject(token) {
+            return new Promise((resolve, reject) => {
+                this.ctx.curl(`${app.config.env === 'local' ? 'http://pmqa.smartcost.com.cn' : 'http://pm.6jlzf.cn'}/api/external/jl/calibration`, {
+                    method: 'POST',
+                    // dateType: 'json',
+                    encoding: 'utf8',
+                    data: {
+                        token,
+                    },
+                    // timeout: 2000,
+                }).then(({ status, data }) => {
+                    if (status === 200) {
+                        const result = JSON.parse(data.toString()).data;
+                        return resolve(result);
+                    }
+                    return reject(new Error('校验失败'));
+                }).catch(err => {
+                    console.log(err);
+                    return reject(err);
+                });
+            });
+        }
+
+        /**
+         * 创建项目和账号(项目管理)
+         * @return {Promise} Promise
+         */
+        async addProjectFromManagement() {
+            const token = this.ctx.helper.createJWT({ account: this.ctx.session.sessionUser.account, code: this.ctx.session.sessionProject.code });
+            return new Promise((resolve, reject) => {
+                this.ctx.curl(`${app.config.env === 'local' ? 'http://pmqa.smartcost.com.cn' : 'http://pm.6jlzf.cn'}/api/external/jl/project/add`, {
+                    method: 'POST',
+                    encoding: 'utf8',
+                    data: { token },
+                }).then(({ status, data }) => {
+                    if (status === 200) {
+                        const result = JSON.parse(data.toString());
+                        return resolve(result);
+                    }
+                    return reject(new Error('添加失败'));
+                }).catch(err => {
+                    console.log(err);
+                    return reject(err);
+                });
+            });
+        }
     }
 
     return Project;

+ 18 - 0
app/service/project_account.js

@@ -369,6 +369,24 @@ module.exports = app => {
             return info;
         }
 
+        async getAccountInfoByAccountWithPid(account, project_id) {
+            if (!account || !project_id) throw new Error('参数错误');
+            this.initSqlBuilder();
+            this.sqlBuilder.columns = ['account', 'name', 'company', 'role', 'is_admin', 'enable', 'telephone', 'mobile', 'account_group'];
+            this.sqlBuilder.setAndWhere('account', {
+                operate: '=',
+                value: `"${account}"`,
+            });
+            this.sqlBuilder.setAndWhere('project_id', {
+                operate: '=',
+                value: project_id,
+            });
+
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'select');
+            const info = await this.db.queryOne(sql, sqlParam);
+
+            return info;
+        }
         async getListByProjectId(columns = '', pid) {
             this.initSqlBuilder();
             this.sqlBuilder.columns = columns !== '' ? columns : ['id', 'account', 'name', 'company', 'role', 'mobile', 'auth_mobile', 'telephone', 'enable', 'is_admin', 'account_group'];

+ 49 - 1
app/view/layout/layout.ejs

@@ -32,7 +32,7 @@
 </div>
 <div class="main">
     <% include ./menu.ejs %>
-
+    
     <div class="main-panel">
         <%- content %>
     </div>
@@ -44,6 +44,10 @@
 <!-- 遮罩层 -->
 <div id="hide-all" style="display:none;position: absolute;width: 100%;height: 100%;top:0;left: 0;z-index:10000000">
 </div>
+
+<!-- 对接项目管理弹窗 -->
+<% include ./modal.ejs %>
+
 <%- modal %>
 <script type="text/javascript">
     toastr.options = {
@@ -79,6 +83,50 @@
     const is_debug = true;
     <% } %>
 </script>
+<style type="text/css">
+
+  .process-box{
+    width: 100%;
+    height: 12px;
+    border-radius: 10px;
+    background: #999;
+    margin-top: 10px;
+    /* border: 1px solid #ff6780; */
+  }
+  .process-child{
+    position: relative;
+    height:100%;
+    border-radius:inherit;
+  }
+
+  .process-animate{
+    background: #007BFF !important;
+    color: white;
+    text-align: center;
+    position: absolute;
+    left: 0;
+    top: 0;
+    bottom: 0;
+    border-radius:inherit;
+    animation: process 1s linear forwards ;
+  }
+  @keyframes process
+  {
+      0%{
+          left:0;right:100%;
+      }
+      20%{
+          right:80%
+      }
+      40%{
+          right:60%;
+      }
+      60%{right:40%;}
+      80%{right:20%;}
+      100%{right:0;}
+  }
+
+</style>
 </body>
 
 </html>

+ 37 - 0
app/view/layout/modal.ejs

@@ -40,3 +40,40 @@
     </div>
 </div>
 
+<!--对接项目管理新增项目-->
+<div class="modal fade" id="add-management" data-backdrop="static">
+  <div class="modal-dialog" role="document">
+      <div class="modal-content">
+          <div class="modal-header">
+              <h5 class="modal-title">新建项目</h5>
+          </div>
+          <div class="modal-body">
+              <span>「项目管理」系统不存在当前项目, 确认新增?</span>
+          </div>
+          <div class="modal-footer">
+              <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">取消</button>
+              <button type="button" class="btn btn-primary btn-sm">确定</button>
+          </div>
+      </div>
+  </div>
+</div>
+<div class="modal fade" id="process-management" data-backdrop="static">
+  <div class="modal-dialog" role="document">
+      <div class="modal-content">
+          <div class="modal-header">
+              <h5 class="modal-title">新增提示</h5>
+          </div>
+          <div class="modal-body">
+              <div>正在新增项目,请稍等...</div>
+              <div class="process-box">
+                <div class="process-child" style="width:99%"> 
+                    <div class="process-animate"></div>
+                </div>
+            </div>
+          </div>
+          <div class="modal-footer">
+              <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">取消</button>
+          </div>
+      </div>
+  </div>
+</div>

+ 92 - 0
app/view/login/login_management.ejs

@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+    <meta http-equiv="x-ua-compatible" content="ie=edge">
+    <title>用户登录-计量支付</title>
+    <link rel="stylesheet" href="/public/css/bootstrap/bootstrap.min.css">
+    <link rel="stylesheet" href="/public/css/main.css">
+    <link rel="stylesheet" href="/public/css/toast.css">
+    <link rel="stylesheet" href="/public/css/font-awesome/font-awesome.min.css">
+    <link rel="shortcut icon" href="/public/images/favicon.ico">
+    <style>
+        html{height:100%;}
+    </style>
+</head>
+<body class="login-new-body img-<%- Math.floor(Math.random()*9 + 1); %>">
+<div class="login-new-b d-flex flex-row align-items-center mt-5 ml-5">
+    <div>
+        <img src="/public/images/image_nav_logo.png">
+    </div>
+    <div class="ml-3">
+        <div class="logo-big-title text-white mt-1">纵横云平台</div>
+        <div class="logo-sm-title mt-1">计量支付&nbsp;&nbsp;&nbsp;结算决算</div>
+    </div>
+    <div class="side-border ml-4 mr-4"></div>
+    <div class="top-subtitle">施工舒心 / 监理省心 / 业主安心</div>
+</div>
+<div class="container">
+    <div class="d-flex justify-content-center align-items-center content-center">
+        <div class="left-login text-white pt-5 px-5">
+            <h2>Hi,您好!</h2>
+            <div class="login-border my-4"></div>
+            <div class="left-login-title" id="project_name"><%- projectData ? projectData.name : '纵横捭阖  数“智”管家' %></div>
+        </div>
+        <% if (maintainData.status === maintainConst.status.ongoing) { %>
+            <form class="right-login position-relative text-center">
+                <h3 class="mt-5 mb-5"><i class="fa fa-wrench"></i>系统正在维护</h3>
+                <div>&nbsp;</div>
+                <h5 class="mt-5 mb-5">预计恢复时间<%- (maintainData.duration !== maintainConst.duration.forever ? '为 ' + ctx.helper.dateTran(parseFloat(maintainData.maintain_time) + ctx.helper.timeAdd(maintainData.duration)) : ' 暂未确定') %></h5>
+                <h5>造成不便敬请谅解。</h5>
+            </form>
+        <% } else { %>
+            <form method="post" action="/login/port" class="right-login position-relative" style="min-width: 360px;">
+                <h3 class="mt-5 mb-4 text-center">欢迎登录</h3>
+                <div class="mx-4">
+                <% if (!accountData) { %>
+                    <h5>您好,<%- accountData.mobile %></h5>
+                    <% if (accountData.bind === 0) { %>
+                        <!--查询账号存在,进行绑定-->
+                        <h5 class="mb-3">系统查询到以下账号,请确认无误后进行绑定。</h5>
+                        <h5>姓名:<span class="text-danger"><%- accountData.name %></span></h5>
+                        <h5>账号:<span class="text-danger"><%- accountData.account %></span></h5>
+                        <h5>手机:<span class="text-danger"><%- accountData.mobile %></span></h5>
+                        <h5>单位:<span class="text-danger"><%- accountData.company %></span></h5>
+                        <h5>职称:<span class="text-danger"><%- accountData.role %></span></h5>
+                        <div class="form-group mt-4">
+                            <input type="hidden" value="<%= ctx.csrf %>" name="_csrf_j" >
+                            <input type="hidden" value="3" name="type" >
+                            <input type="hidden" value="<%= projectData.code %>" name="code" >
+                            <input type="hidden" value="<%= accountData.id %>" name="accountId" >
+                            <button type="submit" class="btn btn-primary btn-block">绑定并登录系统</button>
+                        </div>
+                    <% } %>
+                <% } %>
+                <h5 class="text-danger text-center"><%- errorMessage %></h5>
+                </div>
+            </form>
+        <% } %>
+    <!--项目版-->
+  </div>
+  <div class="text-white fixed-bottom"><p class="text-center mb-1">Copyright © 2019 <a href="https://smartcost.com.cn" target="_blank" class="text-white">珠海纵横创新软件有限公司</a>.All Rights Reserved.<a class="text-white ml-2" href="https://beian.miit.gov.cn" target="_blank">粤ICP备14032472号</a></p></div>
+<!-- JS. -->
+<div class="toast" style="text-align: center">
+    <i class="icon fa"></i>
+    <span class="message"></span>
+</div>
+<!-- JS. -->
+<script src="/public/js/jquery/jquery-3.2.1.min.js"></script>
+<script src="/public/js/popper/popper.min.js"></script>
+<script src="/public/js/bootstrap/bootstrap.min.js"></script>
+<script src="/public/js/global.js"></script>
+<% if (projectData) { %>
+<script>
+    $(document).ready(function() {
+        setLocalCache('project_name', '<%- projectData.name %>');
+        setLocalCache('project_code', '<%- projectData.code %>');
+    });
+</script>
+<% } %>
+</body>
+</html>

+ 2 - 2
app/view/login/login_port.ejs

@@ -68,8 +68,8 @@
             </form>
         <% } %>
     <!--项目版-->
-    <div class="text-white fixed-bottom"><p class="text-center mb-1">Copyright © 2019 <a href="https://smartcost.com.cn" target="_blank" class="text-white">珠海纵横创新软件有限公司</a>.All Rights Reserved.<a class="text-white ml-2" href="https://beian.miit.gov.cn" target="_blank">粤ICP备14032472号</a></p></div>
-</div>
+  </div>
+  <div class="text-white fixed-bottom"><p class="text-center mb-1">Copyright © 2019 <a href="https://smartcost.com.cn" target="_blank" class="text-white">珠海纵横创新软件有限公司</a>.All Rights Reserved.<a class="text-white ml-2" href="https://beian.miit.gov.cn" target="_blank">粤ICP备14032472号</a></p></div>
 <!-- JS. -->
 <div class="toast" style="text-align: center">
     <i class="icon fa"></i>

+ 7 - 0
config/menu.js

@@ -41,6 +41,13 @@ const menu = {
         children: null,
         caption: '概算投资',
     },
+    management: {
+        name: '项目管理',
+        icon: 'fa-cubes',
+        display: false,
+        caption: '项目管理',
+        children: null,
+    },
     // sum: {
     //     name: '总分包',
     //     icon: 'fa-sitemap',

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 34168 - 15081
package-lock.json


+ 1 - 0
package.json

@@ -26,6 +26,7 @@
         "gt3-sdk": "^2.0.0",
         "gulp": "^4.0.0",
         "js-xlsx": "^0.8.22",
+        "jsonwebtoken": "^8.5.1",
         "jszip": "^3.1.3",
         "koa-is-json": "^1.0.0",
         "lodash": "^4.17.11",