基本搭建完毕
| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
> 1%
 | 
			
		||||
last 2 versions
 | 
			
		||||
not dead
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
/*
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2020-09-10 09:12:00
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2020-09-10 18:25:23
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
 */
 | 
			
		||||
module.exports = {
 | 
			
		||||
  root: true,
 | 
			
		||||
  env: {
 | 
			
		||||
    node: true
 | 
			
		||||
  },
 | 
			
		||||
  'extends': [
 | 
			
		||||
    'plugin:vue/essential',
 | 
			
		||||
    'eslint:recommended'
 | 
			
		||||
  ],
 | 
			
		||||
  parserOptions: {
 | 
			
		||||
    parser: 'babel-eslint'
 | 
			
		||||
  },
 | 
			
		||||
  rules: {
 | 
			
		||||
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
 | 
			
		||||
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
 | 
			
		||||
    'no-unused-vars': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
 | 
			
		||||
    'no-empty': process.env.NODE_ENV === 'production' ? 'warn' : 'warn',
 | 
			
		||||
    'vue/no-unused-components': process.env.NODE_ENV === 'production' ? 'warn' : 'warn',
 | 
			
		||||
    'no-control-regex': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
 | 
			
		||||
    'no-useless-escape': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
 | 
			
		||||
  },
 | 
			
		||||
  overrides: [{
 | 
			
		||||
    files: [
 | 
			
		||||
      '**/__tests__/*.{j,t}s?(x)',
 | 
			
		||||
      '**/tests/unit/**/*.spec.{j,t}s?(x)'
 | 
			
		||||
    ],
 | 
			
		||||
    env: {
 | 
			
		||||
      mocha: true
 | 
			
		||||
    }
 | 
			
		||||
  }]
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,23 @@
 | 
			
		|||
.DS_Store
 | 
			
		||||
node_modules
 | 
			
		||||
/dist
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# local env files
 | 
			
		||||
.env.local
 | 
			
		||||
.env.*.local
 | 
			
		||||
 | 
			
		||||
# Log files
 | 
			
		||||
npm-debug.log*
 | 
			
		||||
yarn-debug.log*
 | 
			
		||||
yarn-error.log*
 | 
			
		||||
pnpm-debug.log*
 | 
			
		||||
 | 
			
		||||
# Editor directories and files
 | 
			
		||||
.idea
 | 
			
		||||
.vscode
 | 
			
		||||
*.suo
 | 
			
		||||
*.ntvs*
 | 
			
		||||
*.njsproj
 | 
			
		||||
*.sln
 | 
			
		||||
*.sw?
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-05-07 08:51:17
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2021-12-22 15:39:56
 | 
			
		||||
 * @Description: v1.1
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
## 后端文档
 | 
			
		||||
 | 
			
		||||
http://ty.y68.fun/sales-expo-saas/#/  
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
module.exports = {
 | 
			
		||||
  presets: [
 | 
			
		||||
    '@vue/cli-plugin-babel/preset'
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,58 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-07-13 09:00:16
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2021-10-12 15:32:11
 | 
			
		||||
 * @Description: v1.6
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
## 前端项目目录结构
 | 
			
		||||
 | 
			
		||||
├─api  // axios请求封装  
 | 
			
		||||
│  ├─Disk  // 分类子文件夹  
 | 
			
		||||
│  ├─Rbac  // 分类子文件夹  
 | 
			
		||||
│  ├─_AxiosInterceptors.js  // Axios拦截器  
 | 
			
		||||
│  └─_ResponseHelper.js  // 针对服务器返回值的处理方法  
 | 
			
		||||
├─assets  // Vue静态资源  
 | 
			
		||||
├─biz  // 如果一个常用业务需要用到多个api函数,建议在此封装  
 | 
			
		||||
│  └─Disk  // 分类子文件夹  
 | 
			
		||||
├─components  // Vue组件  
 | 
			
		||||
│  ├─Disk  // 分类子文件夹(按大功能模块划分)  
 | 
			
		||||
│  │  └─Recycle  // 分类子子文件夹(按功能模块划分)  
 | 
			
		||||
│  ├─Home  // 分类子文件夹(按大功能模块划分)   
 | 
			
		||||
│  └─_Common  // 常用Vue组件,在此进行归类,可在其它Vue组件或Vue页面使用  
 | 
			
		||||
│  │   ├─Drag  // 分类子文件夹  
 | 
			
		||||
│  │   └─Tree  // 分类子文件夹  
 | 
			
		||||
├─entity  // 对象实体类,与后端返回的对象有关  
 | 
			
		||||
│  ├─Disk  // 分类子文件夹  
 | 
			
		||||
│  └─Rbac  // 分类子文件夹  
 | 
			
		||||
├─router  
 | 
			
		||||
│  ├─_index.js  // 路由入口文件   
 | 
			
		||||
│  ├─disk.js  // 路由子分类  
 | 
			
		||||
│  └─user-role-auth.js  // 路由子分类   
 | 
			
		||||
├─scss  
 | 
			
		||||
│  ├─HomeScreen  // scss分类子文件夹  
 | 
			
		||||
│  ├─_globals.scss  // 全局scss  
 | 
			
		||||
│  └─_variables.scss  // 常用的scss常量;原则上颜色都必须定义在此  
 | 
			
		||||
├─storage  // 对localStorage或sessionStorage的操作的封装  
 | 
			
		||||
│  └─login-info.js  // 针对登录的storage封装   
 | 
			
		||||
├─store  // Vuex  
 | 
			
		||||
├─svgicon  
 | 
			
		||||
│  └─homescreen  
 | 
			
		||||
├─sys  
 | 
			
		||||
│  ├─SysCode.js  // 信息码,与后端返回值的code对应  
 | 
			
		||||
│  └─SysError.js  // 错误类  
 | 
			
		||||
├─util  // 常用工具类的封装(先看有没有合适用的,没有再自己实现)  
 | 
			
		||||
├─views  // Vue页面  
 | 
			
		||||
│  ├─HomeSubViews  // 业务类子页面  
 | 
			
		||||
│  │  ├─Disk  // 子页面分类  
 | 
			
		||||
│  │  └─Rbac  // 子页面分类  
 | 
			
		||||
│  └─SystemViews  // 管理类子页面  
 | 
			
		||||
├─App.vue  // 全局样式记在此
 | 
			
		||||
├─const.js  // 全局常量记在此
 | 
			
		||||
├─main.js  
 | 
			
		||||
 | 
			
		||||
## 编码格式
 | 
			
		||||
* 习惯性用“;”作为语句结尾  
 | 
			
		||||
* 单行注释文字前面加一个空格,如:// 注释  
 | 
			
		||||
* VUE 的 props 必须写明注释  
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,42 @@
 | 
			
		|||
{
 | 
			
		||||
  "name": "example2",
 | 
			
		||||
  "version": "0.1.0",
 | 
			
		||||
  "private": true,
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "serve": "vue-cli-service serve",
 | 
			
		||||
    "build": "vue-cli-service build",
 | 
			
		||||
    "test:unit": "vue-cli-service test:unit",
 | 
			
		||||
    "lint": "vue-cli-service lint"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@tweenjs/tween.js": "^18.6.4",
 | 
			
		||||
    "axios": "^0.21.4",
 | 
			
		||||
    "ckeditor4-vue": "^1.3.2",
 | 
			
		||||
    "core-js": "^3.6.5",
 | 
			
		||||
    "element-ui": "^2.13.2",
 | 
			
		||||
    "interactjs": "^1.10.11",
 | 
			
		||||
    "jsonwebtoken": "^8.5.1",
 | 
			
		||||
    "velocity-animate": "^1.5.2",
 | 
			
		||||
    "vue": "^2.6.11",
 | 
			
		||||
    "vue-page-split": "^1.2.2",
 | 
			
		||||
    "vue-router": "^3.2.0",
 | 
			
		||||
    "vuex": "^3.4.0"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@vue/cli-plugin-babel": "~4.5.0",
 | 
			
		||||
    "@vue/cli-plugin-eslint": "~4.5.0",
 | 
			
		||||
    "@vue/cli-plugin-router": "~4.5.0",
 | 
			
		||||
    "@vue/cli-plugin-unit-mocha": "~4.5.0",
 | 
			
		||||
    "@vue/cli-plugin-vuex": "~4.5.0",
 | 
			
		||||
    "@vue/cli-service": "~4.5.0",
 | 
			
		||||
    "@vue/test-utils": "^1.0.3",
 | 
			
		||||
    "babel-eslint": "^10.1.0",
 | 
			
		||||
    "chai": "^4.1.2",
 | 
			
		||||
    "eslint": "^6.7.2",
 | 
			
		||||
    "eslint-plugin-vue": "^6.2.2",
 | 
			
		||||
    "sass": "^1.26.5",
 | 
			
		||||
    "sass-loader": "^8.0.2",
 | 
			
		||||
    "svg-sprite-loader": "^6.0.9",
 | 
			
		||||
    "vue-template-compiler": "^2.6.11"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,186 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy Chen
 | 
			
		||||
 * @Date: 2021-02-26 11:50:20
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2021-07-01 11:45:18
 | 
			
		||||
 * @Description: iframe 封装 viewer
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="utf-8">
 | 
			
		||||
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width,user-scalable=no,minimum-scale=1.0,maximum-scale=1.0">
 | 
			
		||||
    <link rel="icon" href="../favicon.ico">
 | 
			
		||||
    <title>I3V_EIM_3D</title>
 | 
			
		||||
 | 
			
		||||
    <link href="../eim-0225/EIMMODEL.min.css" rel="stylesheet">
 | 
			
		||||
    <link href="../eim-0225/bar-custom.css" rel="stylesheet">
 | 
			
		||||
 | 
			
		||||
    <style>
 | 
			
		||||
        html {
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            height: 100%;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        body {
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            height: 100%;
 | 
			
		||||
            margin: 0px;
 | 
			
		||||
            overflow: hidden;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #viewer {
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            height: 100%;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #link-contact {
 | 
			
		||||
            z-index: 1000;
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            right: 10px;
 | 
			
		||||
            bottom: 10px;
 | 
			
		||||
        }
 | 
			
		||||
    </style>
 | 
			
		||||
 | 
			
		||||
    <!-- <script src="../eim-0225/EIMMODEL.min.js" charset="utf-8"></script> -->
 | 
			
		||||
    <script src="../eim-0225/EIMMODEL.js" charset="utf-8"></script>
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
    <div id="viewer">
 | 
			
		||||
        <a id="link-contact" target="_blank" href="../#/login?routeName=Model">联系我们</a>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <script>
 | 
			
		||||
        var toolOption = {
 | 
			
		||||
            home: true, // 是否显示初始化按钮
 | 
			
		||||
            fit: true, // 是否显示自适应按钮
 | 
			
		||||
            restore: true, // 是否显示还原按钮
 | 
			
		||||
            roam: true, // 是否显示漫游按钮
 | 
			
		||||
            multiple: true, // 是否显示框选按钮
 | 
			
		||||
            hide: true, // 是否显示隐藏按钮
 | 
			
		||||
            isolation: true, // 是否显示隔离按钮
 | 
			
		||||
            sectioning: true, // 是否显示剖切按钮
 | 
			
		||||
            color: true, // 是否显示设置颜色按钮
 | 
			
		||||
            setting: true, // 是否显示设置按钮
 | 
			
		||||
            attribute: false, // 是否显示属性按钮
 | 
			
		||||
            measurement: false, // 是否显示测距按钮
 | 
			
		||||
            mark: false, // 标签
 | 
			
		||||
            snapshot: false, // 快照
 | 
			
		||||
            postil: false // 批注
 | 
			
		||||
        };
 | 
			
		||||
    </script>
 | 
			
		||||
 | 
			
		||||
    <script>
 | 
			
		||||
        var args = {} // 获取url以get方式传入的参数
 | 
			
		||||
        // 截取?后面的片段包含的键值
 | 
			
		||||
        window.location.search.substr(1).split('&').forEach(item => {
 | 
			
		||||
            var [k, v] = item.split('=');
 | 
			
		||||
            args[k] = v;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (!args.projectKey) console.error('url 参数必须包括 projectKey');
 | 
			
		||||
        if (!args.modelKeys) console.error('url 参数必须包括 modelKeys');
 | 
			
		||||
 | 
			
		||||
        var MODEL_KEYS = args.modelKeys ? args.modelKeys.split(',') : [];
 | 
			
		||||
        var PROJECT_KEY = args.projectKey || ""; // modelDb
 | 
			
		||||
        var HOST = args.host || "http://139.9.215.236:82";
 | 
			
		||||
 | 
			
		||||
        // 是否显示工具栏(url传参true为字符串),默认值为true
 | 
			
		||||
        var HAS_TOOLBAR = args.hasToolBar === "false" ? false : true;
 | 
			
		||||
        // 控制是否显示viewer右上角的6面体(url传参true为字符串),默认值为true
 | 
			
		||||
        var HAS_CONTROLLER = args.hasController === "false" ? false : true;
 | 
			
		||||
        // viewer的背景颜色
 | 
			
		||||
        var BG_COLOR = args.bgColor;
 | 
			
		||||
 | 
			
		||||
        // 1、用于更新本页面(timeStrmp一旦改变,则意味iframe的src改变,src改变会使iframe会自动刷新)
 | 
			
		||||
        // 2、用于唯一标识本页面
 | 
			
		||||
        var TIMESTAMP = args.timeStamp;
 | 
			
		||||
 | 
			
		||||
        var IS_AUTO_RESIZE = args.isAutoResize ? args.isAutoResize : false;
 | 
			
		||||
 | 
			
		||||
        var SHARE = args.share ? args.share : false;
 | 
			
		||||
    </script>
 | 
			
		||||
 | 
			
		||||
    <script>
 | 
			
		||||
        if (!SHARE) {
 | 
			
		||||
            window.document.getElementById('link-contact').style.display = 'none';
 | 
			
		||||
        }
 | 
			
		||||
    </script>
 | 
			
		||||
 | 
			
		||||
    <script>
 | 
			
		||||
        var option = {
 | 
			
		||||
            host: HOST,
 | 
			
		||||
            viewport: "viewer"
 | 
			
		||||
        }; // viewport指上面div的id 
 | 
			
		||||
 | 
			
		||||
        EIMMODEL.GlobalData.EnableViewController = HAS_CONTROLLER; // 显示viewer右上角的6面体
 | 
			
		||||
        var viewer3D = new EIMMODEL.Viewer(option);
 | 
			
		||||
 | 
			
		||||
        // 添加viewer的背景颜色
 | 
			
		||||
        if (BG_COLOR) viewer3D.setSceneBackGroundColor(BG_COLOR);
 | 
			
		||||
 | 
			
		||||
        // 添加viewer的工具条
 | 
			
		||||
        if (HAS_TOOLBAR) {
 | 
			
		||||
            var bosToolBar = new EIMMODEL.UI.ToolBar(viewer3D);
 | 
			
		||||
            // bosToolBar.createTool(toolOption);
 | 
			
		||||
            bosToolBar.createTool();
 | 
			
		||||
        }
 | 
			
		||||
    </script>
 | 
			
		||||
 | 
			
		||||
    <script>
 | 
			
		||||
        var modelKeysLoaded = [];
 | 
			
		||||
        // viewer3D.viewerImpl.modelManager.addEventListener(
 | 
			
		||||
        viewer3D.registerModelEventListener(
 | 
			
		||||
            EIMMODEL.EVENTS.ON_LOAD_COMPLETE,
 | 
			
		||||
            function (event) {
 | 
			
		||||
                if (MODEL_KEYS.includes(event.modelKey)) {
 | 
			
		||||
                    modelKeysLoaded.push(event.modelKey);
 | 
			
		||||
                }
 | 
			
		||||
                if (modelKeysLoaded.length >= MODEL_KEYS.length) {
 | 
			
		||||
                    // 此时证明模型全加载完了
 | 
			
		||||
                    var bEvent = new CustomEvent("allmodelsloaded", {
 | 
			
		||||
                        detail: {
 | 
			
		||||
                            timeStamp: Number(TIMESTAMP),
 | 
			
		||||
                            modelKeys: MODEL_KEYS
 | 
			
		||||
                        },
 | 
			
		||||
                        bubbles: true, // 是否冒泡
 | 
			
		||||
                        cancelable: true // 是否可以取消事件的默认行为
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    window.parent.dispatchEvent(bEvent);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    </script>
 | 
			
		||||
 | 
			
		||||
    <script>
 | 
			
		||||
        // 往viewer添加模型
 | 
			
		||||
        if (PROJECT_KEY) {
 | 
			
		||||
            if (MODEL_KEYS.length) {
 | 
			
		||||
                // 加载所有模型
 | 
			
		||||
                MODEL_KEYS.forEach(modelKey => {
 | 
			
		||||
                    viewer3D.addView(modelKey, PROJECT_KEY);
 | 
			
		||||
                });
 | 
			
		||||
            } else {
 | 
			
		||||
                console.error('必须至少提供一个 modelKey');
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            console.error('projectKey 不能为空');
 | 
			
		||||
        }
 | 
			
		||||
    </script>
 | 
			
		||||
 | 
			
		||||
    <script>
 | 
			
		||||
        if (IS_AUTO_RESIZE) {
 | 
			
		||||
            window.addEventListener("resize", function () {
 | 
			
		||||
                // viewer3D.autoResize();
 | 
			
		||||
                viewer3D.getViewerImpl().resize(window.innerWidth, window.innerHeight);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    </script>
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
</html>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
.toolComponentDiv>.yj-but,
 | 
			
		||||
.toolModelDiv>.yj-but,
 | 
			
		||||
.toolPostilDiv>.yj-but,
 | 
			
		||||
.homeViewer-div>.yj-but,
 | 
			
		||||
.yj-group:last-child>.yj-but {
 | 
			
		||||
    border-bottom-right-radius: 0 !important;
 | 
			
		||||
    background-image: url("./corner.png");
 | 
			
		||||
    background-repeat: no-repeat;
 | 
			
		||||
    background-size: 6px;
 | 
			
		||||
    background-position: right bottom;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.setModal {
 | 
			
		||||
    background-color: unset;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.sceneColorDiv,
 | 
			
		||||
.fullScreenDiv {
 | 
			
		||||
    background-color: rgba(0, 0, 0, .5);
 | 
			
		||||
    border-style: outset;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    border-color: #767676;
 | 
			
		||||
    border-width: 2px;
 | 
			
		||||
    padding: 0 6px;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    border-radius: 6px;
 | 
			
		||||
}
 | 
			
		||||
| 
		 After Width: | Height: | Size: 221 B  | 
| 
		 After Width: | Height: | Size: 4.2 KiB  | 
| 
						 | 
				
			
			@ -0,0 +1,45 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2020-09-10 09:12:00
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2021-11-02 01:59:46
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
-->
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
 | 
			
		||||
<head>
 | 
			
		||||
  <meta charset="utf-8">
 | 
			
		||||
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
 | 
			
		||||
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=no">
 | 
			
		||||
  <link rel="icon" href="<%= BASE_URL %>favicon.ico">
 | 
			
		||||
  <title><%= htmlWebpackPlugin.options.title %></title>
 | 
			
		||||
  <style>
 | 
			
		||||
    html,
 | 
			
		||||
    body {
 | 
			
		||||
      height: 100%;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    body,
 | 
			
		||||
    h1,
 | 
			
		||||
    h2,
 | 
			
		||||
    h3,
 | 
			
		||||
    h4,
 | 
			
		||||
    h5,
 | 
			
		||||
    h6,
 | 
			
		||||
    p {
 | 
			
		||||
      margin: 0;
 | 
			
		||||
    }
 | 
			
		||||
  </style>
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
  <noscript>
 | 
			
		||||
    <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
 | 
			
		||||
      Please enable it to continue.</strong>
 | 
			
		||||
  </noscript>
 | 
			
		||||
  <div id="app"></div>
 | 
			
		||||
  <!-- built files will be auto injected -->
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
</html>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,58 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2020-09-10 09:12:00
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-05 19:55:32
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <div id="app">
 | 
			
		||||
    <router-view />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  computed: {
 | 
			
		||||
    isLoginPage: function () {
 | 
			
		||||
      return this.$route.name === "Login";
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  watch: {
 | 
			
		||||
    isLoginPage: function (newVal, oldVal) {
 | 
			
		||||
      let body = document.body;
 | 
			
		||||
      if (newVal) {
 | 
			
		||||
        body.classList.add("login");
 | 
			
		||||
      } else {
 | 
			
		||||
        body.classList.remove("login");
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {};
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
@import "./scss/_variables.scss";
 | 
			
		||||
@import "./scss/scrollbar.scss";
 | 
			
		||||
@import "./scss/element-ui-reset.scss"; // 重设element ui的样式都写于这个scss
 | 
			
		||||
 | 
			
		||||
body.login {
 | 
			
		||||
  min-width: unset;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
  min-width: $body-min-width;
 | 
			
		||||
  min-height: $body-min-height;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  font-family: "Microsoft Yahei", "Avenir", Helvetica, Arial, sans-serif;
 | 
			
		||||
  -webkit-font-smoothing: antialiased;
 | 
			
		||||
  -moz-osx-font-smoothing: grayscale;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,50 @@
 | 
			
		|||
/*
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-09-22 09:42:40
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2021-09-22 23:15:29
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import BaseAxios from "./_BaseAxios.js";
 | 
			
		||||
import ResHelper from "../_ResponseHelper.js"
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 登录方法
 | 
			
		||||
 * @param {string} username 用户名(必填)
 | 
			
		||||
 * @param {string} password 密码(必填)
 | 
			
		||||
 * @param {string} captcha 验证码(非必填)
 | 
			
		||||
 * @param {string} checkKey 验证码key(非必填)
 | 
			
		||||
 */
 | 
			
		||||
function login({
 | 
			
		||||
    username,
 | 
			
		||||
    password,
 | 
			
		||||
    captcha,
 | 
			
		||||
    checkKey
 | 
			
		||||
}) {
 | 
			
		||||
    return BaseAxios({
 | 
			
		||||
        url: `/sys/login`,
 | 
			
		||||
        method: "post",
 | 
			
		||||
        data: {
 | 
			
		||||
            username,
 | 
			
		||||
            password,
 | 
			
		||||
            captcha,
 | 
			
		||||
            checkKey
 | 
			
		||||
        }
 | 
			
		||||
    }).then(ResHelper.handler);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 退出登录方法
 | 
			
		||||
 */
 | 
			
		||||
function logout() {
 | 
			
		||||
    return BaseAxios({
 | 
			
		||||
        url: `/sys/logout`,
 | 
			
		||||
        method: "post"
 | 
			
		||||
    }).then(ResHelper.handler);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    login, // 登录方法
 | 
			
		||||
    logout // 退出登录方法
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,25 @@
 | 
			
		|||
/*
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2020-06-23 08:44:33
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2021-09-22 09:41:08
 | 
			
		||||
 * @Description: 基础api的Axios的基类
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import axios from "axios";
 | 
			
		||||
import AxiosInterceptors from '../_AxiosInterceptors.js'
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
    BASE_URL,
 | 
			
		||||
    TIMEOUT
 | 
			
		||||
} from '../../const.js'
 | 
			
		||||
 | 
			
		||||
let requestConfig = {
 | 
			
		||||
    baseURL: BASE_URL, // `baseURL` will be prepended to `url` unless `url` is absolute.
 | 
			
		||||
    timeout: TIMEOUT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const baseAxios = axios.create(requestConfig);
 | 
			
		||||
AxiosInterceptors.setInterceptors(baseAxios);
 | 
			
		||||
 | 
			
		||||
export default baseAxios;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,95 @@
 | 
			
		|||
/*
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-06-19 02:43:26
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2021-12-20 01:00:36
 | 
			
		||||
 * @Description: Axios拦截器
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import router from '../router/_index.js'
 | 
			
		||||
import LoginInfo from "../storage/login-info.js";
 | 
			
		||||
import SysCode from "../sys/SysCode.js"
 | 
			
		||||
import {
 | 
			
		||||
    TOKEY_ATTR_NAME
 | 
			
		||||
} from "../const.js"
 | 
			
		||||
 | 
			
		||||
function setInterceptors(axios) {
 | 
			
		||||
    // 在被then()和catch()方法处理之前,把 客户端请求 拦截下来优先处理
 | 
			
		||||
    axios.interceptors.request.use(function (config) {
 | 
			
		||||
        // api 返回的数据均为json,如果请求没有指明,则默认视为json
 | 
			
		||||
        if (!config.responseType) {
 | 
			
		||||
            config.responseType = 'json';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let regex1 = /\/([A-Za-z0-9_-]+\/)+login/; // 登录系统(含登录系统后台及eim后台)
 | 
			
		||||
        let regex2 = /\/system(\/\S+)+(\/)?/; // rbac后台里的system route下的所有接口
 | 
			
		||||
        // let regex5 = /\/(\w+\/)+register/; // 注册用户
 | 
			
		||||
 | 
			
		||||
        if (
 | 
			
		||||
            !regex1.test(config.url) &&
 | 
			
		||||
            !regex2.test(config.url)
 | 
			
		||||
        ) {
 | 
			
		||||
            // 只要已登录,默认都把 token 加到对后端服务请求的 header 里
 | 
			
		||||
            let user = LoginInfo.getUserInfo();
 | 
			
		||||
 | 
			
		||||
            if (user !== null)
 | 
			
		||||
                config.headers[TOKEY_ATTR_NAME] = user.token;
 | 
			
		||||
            else
 | 
			
		||||
                console.log('本地用户信息为空');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return config;
 | 
			
		||||
    }, function (error) {
 | 
			
		||||
        return Promise.reject(error);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // 在被then()和catch()方法处理之前,把 服务器返回结果 拦截下来优先处理
 | 
			
		||||
    axios.interceptors.response.use(function (response) {
 | 
			
		||||
        return response;
 | 
			
		||||
    }, function (error) {
 | 
			
		||||
        if (error.response) { // 请求已经发送,并且服务器携带着一个http状态码返回了数据,而这个状态码不包括2xx
 | 
			
		||||
            if (error.response.status === 401 &&
 | 
			
		||||
                (
 | 
			
		||||
                    error.response.data.code === SysCode.TOKEN_EXPIRED ||
 | 
			
		||||
                    error.response.data.code === SysCode.TOKEN_FORCED_INVALID ||
 | 
			
		||||
                    error.response.data.code === SysCode.TOKEN_ERROR
 | 
			
		||||
                )) {
 | 
			
		||||
                if (router.currentRoute.name !== "Login") {
 | 
			
		||||
                    router.push({
 | 
			
		||||
                        name: 'Login'
 | 
			
		||||
                    }).catch(e => { });
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                if (!(
 | 
			
		||||
                    router.currentRoute.name === "Error" &&
 | 
			
		||||
                    router.currentRoute.params.code === error.response.data.code &&
 | 
			
		||||
                    router.currentRoute.params.message === error.response.data.message
 | 
			
		||||
                )) {
 | 
			
		||||
                    if (process.env.NODE_ENV !== 'development') {
 | 
			
		||||
                        router.push({
 | 
			
		||||
                            name: 'Error',
 | 
			
		||||
                            params: {
 | 
			
		||||
                                code: error.response.data.code,
 | 
			
		||||
                                message: error.response.data.message
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
                    } else {
 | 
			
		||||
                        console.log('后端接口返回非2XX状态码 :>> ', error.response.data);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                return Promise.reject(new Error(error.response.data.message));
 | 
			
		||||
            }
 | 
			
		||||
        } else if (error.request) { // 请求已经发出,但并无收到来自服务器的任何返回
 | 
			
		||||
            console.log('请求已经发出,但并无收到来自服务器的任何返回:', error.message);
 | 
			
		||||
        } else { // 在建立一个请求的时候发生了错误
 | 
			
		||||
            console.log('在建立一个请求的时候发生了错误:', error.message);
 | 
			
		||||
        }
 | 
			
		||||
        return Promise.reject(error);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return axios;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    setInterceptors
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
/*
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-07-10 20:49:58
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2021-09-22 15:22:02
 | 
			
		||||
 * @Description: 针对服务器返回值的一些处理方法
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import SysCode from "../sys/SysCode.js"
 | 
			
		||||
import SysError from "../sys/SysError.js"
 | 
			
		||||
 | 
			
		||||
// 一般处理方法
 | 
			
		||||
function handler(res) {
 | 
			
		||||
    if (res.data.success) {
 | 
			
		||||
        return res.data.result;
 | 
			
		||||
    } else {
 | 
			
		||||
        throw new SysError(
 | 
			
		||||
            res.data.code,
 | 
			
		||||
            res.data.message,
 | 
			
		||||
            res.data.result,
 | 
			
		||||
            res.data.success,
 | 
			
		||||
            res.data.timestamp
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    handler
 | 
			
		||||
}
 | 
			
		||||
| 
		 After Width: | Height: | Size: 588 B  | 
| 
		 After Width: | Height: | Size: 729 B  | 
| 
		 After Width: | Height: | Size: 782 B  | 
| 
		 After Width: | Height: | Size: 391 B  | 
| 
		 After Width: | Height: | Size: 264 B  | 
| 
		 After Width: | Height: | Size: 266 B  | 
| 
		 After Width: | Height: | Size: 861 B  | 
| 
		 After Width: | Height: | Size: 928 B  | 
| 
		 After Width: | Height: | Size: 775 B  | 
| 
		 After Width: | Height: | Size: 816 B  | 
| 
		 After Width: | Height: | Size: 815 B  | 
| 
		 After Width: | Height: | Size: 885 B  | 
| 
		 After Width: | Height: | Size: 956 B  | 
| 
		 After Width: | Height: | Size: 1.1 KiB  | 
| 
		 After Width: | Height: | Size: 420 B  | 
| 
		 After Width: | Height: | Size: 945 B  | 
| 
		 After Width: | Height: | Size: 729 B  | 
| 
		 After Width: | Height: | Size: 847 B  | 
| 
		 After Width: | Height: | Size: 379 B  | 
| 
		 After Width: | Height: | Size: 653 B  | 
| 
		 After Width: | Height: | Size: 839 B  | 
| 
		 After Width: | Height: | Size: 1.4 KiB  | 
| 
		 After Width: | Height: | Size: 1.4 KiB  | 
| 
		 After Width: | Height: | Size: 1.3 KiB  | 
| 
		 After Width: | Height: | Size: 58 KiB  | 
| 
		 After Width: | Height: | Size: 958 B  | 
| 
		 After Width: | Height: | Size: 4.6 KiB  | 
| 
		 After Width: | Height: | Size: 7.9 KiB  | 
| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
/*
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-09-12 21:49:24
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2021-09-17 09:22:22
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import LoginInfo from "../storage/login-info.js"
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 检查当前用户是否具有某种权限,或某几种权限中的一种
 | 
			
		||||
 * @param {string|Array.<string>} paramValue 权限code或权限code的数组
 | 
			
		||||
 * @returns 
 | 
			
		||||
 */
 | 
			
		||||
function checkAuth(paramValue) {
 | 
			
		||||
    if (paramValue) {
 | 
			
		||||
        let auths = LoginInfo.getRbacFromToken().getCleanAuths();
 | 
			
		||||
 | 
			
		||||
        if (typeof paramValue === 'string') {
 | 
			
		||||
            if (auths.includes(paramValue)) return true;
 | 
			
		||||
            else return false;
 | 
			
		||||
        } else if (Array.isArray(paramValue)) {
 | 
			
		||||
            for (const pv of paramValue) {
 | 
			
		||||
                if (auths.includes(pv)) return true;
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        } else {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    checkAuth
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,70 @@
 | 
			
		|||
/*
 | 
			
		||||
 * @Author: Guanghao
 | 
			
		||||
 * @Date: 2022-01-05 18:48:51
 | 
			
		||||
 * @LastEditors: Guanghao
 | 
			
		||||
 * @LastEditTime: 2022-01-05 18:53:10
 | 
			
		||||
 * @Description: 日志相关数据请求和处理逻辑
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 查询操作日志列表
 | 
			
		||||
 * @returns {Array.<Log>} 日志对象数组
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
async function findByAction() {
 | 
			
		||||
  return [
 | 
			
		||||
    {
 | 
			
		||||
      user: '张三',
 | 
			
		||||
      time: '2021-12-31 18:40:11',
 | 
			
		||||
      module: '演示方案',
 | 
			
		||||
      position: '101主题',
 | 
			
		||||
      action: '编辑'
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      user: '张三',
 | 
			
		||||
      time: '2021-12-31 18:40:11',
 | 
			
		||||
      module: '演示方案',
 | 
			
		||||
      position: '101主题',
 | 
			
		||||
      action: '编辑'
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      user: '张三',
 | 
			
		||||
      time: '2021-12-31 18:40:11',
 | 
			
		||||
      module: '演示方案',
 | 
			
		||||
      position: '101主题',
 | 
			
		||||
      action: '编辑'
 | 
			
		||||
    },
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 查询登录日志列表
 | 
			
		||||
 * @returns {Array.<Log>} 日志对象数组
 | 
			
		||||
 */
 | 
			
		||||
async function findByLogin() {
 | 
			
		||||
  return [
 | 
			
		||||
    {
 | 
			
		||||
      user: '张三',
 | 
			
		||||
      time: '2021-12-31 18:43:55',
 | 
			
		||||
      result: '成功',
 | 
			
		||||
      ip: '192.168.0.0.228'
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      user: '张三',
 | 
			
		||||
      time: '2021-12-31 18:43:55',
 | 
			
		||||
      result: '成功',
 | 
			
		||||
      ip: '192.168.0.0.228'
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      user: '张三',
 | 
			
		||||
      time: '2021-12-31 18:43:55',
 | 
			
		||||
      result: '成功',
 | 
			
		||||
      ip: '192.168.0.0.228'
 | 
			
		||||
    },
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  findByAction,
 | 
			
		||||
  findByLogin
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,67 @@
 | 
			
		|||
/*
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-12-31 14:31:45
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-04 14:00:53
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
 */
 | 
			
		||||
import MenuItem from "../../entity/Ui/Menu/MenuItem.js";
 | 
			
		||||
 | 
			
		||||
async function getBackMenuItems() {
 | 
			
		||||
    return [
 | 
			
		||||
        new MenuItem({
 | 
			
		||||
            title: '组织架构',
 | 
			
		||||
            iconClass: 'el-icon-s-operation',
 | 
			
		||||
            routerName: 'Org',
 | 
			
		||||
        }),
 | 
			
		||||
        new MenuItem({
 | 
			
		||||
            title: '角色管理',
 | 
			
		||||
            iconClass: 'el-icon-user',
 | 
			
		||||
            routerName: 'Role',
 | 
			
		||||
        }),
 | 
			
		||||
        new MenuItem({
 | 
			
		||||
            title: '分类管理',
 | 
			
		||||
            iconClass: 'el-icon-film',
 | 
			
		||||
            routerName: 'Classification',
 | 
			
		||||
        }),
 | 
			
		||||
        new MenuItem({
 | 
			
		||||
            title: '数据统计',
 | 
			
		||||
            iconClass: 'el-icon-s-data',
 | 
			
		||||
            routerName: 'Statistics',
 | 
			
		||||
        }),
 | 
			
		||||
        new MenuItem({
 | 
			
		||||
            title: '专题管理',
 | 
			
		||||
            iconClass: 'el-icon-edit-outline',
 | 
			
		||||
            routerName: 'Topic',
 | 
			
		||||
        }),
 | 
			
		||||
        new MenuItem({
 | 
			
		||||
            title: '平台设置',
 | 
			
		||||
            iconClass: 'el-icon-setting',
 | 
			
		||||
            routerName: 'PlatformSetting',
 | 
			
		||||
        }),
 | 
			
		||||
        new MenuItem({
 | 
			
		||||
            title: '授权管理',
 | 
			
		||||
            iconClass: 'el-icon-finished',
 | 
			
		||||
            routerName: 'Authorization',
 | 
			
		||||
            children: [
 | 
			
		||||
                new MenuItem({
 | 
			
		||||
                    title: '合作伙伴授权管理',
 | 
			
		||||
                    routerName: 'Authorization4Partner',
 | 
			
		||||
                }),
 | 
			
		||||
                new MenuItem({
 | 
			
		||||
                    title: '客户授权管理',
 | 
			
		||||
                    routerName: 'Authorization4Customer',
 | 
			
		||||
                })
 | 
			
		||||
            ]
 | 
			
		||||
        }),
 | 
			
		||||
        new MenuItem({
 | 
			
		||||
            title: '系统日志',
 | 
			
		||||
            iconClass: 'el-icon-notebook-2',
 | 
			
		||||
            routerName: 'Log',
 | 
			
		||||
        }),
 | 
			
		||||
    ];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    getBackMenuItems
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,118 @@
 | 
			
		|||
/*
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-12-18 02:20:09
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-07 16:40:08
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import MenuItem from "../entity/Ui/Menu/MenuItem.js";
 | 
			
		||||
 | 
			
		||||
async function getMainMenuItems() {
 | 
			
		||||
    return [
 | 
			
		||||
        // 内部标签
 | 
			
		||||
        new MenuItem({
 | 
			
		||||
            id: 'ProductAndCases',
 | 
			
		||||
            title: '产品案例',
 | 
			
		||||
            iconSrc: require("../assets/MenuIcons/Main/ProductAndCases1.png"),
 | 
			
		||||
            iconSrcInactive: require("../assets/MenuIcons/Main/ProductAndCases2.png"),
 | 
			
		||||
            routerName: 'ProductAndCases',
 | 
			
		||||
        }),
 | 
			
		||||
        // 内部标签
 | 
			
		||||
        new MenuItem({
 | 
			
		||||
            id: 'Solutions',
 | 
			
		||||
            title: '解决方案',
 | 
			
		||||
            iconSrc: require("../assets/MenuIcons/Main/Solutions1.png"),
 | 
			
		||||
            iconSrcInactive: require("../assets/MenuIcons/Main/Solutions2.png"),
 | 
			
		||||
            routerName: 'Solutions',
 | 
			
		||||
        }),
 | 
			
		||||
        // 内部标签
 | 
			
		||||
        new MenuItem({
 | 
			
		||||
            id: 'Demonstrations',
 | 
			
		||||
            title: '演示方案',
 | 
			
		||||
            iconSrc: require("../assets/MenuIcons/Main/Demonstrations1.png"),
 | 
			
		||||
            iconSrcInactive: require("../assets/MenuIcons/Main/Demonstrations2.png"),
 | 
			
		||||
            routerName: 'Demonstrations',
 | 
			
		||||
        }),
 | 
			
		||||
        // 内部标签
 | 
			
		||||
        new MenuItem({
 | 
			
		||||
            id: 'SalesResources',
 | 
			
		||||
            title: '销售资源',
 | 
			
		||||
            iconSrc: require("../assets/MenuIcons/Main/SalesResources1.png"),
 | 
			
		||||
            iconSrcInactive: require("../assets/MenuIcons/Main/SalesResources2.png"),
 | 
			
		||||
            routerName: 'SalesResources',
 | 
			
		||||
        }),
 | 
			
		||||
        // 内部标签
 | 
			
		||||
        new MenuItem({
 | 
			
		||||
            id: 'PersonalSpace',
 | 
			
		||||
            title: '个人空间',
 | 
			
		||||
            iconSrc: require("../assets/MenuIcons/Main/PersonalSpace1.png"),
 | 
			
		||||
            iconSrcInactive: require("../assets/MenuIcons/Main/PersonalSpace2.png"),
 | 
			
		||||
            routerName: 'PersonalSpace',
 | 
			
		||||
        }),
 | 
			
		||||
        // popover
 | 
			
		||||
        new MenuItem({
 | 
			
		||||
            id: 'More',
 | 
			
		||||
            title: '更多',
 | 
			
		||||
            iconSrc: require("../assets/MenuIcons/Main/More1.png"),
 | 
			
		||||
            iconSrcInactive: require("../assets/MenuIcons/Main/More2.png"),
 | 
			
		||||
            // routerName: 'More',
 | 
			
		||||
        }),
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getSecondaryMenuItems() {
 | 
			
		||||
    return [
 | 
			
		||||
        // 抽屉
 | 
			
		||||
        new MenuItem({
 | 
			
		||||
            id: 'QuickFavorites',
 | 
			
		||||
            title: '快捷收藏',
 | 
			
		||||
            iconSrc: require("../assets/MenuIcons/Secondary/QuickFavorites.png"),
 | 
			
		||||
            // routerName: 'QuickFavorites',
 | 
			
		||||
            drawerName: 'QuickFavorites'
 | 
			
		||||
        }),
 | 
			
		||||
        // 内部标签
 | 
			
		||||
        new MenuItem({
 | 
			
		||||
            id: 'QuickDemonstrations',
 | 
			
		||||
            title: '快捷演示方案',
 | 
			
		||||
            iconSrc: require("../assets/MenuIcons/Secondary/QuickDemonstrations.png"),
 | 
			
		||||
            routerName: 'QuickDemonstrations',
 | 
			
		||||
        }),
 | 
			
		||||
        // 抽屉
 | 
			
		||||
        new MenuItem({
 | 
			
		||||
            id: 'Clients',
 | 
			
		||||
            title: '客户列表',
 | 
			
		||||
            iconSrc: require("../assets/MenuIcons/Secondary/Clients.png"),
 | 
			
		||||
            // routerName: 'Clients',
 | 
			
		||||
            drawerName: 'Clients'
 | 
			
		||||
        }),
 | 
			
		||||
        // 抽屉
 | 
			
		||||
        new MenuItem({
 | 
			
		||||
            id: 'Partners',
 | 
			
		||||
            title: '合作伙伴',
 | 
			
		||||
            iconSrc: require("../assets/MenuIcons/Secondary/Partners.png"),
 | 
			
		||||
            // routerName: 'Partners',
 | 
			
		||||
            drawerName: 'Partners'
 | 
			
		||||
        }),
 | 
			
		||||
        // 抽屉
 | 
			
		||||
        new MenuItem({
 | 
			
		||||
            id: 'PersonalComputer',
 | 
			
		||||
            title: '我的电脑',
 | 
			
		||||
            iconSrc: require("../assets/MenuIcons/Secondary/PersonalComputer.png"),
 | 
			
		||||
            // routerName: 'PersonalComputer',
 | 
			
		||||
            drawerName: 'PersonalComputer'
 | 
			
		||||
        }),
 | 
			
		||||
        // 内部标签
 | 
			
		||||
        new MenuItem({
 | 
			
		||||
            id: 'CloudDesktop',
 | 
			
		||||
            title: '云桌面',
 | 
			
		||||
            iconSrc: require("../assets/MenuIcons/Secondary/CloudDesktop.png"),
 | 
			
		||||
            routerName: 'CloudDesktop',
 | 
			
		||||
        }),
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    getMainMenuItems,
 | 
			
		||||
    getSecondaryMenuItems
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,59 @@
 | 
			
		|||
/*
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2022-01-04 17:33:57
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-05 10:44:18
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import User from "../../entity/Rbac/User.js";
 | 
			
		||||
import PageResult from "../../entity/_Common/PageResult.js"
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 某部门添加一个已存在的用户/移动用户(移动用户:把某用户从某部门移动到另一部门)
 | 
			
		||||
 * @param {string} orgId 目标部门id
 | 
			
		||||
 * @param {string} userId 用户id
 | 
			
		||||
 * @returns {User} 用户对象
 | 
			
		||||
 */
 | 
			
		||||
async function relateUser(orgId, userId) {
 | 
			
		||||
    return new User({
 | 
			
		||||
        id: 'a',
 | 
			
		||||
        token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NDA2Mjc3ODMsInVzZXJuYW1lIjoiMTcxNTk2NTMzOTAifQ.V9Bf0nlpf-QYdEGP2Eu6-U0VXZvuwFEN0fNtmeQxmUI',
 | 
			
		||||
        username: '张三',
 | 
			
		||||
        realname: '张三',
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 根据部门id分页查找部门内用户列表(也可获取到不在组织架构内的用户)
 | 
			
		||||
 * @param {string} orgId 部门id(可以传null或者不传,这样会获取到不在组织架构内的用户)
 | 
			
		||||
 * @param {number} pageSize 每页大小
 | 
			
		||||
 * @param {number} pageNum 第几页
 | 
			
		||||
 * @param {string} orderBy 排序依据字段
 | 
			
		||||
 * @param {string} direction 排序顺序(ASC, DESC)
 | 
			
		||||
 * @returns {Array.<User>} 用户列表
 | 
			
		||||
 */
 | 
			
		||||
async function findUsersByOrgIdAndByPage(orgId, pageSize = 10, pageNum = 1, orderBy = 'id', direction = 'ASC') {
 | 
			
		||||
    return new PageResult([
 | 
			
		||||
        new User({
 | 
			
		||||
            id: 'a',
 | 
			
		||||
            createTime: '2022-01-01 10:00:00',
 | 
			
		||||
            token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NDA2Mjc3ODMsInVzZXJuYW1lIjoiMTcxNTk2NTMzOTAifQ.V9Bf0nlpf-QYdEGP2Eu6-U0VXZvuwFEN0fNtmeQxmUI',
 | 
			
		||||
            username: '张三',
 | 
			
		||||
            realname: '张三',
 | 
			
		||||
        }),
 | 
			
		||||
        new User({
 | 
			
		||||
            id: 'b',
 | 
			
		||||
            createTime: '2022-01-02 10:00:00',
 | 
			
		||||
            token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NDA2Mjc3ODMsInVzZXJuYW1lIjoiMTcxNTk2NTMzOTAifQ.V9Bf0nlpf-QYdEGP2Eu6-U0VXZvuwFEN0fNtmeQxmUI',
 | 
			
		||||
            username: '李四',
 | 
			
		||||
            realname: '李四',
 | 
			
		||||
        })
 | 
			
		||||
    ], 20);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    relateUser,
 | 
			
		||||
    findUsersByOrgIdAndByPage
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,99 @@
 | 
			
		|||
/*
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2022-01-04 16:49:56
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-05 00:56:05
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import Organization from "../../entity/Rbac/Organization.js";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 创建一个部门
 | 
			
		||||
 * @param {string} parentId 父部门id(null代表根节点)
 | 
			
		||||
 * @param {string} name 部门名称
 | 
			
		||||
 * @returns {Organization} 部门对象
 | 
			
		||||
 */
 | 
			
		||||
async function add(parentId, name) {
 | 
			
		||||
    return new Organization({
 | 
			
		||||
        id: 'a',
 | 
			
		||||
        name: '新部门'
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 修改一个部门信息
 | 
			
		||||
 * @param {string} id 部门id
 | 
			
		||||
 * @param {string} name 部门名称
 | 
			
		||||
 * @returns {number} 成功操作的个数
 | 
			
		||||
 */
 | 
			
		||||
async function update(id, name) {
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 移动部门到其它父部门
 | 
			
		||||
 * @param {string} id 部门id
 | 
			
		||||
 * @param {string} parentId 目标父部门id
 | 
			
		||||
 * @returns {Organization} 部门对象
 | 
			
		||||
 */
 | 
			
		||||
async function move(id, parentId) {
 | 
			
		||||
    return new Organization({
 | 
			
		||||
        id: 'a',
 | 
			
		||||
        name: '开发部'
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 通过name获取部门数组
 | 
			
		||||
 * @param {string} name 部门id
 | 
			
		||||
 * @returns {Array.<Organization>} 部门对象数组
 | 
			
		||||
 */
 | 
			
		||||
async function findByName(name) {
 | 
			
		||||
    return [
 | 
			
		||||
        new Organization({
 | 
			
		||||
            id: 'a',
 | 
			
		||||
            name: '开发部'
 | 
			
		||||
        }),
 | 
			
		||||
    ];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 获取某部门下所有层级的子部门
 | 
			
		||||
 * @param {number} id 部门id
 | 
			
		||||
 * @returns {Array.<Organization>} 部门对象数组
 | 
			
		||||
 */
 | 
			
		||||
async function getAllNodes(id) {
 | 
			
		||||
    return [
 | 
			
		||||
        new Organization({
 | 
			
		||||
            id: 'a',
 | 
			
		||||
            name: '开发部'
 | 
			
		||||
        }),
 | 
			
		||||
        new Organization({
 | 
			
		||||
            id: 'b',
 | 
			
		||||
            name: '产品部'
 | 
			
		||||
        }),
 | 
			
		||||
        new Organization({
 | 
			
		||||
            id: 'c',
 | 
			
		||||
            name: '测试部'
 | 
			
		||||
        })
 | 
			
		||||
    ];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 通过id删除某部门
 | 
			
		||||
 * @param {string} id 部门id
 | 
			
		||||
 * @returns {number} 成功操作的个数
 | 
			
		||||
 */
 | 
			
		||||
async function delById(id) {
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    add,
 | 
			
		||||
    update,
 | 
			
		||||
    move,
 | 
			
		||||
    findByName,
 | 
			
		||||
    getAllNodes,
 | 
			
		||||
    delById
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,74 @@
 | 
			
		|||
/*
 | 
			
		||||
 * @Author: Guanghao
 | 
			
		||||
 * @Date: 2022-01-04 18:01:37
 | 
			
		||||
 * @LastEditors: Guanghao
 | 
			
		||||
 * @LastEditTime: 2022-01-05 18:10:49
 | 
			
		||||
 * @Description: 角色相关数据请求和处理逻辑
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import Role from '../../entity/Rbac/Role.js'
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 按角色名称模糊查询,不传或传''可查所有角色
 | 
			
		||||
 * @returns {Array.<Role>} 角色对象数组
 | 
			
		||||
 */
 | 
			
		||||
async function findByName(name = '') {
 | 
			
		||||
  return [
 | 
			
		||||
    new Role({
 | 
			
		||||
      id: 1,
 | 
			
		||||
      name: '产品管理员',
 | 
			
		||||
      description: '产品管理员说明'
 | 
			
		||||
    }),
 | 
			
		||||
    new Role({
 | 
			
		||||
      id: 2,
 | 
			
		||||
      name: '超级管理员',
 | 
			
		||||
      description: '超级管理员说明'
 | 
			
		||||
    }),
 | 
			
		||||
    new Role({
 | 
			
		||||
      id: 3,
 | 
			
		||||
      name: '方案管理员',
 | 
			
		||||
      description: '方案管理员说明'
 | 
			
		||||
    }),
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 新建角色
 | 
			
		||||
 * @param {string} name 角色名称
 | 
			
		||||
 * @param {string} description 角色说明
 | 
			
		||||
 * @returns {Role} 返回新建的角色
 | 
			
		||||
 */
 | 
			
		||||
async function add(name, description) {
 | 
			
		||||
  return new Role({
 | 
			
		||||
    id: 4,
 | 
			
		||||
    name,
 | 
			
		||||
    description
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 编辑角色
 | 
			
		||||
 * @param {number} id 角色id
 | 
			
		||||
 * @param {string} name 角色名称
 | 
			
		||||
 * @param {string} description 角色说明
 | 
			
		||||
 * @returns {number} 成功更新个数
 | 
			
		||||
 */
 | 
			
		||||
async function update(id, name, description) {
 | 
			
		||||
  return 1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 删除角色
 | 
			
		||||
 * @param {number} id 角色id
 | 
			
		||||
 * @returns {number} 成功删除个数
 | 
			
		||||
 */
 | 
			
		||||
async function delById(id) {
 | 
			
		||||
  return 1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  findByName,
 | 
			
		||||
  add,
 | 
			
		||||
  update,
 | 
			
		||||
  delById
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,171 @@
 | 
			
		|||
/*
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2022-01-04 16:50:54
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-10 20:28:39
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import User from "../../entity/Rbac/User.js";
 | 
			
		||||
import CommonResult from "../../entity/_Common/CommonResult.js";
 | 
			
		||||
import UserLoginApi from "../../api/Rbac/UserLogin.js"
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 登录方法
 | 
			
		||||
 * @param {string} username 用户名(必填)
 | 
			
		||||
 * @param {string} password 密码(必填)
 | 
			
		||||
 * @param {string} captcha 验证码(非必填)
 | 
			
		||||
 * @param {string} checkKey 验证码key(非必填)
 | 
			
		||||
 * @returns {User} 用户类
 | 
			
		||||
 */
 | 
			
		||||
async function login({
 | 
			
		||||
  username,
 | 
			
		||||
  password,
 | 
			
		||||
  captcha,
 | 
			
		||||
  checkKey
 | 
			
		||||
}) {
 | 
			
		||||
  // ----------以下是测试数据----------
 | 
			
		||||
 | 
			
		||||
  const user = new User({
 | 
			
		||||
    id: 'a',
 | 
			
		||||
    token: 't-o-k-e-n',
 | 
			
		||||
    username: 'tom',
 | 
			
		||||
    realname: '张三',
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return user;
 | 
			
		||||
 | 
			
		||||
  // ----------以上是测试数据----------
 | 
			
		||||
 | 
			
		||||
  // const _user = await UserLoginApi.login({
 | 
			
		||||
  //   username,
 | 
			
		||||
  //   password,
 | 
			
		||||
  //   captcha,
 | 
			
		||||
  //   checkKey
 | 
			
		||||
  // });
 | 
			
		||||
 | 
			
		||||
  // const user = new User({
 | 
			
		||||
  //   id: _user.userInfo.id,
 | 
			
		||||
  //   token: _user.token,
 | 
			
		||||
  //   username: _user.userInfo.username,
 | 
			
		||||
  //   realname: _user.userInfo.realname,
 | 
			
		||||
  // });
 | 
			
		||||
 | 
			
		||||
  // return user;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 退出登录方法
 | 
			
		||||
 */
 | 
			
		||||
async function logout() {
 | 
			
		||||
  // ----------以下是测试数据----------
 | 
			
		||||
 | 
			
		||||
  return new CommonResult(true, '退出登录成功');
 | 
			
		||||
 | 
			
		||||
  // ----------以上是测试数据----------
 | 
			
		||||
 | 
			
		||||
  // const _result = await UserLoginApi.logout();
 | 
			
		||||
  // return new CommonResult(_result.success, _result.message);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 根据用户名称模糊查询(不传或传''可查所有用户)
 | 
			
		||||
 * @param {string} name 用户名
 | 
			
		||||
 * @returns {Array.<User>} 用户列表
 | 
			
		||||
 */
 | 
			
		||||
async function findByName(name = '') {
 | 
			
		||||
  return [{
 | 
			
		||||
      name: "李杰",
 | 
			
		||||
      department: "战略支持部",
 | 
			
		||||
      role: "系统管理员",
 | 
			
		||||
      status: "正常",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: "李杰",
 | 
			
		||||
      department: "战略支持部",
 | 
			
		||||
      role: "系统管理员",
 | 
			
		||||
      status: "正常",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: "李杰",
 | 
			
		||||
      department: "战略支持部",
 | 
			
		||||
      role: "系统管理员",
 | 
			
		||||
      status: "正常",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: "李杰",
 | 
			
		||||
      department: "战略支持部",
 | 
			
		||||
      role: "系统管理员",
 | 
			
		||||
      status: "异常",
 | 
			
		||||
    },
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 添加一个用户
 | 
			
		||||
 * @param {string} name 用户名
 | 
			
		||||
 * @param {string} password 密码(5-18位,可由数字、大小写字母组成)
 | 
			
		||||
 * @param {string} realName 真实姓名
 | 
			
		||||
 * @param {string} telephone 手机号
 | 
			
		||||
 * @param {string} email 邮箱号
 | 
			
		||||
 * @param {string} orgId 组织架构id(不传或传null则新建的用户不在组织架构内)
 | 
			
		||||
 * @returns {string} 添加成功后的用户的id
 | 
			
		||||
 */
 | 
			
		||||
async function add({
 | 
			
		||||
  name,
 | 
			
		||||
  password,
 | 
			
		||||
  realName,
 | 
			
		||||
  telephone,
 | 
			
		||||
  email,
 | 
			
		||||
  orgId
 | 
			
		||||
}) {
 | 
			
		||||
  return 'a';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 修改用户信息
 | 
			
		||||
 * @param {string} id 用户id
 | 
			
		||||
 * @param {string} name 用户名
 | 
			
		||||
 * @param {string} password 密码(5-18位,可由数字、大小写字母组成)
 | 
			
		||||
 * @param {string} realName 真实姓名
 | 
			
		||||
 * @param {string} telephone 手机号
 | 
			
		||||
 * @param {string} email 邮箱号
 | 
			
		||||
 * @returns 成功修改的个数
 | 
			
		||||
 */
 | 
			
		||||
async function update({
 | 
			
		||||
  id,
 | 
			
		||||
  name,
 | 
			
		||||
  softDelete,
 | 
			
		||||
  realName,
 | 
			
		||||
  telephone,
 | 
			
		||||
  email
 | 
			
		||||
}) {
 | 
			
		||||
  return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 通过id删除用户
 | 
			
		||||
 * @param {string} id 用户id
 | 
			
		||||
 * @returns 成功删除的个数
 | 
			
		||||
 */
 | 
			
		||||
async function delById(id) {
 | 
			
		||||
  return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 获取所有用户数量
 | 
			
		||||
 * @returns 用户数量
 | 
			
		||||
 */
 | 
			
		||||
async function countAll() {
 | 
			
		||||
  return 3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  login,
 | 
			
		||||
  logout,
 | 
			
		||||
  findByName,
 | 
			
		||||
  add,
 | 
			
		||||
  update,
 | 
			
		||||
  delById,
 | 
			
		||||
  countAll
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2020-06-03 10:38:23
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2020-06-03 10:40:17
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
--> 
 | 
			
		||||
 | 
			
		||||
如果一个常用业务需要用到多个api函数,建议在此封装  
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,218 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="header-inner" :class="arrangeMode">
 | 
			
		||||
    <div class="header-menu"></div>
 | 
			
		||||
 | 
			
		||||
    <div class="header-userinfo">
 | 
			
		||||
      <el-popover
 | 
			
		||||
        v-model="isUserInfoVisible"
 | 
			
		||||
        class="userinfo-popover"
 | 
			
		||||
        width="150"
 | 
			
		||||
        :placement="arrangeMode === 'horizontal' ? 'bottom' : 'right'"
 | 
			
		||||
        trigger="click"
 | 
			
		||||
      >
 | 
			
		||||
        <el-menu
 | 
			
		||||
          :default-active="headerActiveIndex"
 | 
			
		||||
          @select="handleUserMenuSelect"
 | 
			
		||||
        >
 | 
			
		||||
          <el-menu-item index="d">
 | 
			
		||||
            <i class="el-icon-warning-outline"></i>
 | 
			
		||||
            <span slot="title">系统数据</span>
 | 
			
		||||
          </el-menu-item>
 | 
			
		||||
          <el-menu-item index="a">
 | 
			
		||||
            <i class="el-icon-user"></i>
 | 
			
		||||
            <span slot="title">个人设置</span>
 | 
			
		||||
          </el-menu-item>
 | 
			
		||||
          <el-menu-item index="b" v-if="isAdmin">
 | 
			
		||||
            <i class="el-icon-setting"></i>
 | 
			
		||||
            <span slot="title">管理界面</span>
 | 
			
		||||
          </el-menu-item>
 | 
			
		||||
          <el-menu-item index="c">
 | 
			
		||||
            <i class="el-icon-switch-button"></i>
 | 
			
		||||
            <span slot="title">退出</span>
 | 
			
		||||
          </el-menu-item>
 | 
			
		||||
        </el-menu>
 | 
			
		||||
        <el-avatar
 | 
			
		||||
          class="main-avatar"
 | 
			
		||||
          slot="reference"
 | 
			
		||||
          src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
 | 
			
		||||
        ></el-avatar>
 | 
			
		||||
      </el-popover>
 | 
			
		||||
    </div>
 | 
			
		||||
    <!-- <el-dialog
 | 
			
		||||
      class="dial-sys-info"
 | 
			
		||||
      title="系统数据"
 | 
			
		||||
      :visible.sync="isSysDataDialVisible"
 | 
			
		||||
      width="50%"
 | 
			
		||||
      :before-close="onSysDataDialClose"
 | 
			
		||||
    >
 | 
			
		||||
      <SysInfo :token="currUserInfo.token" :cdmsSid="currUserInfo.cdmsSid" />
 | 
			
		||||
      <span slot="footer" class="dialog-footer">
 | 
			
		||||
        <el-button type="primary" @click="isSysDataDialVisible = false"
 | 
			
		||||
          >确 定</el-button
 | 
			
		||||
        >
 | 
			
		||||
      </span>
 | 
			
		||||
    </el-dialog> -->
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
import UserLoginApi from "../../api/Rbac/UserLogin.js";
 | 
			
		||||
import LoginInfo from "../../storage/login-info.js";
 | 
			
		||||
// import SysInfo from "./SysInfo.vue";
 | 
			
		||||
// import Menu from "./Menu.vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  components: {
 | 
			
		||||
    // SysInfo
 | 
			
		||||
  },
 | 
			
		||||
  props: {
 | 
			
		||||
    // 排列模式,横向还是竖向
 | 
			
		||||
    arrangeMode: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: "horizontal",
 | 
			
		||||
      validator: function (value) {
 | 
			
		||||
        return ["horizontal", "vertical"].indexOf(value) !== -1;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      isAdmin: false, // 是否管理员,默认不是管理员,在mounted事件里判断
 | 
			
		||||
      isUserInfoVisible: false, // 控制用户信息popover的显示与否
 | 
			
		||||
      isSysDataDialVisible: false, // 系统数据弹窗是否显示
 | 
			
		||||
      currUserInfo: {}, // 当前系统登录数据
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    headerActiveIndex: function () {
 | 
			
		||||
      return this.$route.meta.headerActiveIndex; // 当前页面应选中的头部菜单项的index
 | 
			
		||||
    },
 | 
			
		||||
    // menuDefaultActive: function () {
 | 
			
		||||
    //   let _routerName = this.$route.name;
 | 
			
		||||
    //   return this.mainMenuItems.findIndex(
 | 
			
		||||
    //     (item) => item.routerName === _routerName
 | 
			
		||||
    //   );
 | 
			
		||||
    // },
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    let userInfo = LoginInfo.getUserInfo();
 | 
			
		||||
    this.currUserInfo = userInfo ? userInfo : {};
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    // 处理导航菜单选择事件
 | 
			
		||||
    // handleHeaderMenuSelect(item, index) {
 | 
			
		||||
    //   let routerName;
 | 
			
		||||
    //   switch (index) {
 | 
			
		||||
    //     case 1:
 | 
			
		||||
    //       routerName = "Test";
 | 
			
		||||
    //       break;
 | 
			
		||||
    //     case 2:
 | 
			
		||||
    //       routerName = "Test2";
 | 
			
		||||
    //       break;
 | 
			
		||||
    //   }
 | 
			
		||||
    // },
 | 
			
		||||
 | 
			
		||||
    // 处理用户信息菜单选择事件
 | 
			
		||||
    async handleUserMenuSelect(key, keyPath) {
 | 
			
		||||
      keyPath;
 | 
			
		||||
      switch (key) {
 | 
			
		||||
        case "a": // 设置
 | 
			
		||||
          this.isUserInfoVisible = false;
 | 
			
		||||
          break;
 | 
			
		||||
        case "b": // 管理界面
 | 
			
		||||
          this.isUserInfoVisible = false;
 | 
			
		||||
          break;
 | 
			
		||||
        case "c": {
 | 
			
		||||
          try {
 | 
			
		||||
            await UserLoginApi.logout(); // 无论后台退出是否成功,前端都先行退出
 | 
			
		||||
          } catch (e) {
 | 
			
		||||
            console.log("后端退出登录失败");
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          // 退出登录
 | 
			
		||||
          LoginInfo.removeUser();
 | 
			
		||||
 | 
			
		||||
          let currRouteName = this.$route.name;
 | 
			
		||||
          if (currRouteName)
 | 
			
		||||
            this.$safePush({
 | 
			
		||||
              name: "Login",
 | 
			
		||||
              query: {
 | 
			
		||||
                routeName: currRouteName,
 | 
			
		||||
              },
 | 
			
		||||
            });
 | 
			
		||||
          else this.$safePush({ name: "Login" });
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        case "d":
 | 
			
		||||
          // 系统数据
 | 
			
		||||
          this.isSysDataDialVisible = true;
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 系统数据弹窗的关闭事件回调
 | 
			
		||||
    onSysDataDialClose(done) {
 | 
			
		||||
      done();
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
@import "~@/scss/_variables";
 | 
			
		||||
.header-inner {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  margin: 0 auto;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
 | 
			
		||||
  &.horizontal {
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
    .header-userinfo {
 | 
			
		||||
      flex-direction: row;
 | 
			
		||||
      margin-right: 36px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.vertical {
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    .header-userinfo {
 | 
			
		||||
      flex-direction: column;
 | 
			
		||||
      margin-bottom: 36px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .header-menu {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .header-userinfo {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: flex-end;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    .userinfo-popover {
 | 
			
		||||
      // 让头像垂直居中
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      .main-avatar {
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .dial-sys-info {
 | 
			
		||||
    ::v-deep .el-dialog {
 | 
			
		||||
      min-width: 530px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.el-menu {
 | 
			
		||||
  border-right: none; // 取消垂直导航菜单默认的右边框
 | 
			
		||||
  &.el-menu--horizontal {
 | 
			
		||||
    border-bottom: none; // 取消水平导航菜单默认的下边框
 | 
			
		||||
  }
 | 
			
		||||
  > .el-menu-item {
 | 
			
		||||
    height: 36px;
 | 
			
		||||
    line-height: 36px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,195 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-12-18 16:30:05
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-07 17:16:09
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
-->
 | 
			
		||||
<!--
 | 
			
		||||
props: 看下面注释
 | 
			
		||||
event: 
 | 
			
		||||
1.select index: 选中菜单项的index, item: 选中菜单项的MenuItem类型对象
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="menu" :class="arrangeMode">
 | 
			
		||||
    <div class="menu-item-area" v-for="(item, index) in menuItems" :key="index">
 | 
			
		||||
      <div
 | 
			
		||||
        class="menu-item"
 | 
			
		||||
        :class="[index === activeIndex ? 'active' : '']"
 | 
			
		||||
        @click="onItemClick(item, index)"
 | 
			
		||||
      >
 | 
			
		||||
        <el-tooltip
 | 
			
		||||
          v-if="displayMode === 'icon'"
 | 
			
		||||
          class="text-tip"
 | 
			
		||||
          effect="light"
 | 
			
		||||
          :content="item.title"
 | 
			
		||||
          :open-delay="750"
 | 
			
		||||
          :placement="arrangeMode === 'horizontal' ? 'bottom' : 'right'"
 | 
			
		||||
        >
 | 
			
		||||
          <i v-if="iconMode === 'elementUi'" :class="item.iconClass"></i>
 | 
			
		||||
          <img v-if="iconMode === 'img'" class="icon-img" :src="item.iconSrc" />
 | 
			
		||||
        </el-tooltip>
 | 
			
		||||
 | 
			
		||||
        <i
 | 
			
		||||
          v-if="iconMode === 'elementUi'"
 | 
			
		||||
          class="icon-ele"
 | 
			
		||||
          :class="item.iconClass"
 | 
			
		||||
        ></i>
 | 
			
		||||
        <img
 | 
			
		||||
          v-if="iconMode === 'img' && displayMode === 'both'"
 | 
			
		||||
          class="icon-img"
 | 
			
		||||
          :src="item.iconSrc"
 | 
			
		||||
        />
 | 
			
		||||
        <span v-if="displayMode !== 'icon'">{{ item.title }}</span>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="sub-items" v-if="item.children && item.children.length">
 | 
			
		||||
        <div
 | 
			
		||||
          class="sub-item"
 | 
			
		||||
          v-for="(item, index) in item.children"
 | 
			
		||||
          :key="index"
 | 
			
		||||
        >
 | 
			
		||||
          {{ item.title }}
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  props: {
 | 
			
		||||
    // 主菜单栏的信息数组
 | 
			
		||||
    menuItems: {
 | 
			
		||||
      type: Array,
 | 
			
		||||
      default: function () {
 | 
			
		||||
        return [];
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    // 排列模式,横向还是竖向
 | 
			
		||||
    arrangeMode: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: "horizontal",
 | 
			
		||||
      validator: function (value) {
 | 
			
		||||
        return ["horizontal", "vertical"].indexOf(value) !== -1;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    // 显示模式,只显示文字、只显示图标,还是都显示
 | 
			
		||||
    displayMode: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: "both",
 | 
			
		||||
      validator: function (value) {
 | 
			
		||||
        return ["text", "icon", "both"].indexOf(value) !== -1;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    // 图标来源,elementUi自带的图标、png jpg等格式图片、svg图标(暂不支持)
 | 
			
		||||
    iconMode: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: "elementUi",
 | 
			
		||||
      validator: function (value) {
 | 
			
		||||
        return ["elementUi", "img", "svg"].indexOf(value) !== -1;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    // 当前激活菜单的 index (支持.sync)
 | 
			
		||||
    defaultActive: { type: Number, default: 0 },
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    activeIndex: {
 | 
			
		||||
      get() {
 | 
			
		||||
        return this.activeIndex_;
 | 
			
		||||
      },
 | 
			
		||||
      set(val) {
 | 
			
		||||
        // this.$emit("update:defaultActive", val); // 暂时用不上
 | 
			
		||||
        this.activeIndex_ = val;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  watch: {
 | 
			
		||||
    defaultActive: function (val, oldVal) {
 | 
			
		||||
      this.activeIndex_ = val;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      activeIndex_: this.defaultActive,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {},
 | 
			
		||||
  methods: {
 | 
			
		||||
    onItemClick(item, index) {
 | 
			
		||||
      this.activeIndex = index;
 | 
			
		||||
      this.$emit("select", item, index);
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
@import "~@/scss/_variables";
 | 
			
		||||
.menu {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: flex-start;
 | 
			
		||||
  align-items: stretch;
 | 
			
		||||
  &.horizontal {
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
    .menu-item-area {
 | 
			
		||||
      .menu-item {
 | 
			
		||||
        flex-direction: row;
 | 
			
		||||
        border-bottom: solid 2px transparent;
 | 
			
		||||
        .icon-img {
 | 
			
		||||
          padding-right: 4px;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  &.vertical {
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    .menu-item-area {
 | 
			
		||||
      .menu-item {
 | 
			
		||||
        flex-direction: row;
 | 
			
		||||
        justify-content: flex-start;
 | 
			
		||||
        // padding-left: 48px;
 | 
			
		||||
        // border-right: solid 2px transparent;
 | 
			
		||||
        .icon-img,
 | 
			
		||||
        .icon-ele {
 | 
			
		||||
          padding-right: 8px;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  .menu-item-area {
 | 
			
		||||
    .menu-item {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      // justify-content: flex-start;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      cursor: pointer;
 | 
			
		||||
      color: $font-color-dark;
 | 
			
		||||
      padding: 8px;
 | 
			
		||||
      margin: 8px;
 | 
			
		||||
      border-radius: 4px;
 | 
			
		||||
      transition: border-color 0.3s, background-color 0.3s, color 0.3s;
 | 
			
		||||
 | 
			
		||||
      &.active {
 | 
			
		||||
        // border-bottom-color: $theme-main-color;
 | 
			
		||||
        // border-right-color: $theme-main-color;
 | 
			
		||||
        background-color: $theme-main-color;
 | 
			
		||||
        color: #fff;
 | 
			
		||||
      }
 | 
			
		||||
      .icon-img {
 | 
			
		||||
        width: 24px;
 | 
			
		||||
        height: 24px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    .sub-items {
 | 
			
		||||
      .sub-item {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        justify-content: flex-start;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
        padding: 8px;
 | 
			
		||||
        padding-left: 48px;
 | 
			
		||||
        margin: 8px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,96 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2022-01-04 11:46:31
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-06 17:40:39
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="menu">
 | 
			
		||||
    <el-menu
 | 
			
		||||
      v-if="menuItems && menuItems.length"
 | 
			
		||||
      :default-active="activeIndex"
 | 
			
		||||
      @select="handleSelect"
 | 
			
		||||
      @open="handleOpen"
 | 
			
		||||
      @close="handleClose"
 | 
			
		||||
    >
 | 
			
		||||
      <template v-for="(item, index) in menuItems">
 | 
			
		||||
        <el-submenu
 | 
			
		||||
          v-if="item.children && item.children.length"
 | 
			
		||||
          :index="item.routerName"
 | 
			
		||||
          :key="index"
 | 
			
		||||
        >
 | 
			
		||||
          <template slot="title">
 | 
			
		||||
            <i :class="item.iconClass"></i>
 | 
			
		||||
            <span>{{ item.title }}</span>
 | 
			
		||||
          </template>
 | 
			
		||||
          <template v-for="(subItem, index2) in item.children">
 | 
			
		||||
            <el-menu-item :index="subItem.routerName" :key="index2">
 | 
			
		||||
              {{ subItem.title }}
 | 
			
		||||
            </el-menu-item>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-submenu>
 | 
			
		||||
        <el-menu-item v-else :index="item.routerName" :key="index">
 | 
			
		||||
          <i :class="item.iconClass"></i>
 | 
			
		||||
          <span slot="title">{{ item.title }}</span>
 | 
			
		||||
        </el-menu-item>
 | 
			
		||||
      </template>
 | 
			
		||||
    </el-menu>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  props: {
 | 
			
		||||
    // 主菜单栏的信息数组
 | 
			
		||||
    menuItems: {
 | 
			
		||||
      type: Array,
 | 
			
		||||
      default: function () {
 | 
			
		||||
        return [];
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    // 当前激活菜单的 index (支持.sync)
 | 
			
		||||
    defaultActive: { type: String },
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    activeIndex: {
 | 
			
		||||
      get() {
 | 
			
		||||
        if (this.activeIndex_) return this.activeIndex_;
 | 
			
		||||
        else return this.menuItems[0].routerName;
 | 
			
		||||
      },
 | 
			
		||||
      set(val) {
 | 
			
		||||
        // this.$emit("update:defaultActive", val); // 暂时用不上
 | 
			
		||||
        this.activeIndex_ = val;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  watch: {
 | 
			
		||||
    defaultActive: function (val, oldVal) {
 | 
			
		||||
      this.activeIndex_ = val;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      activeIndex_: this.defaultActive,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    handleSelect(index, indexPath) {
 | 
			
		||||
      // console.log("index :>> ", index);
 | 
			
		||||
      // console.log("indexPath :>> ", indexPath);
 | 
			
		||||
      const routerName = index;
 | 
			
		||||
      this.$safePush({ name: routerName });
 | 
			
		||||
    },
 | 
			
		||||
    handleOpen(key, keyPath) {},
 | 
			
		||||
    handleClose(key, keyPath) {},
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.el-menu {
 | 
			
		||||
  border-right: none; // 取消垂直导航菜单默认的右边框
 | 
			
		||||
  &.el-menu--horizontal {
 | 
			
		||||
    border-bottom: none; // 取消水平导航菜单默认的下边框
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,294 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2022-01-05 17:30:05
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-10 21:21:34
 | 
			
		||||
 * @Description: 左边导航栏(主栏)
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="header-inner" :class="arrangeMode">
 | 
			
		||||
    <div class="header-menu">
 | 
			
		||||
      <div
 | 
			
		||||
        class="header-logo"
 | 
			
		||||
        :style="{
 | 
			
		||||
          width: `${HEADER_THICKNESS - 1}px`,
 | 
			
		||||
          height: `${HEADER_THICKNESS - 1}px`,
 | 
			
		||||
        }"
 | 
			
		||||
      >
 | 
			
		||||
        <el-image
 | 
			
		||||
          style="width: 100%; height: 100%"
 | 
			
		||||
          :src="logoSrc"
 | 
			
		||||
          :fit="'contain'"
 | 
			
		||||
        ></el-image>
 | 
			
		||||
      </div>
 | 
			
		||||
      <Menu
 | 
			
		||||
        :menuItems="mainMenuItems"
 | 
			
		||||
        :arrangeMode="arrangeMode"
 | 
			
		||||
        :displayMode="'both'"
 | 
			
		||||
        :defaultActive="menuDefaultActive"
 | 
			
		||||
        @select="handleHeaderMenuSelect"
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="header-userinfo">
 | 
			
		||||
      <el-popover
 | 
			
		||||
        v-model="isUserInfoVisible"
 | 
			
		||||
        class="userinfo-popover"
 | 
			
		||||
        width="150"
 | 
			
		||||
        :placement="arrangeMode === 'horizontal' ? 'bottom' : 'right'"
 | 
			
		||||
        trigger="click"
 | 
			
		||||
      >
 | 
			
		||||
        <el-menu
 | 
			
		||||
          :default-active="headerActiveIndex"
 | 
			
		||||
          @select="handleUserMenuSelect"
 | 
			
		||||
        >
 | 
			
		||||
          <el-menu-item index="e">
 | 
			
		||||
            <i class="el-icon-setting"></i>
 | 
			
		||||
            <span slot="title">后台管理</span>
 | 
			
		||||
          </el-menu-item>
 | 
			
		||||
          <el-menu-item index="d">
 | 
			
		||||
            <i class="el-icon-warning-outline"></i>
 | 
			
		||||
            <span slot="title">系统数据</span>
 | 
			
		||||
          </el-menu-item>
 | 
			
		||||
          <el-menu-item index="a">
 | 
			
		||||
            <i class="el-icon-user"></i>
 | 
			
		||||
            <span slot="title">个人设置</span>
 | 
			
		||||
          </el-menu-item>
 | 
			
		||||
          <el-menu-item index="b" v-if="isAdmin">
 | 
			
		||||
            <i class="el-icon-setting"></i>
 | 
			
		||||
            <span slot="title">管理界面</span>
 | 
			
		||||
          </el-menu-item>
 | 
			
		||||
          <el-menu-item index="c">
 | 
			
		||||
            <i class="el-icon-switch-button"></i>
 | 
			
		||||
            <span slot="title">退出</span>
 | 
			
		||||
          </el-menu-item>
 | 
			
		||||
        </el-menu>
 | 
			
		||||
        <el-avatar
 | 
			
		||||
          class="main-avatar"
 | 
			
		||||
          slot="reference"
 | 
			
		||||
          src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
 | 
			
		||||
        ></el-avatar>
 | 
			
		||||
      </el-popover>
 | 
			
		||||
    </div>
 | 
			
		||||
    <el-dialog
 | 
			
		||||
      class="dial-sys-info"
 | 
			
		||||
      title="系统数据"
 | 
			
		||||
      :visible.sync="isSysDataDialVisible"
 | 
			
		||||
      width="50%"
 | 
			
		||||
      :before-close="onSysDataDialClose"
 | 
			
		||||
    >
 | 
			
		||||
      <SysInfo :token="currUserInfo.token" :cdmsSid="currUserInfo.cdmsSid" />
 | 
			
		||||
      <span slot="footer" class="dialog-footer">
 | 
			
		||||
        <el-button type="primary" @click="isSysDataDialVisible = false"
 | 
			
		||||
          >确 定</el-button
 | 
			
		||||
        >
 | 
			
		||||
      </span>
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
import Qs from "qs";
 | 
			
		||||
import MenuBiz from "../../biz/Menu.js";
 | 
			
		||||
import UserBiz from "../../biz/Rbac/User.js";
 | 
			
		||||
import LoginInfo from "../../storage/login-info.js";
 | 
			
		||||
import SysInfo from "./SysInfo.vue";
 | 
			
		||||
import Menu from "./Menu.vue";
 | 
			
		||||
import { HEADER_THICKNESS, PROJECT_NAME } from "../../const.js";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  components: { SysInfo, Menu },
 | 
			
		||||
  props: {
 | 
			
		||||
    // 排列模式,横向还是竖向
 | 
			
		||||
    arrangeMode: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: "horizontal",
 | 
			
		||||
      validator: function (value) {
 | 
			
		||||
        return ["horizontal", "vertical"].indexOf(value) !== -1;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    menuDefaultActive: {
 | 
			
		||||
      type: String,
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      HEADER_THICKNESS,
 | 
			
		||||
      logoSrc: require("../../assets/logos/i3v-logo-single-flat.png"),
 | 
			
		||||
      isAdmin: false, // 是否管理员,默认不是管理员,在mounted事件里判断
 | 
			
		||||
      isUserInfoVisible: false, // 控制用户信息popover的显示与否
 | 
			
		||||
      isSysDataDialVisible: false, // 系统数据弹窗是否显示
 | 
			
		||||
      currUserInfo: {}, // 当前系统登录数据
 | 
			
		||||
      mainMenuItems: [], // 主菜单栏的信息数组
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    // 头像popover
 | 
			
		||||
    headerActiveIndex: function () {
 | 
			
		||||
      return this.$route.meta.headerActiveIndex; // 当前页面应选中的头部菜单项的index
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  async beforeMount() {
 | 
			
		||||
    this.mainMenuItems = await MenuBiz.getMainMenuItems();
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    let userInfo = LoginInfo.getUserInfo();
 | 
			
		||||
    this.currUserInfo = userInfo ? userInfo : {};
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    // 处理导航菜单选择事件
 | 
			
		||||
    handleHeaderMenuSelect(item, index) {
 | 
			
		||||
      let routerName;
 | 
			
		||||
      routerName = item.routerName;
 | 
			
		||||
      if (routerName) {
 | 
			
		||||
        console.log("routerName :>> ", routerName);
 | 
			
		||||
        this.$safePush({ name: routerName });
 | 
			
		||||
        // this.$safePush(
 | 
			
		||||
        //   {
 | 
			
		||||
        //     name: "Main",
 | 
			
		||||
        //     params: { routerName },
 | 
			
		||||
        //   },
 | 
			
		||||
        //   (success) => {
 | 
			
		||||
        //     document.title = PROJECT_NAME + "-" + item.title;
 | 
			
		||||
        //   }
 | 
			
		||||
        // );
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 处理用户信息菜单选择事件
 | 
			
		||||
    async handleUserMenuSelect(key, keyPath) {
 | 
			
		||||
      keyPath;
 | 
			
		||||
      switch (key) {
 | 
			
		||||
        case "a": // 设置
 | 
			
		||||
          this.isUserInfoVisible = false;
 | 
			
		||||
          break;
 | 
			
		||||
        case "b": // 管理界面
 | 
			
		||||
          this.isUserInfoVisible = false;
 | 
			
		||||
          break;
 | 
			
		||||
        case "c": {
 | 
			
		||||
          try {
 | 
			
		||||
            await UserBiz.logout(); // 无论后台退出是否成功,前端都先行退出
 | 
			
		||||
          } catch (e) {
 | 
			
		||||
            console.log("后端退出登录失败");
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          // 退出登录
 | 
			
		||||
          LoginInfo.removeUser();
 | 
			
		||||
 | 
			
		||||
          const currRouteName = this.$route.name;
 | 
			
		||||
          const paramsString = Qs.stringify(this.$route.params, {
 | 
			
		||||
            encode: false,
 | 
			
		||||
          });
 | 
			
		||||
 | 
			
		||||
          if (currRouteName)
 | 
			
		||||
            this.$safePush({
 | 
			
		||||
              name: "Login",
 | 
			
		||||
              query: {
 | 
			
		||||
                routeName: currRouteName,
 | 
			
		||||
                paramsString: paramsString ? paramsString : undefined,
 | 
			
		||||
              },
 | 
			
		||||
            });
 | 
			
		||||
          else this.$safePush({ name: "Login" });
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        case "d":
 | 
			
		||||
          // 系统数据
 | 
			
		||||
          this.isSysDataDialVisible = true;
 | 
			
		||||
          break;
 | 
			
		||||
        case "e": {
 | 
			
		||||
          // 后台管理
 | 
			
		||||
          const routeUrl = this.$router.resolve({
 | 
			
		||||
            name: "Org",
 | 
			
		||||
          });
 | 
			
		||||
          window.open(routeUrl.href, "_blank");
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 系统数据弹窗的关闭事件回调
 | 
			
		||||
    onSysDataDialClose(done) {
 | 
			
		||||
      done();
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
@import "~@/scss/_variables";
 | 
			
		||||
.header-inner {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  margin: 0 auto;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
 | 
			
		||||
  .header-menu {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: flex-start;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.horizontal {
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
    .header-menu {
 | 
			
		||||
      flex-direction: row;
 | 
			
		||||
      .header-logo {
 | 
			
		||||
        box-sizing: border-box;
 | 
			
		||||
        padding: 4px;
 | 
			
		||||
        margin-right: 16px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    .header-userinfo {
 | 
			
		||||
      flex-direction: row;
 | 
			
		||||
      margin-right: 36px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.vertical {
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    .header-menu {
 | 
			
		||||
      flex-direction: column;
 | 
			
		||||
      .header-logo {
 | 
			
		||||
        box-sizing: border-box;
 | 
			
		||||
        padding: 4px;
 | 
			
		||||
        margin-bottom: 16px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    .header-userinfo {
 | 
			
		||||
      flex-direction: column;
 | 
			
		||||
      margin-bottom: 36px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .header-userinfo {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: flex-end;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    .userinfo-popover {
 | 
			
		||||
      // 让头像垂直居中
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      .main-avatar {
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .dial-sys-info {
 | 
			
		||||
    ::v-deep .el-dialog {
 | 
			
		||||
      min-width: 530px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.el-menu {
 | 
			
		||||
  border-right: none; // 取消垂直导航菜单默认的右边框
 | 
			
		||||
  &.el-menu--horizontal {
 | 
			
		||||
    border-bottom: none; // 取消水平导航菜单默认的下边框
 | 
			
		||||
  }
 | 
			
		||||
  > .el-menu-item {
 | 
			
		||||
    height: 36px;
 | 
			
		||||
    line-height: 36px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,113 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2022-01-05 17:30:05
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-07 16:57:02
 | 
			
		||||
 * @Description: 右边导航栏(副栏)
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="header-inner" :class="arrangeMode">
 | 
			
		||||
    <div class="header-menu">
 | 
			
		||||
      <Menu
 | 
			
		||||
        :menuItems="secondaryMenuItems"
 | 
			
		||||
        :arrangeMode="arrangeMode"
 | 
			
		||||
        :displayMode="'icon'"
 | 
			
		||||
        :defaultActive="menuDefaultActive"
 | 
			
		||||
        :hasActiveEffect="true"
 | 
			
		||||
        @select="handleHeaderMenuSelect"
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
import MenuBiz from "../../biz/Menu.js";
 | 
			
		||||
import Menu from "./Menu.vue";
 | 
			
		||||
import { HIDE_DRAWER_WHEN_PUSH } from "../../const.js";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  components: { Menu },
 | 
			
		||||
  props: {
 | 
			
		||||
    // 排列模式,横向还是竖向
 | 
			
		||||
    arrangeMode: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: "horizontal",
 | 
			
		||||
      validator: function (value) {
 | 
			
		||||
        return ["horizontal", "vertical"].indexOf(value) !== -1;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    menuDefaultActive: {
 | 
			
		||||
      type: String,
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      secondaryMenuItems: [], // 主菜单栏的信息数组
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    // menuDefaultActive: function () {
 | 
			
		||||
    //   // let _routerName = this.$route.name;
 | 
			
		||||
    //   // return this.secondaryMenuItems.findIndex(
 | 
			
		||||
    //   //   (item) => item.routerName === _routerName
 | 
			
		||||
    //   // );
 | 
			
		||||
    //   const _routerName = this.$route.name;
 | 
			
		||||
    //   const item = this.secondaryMenuItems.find(
 | 
			
		||||
    //     (item) => item.routerName === _routerName
 | 
			
		||||
    //   );
 | 
			
		||||
    //   return item ? item.id : "";
 | 
			
		||||
    // },
 | 
			
		||||
  },
 | 
			
		||||
  async beforeMount() {
 | 
			
		||||
    this.secondaryMenuItems = await MenuBiz.getSecondaryMenuItems();
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {},
 | 
			
		||||
  methods: {
 | 
			
		||||
    // 处理导航菜单选择事件
 | 
			
		||||
    handleHeaderMenuSelect(item, index) {
 | 
			
		||||
      let routerName, drawerName;
 | 
			
		||||
      routerName = item.routerName;
 | 
			
		||||
      drawerName = item.drawerName;
 | 
			
		||||
      if (routerName) {
 | 
			
		||||
        // this.$safePush({ name: routerName });
 | 
			
		||||
 | 
			
		||||
        let drawerName;
 | 
			
		||||
        if (!HIDE_DRAWER_WHEN_PUSH) {
 | 
			
		||||
          drawerName = this.$route.query.drawerName;
 | 
			
		||||
        }
 | 
			
		||||
        this.$safePush({
 | 
			
		||||
          name: "Main",
 | 
			
		||||
          params: { routerName },
 | 
			
		||||
          query: { drawerName },
 | 
			
		||||
        });
 | 
			
		||||
      } else if (drawerName) {
 | 
			
		||||
        this.$safePush({
 | 
			
		||||
          name: "Main",
 | 
			
		||||
          query: { drawerName },
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
@import "~@/scss/_variables";
 | 
			
		||||
.header-inner {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  margin: 0 auto;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
 | 
			
		||||
  &.horizontal {
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.vertical {
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .header-menu {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,186 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-12-18 16:30:05
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-10 21:19:25
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
-->
 | 
			
		||||
<!--
 | 
			
		||||
props: 看下面注释
 | 
			
		||||
event: 
 | 
			
		||||
1.select index: 选中菜单项的index, item: 选中菜单项的MenuItem类型对象
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div
 | 
			
		||||
    class="menu"
 | 
			
		||||
    :class="[arrangeMode, { 'has-active-effect': hasActiveEffect }]"
 | 
			
		||||
  >
 | 
			
		||||
    <div
 | 
			
		||||
      class="menu-item"
 | 
			
		||||
      :class="{ active: item.id === activeId }"
 | 
			
		||||
      v-for="(item, index) in menuItems"
 | 
			
		||||
      :key="index"
 | 
			
		||||
      @click="onItemClick(item, index)"
 | 
			
		||||
    >
 | 
			
		||||
      <el-tooltip
 | 
			
		||||
        v-if="displayMode === 'icon'"
 | 
			
		||||
        class="text-tip"
 | 
			
		||||
        effect="light"
 | 
			
		||||
        :content="item.title"
 | 
			
		||||
        :open-delay="750"
 | 
			
		||||
        :placement="arrangeMode === 'horizontal' ? 'bottom' : 'right'"
 | 
			
		||||
      >
 | 
			
		||||
        <img
 | 
			
		||||
          class="icon-img"
 | 
			
		||||
          :src="
 | 
			
		||||
            item.id !== activeId && item.iconSrcInactive
 | 
			
		||||
              ? item.iconSrcInactive
 | 
			
		||||
              : item.iconSrc
 | 
			
		||||
          "
 | 
			
		||||
          alt="无图标"
 | 
			
		||||
        />
 | 
			
		||||
      </el-tooltip>
 | 
			
		||||
 | 
			
		||||
      <img
 | 
			
		||||
        v-if="displayMode === 'both'"
 | 
			
		||||
        class="icon-img before-title"
 | 
			
		||||
        :src="
 | 
			
		||||
          item.id !== activeId && item.iconSrcInactive
 | 
			
		||||
            ? item.iconSrcInactive
 | 
			
		||||
            : item.iconSrc
 | 
			
		||||
        "
 | 
			
		||||
        alt="无图标"
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      <span class="title-text" v-if="displayMode !== 'icon'">{{
 | 
			
		||||
        item.title
 | 
			
		||||
      }}</span>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  props: {
 | 
			
		||||
    // 主菜单栏的信息数组
 | 
			
		||||
    menuItems: {
 | 
			
		||||
      type: Array,
 | 
			
		||||
      default: function () {
 | 
			
		||||
        return [];
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    // 排列模式,横向还是竖向
 | 
			
		||||
    arrangeMode: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: "horizontal",
 | 
			
		||||
      validator: function (value) {
 | 
			
		||||
        return ["horizontal", "vertical"].indexOf(value) !== -1;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    // 显示模式,只显示文字、只显示图标,还是都显示
 | 
			
		||||
    displayMode: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: "both",
 | 
			
		||||
      validator: function (value) {
 | 
			
		||||
        return ["text", "icon", "both"].indexOf(value) !== -1;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    // 是否有选中后的效果(如底色变深/字体变色)
 | 
			
		||||
    hasActiveEffect: {
 | 
			
		||||
      type: Boolean,
 | 
			
		||||
      default: true,
 | 
			
		||||
    },
 | 
			
		||||
    // 当前激活菜单的 index (支持.sync)
 | 
			
		||||
    defaultActive: { type: String },
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    activeId: {
 | 
			
		||||
      get() {
 | 
			
		||||
        return this.activeId_;
 | 
			
		||||
      },
 | 
			
		||||
      set(val) {
 | 
			
		||||
        // this.$emit("update:defaultActive", val); // 暂时用不上
 | 
			
		||||
        this.activeId_ = val;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  watch: {
 | 
			
		||||
    defaultActive: function (val, oldVal) {
 | 
			
		||||
      this.activeId_ = val;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      activeId_: this.defaultActive,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {},
 | 
			
		||||
  methods: {
 | 
			
		||||
    onItemClick(item, index) {
 | 
			
		||||
      this.activeId = item.id;
 | 
			
		||||
      this.$emit("select", item, index);
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
@import "~@/scss/_variables";
 | 
			
		||||
.menu {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: flex-start;
 | 
			
		||||
  align-items: stretch;
 | 
			
		||||
  &.horizontal {
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
    .menu-item {
 | 
			
		||||
      flex-direction: row;
 | 
			
		||||
      padding: 0 16px;
 | 
			
		||||
      // border-bottom: solid 2px transparent;
 | 
			
		||||
      .icon-img {
 | 
			
		||||
        &.before-title {
 | 
			
		||||
          padding-right: 4px;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  &.vertical {
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    .menu-item {
 | 
			
		||||
      flex-direction: column;
 | 
			
		||||
      padding: 16px 0;
 | 
			
		||||
      // border-right: solid 2px transparent;
 | 
			
		||||
      .icon-img {
 | 
			
		||||
        &.before-title {
 | 
			
		||||
          padding-bottom: 4px;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  .menu-item {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    color: $font-color-light;
 | 
			
		||||
    transition: border-color 0.3s, background-color 0.3s, color 0.3s;
 | 
			
		||||
 | 
			
		||||
    .icon-img {
 | 
			
		||||
      width: 24px;
 | 
			
		||||
      height: 24px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.has-active-effect {
 | 
			
		||||
    .menu-item {
 | 
			
		||||
      &.active {
 | 
			
		||||
        // border-bottom-color: $theme-main-color;
 | 
			
		||||
        // border-right-color: $theme-main-color;
 | 
			
		||||
        // background-color: $theme-background-dark;
 | 
			
		||||
        .title-text {
 | 
			
		||||
          color: $theme-main-color;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,39 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-07-31 00:45:24
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2021-09-22 23:13:30
 | 
			
		||||
 * @Description: 系统数据(方便开发调试)
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="container">
 | 
			
		||||
    <div class="item">
 | 
			
		||||
      <div class="title">TOKEN</div>
 | 
			
		||||
      <el-input readonly type="textarea" :rows="3" :value="token"></el-input>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="item">
 | 
			
		||||
      <div class="title">CDMS SID</div>
 | 
			
		||||
      <el-input readonly :value="cdmsSid" size="small"></el-input>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  props: {
 | 
			
		||||
    token: { type: String },
 | 
			
		||||
    cdmsSid: { type: String },
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {};
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.item {
 | 
			
		||||
  margin-bottom: 10px;
 | 
			
		||||
  .title {
 | 
			
		||||
    padding-left: 5px;
 | 
			
		||||
    margin-bottom: 5px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2022-01-06 19:05:20
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-06 19:05:20
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div></div>
 | 
			
		||||
</template>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,99 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-08-31 15:26:21
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-04 17:22:09
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="container">
 | 
			
		||||
    <el-form ref="form" :model="form" :rules="orgRules" label-width="100px">
 | 
			
		||||
      <el-form-item label="上级部门名称">
 | 
			
		||||
        <el-input :value="org.name" readonly></el-input>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="部门名称" prop="name">
 | 
			
		||||
        <el-input v-model="form.name"></el-input>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item class="btns">
 | 
			
		||||
        <el-button @click="onCancel">{{ btnCancelName }}</el-button>
 | 
			
		||||
        <el-button type="primary" @click="onSubmit">确定</el-button>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
    </el-form>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
import OrganizationBiz from "../../../biz/Rbac/Organization.js";
 | 
			
		||||
export default {
 | 
			
		||||
  props: {
 | 
			
		||||
    org: { type: Object, required: true }, // 当前的目标上级部门
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      form: {
 | 
			
		||||
        // id: 0,
 | 
			
		||||
        name: "",
 | 
			
		||||
      },
 | 
			
		||||
      orgRules: {
 | 
			
		||||
        name: [{ required: true, message: "请填写部门名称", trigger: "none" }],
 | 
			
		||||
      },
 | 
			
		||||
      btnCancelName: "取消",
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  updated() {},
 | 
			
		||||
  methods: {
 | 
			
		||||
    onSubmit() {
 | 
			
		||||
      this.$refs["form"].validate(async (valid) => {
 | 
			
		||||
        if (valid) {
 | 
			
		||||
          this.$confirm(
 | 
			
		||||
            `确定把在部门 ${this.org.name} 下新增 ${this.form.name} 子部门?`,
 | 
			
		||||
            {
 | 
			
		||||
              title: "确认新增",
 | 
			
		||||
              showCancelButton: true,
 | 
			
		||||
              showConfirmButton: true,
 | 
			
		||||
            }
 | 
			
		||||
          )
 | 
			
		||||
            .then(async () => {
 | 
			
		||||
              this.form.name = this.form.name.trim();
 | 
			
		||||
              try {
 | 
			
		||||
                let newOrg = await OrganizationBiz.add(
 | 
			
		||||
                  this.org.id < 0 ? null : this.org.id,
 | 
			
		||||
                  this.form.name
 | 
			
		||||
                );
 | 
			
		||||
                if (newOrg) {
 | 
			
		||||
                  this.$message.success(
 | 
			
		||||
                    `${this.org.name} 成功增加下级 ${this.form.name}`
 | 
			
		||||
                  );
 | 
			
		||||
                  this.btnCancelName = "退出";
 | 
			
		||||
                  this.$emit("success", this.org, newOrg);
 | 
			
		||||
                }
 | 
			
		||||
              } catch (e) {
 | 
			
		||||
                this.$message.error(e.message);
 | 
			
		||||
              }
 | 
			
		||||
            })
 | 
			
		||||
            .catch((e) => {});
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onCancel() {
 | 
			
		||||
      this.$emit("cancel");
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 供外部调用,更新本组件的一些显示
 | 
			
		||||
    updateForm() {
 | 
			
		||||
      this.$refs["form"].clearValidate();
 | 
			
		||||
      this.btnCancelName = "取消";
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.el-form-item.btns {
 | 
			
		||||
  padding-top: 20px;
 | 
			
		||||
  margin-bottom: 0;
 | 
			
		||||
  ::v-deep .el-form-item__content {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: flex-end;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,105 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-08-29 02:03:41
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-04 17:22:26
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="container">
 | 
			
		||||
    <el-form ref="form" :model="form" label-width="90px">
 | 
			
		||||
      <el-form-item label="原部门名称">
 | 
			
		||||
        <el-input :value="org.name" readonly></el-input>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="新部门名称">
 | 
			
		||||
        <el-input v-model="form.name"></el-input>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item class="btns">
 | 
			
		||||
        <el-button @click="onCancel">{{ btnCancelName }}</el-button>
 | 
			
		||||
        <el-button type="primary" @click="onSubmit">确定</el-button>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
    </el-form>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
import OrganizationBiz from "../../../biz/Rbac/Organization.js";
 | 
			
		||||
export default {
 | 
			
		||||
  props: {
 | 
			
		||||
    org: { type: Object, required: true }, // 当前编辑的部门
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      form: {
 | 
			
		||||
        // id: 0,
 | 
			
		||||
        name: "",
 | 
			
		||||
      },
 | 
			
		||||
      btnCancelName: "取消",
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  updated() {},
 | 
			
		||||
  watch: {
 | 
			
		||||
    org: {
 | 
			
		||||
      handler: function (newVal, oldVal) {
 | 
			
		||||
        // this.form.id = newVal.id;
 | 
			
		||||
        this.form.name = newVal.name;
 | 
			
		||||
      },
 | 
			
		||||
      immediate: true,
 | 
			
		||||
      deep: true,
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    onSubmit() {
 | 
			
		||||
      if (this.form.name.trim() === this.org.name) {
 | 
			
		||||
        this.$message.warning("新旧部门名称相同");
 | 
			
		||||
      } else {
 | 
			
		||||
        this.$confirm(
 | 
			
		||||
          `确定把部门名称从 ${this.org.name} 修改为 ${this.form.name}?`,
 | 
			
		||||
          {
 | 
			
		||||
            title: "确认修改",
 | 
			
		||||
            showCancelButton: true,
 | 
			
		||||
            showConfirmButton: true,
 | 
			
		||||
          }
 | 
			
		||||
        )
 | 
			
		||||
          .then(async () => {
 | 
			
		||||
            this.form.name = this.form.name.trim();
 | 
			
		||||
            try {
 | 
			
		||||
              let count = await OrganizationBiz.update(
 | 
			
		||||
                this.org.id < 0 ? null : this.org.id,
 | 
			
		||||
                this.form.name
 | 
			
		||||
              );
 | 
			
		||||
              if (count) {
 | 
			
		||||
                this.$message.success(
 | 
			
		||||
                  `${this.org.name} 成功修改为 ${this.form.name}`
 | 
			
		||||
                );
 | 
			
		||||
                this.btnCancelName = "退出";
 | 
			
		||||
                this.org.name = this.form.name; // 更新一下组织架构树状节点
 | 
			
		||||
              }
 | 
			
		||||
            } catch (e) {
 | 
			
		||||
              this.$message.error(e.message);
 | 
			
		||||
            }
 | 
			
		||||
          })
 | 
			
		||||
          .catch((e) => {});
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onCancel() {
 | 
			
		||||
      this.$emit("cancel");
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 供外部调用,更新本组件的一些显示
 | 
			
		||||
    updateForm() {
 | 
			
		||||
      this.btnCancelName = "取消";
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.el-form-item.btns {
 | 
			
		||||
  padding-top: 20px;
 | 
			
		||||
  margin-bottom: 0;
 | 
			
		||||
  ::v-deep .el-form-item__content {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: flex-end;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,161 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-08-29 02:03:27
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-04 17:23:42
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="container">
 | 
			
		||||
    <LightSplit :ratios="[1, 1]" :minHeight="300">
 | 
			
		||||
      <template v-slot:1>
 | 
			
		||||
        <div class="tree-container">
 | 
			
		||||
          <el-tree
 | 
			
		||||
            ref="org-tree"
 | 
			
		||||
            :data="treeData"
 | 
			
		||||
            :node-key="'id'"
 | 
			
		||||
            :props="treeProps"
 | 
			
		||||
            :show-checkbox="false"
 | 
			
		||||
            :highlight-current="true"
 | 
			
		||||
            :expand-on-click-node="true"
 | 
			
		||||
            :default-expanded-keys="defaultExpandedKeys"
 | 
			
		||||
            @node-click="onTreeNodeClick"
 | 
			
		||||
            @current-change="onOrgTreeCurrChange"
 | 
			
		||||
          ></el-tree>
 | 
			
		||||
        </div>
 | 
			
		||||
      </template>
 | 
			
		||||
      <template v-slot:2>
 | 
			
		||||
        <el-form
 | 
			
		||||
          ref="form"
 | 
			
		||||
          :model="form"
 | 
			
		||||
          :label-position="'top'"
 | 
			
		||||
          :rules="orgRules"
 | 
			
		||||
        >
 | 
			
		||||
          <el-form-item label="目标上级部门名称" prop="parentName">
 | 
			
		||||
            <el-input
 | 
			
		||||
              :value="form.parentName"
 | 
			
		||||
              placeholder="请在左侧选择"
 | 
			
		||||
              readonly
 | 
			
		||||
            ></el-input>
 | 
			
		||||
          </el-form-item>
 | 
			
		||||
          <el-form-item label="正在移动部门名称">
 | 
			
		||||
            <el-input :value="org.name" readonly></el-input>
 | 
			
		||||
          </el-form-item>
 | 
			
		||||
          <el-form-item class="btns">
 | 
			
		||||
            <el-button @click="onCancel">{{ btnCancelName }}</el-button>
 | 
			
		||||
            <el-button type="primary" @click="onSubmit">确定</el-button>
 | 
			
		||||
          </el-form-item>
 | 
			
		||||
        </el-form>
 | 
			
		||||
      </template>
 | 
			
		||||
    </LightSplit>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
import OrganizationBiz from "../../../biz/Rbac/Organization.js";
 | 
			
		||||
import LightSplit from "../../../components/_Common/LightSplit.vue";
 | 
			
		||||
export default {
 | 
			
		||||
  components: { LightSplit },
 | 
			
		||||
  props: {
 | 
			
		||||
    treeData: { type: Array },
 | 
			
		||||
    org: { type: Object, required: true }, // 当前移动的部门
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      form: {
 | 
			
		||||
        parentId: 0,
 | 
			
		||||
        parentName: "",
 | 
			
		||||
        targetParentData: null,
 | 
			
		||||
      },
 | 
			
		||||
      orgRules: {
 | 
			
		||||
        parentName: [
 | 
			
		||||
          {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: "请选择目标上级部门",
 | 
			
		||||
            trigger: "none",
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
      defaultExpandedKeys: [null],
 | 
			
		||||
      treeProps: {
 | 
			
		||||
        label: "name",
 | 
			
		||||
        children: "Children",
 | 
			
		||||
      },
 | 
			
		||||
      btnCancelName: "取消",
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    onSubmit() {
 | 
			
		||||
      this.$refs["form"].validate(async (valid) => {
 | 
			
		||||
        if (valid) {
 | 
			
		||||
          if (this.org.id === this.form.parentId) {
 | 
			
		||||
            this.$message.warning(`部门不能移动到自身内`);
 | 
			
		||||
          } else {
 | 
			
		||||
            this.$confirm(
 | 
			
		||||
              `确定把在部门 ${this.org.name} 移动到 ${this.form.parentName} 部门下面?`,
 | 
			
		||||
              {
 | 
			
		||||
                title: "确认新增",
 | 
			
		||||
                showCancelButton: true,
 | 
			
		||||
                showConfirmButton: true,
 | 
			
		||||
              }
 | 
			
		||||
            )
 | 
			
		||||
              .then(async () => {
 | 
			
		||||
                try {
 | 
			
		||||
                  let org = OrganizationBiz.move(
 | 
			
		||||
                    this.org.id < 0 ? null : this.org.id,
 | 
			
		||||
                    this.form.parentId !== null && this.form.parentId < 1 // 由于需要维护树状控件,树状控件不支持节点对象的id为null,故根节点id都设置为-1
 | 
			
		||||
                      ? null
 | 
			
		||||
                      : this.form.parentId
 | 
			
		||||
                  );
 | 
			
		||||
                  if (org) {
 | 
			
		||||
                    this.$message.success(
 | 
			
		||||
                      `${this.org.name} 移动到 ${this.form.parentName} 部门下`
 | 
			
		||||
                    );
 | 
			
		||||
                    this.btnCancelName = "退出";
 | 
			
		||||
                    this.$emit("success", this.org, this.form.targetParentData);
 | 
			
		||||
                  }
 | 
			
		||||
                } catch (e) {
 | 
			
		||||
                  this.$message.error(e.message);
 | 
			
		||||
                }
 | 
			
		||||
              })
 | 
			
		||||
              .catch((e) => {});
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onCancel() {
 | 
			
		||||
      this.$emit("cancel");
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onTreeNodeClick() {},
 | 
			
		||||
    // 当组织架构树被改变选中时
 | 
			
		||||
    onOrgTreeCurrChange(data, node) {
 | 
			
		||||
      this.form.parentId = data.id;
 | 
			
		||||
      this.form.parentName = data.name;
 | 
			
		||||
      this.form.targetParentData = data;
 | 
			
		||||
      // this.$refs["form"].clearValidate();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 供外部调用,更新本组件的一些显示
 | 
			
		||||
    updateForm() {
 | 
			
		||||
      this.$refs["form"].clearValidate();
 | 
			
		||||
      this.btnCancelName = "取消";
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.tree-container {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  max-height: 600px;
 | 
			
		||||
  overflow-y: auto;
 | 
			
		||||
}
 | 
			
		||||
.el-form-item.btns {
 | 
			
		||||
  padding-top: 20px;
 | 
			
		||||
  margin-bottom: 0;
 | 
			
		||||
  ::v-deep .el-form-item__content {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: flex-end;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,80 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Guanghao
 | 
			
		||||
 * @Date: 2022-01-05 11:53:19
 | 
			
		||||
 * @LastEditors: Guanghao
 | 
			
		||||
 * @LastEditTime: 2022-01-05 18:21:24
 | 
			
		||||
 * @Description: 角色新建框
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="container">
 | 
			
		||||
    <el-form ref="form" :model="form" :rules="rules" label-width="100px">
 | 
			
		||||
      <el-form-item label="角色名称" prop="name">
 | 
			
		||||
        <el-input v-model="form.name" placeholder="请输入" />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="角色说明" prop="description">
 | 
			
		||||
        <el-input
 | 
			
		||||
          type="textarea"
 | 
			
		||||
          v-model="form.description"
 | 
			
		||||
          placeholder="请输入"
 | 
			
		||||
        />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item class="btns">
 | 
			
		||||
        <el-button @click="$emit('on-cancel')">取消</el-button>
 | 
			
		||||
        <el-button type="primary" @click="handleConfirmClick">确定</el-button>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
    </el-form>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import RoleBiz from "../../../biz/Rbac/Role.js";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      // 角色表单
 | 
			
		||||
      form: {
 | 
			
		||||
        name: "",
 | 
			
		||||
        description: "",
 | 
			
		||||
      },
 | 
			
		||||
      // 角色表单验证
 | 
			
		||||
      rules: {
 | 
			
		||||
        name: [{ required: true, message: "请先填写角色名称" }],
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  methods: {
 | 
			
		||||
    // 点击-确定
 | 
			
		||||
    handleConfirmClick() {
 | 
			
		||||
      this.$refs["form"].validate(async (valid) => {
 | 
			
		||||
        if (valid) {
 | 
			
		||||
          const { name, description } = this.form;
 | 
			
		||||
 | 
			
		||||
          try {
 | 
			
		||||
            const res = await RoleBiz.add(name, description);
 | 
			
		||||
            console.log("roleAdd", res);
 | 
			
		||||
 | 
			
		||||
            this.$message.success("新建角色成功");
 | 
			
		||||
            this.$emit("on-submit", res);
 | 
			
		||||
          } catch ({ message }) {
 | 
			
		||||
            this.$message.error(message);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.el-form-item.btns {
 | 
			
		||||
  padding-top: 20px;
 | 
			
		||||
  margin-bottom: 0;
 | 
			
		||||
  ::v-deep .el-form-item__content {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: flex-end;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,93 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Guanghao
 | 
			
		||||
 * @Date: 2022-01-05 11:53:19
 | 
			
		||||
 * @LastEditors: Guanghao
 | 
			
		||||
 * @LastEditTime: 2022-01-05 18:21:17
 | 
			
		||||
 * @Description: 角色编辑框
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="container">
 | 
			
		||||
    <el-form ref="form" :model="form" :rules="rules" label-width="100px">
 | 
			
		||||
      <el-form-item label="角色名称" prop="name">
 | 
			
		||||
        <el-input v-model="form.name" placeholder="请输入" />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="角色说明" prop="description">
 | 
			
		||||
        <el-input
 | 
			
		||||
          type="textarea"
 | 
			
		||||
          v-model="form.description"
 | 
			
		||||
          placeholder="请输入"
 | 
			
		||||
        />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item class="btns">
 | 
			
		||||
        <el-button @click="$emit('on-cancel')">取消</el-button>
 | 
			
		||||
        <el-button type="primary" @click="handleConfirmClick">确定</el-button>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
    </el-form>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import Dialog from "../../_Common/Dialog.vue";
 | 
			
		||||
 | 
			
		||||
import RoleBiz from "../../../biz/Rbac/Role.js";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  components: {
 | 
			
		||||
    Dialog,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  props: {
 | 
			
		||||
    role: { type: Object, required: true }, // 要编辑的对象
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  data() {
 | 
			
		||||
    const { id, name, description } = this.role;
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      // 角色表单
 | 
			
		||||
      form: {
 | 
			
		||||
        id,
 | 
			
		||||
        name,
 | 
			
		||||
        description,
 | 
			
		||||
      },
 | 
			
		||||
      // 角色表单验证
 | 
			
		||||
      rules: {
 | 
			
		||||
        name: [{ required: true, message: "请先填写角色名称" }],
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  methods: {
 | 
			
		||||
    // 点击-确定
 | 
			
		||||
    handleConfirmClick() {
 | 
			
		||||
      this.$refs["form"].validate(async (valid) => {
 | 
			
		||||
        if (valid) {
 | 
			
		||||
          const { id, name, description } = this.form;
 | 
			
		||||
 | 
			
		||||
          try {
 | 
			
		||||
            const res = await RoleBiz.update(id, name, description);
 | 
			
		||||
            console.log("roleUpdate", res);
 | 
			
		||||
 | 
			
		||||
            this.$message.success("编辑角色成功");
 | 
			
		||||
            this.$emit("on-submit", res);
 | 
			
		||||
          } catch ({ message }) {
 | 
			
		||||
            this.$message.error(message);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.el-form-item.btns {
 | 
			
		||||
  padding-top: 20px;
 | 
			
		||||
  margin-bottom: 0;
 | 
			
		||||
  ::v-deep .el-form-item__content {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: flex-end;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,121 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-08-17 14:23:18
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-04 17:26:55
 | 
			
		||||
 * @Description: 用户添加
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<!-- 用户添加成功后,会触发user-add-success事件,参数为新用户 -->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="container">
 | 
			
		||||
    <el-dialog
 | 
			
		||||
      title="添加用户"
 | 
			
		||||
      :visible="isVisible"
 | 
			
		||||
      :before-close="beforeClose"
 | 
			
		||||
      width="30%"
 | 
			
		||||
    >
 | 
			
		||||
      <el-form
 | 
			
		||||
        :model="newUser"
 | 
			
		||||
        :rules="newUserRules"
 | 
			
		||||
        ref="newUserForm"
 | 
			
		||||
        label-width="70px"
 | 
			
		||||
        class="newUserForm"
 | 
			
		||||
      >
 | 
			
		||||
        <el-form-item label="部门名" prop="name">
 | 
			
		||||
          <el-input :value="org.name"></el-input>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="用户名" prop="name">
 | 
			
		||||
          <el-input v-model="newUser.name"></el-input>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="密码" prop="password">
 | 
			
		||||
          <el-input
 | 
			
		||||
            v-model="newUser.password"
 | 
			
		||||
            autocomplete="new-password"
 | 
			
		||||
          ></el-input>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-form>
 | 
			
		||||
      <span slot="footer" class="dialog-footer">
 | 
			
		||||
        <el-button @click="onQuitClick">退 出</el-button>
 | 
			
		||||
        <el-button type="primary" @click="onConfirmClick">确 定</el-button>
 | 
			
		||||
      </span>
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
// import SystemApi from "../../../api/Rbac/System.js";
 | 
			
		||||
import UserBiz from "../../../biz/Rbac/User.js";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  props: {
 | 
			
		||||
    isVisible: {
 | 
			
		||||
      type: Boolean,
 | 
			
		||||
      default: false,
 | 
			
		||||
    },
 | 
			
		||||
    org: {
 | 
			
		||||
      type: Object,
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      newUser: {
 | 
			
		||||
        name: "",
 | 
			
		||||
        password: "",
 | 
			
		||||
      },
 | 
			
		||||
      newUserRules: {
 | 
			
		||||
        name: [{ required: true, message: "请填写用户名", trigger: "none" }],
 | 
			
		||||
        password: [
 | 
			
		||||
          { required: true, message: "请填写密码", trigger: "none" },
 | 
			
		||||
          {
 | 
			
		||||
            min: 5,
 | 
			
		||||
            max: 18,
 | 
			
		||||
            message: "长度在 5 到 18 个字符",
 | 
			
		||||
            trigger: "none",
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    onQuitClick() {
 | 
			
		||||
      this.$emit("update:isVisible", false);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onConfirmClick() {
 | 
			
		||||
      this.$refs["newUserForm"].validate((valid) => {
 | 
			
		||||
        if (valid) {
 | 
			
		||||
          UserBiz.add({
 | 
			
		||||
            name: this.newUser.name,
 | 
			
		||||
            password: this.newUser.password,
 | 
			
		||||
            orgId: this.org.id < 0 ? null : this.org.id,
 | 
			
		||||
          })
 | 
			
		||||
            .then((userId) => {
 | 
			
		||||
              this.$message({
 | 
			
		||||
                message: "添加用户成功",
 | 
			
		||||
                type: "success",
 | 
			
		||||
              });
 | 
			
		||||
              this.$emit("user-add-success", userId);
 | 
			
		||||
              console.log("userId :>> ", userId);
 | 
			
		||||
            })
 | 
			
		||||
            .catch((err) => {
 | 
			
		||||
              this.$message({
 | 
			
		||||
                message: err.message,
 | 
			
		||||
                type: "error",
 | 
			
		||||
              });
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
          return false;
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // “添加用户”对话框的❌被点击时
 | 
			
		||||
    beforeClose(done) {
 | 
			
		||||
      this.$emit("update:isVisible", false);
 | 
			
		||||
      done();
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,381 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-06-19 00:51:47
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-04 17:41:04
 | 
			
		||||
 * @Description: 用户列表
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="user">
 | 
			
		||||
    <el-table :data="userInfo.rows" style="width: 100%">
 | 
			
		||||
      <el-table-column prop="id" label="ID" width="100"></el-table-column>
 | 
			
		||||
      <el-table-column
 | 
			
		||||
        prop="name"
 | 
			
		||||
        label="用户名"
 | 
			
		||||
        show-overflow-tooltip
 | 
			
		||||
      ></el-table-column>
 | 
			
		||||
      <el-table-column
 | 
			
		||||
        prop="Organization.name"
 | 
			
		||||
        label="部门"
 | 
			
		||||
        width="100"
 | 
			
		||||
        :formatter="departmentNameFormatter"
 | 
			
		||||
        show-overflow-tooltip
 | 
			
		||||
      ></el-table-column>
 | 
			
		||||
      <el-table-column
 | 
			
		||||
        prop="softDelete"
 | 
			
		||||
        label="禁用"
 | 
			
		||||
        width="80"
 | 
			
		||||
        :formatter="booleanFormatter"
 | 
			
		||||
      ></el-table-column>
 | 
			
		||||
      <el-table-column
 | 
			
		||||
        prop="createTime"
 | 
			
		||||
        label="创建时间"
 | 
			
		||||
        width="180"
 | 
			
		||||
        :formatter="timeFormatter"
 | 
			
		||||
      ></el-table-column>
 | 
			
		||||
      <!-- <el-table-column
 | 
			
		||||
        prop="modifyTime"
 | 
			
		||||
        label="修改时间"
 | 
			
		||||
        width="180"
 | 
			
		||||
        :formatter="timeFormatter"
 | 
			
		||||
      ></el-table-column> -->
 | 
			
		||||
      <el-table-column label="操作" width="100" fixed="right">
 | 
			
		||||
        <template slot-scope="scope">
 | 
			
		||||
          <el-button
 | 
			
		||||
            class="btn-icon"
 | 
			
		||||
            icon="el-icon-edit"
 | 
			
		||||
            type="text"
 | 
			
		||||
            @click="onEditBtnClick(scope.$index, scope.row)"
 | 
			
		||||
          ></el-button>
 | 
			
		||||
 | 
			
		||||
          <el-button
 | 
			
		||||
            class="btn-icon"
 | 
			
		||||
            icon="el-icon-delete"
 | 
			
		||||
            type="text"
 | 
			
		||||
            @click="onDelBtnClick(scope.$index, scope.row)"
 | 
			
		||||
          ></el-button>
 | 
			
		||||
 | 
			
		||||
          <el-button
 | 
			
		||||
            class="btn-icon"
 | 
			
		||||
            icon="el-icon-rank"
 | 
			
		||||
            type="text"
 | 
			
		||||
            @click="onMoveBtnClick(scope.$index, scope.row)"
 | 
			
		||||
          ></el-button>
 | 
			
		||||
        </template>
 | 
			
		||||
      </el-table-column>
 | 
			
		||||
    </el-table>
 | 
			
		||||
    <div class="pagination">
 | 
			
		||||
      <el-pagination
 | 
			
		||||
        background
 | 
			
		||||
        layout="prev, pager, next"
 | 
			
		||||
        :total="userInfo.totalCount"
 | 
			
		||||
        :page-size="userInfo.pageSize"
 | 
			
		||||
        :current-page.sync="userInfo.pageNum"
 | 
			
		||||
      ></el-pagination>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <el-dialog
 | 
			
		||||
      v-if="editingUser"
 | 
			
		||||
      title="修改用户"
 | 
			
		||||
      :visible.sync="isDialEditUserVisible"
 | 
			
		||||
      width="30%"
 | 
			
		||||
      :before-close="onDialEditUserClose"
 | 
			
		||||
    >
 | 
			
		||||
      <el-form
 | 
			
		||||
        :model="editingUser"
 | 
			
		||||
        :rules="userRules"
 | 
			
		||||
        ref="edit-user-form"
 | 
			
		||||
        label-width="70px"
 | 
			
		||||
        class="edit-user-form"
 | 
			
		||||
      >
 | 
			
		||||
        <el-form-item label="用户名" prop="name">
 | 
			
		||||
          <el-input v-model="editingUser.name"></el-input>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="是否禁用">
 | 
			
		||||
          <el-radio-group v-model="editingUser.softDelete">
 | 
			
		||||
            <el-radio :label="true">是</el-radio>
 | 
			
		||||
            <el-radio :label="false">否</el-radio>
 | 
			
		||||
          </el-radio-group>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-form>
 | 
			
		||||
      <span slot="footer" class="dialog-footer">
 | 
			
		||||
        <el-button @click="isDialEditUserVisible = false">退 出</el-button>
 | 
			
		||||
        <el-button type="primary" @click="onEditUserConfirmClick"
 | 
			
		||||
          >确 定</el-button
 | 
			
		||||
        >
 | 
			
		||||
      </span>
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
 | 
			
		||||
    <el-dialog
 | 
			
		||||
      title="移动用户"
 | 
			
		||||
      width="50%"
 | 
			
		||||
      :visible.sync="isDialMoveUserVisible"
 | 
			
		||||
      :before-close="onDialMoveUserClose"
 | 
			
		||||
      @open="onDialMoveUserOpen"
 | 
			
		||||
    >
 | 
			
		||||
      <UserMove
 | 
			
		||||
        ref="user-move-form"
 | 
			
		||||
        :user="editingUser"
 | 
			
		||||
        :treeData="orgTreeData"
 | 
			
		||||
        @cancel="isDialMoveUserVisible = false"
 | 
			
		||||
        @success="onUserMoveSuccess"
 | 
			
		||||
      />
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
import UserMove from "../../../components/Rbac/User/UserMove.vue";
 | 
			
		||||
import UserBiz from "../../../biz/Rbac/User.js";
 | 
			
		||||
import OrgAndUserBiz from "../../../biz/Rbac/OrgAndUser.js";
 | 
			
		||||
import DateHelper from "../../../util/DateHelper.js";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  components: { UserMove },
 | 
			
		||||
  props: {
 | 
			
		||||
    orgId: { type: Number }, // orgId是-1的时候代表根节点
 | 
			
		||||
    orgTreeData: { type: Array }, // 组织架构树结构,主要用于移动用户
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      isDialEditUserVisible: false,
 | 
			
		||||
      userRules: {
 | 
			
		||||
        name: [{ required: true, message: "请填写用户名", trigger: "none" }],
 | 
			
		||||
        password: [{ required: true, message: "请填写密码", trigger: "none" }],
 | 
			
		||||
      },
 | 
			
		||||
      editingUser: null,
 | 
			
		||||
      editingUserPrototype: null,
 | 
			
		||||
      userInfo: {
 | 
			
		||||
        rows: [],
 | 
			
		||||
        totalCount: 0,
 | 
			
		||||
        pageSize: 10,
 | 
			
		||||
        pageNum: 1,
 | 
			
		||||
      },
 | 
			
		||||
      isDialMoveUserVisible: false, // 移动用户对话框是否显示
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    // 共几页
 | 
			
		||||
    pageCount: function () {
 | 
			
		||||
      return Math.ceil(this.userInfo.totalCount / this.userInfo.pageSize);
 | 
			
		||||
    },
 | 
			
		||||
    myOrgId: function () {
 | 
			
		||||
      return this.orgId < 0 ? null : this.orgId;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  watch: {
 | 
			
		||||
    "userInfo.pageNum": function (newVal, oldVal) {
 | 
			
		||||
      this.getCurrPageUsers();
 | 
			
		||||
    },
 | 
			
		||||
    orgId: function (newVal, oldVal) {
 | 
			
		||||
      this.getCurrPageUsers();
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.getCurrPageUsers();
 | 
			
		||||
    // this.getAllRoles();
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    // 获取当前页面的用户信息列表
 | 
			
		||||
    async getCurrPageUsers() {
 | 
			
		||||
      // if (this.myOrgId === null) {
 | 
			
		||||
      //   // 查询所有用户
 | 
			
		||||
      //   UserBiz.findByPage(this.userInfo.pageSize, this.userInfo.pageNum).then(
 | 
			
		||||
      //     (result) => {
 | 
			
		||||
      //       this.userInfo.rows = result.rows;
 | 
			
		||||
      //       this.userInfo.totalCount = result.count;
 | 
			
		||||
      //     }
 | 
			
		||||
      //   );
 | 
			
		||||
      // } else {
 | 
			
		||||
      //   OrgAndUserBiz.findUsersByOrgIdAndByPage(
 | 
			
		||||
      //     this.myOrgId,
 | 
			
		||||
      //     this.userInfo.pageSize,
 | 
			
		||||
      //     this.userInfo.pageNum
 | 
			
		||||
      //   ).then((result) => {
 | 
			
		||||
      //     this.userInfo.rows = result.rows;
 | 
			
		||||
      //     this.userInfo.totalCount = result.count;
 | 
			
		||||
      //   });
 | 
			
		||||
      // }
 | 
			
		||||
 | 
			
		||||
      await OrgAndUserBiz.findUsersByOrgIdAndByPage(
 | 
			
		||||
        this.myOrgId,
 | 
			
		||||
        this.userInfo.pageSize,
 | 
			
		||||
        this.userInfo.pageNum
 | 
			
		||||
      ).then((result) => {
 | 
			
		||||
        this.userInfo.rows = result.rows;
 | 
			
		||||
        this.userInfo.totalCount = result.count;
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // table里每一row的“编辑”按钮被点击时
 | 
			
		||||
    onEditBtnClick(index, row) {
 | 
			
		||||
      this.editingUserPrototype = row;
 | 
			
		||||
      this.editingUser = JSON.parse(JSON.stringify(row));
 | 
			
		||||
      this.isDialEditUserVisible = true;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // “修改用户”的对话框的确定按钮被点击时
 | 
			
		||||
    onEditUserConfirmClick() {
 | 
			
		||||
      this.$refs["edit-user-form"].validate((valid) => {
 | 
			
		||||
        if (valid) {
 | 
			
		||||
          let user = {};
 | 
			
		||||
          for (const key in this.editingUser) {
 | 
			
		||||
            if (Object.hasOwnProperty.call(this.editingUser, key)) {
 | 
			
		||||
              if (this.editingUser[key] !== this.editingUserPrototype[key]) {
 | 
			
		||||
                user[key] = this.editingUser[key];
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (JSON.stringify(user) !== "{}") {
 | 
			
		||||
            user.id = this.editingUser.id;
 | 
			
		||||
            UserBiz.update(user)
 | 
			
		||||
              .then((count) => {
 | 
			
		||||
                if (count === 1) {
 | 
			
		||||
                  this.$message({
 | 
			
		||||
                    message: "用户修改成功",
 | 
			
		||||
                    type: "success",
 | 
			
		||||
                  });
 | 
			
		||||
                  this.isDialEditUserVisible = false;
 | 
			
		||||
                  this.getCurrPageUsers();
 | 
			
		||||
                }
 | 
			
		||||
              })
 | 
			
		||||
              .catch((err) => {
 | 
			
		||||
                this.$message({
 | 
			
		||||
                  message: err.message,
 | 
			
		||||
                  type: "error",
 | 
			
		||||
                });
 | 
			
		||||
              });
 | 
			
		||||
          } else {
 | 
			
		||||
            this.$message({
 | 
			
		||||
              message: "用户信息没有任何修改",
 | 
			
		||||
              type: "warning",
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // “修改用户”的对话框的❌被点击时
 | 
			
		||||
    onDialEditUserClose(done) {
 | 
			
		||||
      done();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // table里每一row的“删除”按钮被点击时
 | 
			
		||||
    onDelBtnClick(index, row) {
 | 
			
		||||
      this.$confirm("此操作将永久删除该用户, 是否继续?", "提示", {
 | 
			
		||||
        confirmButtonText: "确定",
 | 
			
		||||
        cancelButtonText: "取消",
 | 
			
		||||
        type: "warning",
 | 
			
		||||
      })
 | 
			
		||||
        .then(() => {
 | 
			
		||||
          UserBiz.delById(row.id)
 | 
			
		||||
            .then((count) => {
 | 
			
		||||
              this.$message({
 | 
			
		||||
                type: "success",
 | 
			
		||||
                message: "用户删除成功",
 | 
			
		||||
              });
 | 
			
		||||
              this.getCurrPageUsers();
 | 
			
		||||
            })
 | 
			
		||||
            .catch((err) => {
 | 
			
		||||
              this.$message({
 | 
			
		||||
                message: err.message,
 | 
			
		||||
                type: "error",
 | 
			
		||||
              });
 | 
			
		||||
            });
 | 
			
		||||
        })
 | 
			
		||||
        .catch(() => {
 | 
			
		||||
          this.$message({
 | 
			
		||||
            type: "info",
 | 
			
		||||
            message: "已取消删除",
 | 
			
		||||
          });
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onMoveBtnClick(index, row) {
 | 
			
		||||
      this.editingUser = JSON.parse(JSON.stringify(row));
 | 
			
		||||
      // this.editingUser = row;
 | 
			
		||||
      this.isDialMoveUserVisible = true;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 去到表格的最后一页(外部调用)
 | 
			
		||||
    async goToLastPage() {
 | 
			
		||||
      let userCount = await UserBiz.countAll();
 | 
			
		||||
      this.userInfo.totalCount = userCount;
 | 
			
		||||
 | 
			
		||||
      if (this.userInfo.pageNum === this.pageCount) {
 | 
			
		||||
        // 已在最后一页
 | 
			
		||||
        this.getCurrPageUsers();
 | 
			
		||||
      } else {
 | 
			
		||||
        // 未在最后一页
 | 
			
		||||
        this.userInfo.pageNum = this.pageCount;
 | 
			
		||||
        this.getCurrPageUsers();
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // “移动用户”的对话框的❌被点击时
 | 
			
		||||
    onDialMoveUserClose(done) {
 | 
			
		||||
      done();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 当移动用户的对话框的open事件被触发时
 | 
			
		||||
    onDialMoveUserOpen() {
 | 
			
		||||
      let $refForm = this.$refs["user-move-form"];
 | 
			
		||||
      // 第一次打开dialog时,表单会不存在
 | 
			
		||||
      if ($refForm) {
 | 
			
		||||
        $refForm.updateForm();
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 当移动用户的对话框里移动用户成功时
 | 
			
		||||
    async onUserMoveSuccess(currNodeData, targetParentData) {
 | 
			
		||||
      // TODO: 非常神奇,马上获取本页用户,被移动的用户还在;但是如果隔1秒获取,则被移动的用户已不在本页;而后端明明是等(await)用户保存成功后才返回的
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
        this.getCurrPageUsers();
 | 
			
		||||
      }, 1000);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    departmentNameFormatter(row, column, cellValue, index) {
 | 
			
		||||
      return cellValue ? cellValue : "-";
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 表格的布尔值格式器
 | 
			
		||||
    booleanFormatter(row, column, cellValue, index) {
 | 
			
		||||
      return cellValue ? "是" : "否";
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 表格的时间格式器
 | 
			
		||||
    timeFormatter(row, column, cellValue, index) {
 | 
			
		||||
      let date = new Date(cellValue);
 | 
			
		||||
      let dateStr = DateHelper.format(date, "yyyy-MM-dd hh:mm");
 | 
			
		||||
      return dateStr;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.user {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
  // padding: 10px;
 | 
			
		||||
  .el-table {
 | 
			
		||||
    .el-button {
 | 
			
		||||
      padding: 0px;
 | 
			
		||||
      &.btn-icon {
 | 
			
		||||
        color: #000;
 | 
			
		||||
        &:hover {
 | 
			
		||||
          color: #409eff;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  .pagination {
 | 
			
		||||
    margin-top: 8px;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    .el-pagination {
 | 
			
		||||
      float: right;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,157 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-09-03 11:26:04
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-04 17:38:31
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="container">
 | 
			
		||||
    <LightSplit :ratios="[1, 1]" :minHeight="300">
 | 
			
		||||
      <template v-slot:1>
 | 
			
		||||
        <div class="tree-container">
 | 
			
		||||
          <el-tree
 | 
			
		||||
            ref="org-tree"
 | 
			
		||||
            :data="treeData"
 | 
			
		||||
            :node-key="'id'"
 | 
			
		||||
            :props="treeProps"
 | 
			
		||||
            :show-checkbox="false"
 | 
			
		||||
            :highlight-current="true"
 | 
			
		||||
            :expand-on-click-node="true"
 | 
			
		||||
            :default-expanded-keys="defaultExpandedKeys"
 | 
			
		||||
            @node-click="onTreeNodeClick"
 | 
			
		||||
            @current-change="onOrgTreeCurrChange"
 | 
			
		||||
          ></el-tree>
 | 
			
		||||
        </div>
 | 
			
		||||
      </template>
 | 
			
		||||
      <template v-slot:2>
 | 
			
		||||
        <el-form
 | 
			
		||||
          ref="form"
 | 
			
		||||
          :model="form"
 | 
			
		||||
          :label-position="'top'"
 | 
			
		||||
          :rules="orgRules"
 | 
			
		||||
        >
 | 
			
		||||
          <el-form-item label="目标部门名称" prop="parentName">
 | 
			
		||||
            <el-input
 | 
			
		||||
              :value="form.parentName"
 | 
			
		||||
              placeholder="请在左侧选择"
 | 
			
		||||
              readonly
 | 
			
		||||
            ></el-input>
 | 
			
		||||
          </el-form-item>
 | 
			
		||||
          <el-form-item label="移动用户名称">
 | 
			
		||||
            <el-input :value="user.name" readonly></el-input>
 | 
			
		||||
          </el-form-item>
 | 
			
		||||
          <el-form-item class="btns">
 | 
			
		||||
            <el-button @click="onCancel">{{ btnCancelName }}</el-button>
 | 
			
		||||
            <el-button type="primary" @click="onSubmit">确定</el-button>
 | 
			
		||||
          </el-form-item>
 | 
			
		||||
        </el-form>
 | 
			
		||||
      </template>
 | 
			
		||||
    </LightSplit>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
// import OrganizationApi from "../../../api/Rbac/Organization.js";
 | 
			
		||||
import OrgAndUserBiz from "../../../biz/Rbac/OrgAndUser.js";
 | 
			
		||||
import LightSplit from "../../../components/_Common/LightSplit.vue";
 | 
			
		||||
export default {
 | 
			
		||||
  components: { LightSplit },
 | 
			
		||||
  props: {
 | 
			
		||||
    treeData: { type: Array },
 | 
			
		||||
    user: { type: Object, required: true }, // 当前移动的用户对象
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      form: {
 | 
			
		||||
        parentId: 0,
 | 
			
		||||
        parentName: "",
 | 
			
		||||
        targetParentData: null,
 | 
			
		||||
      },
 | 
			
		||||
      orgRules: {
 | 
			
		||||
        parentName: [
 | 
			
		||||
          {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: "请选择目标上级部门",
 | 
			
		||||
            trigger: "none",
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
      defaultExpandedKeys: [null],
 | 
			
		||||
      treeProps: {
 | 
			
		||||
        label: "name",
 | 
			
		||||
        children: "Children",
 | 
			
		||||
      },
 | 
			
		||||
      btnCancelName: "取消",
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    onSubmit() {
 | 
			
		||||
      this.$refs["form"].validate(async (valid) => {
 | 
			
		||||
        if (valid) {
 | 
			
		||||
          this.$confirm(
 | 
			
		||||
            `确定把在用户 ${this.user.name} 移动到 ${this.form.parentName} 部门下面?`,
 | 
			
		||||
            {
 | 
			
		||||
              title: "确认移动",
 | 
			
		||||
              showCancelButton: true,
 | 
			
		||||
              showConfirmButton: true,
 | 
			
		||||
            }
 | 
			
		||||
          )
 | 
			
		||||
            .then(async () => {
 | 
			
		||||
              try {
 | 
			
		||||
                let user = OrgAndUserBiz.relateUser(
 | 
			
		||||
                  this.form.parentId < 0 ? null : this.form.parentId,
 | 
			
		||||
                  this.user.id
 | 
			
		||||
                );
 | 
			
		||||
                if (user) {
 | 
			
		||||
                  this.$message.success(
 | 
			
		||||
                    `${this.user.name} 移动到 ${this.form.parentName} 部门下`
 | 
			
		||||
                  );
 | 
			
		||||
                  this.btnCancelName = "退出";
 | 
			
		||||
                  this.$emit("success", user, this.form.targetParentData);
 | 
			
		||||
                }
 | 
			
		||||
              } catch (e) {
 | 
			
		||||
                this.$message.error(e.message);
 | 
			
		||||
              }
 | 
			
		||||
            })
 | 
			
		||||
            .catch((e) => {});
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onCancel() {
 | 
			
		||||
      this.$emit("cancel");
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onTreeNodeClick() {},
 | 
			
		||||
 | 
			
		||||
    // 当组织架构树被改变选中时
 | 
			
		||||
    onOrgTreeCurrChange(data, node) {
 | 
			
		||||
      this.form.parentId = data.id;
 | 
			
		||||
      this.form.parentName = data.name;
 | 
			
		||||
      this.form.targetParentData = data;
 | 
			
		||||
      // this.$refs["form"].clearValidate();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 供外部调用,更新本组件的一些显示
 | 
			
		||||
    updateForm() {
 | 
			
		||||
      this.$refs["form"].clearValidate();
 | 
			
		||||
      this.btnCancelName = "取消";
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.tree-container {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  max-height: 600px;
 | 
			
		||||
  overflow-y: auto;
 | 
			
		||||
}
 | 
			
		||||
.el-form-item.btns {
 | 
			
		||||
  padding-top: 20px;
 | 
			
		||||
  margin-bottom: 0;
 | 
			
		||||
  ::v-deep .el-form-item__content {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: flex-end;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,84 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="page">
 | 
			
		||||
    <div class="page-container">
 | 
			
		||||
      <div class="page-content">
 | 
			
		||||
        <div class="page-title">
 | 
			
		||||
          <h1>{{errorCode}}</h1>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="page-subtitle">
 | 
			
		||||
          <p>{{errorMessage}}</p>
 | 
			
		||||
        </div>
 | 
			
		||||
        <el-button class="btn-main" type="primary" size="medium" @click="toHome">去首页</el-button>
 | 
			
		||||
        <div class="btn-container">
 | 
			
		||||
          或者
 | 
			
		||||
          <el-button type="text" @click="goBack">返回上页 ></el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  name: "Error",
 | 
			
		||||
  data() {
 | 
			
		||||
    return {};
 | 
			
		||||
  },
 | 
			
		||||
  props: {},
 | 
			
		||||
  computed: {
 | 
			
		||||
    errorCode() {
 | 
			
		||||
      return this.$route.params.code;
 | 
			
		||||
    },
 | 
			
		||||
    errorMessage() {
 | 
			
		||||
      return this.$route.params.message;
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    goBack() {
 | 
			
		||||
      this.$router.go(-1);
 | 
			
		||||
    },
 | 
			
		||||
    toHome() {
 | 
			
		||||
      this.$router.push("/").catch(e => {}); // 这个奇怪的catch请参考:https://github.com/vuejs/vue-router/issues/2873
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
@import "../../scss/_variables";
 | 
			
		||||
.page-container {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  padding-top: 180px;
 | 
			
		||||
  .page-content {
 | 
			
		||||
    align-items: flex-start;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    min-width: 384px;
 | 
			
		||||
    max-width: 500px;
 | 
			
		||||
    .page-title,
 | 
			
		||||
    .page-subtitle {
 | 
			
		||||
      color: #646464;
 | 
			
		||||
      line-height: 1.4;
 | 
			
		||||
    }
 | 
			
		||||
    .page-subtitle {
 | 
			
		||||
      font-size: 18px;
 | 
			
		||||
      margin-top: 10px;
 | 
			
		||||
    }
 | 
			
		||||
    .btn-main,
 | 
			
		||||
    .btn-container {
 | 
			
		||||
      margin-top: 20px;
 | 
			
		||||
    }
 | 
			
		||||
    .btn-container {
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      // color: $main-color-light;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      font-size: 14px;
 | 
			
		||||
      .el-button {
 | 
			
		||||
        margin-left: 10px;
 | 
			
		||||
        padding: 0;
 | 
			
		||||
        // color: $main-color-light;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,74 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="page">
 | 
			
		||||
    <div class="page-container">
 | 
			
		||||
      <div class="page-content">
 | 
			
		||||
        <div class="page-title">
 | 
			
		||||
          <h1>404</h1>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="page-subtitle">
 | 
			
		||||
          <p>本页不存在</p>
 | 
			
		||||
        </div>
 | 
			
		||||
        <el-button class="btn-main" type="primary" size="medium" @click="toHome">去首页</el-button>
 | 
			
		||||
        <div class="btn-container">
 | 
			
		||||
          或者
 | 
			
		||||
          <el-button type="text" @click="goBack">返回上页 ></el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  name: "NotFound",
 | 
			
		||||
  data() {},
 | 
			
		||||
  props: {},
 | 
			
		||||
  methods: {
 | 
			
		||||
    goBack() {
 | 
			
		||||
      this.$router.go(-1);
 | 
			
		||||
    },
 | 
			
		||||
    toHome() {
 | 
			
		||||
      this.$router.push("/").catch(e => {}); // 这个奇怪的catch请参考:https://github.com/vuejs/vue-router/issues/2873
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
@import "../../scss/_variables";
 | 
			
		||||
.page-container {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  padding-top: 180px;
 | 
			
		||||
  .page-content {
 | 
			
		||||
    align-items: flex-start;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    min-width: 384px;
 | 
			
		||||
    max-width: 500px;
 | 
			
		||||
    .page-title,
 | 
			
		||||
    .page-subtitle {
 | 
			
		||||
      color: #646464;
 | 
			
		||||
      line-height: 1.4;
 | 
			
		||||
    }
 | 
			
		||||
    .page-subtitle {
 | 
			
		||||
      font-size: 18px;
 | 
			
		||||
      margin-top: 10px;
 | 
			
		||||
    }
 | 
			
		||||
    .btn-main,
 | 
			
		||||
    .btn-container {
 | 
			
		||||
      margin-top: 20px;
 | 
			
		||||
    }
 | 
			
		||||
    .btn-container {
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      // color: $main-color-light;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      font-size: 14px;
 | 
			
		||||
      .el-button {
 | 
			
		||||
        margin-left: 10px;
 | 
			
		||||
        padding: 0;
 | 
			
		||||
        // color: $main-color-light;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,81 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-08-25 01:20:11
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2021-09-17 11:04:53
 | 
			
		||||
 * @Description: 富文本编辑器
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<!--
 | 
			
		||||
参考
 | 
			
		||||
https://ckeditor.com/docs/ckeditor4/latest/guide/dev_vue.html#basic-usage
 | 
			
		||||
https://ckeditor.com/latest/samples/toolbarconfigurator/index.html#basic
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="editor-container">
 | 
			
		||||
    <ckeditor
 | 
			
		||||
      v-model="data"
 | 
			
		||||
      :config="editorConfig"
 | 
			
		||||
      @ready="onEditorReady"
 | 
			
		||||
    ></ckeditor>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
import CKEditor from "ckeditor4-vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  components: {
 | 
			
		||||
    ckeditor: CKEditor.component,
 | 
			
		||||
  },
 | 
			
		||||
  props: {
 | 
			
		||||
    editorData: { type: String },
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    data: {
 | 
			
		||||
      get() {
 | 
			
		||||
        return this.editorData;
 | 
			
		||||
      },
 | 
			
		||||
      set(val) {
 | 
			
		||||
        this.$emit("update:editorData", val);
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      // editorData: "<p>Content of the editor.</p>",
 | 
			
		||||
      editorConfig: {
 | 
			
		||||
        height: 300,
 | 
			
		||||
        toolbarGroups: [
 | 
			
		||||
          { name: "document", groups: ["mode", "document", "doctools"] },
 | 
			
		||||
          { name: "clipboard", groups: ["clipboard", "undo"] },
 | 
			
		||||
          {
 | 
			
		||||
            name: "editing",
 | 
			
		||||
            groups: ["find", "selection", "spellchecker", "editing"],
 | 
			
		||||
          },
 | 
			
		||||
          { name: "forms", groups: ["forms"] },
 | 
			
		||||
          // "/",
 | 
			
		||||
          { name: "basicstyles", groups: ["basicstyles", "cleanup"] },
 | 
			
		||||
          {
 | 
			
		||||
            name: "paragraph",
 | 
			
		||||
            groups: ["list", "indent", "blocks", "align", "bidi", "paragraph"],
 | 
			
		||||
          },
 | 
			
		||||
          { name: "links", groups: ["links"] },
 | 
			
		||||
          { name: "insert", groups: ["insert"] },
 | 
			
		||||
          // "/",
 | 
			
		||||
          { name: "styles", groups: ["styles"] },
 | 
			
		||||
          { name: "colors", groups: ["colors"] },
 | 
			
		||||
          { name: "tools", groups: ["tools"] },
 | 
			
		||||
          { name: "others", groups: ["others"] },
 | 
			
		||||
          { name: "about", groups: ["about"] },
 | 
			
		||||
        ],
 | 
			
		||||
        removeButtons:
 | 
			
		||||
          "Save,NewPage,Print,Language,BidiRtl,BidiLtr,Blockquote,CreateDiv,Outdent,Indent,Underline,Italic,Subscript,Superscript,Templates,Cut,Copy,Paste,PasteFromWord,Find,Replace,SelectAll,Scayt,Form,Checkbox,Radio,TextField,Textarea,Select,Button,ImageButton,HiddenField,RemoveFormat,CopyFormatting,Link,Unlink,Anchor,Image,Flash,Table,HorizontalRule,Smiley,SpecialChar,PageBreak,Iframe,Styles,Format,Font,FontSize,TextColor,BGColor,ShowBlocks,About,Source,ExportPdf,Preview",
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    onEditorReady() {},
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,47 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Guanghao
 | 
			
		||||
 * @Date: 2022-01-04 17:18:23
 | 
			
		||||
 * @LastEditors: Guanghao
 | 
			
		||||
 * @LastEditTime: 2022-01-05 15:47:30
 | 
			
		||||
 * @Description: 弹框组件,
 | 
			
		||||
        直接使用方式:
 | 
			
		||||
              <Dialog title="" visible="">
 | 
			
		||||
                要写入的内容
 | 
			
		||||
              </Dialog>
 | 
			
		||||
        当页面不是直接使用时应采取以下方式(比如新建/编辑框):
 | 
			
		||||
              <Dialog v-bind="$attrs" v-on="$listeners" @on-confirm.native="">
 | 
			
		||||
                要写入的内容
 | 
			
		||||
              </Dialog>
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <el-dialog :title="title" :visible="visible" :width="width">
 | 
			
		||||
    <!-- 内容 -->
 | 
			
		||||
    <slot />
 | 
			
		||||
 | 
			
		||||
    <!-- 底部按钮 -->
 | 
			
		||||
    <span slot="footer" class="dialog-footer">
 | 
			
		||||
      <el-button @click="$emit('update:visible', false)">取 消</el-button>
 | 
			
		||||
      <el-button type="primary" @click="$emit('on-confirm')">确 定</el-button>
 | 
			
		||||
    </span>
 | 
			
		||||
  </el-dialog>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  name: "Dialog",
 | 
			
		||||
 | 
			
		||||
  props: {
 | 
			
		||||
    visible: { type: Boolean, required: true }, // 是否显示弹框
 | 
			
		||||
    title: { type: String, required: true }, // 弹框标题
 | 
			
		||||
    width: { type: String, default: "30%" }, // 弹框宽度,格式:30%、30px
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  created() {
 | 
			
		||||
    console.log(this.$props);
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,327 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2020-06-07 03:55:51
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-01 02:43:57
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
--> 
 | 
			
		||||
<!--
 | 
			
		||||
由于element ui的对话框dialog有填充满页面body遮挡层,故本组件不适用
 | 
			
		||||
本组件适用于任何自定义的div
 | 
			
		||||
 | 
			
		||||
事件:
 | 
			
		||||
drag-start 事件对象:{id - 父组件给予本组件的id, originalX - 拖动前的 translate x 值, originalY - 拖动前的 translate y 值}
 | 
			
		||||
drag-move 事件对象:{
 | 
			
		||||
  id - 父组件给予本组件的id
 | 
			
		||||
  currentX -  移动后的 translate x 值
 | 
			
		||||
  currentY - 移动后的 translate y 值
 | 
			
		||||
  movedX - 本次x移动的距离
 | 
			
		||||
  movedY - 本次y移动的距离
 | 
			
		||||
  deltaX - x轴上移动方向,正数向右,负数向左
 | 
			
		||||
  deltaY - y轴上移动方向,正数向上,负数向下
 | 
			
		||||
}
 | 
			
		||||
drag-end 事件对象:{
 | 
			
		||||
  id - 父组件给予本组件的id  
 | 
			
		||||
  currentX -  移动后的 translate x 值
 | 
			
		||||
  currentY - 移动后的 translate y 值
 | 
			
		||||
}
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <div
 | 
			
		||||
    :id="areaDraggableId"
 | 
			
		||||
    class="area-draggable"
 | 
			
		||||
    ref="area-draggable"
 | 
			
		||||
    v-if="isVisible"
 | 
			
		||||
  >
 | 
			
		||||
    <slot></slot>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
// 参考:https://interactjs.io/
 | 
			
		||||
import InteractJs from "interactjs";
 | 
			
		||||
// import Velocity from "velocity-animate";
 | 
			
		||||
export default {
 | 
			
		||||
  props: {
 | 
			
		||||
    // 父组件给予本组件的id
 | 
			
		||||
    id: { type: [Number, String] },
 | 
			
		||||
    // 组件的 translate x
 | 
			
		||||
    translateX: { type: Number },
 | 
			
		||||
    // 组件的 translate y
 | 
			
		||||
    translateY: { type: Number },
 | 
			
		||||
    // 标记鼠标无法移动的区域,建议用类名表示(例:".ignore-area"),默认整个可移动模块都可以用鼠标移动
 | 
			
		||||
    ignoreArea: { type: String, required: false },
 | 
			
		||||
    // 可移动模块的“移动区域”,默认为可移动模块的父标签
 | 
			
		||||
    restriction: { type: String, default: "parent" },
 | 
			
		||||
    // 在“甩动”可移动模块时,是否采用惯性移动一定距离;无惯性时,可移动模块会立刻停止移动
 | 
			
		||||
    inertia: { type: Boolean, default: true },
 | 
			
		||||
    // 可移动模块是否可以部分移出“移动区域”,当为false时,可移动模块整个都必须在“移动区域”内
 | 
			
		||||
    allowGoToOutside: { type: Boolean, default: false },
 | 
			
		||||
    // 可移动模块是否可见,默认可见。从false改为true后,本可移动模块会恢复到原始位置(因为用的是v-if,而不是v-show)。
 | 
			
		||||
    // 可在父组件调用本组件时,使用v-show。
 | 
			
		||||
    // 父组件调用例子:<Draggable class="draggable-window" v-show="isDialogVisible" :ignoreArea="'.table'">你的内容</Draggable>
 | 
			
		||||
    isVisible: { type: Boolean, default: true },
 | 
			
		||||
    // 移动方向,both/horizontal/vertical,默认是全部方向
 | 
			
		||||
    moveDirection: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: "both",
 | 
			
		||||
      validator: function (value) {
 | 
			
		||||
        return ["both", "horizontal", "vertical"].indexOf(value) !== -1;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    // 是否禁止拖动效果
 | 
			
		||||
    isFrozen: { style: Boolean },
 | 
			
		||||
    // 鼠标移入本组件区域内,curser是否改变(改变的话,默认会显示为十字箭头;不改变的话,就是系统原来的默认箭头)
 | 
			
		||||
    toStyleCursor: { type: Boolean, default: false },
 | 
			
		||||
    // 移动完毕后的回调,传递一个Rebound()函数作为参数,调用Rebound()可将本组件恢复到原位
 | 
			
		||||
    handleMoveEnd: { type: Function },
 | 
			
		||||
  },
 | 
			
		||||
  watch: {
 | 
			
		||||
    translateX: {
 | 
			
		||||
      immediate: false,
 | 
			
		||||
      handler(val, oldVal) {
 | 
			
		||||
        this.moveTo({ x: val });
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    translateY: {
 | 
			
		||||
      immediate: false,
 | 
			
		||||
      handler(val, oldVal) {
 | 
			
		||||
        this.moveTo({ y: val });
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      // 本组件的 dom id,由随机数和时间戳组成,保证唯一性
 | 
			
		||||
      areaDraggableId: `now-${Date.now()}-random-${Math.floor(
 | 
			
		||||
        Math.random() * (9999999 - 1000000 + 1) + 1000000
 | 
			
		||||
      )}${this.id ? "-" + this.id : "-0"}`,
 | 
			
		||||
      zIndexWhenMovingHeavy: "1000", // 组件移动时的z-index
 | 
			
		||||
      zIndexWhenMovingLight: "100", // 组件移动时的z-index
 | 
			
		||||
      originalX: 0, // 组件在每次拖动前的 translate x
 | 
			
		||||
      originalY: 0, // 组件在每次拖动前的 translate y
 | 
			
		||||
      originalZindex: "", // 组件在初始化时的 z-index
 | 
			
		||||
      originalTransform: "", // 组件在初始化时的 transform
 | 
			
		||||
      originalTransition: "", // 组件在初始化时的 transition
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    const bodyOverflow =
 | 
			
		||||
      document.getElementsByTagName("body")[0].style.overflow; // 由于拖动时,组件用于把body的滚动条隐藏掉
 | 
			
		||||
 | 
			
		||||
    this.originalZindex = this.$refs["area-draggable"].style.zIndex;
 | 
			
		||||
    this.originalTransform = this.$refs["area-draggable"].style.transform;
 | 
			
		||||
    this.originalTransition = this.$refs["area-draggable"].style.transition;
 | 
			
		||||
 | 
			
		||||
    if (this.translateX || this.translateY)
 | 
			
		||||
      this.moveTo({ x: this.translateX, y: this.translateY });
 | 
			
		||||
 | 
			
		||||
    const dragStartListener = (event) => {
 | 
			
		||||
      if (!this.isFrozen) {
 | 
			
		||||
        const target = event.target;
 | 
			
		||||
        const x = parseFloat(target.getAttribute("data-x")) || 0;
 | 
			
		||||
        const y = parseFloat(target.getAttribute("data-y")) || 0;
 | 
			
		||||
        this.originalX = x;
 | 
			
		||||
        this.originalY = y;
 | 
			
		||||
        target.style.zIndex = this.zIndexWhenMoving;
 | 
			
		||||
        document.getElementsByTagName("body")[0].style.overflow = "hidden";
 | 
			
		||||
        this.$emit("drag-start", { id: this.id, originalX: x, originalY: y });
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const dragMoveListener = (event) => {
 | 
			
		||||
      if (!this.isFrozen) {
 | 
			
		||||
        const target = event.target;
 | 
			
		||||
        // parentElement和parentNode一样,只是parentElement是ie的标准
 | 
			
		||||
        // var target = event.target.parentNode || event.target.parentElement;
 | 
			
		||||
 | 
			
		||||
        const movedX = this.moveDirection === "horizontal" ? event.dx : 0;
 | 
			
		||||
        const x = (parseFloat(target.getAttribute("data-x")) || 0) + movedX;
 | 
			
		||||
        const movedY = this.moveDirection === "vertical" ? event.dy : 0;
 | 
			
		||||
        const y = (parseFloat(target.getAttribute("data-y")) || 0) + movedY;
 | 
			
		||||
 | 
			
		||||
        // target.style.transform =
 | 
			
		||||
        //   target.style.webkitTransform = `translate(${x}px, ${y}px)`;
 | 
			
		||||
        // target.setAttribute("data-x", x);
 | 
			
		||||
        // target.setAttribute("data-y", y);
 | 
			
		||||
 | 
			
		||||
        this.moveTo({ x, y, zIndex: this.zIndexWhenMoving });
 | 
			
		||||
 | 
			
		||||
        this.$emit("drag-move", {
 | 
			
		||||
          id: this.id,
 | 
			
		||||
          currentX: x, // 移动后的x值
 | 
			
		||||
          currentY: y,
 | 
			
		||||
          movedX, // 本次移动的距离
 | 
			
		||||
          movedY,
 | 
			
		||||
          deltaX: event.delta.x, // 正数向右,负数向左
 | 
			
		||||
          deltaY: event.delta.y,
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const dragEndListener = (event) => {
 | 
			
		||||
      if (!this.isFrozen) {
 | 
			
		||||
        const target = event.target;
 | 
			
		||||
        document.getElementsByTagName("body")[0].style.overflow = bodyOverflow;
 | 
			
		||||
 | 
			
		||||
        if (this.handleMoveEnd) {
 | 
			
		||||
          this.handleMoveEnd(this.Rebound);
 | 
			
		||||
        } else {
 | 
			
		||||
          target.style.zIndex = this.originalZindex;
 | 
			
		||||
          // target.style.transition = "";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const x = parseFloat(target.getAttribute("data-x")) || 0;
 | 
			
		||||
        const y = parseFloat(target.getAttribute("data-y")) || 0;
 | 
			
		||||
        this.$emit("drag-end", {
 | 
			
		||||
          id: this.id,
 | 
			
		||||
          currentX: x, // 移动后的x值
 | 
			
		||||
          currentY: y,
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // InteractJs("#" + this.myId).draggable({
 | 
			
		||||
    const interact = InteractJs(this.$refs["area-draggable"]).draggable({
 | 
			
		||||
      // enable inertial throwing
 | 
			
		||||
      inertia: this.inertia,
 | 
			
		||||
      // Prevent actions on child
 | 
			
		||||
      // ref: https://interactjs.io/docs/faq/#prevent-actions-on-child
 | 
			
		||||
      ignoreFrom: this.ignoreArea,
 | 
			
		||||
      // keep the element within the area of it's parent
 | 
			
		||||
      modifiers: [
 | 
			
		||||
        InteractJs.modifiers.restrictRect({
 | 
			
		||||
          restriction: this.restriction,
 | 
			
		||||
          endOnly: this.allowGoToOutside,
 | 
			
		||||
        }),
 | 
			
		||||
      ],
 | 
			
		||||
      // enable autoScroll
 | 
			
		||||
      autoScroll: false,
 | 
			
		||||
      // https://interactjs.io/docs/action-options/#cursorchecker
 | 
			
		||||
      // cursorChecker(action, interactable, element, interacting) {
 | 
			
		||||
      //   // don't set a cursor for drag actions
 | 
			
		||||
      //   return null;
 | 
			
		||||
      // },
 | 
			
		||||
      listeners: {
 | 
			
		||||
        start: dragStartListener,
 | 
			
		||||
        // call this function on every dragmove event
 | 
			
		||||
        move: dragMoveListener,
 | 
			
		||||
        // call this function on every dragend event
 | 
			
		||||
        end: dragEndListener,
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    interact.styleCursor(this.toStyleCursor);
 | 
			
		||||
 | 
			
		||||
    // interact.dropzone({
 | 
			
		||||
    //   accept: ".area-draggable",
 | 
			
		||||
    //   overlap: "center",
 | 
			
		||||
    //   ondropactivate: function (event) {},
 | 
			
		||||
    //   ondragenter: (event) => {
 | 
			
		||||
    //     // console.log("event :>> ", event);
 | 
			
		||||
    //     const draggableElement = event.relatedTarget;
 | 
			
		||||
    //     const dropzoneElement = event.target;
 | 
			
		||||
    //     this.$emit("drag-enter", {
 | 
			
		||||
    //       draggableElement,
 | 
			
		||||
    //       dropzoneElement,
 | 
			
		||||
    //       id: this.id,
 | 
			
		||||
    //     });
 | 
			
		||||
    //   },
 | 
			
		||||
    //   ondragleave: function (event) {},
 | 
			
		||||
    //   ondrop: function (event) {},
 | 
			
		||||
    //   ondropdeactivate: function (event) {},
 | 
			
		||||
    // });
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    moveTo({ x = 0, y = 0, zIndex = this.zIndexWhenMovingHeavy }) {
 | 
			
		||||
      const target = this.$refs["area-draggable"];
 | 
			
		||||
 | 
			
		||||
      if (zIndex) {
 | 
			
		||||
        target.style.zIndex = zIndex;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      target.style.transform =
 | 
			
		||||
        target.style.webkitTransform = `translate(${x}px, ${y}px)`;
 | 
			
		||||
      target.setAttribute("data-x", x);
 | 
			
		||||
      target.setAttribute("data-y", y);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 移动到某处(供外部调用)
 | 
			
		||||
    MoveTo({
 | 
			
		||||
      x = 0,
 | 
			
		||||
      y = 0,
 | 
			
		||||
      zIndex = this.zIndexWhenMovingLight,
 | 
			
		||||
      transitionDuration = null, // 移动动效时长,整数,单位毫秒
 | 
			
		||||
      transitionTimingFunction = null, // 移动动效函数,linear, ease等
 | 
			
		||||
      afterMoveTo,
 | 
			
		||||
    }) {
 | 
			
		||||
      const target = this.$refs["area-draggable"];
 | 
			
		||||
      const _x = parseFloat(target.getAttribute("data-x")) || 0;
 | 
			
		||||
      const _y = parseFloat(target.getAttribute("data-y")) || 0;
 | 
			
		||||
 | 
			
		||||
      if (x !== _x || y !== _y) {
 | 
			
		||||
        if (transitionDuration) {
 | 
			
		||||
          target.style.transition = `transform ${transitionDuration}ms ${
 | 
			
		||||
            transitionTimingFunction ? transitionTimingFunction : "ease"
 | 
			
		||||
          }`;
 | 
			
		||||
 | 
			
		||||
          if (zIndex) {
 | 
			
		||||
            target.style.zIndex = zIndex;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          setTimeout(() => {
 | 
			
		||||
            if (afterMoveTo) afterMoveTo();
 | 
			
		||||
            target.style.transition = "";
 | 
			
		||||
            target.style.zIndex = this.originalZindex;
 | 
			
		||||
          }, transitionDuration);
 | 
			
		||||
        } else {
 | 
			
		||||
          if (afterMoveTo) afterMoveTo();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        target.style.transform =
 | 
			
		||||
          target.style.webkitTransform = `translate(${x}px, ${y}px)`;
 | 
			
		||||
        target.setAttribute("data-x", x);
 | 
			
		||||
        target.setAttribute("data-y", y);
 | 
			
		||||
      } else {
 | 
			
		||||
        afterMoveTo && afterMoveTo();
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 恢复到上次移动后的位置(供外部调用)
 | 
			
		||||
    Rebound({
 | 
			
		||||
      transitionDuration = null, // 移动动效时长,整数,单位毫秒
 | 
			
		||||
      transitionTimingFunction = null, // 移动动效函数,linear, ease等
 | 
			
		||||
      afterRebound,
 | 
			
		||||
    }) {
 | 
			
		||||
      const target = this.$refs["area-draggable"];
 | 
			
		||||
      const x = parseFloat(target.getAttribute("data-x")) || 0;
 | 
			
		||||
      const y = parseFloat(target.getAttribute("data-y")) || 0;
 | 
			
		||||
      if (this.originalX !== x || this.originalY !== y) {
 | 
			
		||||
        this.MoveTo({
 | 
			
		||||
          x: this.originalX,
 | 
			
		||||
          y: this.originalY,
 | 
			
		||||
          transitionDuration,
 | 
			
		||||
          transitionTimingFunction,
 | 
			
		||||
          afterMoveTo: () => {
 | 
			
		||||
            afterRebound && afterRebound();
 | 
			
		||||
          },
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 把移动的痕迹抹除(供外部调用)
 | 
			
		||||
    Clear() {
 | 
			
		||||
      const target = this.$refs["area-draggable"];
 | 
			
		||||
      target.style.transform = this.originalTransform;
 | 
			
		||||
      target.style.transition = this.originalTransition;
 | 
			
		||||
      target.style.zIndex = this.originalZindex;
 | 
			
		||||
      target.setAttribute("data-x", 0);
 | 
			
		||||
      target.setAttribute("data-y", 0);
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.area-draggable {
 | 
			
		||||
  touch-action: none;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,196 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2020-07-05 20:44:16
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2021-08-23 11:53:13
 | 
			
		||||
 * @Description: 可拖动的对话框(样式仿照element ui)
 | 
			
		||||
--> 
 | 
			
		||||
<template>
 | 
			
		||||
  <Draggable
 | 
			
		||||
    :style="style"
 | 
			
		||||
    class="draggable-window"
 | 
			
		||||
    v-show="isDialogVisible"
 | 
			
		||||
    :ignoreArea="'.dialog-inner__body'"
 | 
			
		||||
    :allowGoToOutside="allowGoToOutside"
 | 
			
		||||
    :restriction="restriction"
 | 
			
		||||
    ref="drag"
 | 
			
		||||
    :moveDirection="moveDirection"
 | 
			
		||||
  >
 | 
			
		||||
    <div class="dialog-inner">
 | 
			
		||||
      <div class="dialog-inner__header">
 | 
			
		||||
        <span v-if="isSpanTitle" class="title">{{ title }}</span>
 | 
			
		||||
        <slot name="title"></slot>
 | 
			
		||||
        <div class="header-btns">
 | 
			
		||||
          <button class="header-btn" @click="handleDialogMinimize">
 | 
			
		||||
            <i
 | 
			
		||||
              :class="minimized ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"
 | 
			
		||||
            ></i>
 | 
			
		||||
          </button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <transition
 | 
			
		||||
        name="flatten"
 | 
			
		||||
        v-bind:css="false"
 | 
			
		||||
        @before-enter="beforeEnter"
 | 
			
		||||
        @enter="enter"
 | 
			
		||||
        @after-enter="afterEnter"
 | 
			
		||||
        @before-leave="beforeLeave"
 | 
			
		||||
        @leave="leave"
 | 
			
		||||
        @after-leave="afterLeave"
 | 
			
		||||
      >
 | 
			
		||||
        <div v-show="!minimized" ref="dial-body" class="dialog-inner__body">
 | 
			
		||||
          <slot></slot>
 | 
			
		||||
        </div>
 | 
			
		||||
      </transition>
 | 
			
		||||
    </div>
 | 
			
		||||
  </Draggable>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
import Draggable from "./Draggable.vue";
 | 
			
		||||
import Velocity from "velocity-animate";
 | 
			
		||||
export default {
 | 
			
		||||
  components: { Draggable },
 | 
			
		||||
  props: {
 | 
			
		||||
    // 默认是否显示
 | 
			
		||||
    visible: { type: Boolean, default: false },
 | 
			
		||||
    // 是否最小化窗口
 | 
			
		||||
    minimized: { type: Boolean, default: false },
 | 
			
		||||
    // 关闭窗口后再打开,是否复位
 | 
			
		||||
    isReposition: { type: Boolean, default: true },
 | 
			
		||||
    // 对话框的标题
 | 
			
		||||
    title: { type: String, default: "提示" },
 | 
			
		||||
    // 对话框的宽度
 | 
			
		||||
    width: { type: String, default: "30%" },
 | 
			
		||||
    // 对话框的顶部margin
 | 
			
		||||
    marginTop: { type: String, default: "15vh" },
 | 
			
		||||
    // 是否居中
 | 
			
		||||
    isCentered: { type: Boolean, default: true },
 | 
			
		||||
    // 对话框是否可以部分移出“移动区域”
 | 
			
		||||
    allowGoToOutside: { type: Boolean, default: true },
 | 
			
		||||
    // 对话框的“移动区域”,默认为对话框的父标签
 | 
			
		||||
    restriction: { type: String, default: "parent" },
 | 
			
		||||
    // 移动方向,both/horizontal/vertical,默认是全部方向
 | 
			
		||||
    moveDirection: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: "both",
 | 
			
		||||
      validator: function (value) {
 | 
			
		||||
        return ["both", "horizontal", "vertical"].indexOf(value) !== -1;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      isDialogVisible: this.visible,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    isSpanTitle: function () {
 | 
			
		||||
      return !this.$slots.title;
 | 
			
		||||
    },
 | 
			
		||||
    style: function () {
 | 
			
		||||
      let w = `width: ${this.width};`;
 | 
			
		||||
      let l = `left: calc(50% - ${this.width} / 2);`;
 | 
			
		||||
      let mt = `margin-top: ${this.marginTop};`;
 | 
			
		||||
 | 
			
		||||
      let stl = w + mt;
 | 
			
		||||
      if (this.isCentered) stl += l;
 | 
			
		||||
 | 
			
		||||
      return stl;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  watch: {
 | 
			
		||||
    visible: function (newVal) {
 | 
			
		||||
      this.isDialogVisible = newVal;
 | 
			
		||||
      if (newVal && this.isReposition) {
 | 
			
		||||
        this.$refs.drag.$el.style.transform = "unset";
 | 
			
		||||
        this.$refs.drag.$el.setAttribute("data-x", "");
 | 
			
		||||
        this.$refs.drag.$el.setAttribute("data-y", "");
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  beforeUpdate() {},
 | 
			
		||||
  mounted() {},
 | 
			
		||||
  methods: {
 | 
			
		||||
    handleDialogClose() {
 | 
			
		||||
      this.$emit("close");
 | 
			
		||||
      this.isDialogVisible = false;
 | 
			
		||||
      this.$emit("update:visible", false);
 | 
			
		||||
    },
 | 
			
		||||
    handleDialogMinimize() {
 | 
			
		||||
      this.$emit("update:minimized", !this.minimized);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    beforeEnter(el) {
 | 
			
		||||
      el.style.height = "0";
 | 
			
		||||
      el.style.overflow = "hidden";
 | 
			
		||||
    },
 | 
			
		||||
    enter(el, done) {
 | 
			
		||||
      Velocity(
 | 
			
		||||
        el,
 | 
			
		||||
        { height: el.scrollHeight }, // scrollHeight是一个不固定的高度,故只能用velocity库来做缩放了
 | 
			
		||||
        { duration: 300, complete: done }
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
    afterEnter(el) {
 | 
			
		||||
      el.style.height = "";
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    beforeLeave(el) {},
 | 
			
		||||
 | 
			
		||||
    leave(el, done) {
 | 
			
		||||
      Velocity(el, { height: "0px" }, { duration: 300, complete: done });
 | 
			
		||||
    },
 | 
			
		||||
    afterLeave(el) {
 | 
			
		||||
      el.style.height = "";
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang='scss' scoped>
 | 
			
		||||
.draggable-window {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  z-index: 2000;
 | 
			
		||||
 | 
			
		||||
  .dialog-inner {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    background: #fff;
 | 
			
		||||
    border-radius: 2px;
 | 
			
		||||
    box-shadow: 0 1px 8px rgba(0, 0, 0, 0.5);
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
    .dialog-inner__header {
 | 
			
		||||
      padding: 10px 20px 10px;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      &:hover {
 | 
			
		||||
        background-color: #eee;
 | 
			
		||||
      }
 | 
			
		||||
      .title {
 | 
			
		||||
        line-height: 18px;
 | 
			
		||||
        font-size: 12px;
 | 
			
		||||
        color: #303133;
 | 
			
		||||
      }
 | 
			
		||||
      .header-btns {
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        top: 0;
 | 
			
		||||
        right: 20px;
 | 
			
		||||
        .header-btn {
 | 
			
		||||
          width: 38px;
 | 
			
		||||
          height: 38px;
 | 
			
		||||
          padding: 0;
 | 
			
		||||
          background: transparent;
 | 
			
		||||
          border: none;
 | 
			
		||||
          outline: none;
 | 
			
		||||
          cursor: pointer;
 | 
			
		||||
          font-size: 16px;
 | 
			
		||||
          &:hover {
 | 
			
		||||
            background-color: #b3d8ff;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    .dialog-inner__body {
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,75 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-08-24 01:47:34
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2021-08-26 10:47:05
 | 
			
		||||
 * @Description: 用户头像组件
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <el-image
 | 
			
		||||
    class="icon-image"
 | 
			
		||||
    :style="iconSize"
 | 
			
		||||
    :src="iconPreviewUrl"
 | 
			
		||||
    :fit="'contain'"
 | 
			
		||||
  >
 | 
			
		||||
    <div slot="error" class="image-slot">
 | 
			
		||||
      <i class="el-icon-picture-outline"></i>
 | 
			
		||||
    </div>
 | 
			
		||||
  </el-image>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
import Qs from "qs";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  props: {
 | 
			
		||||
    size: { type: Number, default: 48 }, // 头像大小
 | 
			
		||||
    iconBaseUrl: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      required: true,
 | 
			
		||||
    },
 | 
			
		||||
    data: {
 | 
			
		||||
      type: Object,
 | 
			
		||||
      default: () => {
 | 
			
		||||
        return {};
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      flag: Date.now(),
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    iconSize: function () {
 | 
			
		||||
      return `width: ${this.size}px; height: ${this.size}px`;
 | 
			
		||||
    },
 | 
			
		||||
    iconPreviewUrl: function () {
 | 
			
		||||
      return (
 | 
			
		||||
        this.iconBaseUrl +
 | 
			
		||||
        "?" +
 | 
			
		||||
        Qs.stringify({ ...this.data, flag: this.flag }, { encode: false })
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    reflash() {
 | 
			
		||||
      this.flag = Date.now();
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
@import "~@/scss/_variables.scss";
 | 
			
		||||
.icon-image {
 | 
			
		||||
  border: 1px solid $theme-second-font-color-half;
 | 
			
		||||
  border-radius: 50%;
 | 
			
		||||
  ::v-deep .image-slot {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,107 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-08-07 16:02:32
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-04 17:50:37
 | 
			
		||||
 * @Description: 轻量级分列器(取代el-row和el-col)
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<!--
 | 
			
		||||
用法:
 | 
			
		||||
<LightSplit :ratios="[1, 1, 1]">
 | 
			
		||||
    <template v-slot:1>aaa</template>
 | 
			
		||||
    <template v-slot:2>bbb</template>
 | 
			
		||||
    <template v-slot:3>ccc</template>
 | 
			
		||||
    ...
 | 
			
		||||
    <template v-slot:n>zzz</template>
 | 
			
		||||
</LightSplit>
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="light-split-container" :style="containerStyle">
 | 
			
		||||
    <template v-if="percents.length > 0">
 | 
			
		||||
      <div class="row">
 | 
			
		||||
        <div
 | 
			
		||||
          v-for="(per, i) in percents"
 | 
			
		||||
          :key="i"
 | 
			
		||||
          :style="{ width: per + '%' }"
 | 
			
		||||
          class="col"
 | 
			
		||||
        >
 | 
			
		||||
          <slot :name="i + 1"></slot>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </template>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  props: {
 | 
			
		||||
    ratios: {
 | 
			
		||||
      type: Array, // 必须由正整数组成的数组
 | 
			
		||||
      required: true,
 | 
			
		||||
      validator: function (ratios) {
 | 
			
		||||
        for (const s of ratios)
 | 
			
		||||
          if (typeof s !== "number" || s <= 0) return false;
 | 
			
		||||
        return true;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    minHeight: {
 | 
			
		||||
      type: Number,
 | 
			
		||||
    },
 | 
			
		||||
    // 本组件最大高度,超出此高度则出现滚动条
 | 
			
		||||
    maxHeight: {
 | 
			
		||||
      type: Number,
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    containerStyle: function () {
 | 
			
		||||
      let styleObj = {};
 | 
			
		||||
      if (this.maxHeight || this.minHeight) {
 | 
			
		||||
        styleObj.overflowY = "auto";
 | 
			
		||||
        if (this.maxHeight) {
 | 
			
		||||
          styleObj.maxHeight = this.maxHeight + "px";
 | 
			
		||||
        }
 | 
			
		||||
        if (this.minHeight) {
 | 
			
		||||
          styleObj.minHeight = this.minHeight + "px";
 | 
			
		||||
        }
 | 
			
		||||
        return styleObj;
 | 
			
		||||
      } else return {};
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      percents: [],
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  beforeMount() {
 | 
			
		||||
    let total = 0;
 | 
			
		||||
    this.ratios.map((s) => (total += s));
 | 
			
		||||
    this.percents = [];
 | 
			
		||||
    for (const ratio of this.ratios) {
 | 
			
		||||
      let per = 100 * (ratio / total);
 | 
			
		||||
      this.percents.push(per);
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
@import "~@/scss/_variables.scss";
 | 
			
		||||
.light-split-container {
 | 
			
		||||
  overflow-y: auto;
 | 
			
		||||
  .row {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
    justify-content: space-evenly;
 | 
			
		||||
    align-items: stretch;
 | 
			
		||||
    flex-wrap: nowrap;
 | 
			
		||||
    .col {
 | 
			
		||||
      border: solid 1px $theme-background-dark;
 | 
			
		||||
      padding: 8px;
 | 
			
		||||
 | 
			
		||||
      &:not(:last-child) {
 | 
			
		||||
        border-right: none;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,386 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-12-20 09:54:19
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-06 13:59:12
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="sliders-container" ref="sliders-container">
 | 
			
		||||
    <Draggable
 | 
			
		||||
      :class="[index === activeIndex ? 'active' : '']"
 | 
			
		||||
      class="slider"
 | 
			
		||||
      v-for="(item, index) in sliders"
 | 
			
		||||
      ref="sliders"
 | 
			
		||||
      :key="index"
 | 
			
		||||
      :id="index"
 | 
			
		||||
      :inertia="false"
 | 
			
		||||
      :moveDirection="'horizontal'"
 | 
			
		||||
      :isFrozen="isFrozen"
 | 
			
		||||
      :style="{ width: perSliderWidth + 'px' }"
 | 
			
		||||
      @drag-start="onDragStart"
 | 
			
		||||
      @drag-move="onDragMove"
 | 
			
		||||
      @drag-end="onDragEnd"
 | 
			
		||||
      @drag-enter="onDragEnter"
 | 
			
		||||
      @mousedown.native="onSliderMousedown($event, index)"
 | 
			
		||||
    >
 | 
			
		||||
      <slot
 | 
			
		||||
        v-bind:item="item"
 | 
			
		||||
        v-bind:index="index"
 | 
			
		||||
        v-bind:isActive="index === activeIndex"
 | 
			
		||||
      ></slot>
 | 
			
		||||
    </Draggable>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
import Draggable from "./Drag/Draggable.vue";
 | 
			
		||||
// import Velocity from "velocity-animate";
 | 
			
		||||
export default {
 | 
			
		||||
  components: { Draggable },
 | 
			
		||||
  props: {
 | 
			
		||||
    // 滑块对象的数组
 | 
			
		||||
    sliders: {
 | 
			
		||||
      type: Array,
 | 
			
		||||
      default: function () {
 | 
			
		||||
        return [];
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    // 当前激活菜单的 index (支持.sync)
 | 
			
		||||
    currentActive: { type: Number, default: 0 },
 | 
			
		||||
    // 滑块移动方向
 | 
			
		||||
    direction: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: "horizontal",
 | 
			
		||||
      validator: function (value) {
 | 
			
		||||
        return ["horizontal", "vertical"].indexOf(value) !== -1;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    // 滑块滑动的时间
 | 
			
		||||
    millisecondOfMove: { type: Number, default: 250 },
 | 
			
		||||
    // 每个滑块的宽度(不传则不设定宽度)
 | 
			
		||||
    perSliderWidth: { type: Number },
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    // 当前被激活(选中)的标签index
 | 
			
		||||
    activeIndex: {
 | 
			
		||||
      get() {
 | 
			
		||||
        return this.currentActive;
 | 
			
		||||
      },
 | 
			
		||||
      set(val) {
 | 
			
		||||
        this.calcCurrSliderKeyPoints(val);
 | 
			
		||||
        this.activeSliderWidth = this.slidersInfo[val].width;
 | 
			
		||||
        this.$emit("update:currentActive", val);
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      slidersInfo: [], // {dom, width, midPoint}
 | 
			
		||||
      activeSliderStartPoint: 0, // slider 头部坐标
 | 
			
		||||
      activeSliderEndPoint: 0, // slider 尾部坐标
 | 
			
		||||
      activeSliderWidth: 0, // slider 的宽度
 | 
			
		||||
      indexOfSliderNeedToMoveCurrentlyAndActiveSliderShouldBe: 0,
 | 
			
		||||
      isFrozen: false,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.$nextTick(() => {
 | 
			
		||||
      this.refreshSlidersInfo();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // this.calcAllTabsMidPoints();
 | 
			
		||||
 | 
			
		||||
    // https://stackoverflow.com/questions/11700927/horizontal-scrolling-with-mouse-wheel-in-a-div
 | 
			
		||||
    const slidersContainer = this.$refs["sliders-container"];
 | 
			
		||||
    slidersContainer.addEventListener(
 | 
			
		||||
      "mousewheel",
 | 
			
		||||
      (e) => {
 | 
			
		||||
        e = window.event || e;
 | 
			
		||||
        var delta = Math.max(-1, Math.min(1, e.wheelDelta || -e.detail));
 | 
			
		||||
        slidersContainer.scrollLeft -= delta * 80; // Multiplied by 80
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
      },
 | 
			
		||||
      false
 | 
			
		||||
    );
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    DoFrozen() {
 | 
			
		||||
      this.isFrozen = true;
 | 
			
		||||
    },
 | 
			
		||||
    DisFrozen() {
 | 
			
		||||
      this.isFrozen = false;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onDragStart(info) {},
 | 
			
		||||
    onDragMove(info) {
 | 
			
		||||
      // 移动后的 slider 头部的坐标
 | 
			
		||||
      const activeSliderStartPointCurr =
 | 
			
		||||
        this.activeSliderStartPoint + info.currentX;
 | 
			
		||||
      // 移动后的 slider 尾部的坐标
 | 
			
		||||
      const activeSliderEndPointCurr =
 | 
			
		||||
        this.activeSliderEndPoint + info.currentX;
 | 
			
		||||
 | 
			
		||||
      const midPoints = this.slidersInfo.map((ti) => ti.midPoint);
 | 
			
		||||
      // 当前激活 slider 之前的 sliders 的中间点集合
 | 
			
		||||
      const midPointsBefore = midPoints.slice(0, this.activeIndex);
 | 
			
		||||
      // 当前激活 slider 之后的 sliders 的中间点集合
 | 
			
		||||
      const midPointsAfter = midPoints.slice(this.activeIndex + 1);
 | 
			
		||||
 | 
			
		||||
      let index = this.activeIndex;
 | 
			
		||||
      // 查找需要移动的 slider 的index
 | 
			
		||||
      function findNeedToMoveIndex() {
 | 
			
		||||
        if (info.currentX > 0) {
 | 
			
		||||
          for (const k in midPointsAfter) {
 | 
			
		||||
            const point = midPointsAfter[k];
 | 
			
		||||
            if (point > activeSliderEndPointCurr) {
 | 
			
		||||
              index += Number(k);
 | 
			
		||||
              return;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          index = midPoints.length - 1; // 最后一个 slider
 | 
			
		||||
        } else {
 | 
			
		||||
          for (const k in midPointsBefore.reverse()) {
 | 
			
		||||
            const point = midPointsBefore[k];
 | 
			
		||||
            if (point < activeSliderStartPointCurr) {
 | 
			
		||||
              index -= Number(k);
 | 
			
		||||
              return;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          index = 0; // 第0个 slider
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      findNeedToMoveIndex();
 | 
			
		||||
 | 
			
		||||
      this.indexOfSliderNeedToMoveCurrentlyAndActiveSliderShouldBe = index;
 | 
			
		||||
 | 
			
		||||
      // console.log("当前需要移动的 slider index :>> ", index);
 | 
			
		||||
 | 
			
		||||
      if (index !== this.activeIndex) {
 | 
			
		||||
        const refSlider = this.$refs["sliders"][index];
 | 
			
		||||
        let _index = index;
 | 
			
		||||
        if (index > this.activeIndex) {
 | 
			
		||||
          // 当需要移动的 slider 在当前激活 slider 的后面时
 | 
			
		||||
          refSlider.MoveTo({
 | 
			
		||||
            x: -this.activeSliderWidth,
 | 
			
		||||
            transitionDuration: this.millisecondOfMove,
 | 
			
		||||
            afterMoveTo: () => {},
 | 
			
		||||
          });
 | 
			
		||||
          // 当需要移动的 slider 的后面的 sliders 需要复原
 | 
			
		||||
          while (_index < midPoints.length - 1) {
 | 
			
		||||
            _index++;
 | 
			
		||||
            this.$refs["sliders"][_index].Rebound({
 | 
			
		||||
              transitionDuration: this.millisecondOfMove,
 | 
			
		||||
              afterRebound: () => {},
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          // 当需要移动的 slider 在当前激活 slider 的前面时
 | 
			
		||||
          refSlider.MoveTo({
 | 
			
		||||
            x: this.activeSliderWidth,
 | 
			
		||||
            transitionDuration: this.millisecondOfMove,
 | 
			
		||||
            afterMoveTo: () => {},
 | 
			
		||||
          });
 | 
			
		||||
          // 当需要移动的 slider 的前面的 sliders 需要复原
 | 
			
		||||
          while (_index > 0) {
 | 
			
		||||
            _index--;
 | 
			
		||||
            this.$refs["sliders"][_index].Rebound({
 | 
			
		||||
              transitionDuration: this.millisecondOfMove,
 | 
			
		||||
              afterRebound: () => {},
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        // 当需要移动的 slider 就是当前激活的 slider 时
 | 
			
		||||
        let _index = 0;
 | 
			
		||||
        while (_index < midPoints.length - 1) {
 | 
			
		||||
          if (_index !== this.activeIndex) {
 | 
			
		||||
            this.$refs["sliders"][_index].Rebound({
 | 
			
		||||
              transitionDuration: this.millisecondOfMove,
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
          _index++;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onDragEnd(info) {
 | 
			
		||||
      const index =
 | 
			
		||||
        this.indexOfSliderNeedToMoveCurrentlyAndActiveSliderShouldBe;
 | 
			
		||||
 | 
			
		||||
      const widths = this.slidersInfo.map((ti) => ti.width);
 | 
			
		||||
      let point = 0;
 | 
			
		||||
      if (index > this.activeIndex) {
 | 
			
		||||
        // 移动到原位置的右边
 | 
			
		||||
        for (let i = this.activeIndex + 1; i <= index; i++) {
 | 
			
		||||
          point += widths[i];
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        // 移动到原位置的左边 或保持原位
 | 
			
		||||
        for (let i = index; i < this.activeIndex; i++) {
 | 
			
		||||
          point -= widths[i];
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const currSlider = this.$refs["sliders"][this.activeIndex];
 | 
			
		||||
 | 
			
		||||
      this.isFrozen = true;
 | 
			
		||||
 | 
			
		||||
      currSlider.MoveTo({
 | 
			
		||||
        x: point,
 | 
			
		||||
        transitionDuration: this.millisecondOfMove,
 | 
			
		||||
        zIndex: 1000,
 | 
			
		||||
 | 
			
		||||
        afterMoveTo: () => {
 | 
			
		||||
          this.dragAndDrop(
 | 
			
		||||
            this.sliders,
 | 
			
		||||
            this.activeIndex,
 | 
			
		||||
            this.indexOfSliderNeedToMoveCurrentlyAndActiveSliderShouldBe
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
          this.$nextTick(() => {
 | 
			
		||||
            this.$refs["sliders"].forEach((slider) => {
 | 
			
		||||
              slider.Clear();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            this.refreshSlidersInfo();
 | 
			
		||||
            // this.calcAllTabsMidPoints();
 | 
			
		||||
 | 
			
		||||
            this.activeIndex =
 | 
			
		||||
              this.indexOfSliderNeedToMoveCurrentlyAndActiveSliderShouldBe;
 | 
			
		||||
 | 
			
		||||
            this.isFrozen = false;
 | 
			
		||||
          });
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onDragEnter(info) {},
 | 
			
		||||
 | 
			
		||||
    onSliderMousedown(event, index) {
 | 
			
		||||
      switch (event.button) {
 | 
			
		||||
        case 0: // 左键
 | 
			
		||||
          if (!this.isFrozen) this.activeIndex = index;
 | 
			
		||||
          break;
 | 
			
		||||
        case 1: // 中轮
 | 
			
		||||
          break;
 | 
			
		||||
        case 2: // 右键
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // handleMoveEnd(Rebound) {
 | 
			
		||||
    //   Rebound({});
 | 
			
		||||
    // },
 | 
			
		||||
 | 
			
		||||
    // 更新所有 slider 的信息
 | 
			
		||||
    refreshSlidersInfo() {
 | 
			
		||||
      const slidersRef = this.$refs["sliders"];
 | 
			
		||||
      this.slidersInfo = [];
 | 
			
		||||
      // 找出 slider 的宽度
 | 
			
		||||
      for (const sliderRef of slidersRef) {
 | 
			
		||||
        const dom = sliderRef.$el;
 | 
			
		||||
        const width = dom.getBoundingClientRect().width;
 | 
			
		||||
 | 
			
		||||
        const rectSlider = dom.getBoundingClientRect();
 | 
			
		||||
        const rectContainer =
 | 
			
		||||
          this.$refs["sliders-container"].getBoundingClientRect();
 | 
			
		||||
        const startPoint = rectSlider.left - rectContainer.left;
 | 
			
		||||
        const endPoint = rectSlider.right - rectContainer.left;
 | 
			
		||||
        const midPoint = startPoint + width / 2;
 | 
			
		||||
 | 
			
		||||
        this.slidersInfo.push({
 | 
			
		||||
          dom,
 | 
			
		||||
          width,
 | 
			
		||||
          startPoint,
 | 
			
		||||
          endPoint,
 | 
			
		||||
          midPoint,
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 计算当前激活的 slider 的头部和尾部相对坐标点
 | 
			
		||||
    calcCurrSliderKeyPoints(activeIndex) {
 | 
			
		||||
      // ----------下面是旧写法------------------------------------------
 | 
			
		||||
      // const widths = this.slidersInfo.map((ti) => ti.width);
 | 
			
		||||
      // const widthsLess = widths.slice(0, activeIndex);
 | 
			
		||||
      // const widthsMore = widths.slice(0, activeIndex + 1);
 | 
			
		||||
 | 
			
		||||
      // let sumStartPoint = 0,
 | 
			
		||||
      //   sumEndPoint = 0;
 | 
			
		||||
      // widthsLess.forEach((el) => (sumStartPoint += el));
 | 
			
		||||
      // widthsMore.forEach((el) => (sumEndPoint += el));
 | 
			
		||||
 | 
			
		||||
      // this.activeSliderStartPoint = sumStartPoint;
 | 
			
		||||
      // this.activeSliderEndPoint = sumEndPoint;
 | 
			
		||||
 | 
			
		||||
      // ----------下面是新写法------------------------------------------
 | 
			
		||||
      const doms = this.slidersInfo.map((ti) => ti.dom);
 | 
			
		||||
      const rectActive = doms[activeIndex].getBoundingClientRect();
 | 
			
		||||
      const rectContainer =
 | 
			
		||||
        this.$refs["sliders-container"].getBoundingClientRect();
 | 
			
		||||
 | 
			
		||||
      const startPoint = rectActive.left - rectContainer.left;
 | 
			
		||||
      const endPoint = rectActive.right - rectContainer.left;
 | 
			
		||||
 | 
			
		||||
      this.activeSliderStartPoint = startPoint;
 | 
			
		||||
      this.activeSliderEndPoint = endPoint;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 找出 slider 的中间点(弃用)
 | 
			
		||||
    calcAllTabsMidPoints() {
 | 
			
		||||
      for (const key in this.slidersInfo) {
 | 
			
		||||
        if (Object.hasOwnProperty.call(this.slidersInfo, key)) {
 | 
			
		||||
          const ti = this.slidersInfo[key];
 | 
			
		||||
          if (key - 1 >= 0) {
 | 
			
		||||
            ti.midPoint =
 | 
			
		||||
              this.slidersInfo[key - 1].midPoint +
 | 
			
		||||
              this.slidersInfo[key - 1].width / 2 +
 | 
			
		||||
              ti.width / 2;
 | 
			
		||||
          } else {
 | 
			
		||||
            ti.midPoint = ti.width / 2;
 | 
			
		||||
          }
 | 
			
		||||
          // ti.title = this.sliders[key].title;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    swap(arr, i1, i2) {
 | 
			
		||||
      arr[i1] = arr.splice(i2, 1, arr[i1])[0];
 | 
			
		||||
      return arr;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    dragAndDrop(arr, i1, i2) {
 | 
			
		||||
      let o = arr.splice(i1, 1)[0];
 | 
			
		||||
      arr.splice(i2, 0, o);
 | 
			
		||||
      return arr;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
@import "~@/scss/_variables.scss";
 | 
			
		||||
.sliders-container {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  overflow: auto;
 | 
			
		||||
 | 
			
		||||
  scrollbar-width: none; /* For Firefox */
 | 
			
		||||
  -ms-overflow-style: none; /* For Internet Explorer and Edge */
 | 
			
		||||
  &::-webkit-scrollbar {
 | 
			
		||||
    /* For Chrome, Safari, and Opera */
 | 
			
		||||
    // width: 0px;
 | 
			
		||||
    height: 0px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .slider {
 | 
			
		||||
    // flex-grow: 1;
 | 
			
		||||
    flex-shrink: 0;
 | 
			
		||||
    user-select: none;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,162 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2022-01-01 05:27:31
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-06 14:32:19
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="tabs-container">
 | 
			
		||||
    <Sliders
 | 
			
		||||
      class="tabs-sliders"
 | 
			
		||||
      ref="tabs-sliders"
 | 
			
		||||
      :sliders="tabs"
 | 
			
		||||
      :currentActive.sync="currentActive"
 | 
			
		||||
    >
 | 
			
		||||
      <template v-slot="{ item, index, isActive }">
 | 
			
		||||
        <div
 | 
			
		||||
          class="tab"
 | 
			
		||||
          :class="{ active: isActive }"
 | 
			
		||||
          :style="{
 | 
			
		||||
            width: `${perTabWidth}px`,
 | 
			
		||||
            maxWidth: `${perTabMaxWidth}px`,
 | 
			
		||||
            padding: `0 ${perTabPadding}px`,
 | 
			
		||||
          }"
 | 
			
		||||
          @mouseenter="onTabMouseenter(item, index)"
 | 
			
		||||
        >
 | 
			
		||||
          <span
 | 
			
		||||
            class="title"
 | 
			
		||||
            :style="{
 | 
			
		||||
              width: `${titleWidth}px`,
 | 
			
		||||
            }"
 | 
			
		||||
          >
 | 
			
		||||
            {{ item.title }}
 | 
			
		||||
          </span>
 | 
			
		||||
          <span
 | 
			
		||||
            class="close"
 | 
			
		||||
            :style="{ width: `${tabCloseWidth}px` }"
 | 
			
		||||
            @click="onTabCloseClick(item, index)"
 | 
			
		||||
            @mousedown="onTabCloseMousedown(item, index)"
 | 
			
		||||
            @mouseup="onTabCloseMouseup(item, index)"
 | 
			
		||||
            @mouseenter="onTabCloseMouseenter(item, index)"
 | 
			
		||||
            @mouseleave="onTabCloseMouseleave(item, index)"
 | 
			
		||||
            ><i class="el-icon-close icon"></i
 | 
			
		||||
          ></span>
 | 
			
		||||
        </div>
 | 
			
		||||
      </template>
 | 
			
		||||
    </Sliders>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
import Sliders from "../Sliders.vue";
 | 
			
		||||
export default {
 | 
			
		||||
  components: { Sliders },
 | 
			
		||||
  props: {
 | 
			
		||||
    // 标签数据对象的属性,数据对象的类型是@/entity/Ui/Tab/Tab.js
 | 
			
		||||
    tabs: {
 | 
			
		||||
      type: Array,
 | 
			
		||||
      default: function () {
 | 
			
		||||
        return [];
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    // 当前激活菜单的 index (支持.sync)
 | 
			
		||||
    tabsCurrActiveIndex: { type: Number, default: 0 },
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    currentActive: {
 | 
			
		||||
      get() {
 | 
			
		||||
        return this.tabsCurrActiveIndex;
 | 
			
		||||
      },
 | 
			
		||||
      set(val) {
 | 
			
		||||
        this.$emit("update:tabsCurrActiveIndex", val);
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    titleWidth: function () {
 | 
			
		||||
      return (
 | 
			
		||||
        Math.min(this.perTabWidth, this.perTabMaxWidth) -
 | 
			
		||||
        this.tabCloseWidth -
 | 
			
		||||
        this.perTabPadding * 2
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      isTabCloseMousedown: false,
 | 
			
		||||
      tabCloseWidth: 18,
 | 
			
		||||
      perTabPadding: 8,
 | 
			
		||||
      perTabMaxWidth: 200,
 | 
			
		||||
      perTabWidth: this.perTabMaxWidth,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    const rectSliders = this.$refs["tabs-sliders"].$el.getBoundingClientRect();
 | 
			
		||||
    this.perTabWidth = rectSliders.width / this.tabs.length;
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    onTabCloseClick(item, index) {},
 | 
			
		||||
    onTabMouseenter(item, index) {
 | 
			
		||||
      // if (!this.isTabCloseMousedown) {
 | 
			
		||||
      //   this.$refs["tabs-sliders"].DisFrozen();
 | 
			
		||||
      // }
 | 
			
		||||
    },
 | 
			
		||||
    onTabCloseMousedown(item, index) {
 | 
			
		||||
      // this.$refs["tabs-sliders"].DoFrozen();
 | 
			
		||||
      // this.isTabCloseMousedown = true;
 | 
			
		||||
    },
 | 
			
		||||
    onTabCloseMouseup(item, index) {
 | 
			
		||||
      // this.$refs["tabs-sliders"].DisFrozen();
 | 
			
		||||
      // this.isTabCloseMousedown = false;
 | 
			
		||||
    },
 | 
			
		||||
    onTabCloseMouseenter(item, index) {
 | 
			
		||||
      // this.$refs["tabs-sliders"].DoFrozen();
 | 
			
		||||
      // console.log("onTabCloseMouseenter");
 | 
			
		||||
    },
 | 
			
		||||
    onTabCloseMouseleave(item, index) {
 | 
			
		||||
      // this.$refs["tabs-sliders"].DisFrozen();
 | 
			
		||||
      // if (!this.isTabCloseMousedown) {
 | 
			
		||||
      //   this.$refs["tabs-sliders"].DisFrozen();
 | 
			
		||||
      // }
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
@import "~@/scss/_variables.scss";
 | 
			
		||||
.tabs-container {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 36px;
 | 
			
		||||
  line-height: 36px;
 | 
			
		||||
  background-color: $theme-background-dark;
 | 
			
		||||
  .tabs-sliders {
 | 
			
		||||
    .tab {
 | 
			
		||||
      height: 100%;
 | 
			
		||||
      box-sizing: border-box;
 | 
			
		||||
      background-color: $theme-background-dark;
 | 
			
		||||
      cursor: default;
 | 
			
		||||
      &.active {
 | 
			
		||||
        background-color: #fff;
 | 
			
		||||
      }
 | 
			
		||||
      .title {
 | 
			
		||||
        display: block;
 | 
			
		||||
        float: left;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
      }
 | 
			
		||||
      .close {
 | 
			
		||||
        display: block;
 | 
			
		||||
        box-sizing: border-box;
 | 
			
		||||
        float: left;
 | 
			
		||||
        padding-left: 4px;
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
        .icon {
 | 
			
		||||
          border-radius: 50%;
 | 
			
		||||
          padding: 1px;
 | 
			
		||||
          &:hover {
 | 
			
		||||
            background-color: #bbb;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,172 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-08-28 17:29:55
 | 
			
		||||
 * @LastEditors: Guanghao
 | 
			
		||||
 * @LastEditTime: 2022-01-05 18:02:56
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="tree-item">
 | 
			
		||||
    <div class="title">{{ data.name }}</div>
 | 
			
		||||
    <div class="btn" v-if="hasBtn">
 | 
			
		||||
      <el-popover
 | 
			
		||||
        placement="right"
 | 
			
		||||
        trigger="hover"
 | 
			
		||||
        popper-class="tree-node-pop"
 | 
			
		||||
        width="120"
 | 
			
		||||
      >
 | 
			
		||||
        <div slot="reference" class="dots">
 | 
			
		||||
          <svg-icon icon-class="vertical-more"></svg-icon>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="title">{{ data.name }}</div>
 | 
			
		||||
 | 
			
		||||
        <el-menu
 | 
			
		||||
          @select="
 | 
			
		||||
            (index, indexPath) => {
 | 
			
		||||
              this.$emit('menu-select', index, indexPath, data);
 | 
			
		||||
            }
 | 
			
		||||
          "
 | 
			
		||||
        >
 | 
			
		||||
          <el-menu-item index="edit" v-if="btns.includes('edit')">
 | 
			
		||||
            <div class="menu-item-content">
 | 
			
		||||
              <div class="icon">
 | 
			
		||||
                <svg-icon icon-class="edit"></svg-icon>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="text">编 辑</div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </el-menu-item>
 | 
			
		||||
          <el-menu-item index="add" v-if="btns.includes('add')">
 | 
			
		||||
            <div class="menu-item-content">
 | 
			
		||||
              <div class="icon">
 | 
			
		||||
                <svg-icon icon-class="add"></svg-icon>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="text">{{ addBtnName }}</div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </el-menu-item>
 | 
			
		||||
          <el-menu-item index="move" v-if="btns.includes('move')">
 | 
			
		||||
            <div class="menu-item-content">
 | 
			
		||||
              <div class="icon">
 | 
			
		||||
                <svg-icon icon-class="move" class="move"></svg-icon>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="text">移 动</div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </el-menu-item>
 | 
			
		||||
          <el-menu-item index="delete" v-if="btns.includes('delete')">
 | 
			
		||||
            <div class="menu-item-content">
 | 
			
		||||
              <div class="icon">
 | 
			
		||||
                <svg-icon icon-class="delete"></svg-icon>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="text">删 除</div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </el-menu-item>
 | 
			
		||||
        </el-menu>
 | 
			
		||||
      </el-popover>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  components: {},
 | 
			
		||||
  props: {
 | 
			
		||||
    // 节点数据,一般至少包含id和name
 | 
			
		||||
    data: {
 | 
			
		||||
      type: Object,
 | 
			
		||||
      default: () => {
 | 
			
		||||
        return {};
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    // 是否有操作按钮
 | 
			
		||||
    hasBtn: {
 | 
			
		||||
      type: Boolean,
 | 
			
		||||
      default: true,
 | 
			
		||||
    },
 | 
			
		||||
    // 操作按钮的种类(edit/add/move/delete)
 | 
			
		||||
    btns: {
 | 
			
		||||
      type: Array, // 字符串数组
 | 
			
		||||
      default: () => {
 | 
			
		||||
        return ["edit", "add", "move", "delete"];
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    addBtnName: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: "新增子节点",
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {};
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
@import "~@/scss/_variables.scss";
 | 
			
		||||
$dots-btn-width: 22px;
 | 
			
		||||
.tree-item {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  .title {
 | 
			
		||||
    float: left;
 | 
			
		||||
    width: calc(100% - #{$dots-btn-width});
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    text-overflow: ellipsis;
 | 
			
		||||
  }
 | 
			
		||||
  .btn {
 | 
			
		||||
    float: right;
 | 
			
		||||
    width: $dots-btn-width;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    display: none;
 | 
			
		||||
    .dots {
 | 
			
		||||
      box-sizing: border-box;
 | 
			
		||||
      padding: 3px 0;
 | 
			
		||||
    }
 | 
			
		||||
    &:hover {
 | 
			
		||||
      background-color: $theme-background-dark;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  &:hover {
 | 
			
		||||
    .btn {
 | 
			
		||||
      display: block;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.el-popover.tree-node-pop {
 | 
			
		||||
  .title {
 | 
			
		||||
    padding: 4px 8px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    background-color: $theme-background-dark;
 | 
			
		||||
  }
 | 
			
		||||
  .el-menu {
 | 
			
		||||
    border-right: none; // 取消垂直导航菜单默认的右边框
 | 
			
		||||
    .el-menu-item {
 | 
			
		||||
      padding-left: 4px !important;
 | 
			
		||||
      line-height: 32px;
 | 
			
		||||
      height: 32px;
 | 
			
		||||
      .menu-item-content {
 | 
			
		||||
        .icon {
 | 
			
		||||
          float: left;
 | 
			
		||||
          width: 20px;
 | 
			
		||||
          height: 32px;
 | 
			
		||||
          box-sizing: border-box;
 | 
			
		||||
          padding: 6px 0;
 | 
			
		||||
          margin-right: 5px;
 | 
			
		||||
          svg {
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        .text {
 | 
			
		||||
          font-size: 14px;
 | 
			
		||||
          color: $font-color-light;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.svg-icon {
 | 
			
		||||
  width: 20px;
 | 
			
		||||
  height: 20px;
 | 
			
		||||
  color: $font-color-light;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,173 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2020-09-30 08:54:15
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2021-06-06 02:21:47
 | 
			
		||||
 * @Description: 文件上传组件(部分接口仿照Element的Upload组件)
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <div class="upload" @click="handleBtnFileUpload">
 | 
			
		||||
      <slot></slot>
 | 
			
		||||
    </div>
 | 
			
		||||
    <input
 | 
			
		||||
      type="file"
 | 
			
		||||
      name="resource"
 | 
			
		||||
      :multiple="multiple"
 | 
			
		||||
      ref="resource"
 | 
			
		||||
      @change="handleFileInputChange"
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
import UploadAxios from "../../api/Disk/_UploadAxios.js";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  props: {
 | 
			
		||||
    disabled: { type: Boolean, default: false }, // 是否不可用
 | 
			
		||||
    beforeUploadAll: Function, // 上传所有文件之前的钩子,返回false将会中止所有文件上传
 | 
			
		||||
    beforeUpload: Function, // 上传单个文件之前的钩子,返回false将会中止单个文件上传
 | 
			
		||||
    onBegin: Function, // 开始上传单个文件时的钩子,后于beforeUpload,文件必定会开始上传
 | 
			
		||||
    onChange: Function,
 | 
			
		||||
    onSuccess: Function,
 | 
			
		||||
    onProgress: Function,
 | 
			
		||||
    onError: Function,
 | 
			
		||||
    onExceed: Function,
 | 
			
		||||
    autoUpload: { type: Boolean, default: true },
 | 
			
		||||
    multiple: { type: Boolean, default: false },
 | 
			
		||||
    limit: { type: Number },
 | 
			
		||||
    action: { type: String, required: true },
 | 
			
		||||
    extraData: Object,
 | 
			
		||||
    fileSizeAttrName: String, // 文件大小 字段对应的字段名(如果不传,则每个文件的大小不会上传服务器)
 | 
			
		||||
    fileNameAttrName: String // 文件名 字段对应的字段名(如果不传,则每个文件的文件名不会上传服务器)
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      fileInput: null
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.fileInput = this.$refs["resource"];
 | 
			
		||||
    this.fileInput.style.display = "none";
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    setFiles(files) {
 | 
			
		||||
      if (this.fileInput) {
 | 
			
		||||
        this.fileInput.files = files;
 | 
			
		||||
      } else {
 | 
			
		||||
        throw new Error("fileInput 未初始化");
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 清空选择的所有文件(恢复input的初始状态,目的是使onChange事件在选择同一文件时也会触发)
 | 
			
		||||
    clearFiles() {
 | 
			
		||||
      this.fileInput.value = null;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 上传按钮被物理点击
 | 
			
		||||
    handleBtnFileUpload() {
 | 
			
		||||
      if (!this.disabled) this.fileInput && this.fileInput.click();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 选择要上传的文件时
 | 
			
		||||
    handleFileInputChange(event) {
 | 
			
		||||
      let files = event.target.files;
 | 
			
		||||
      this.onChange && this.onChange(files);
 | 
			
		||||
      if (this.autoUpload) {
 | 
			
		||||
        this.upload(files);
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 上传文件
 | 
			
		||||
    upload(files) {
 | 
			
		||||
      let _files = files ? files : this.fileInput.files;
 | 
			
		||||
      if (this.beforeUploadAll) {
 | 
			
		||||
        const beforeAll = this.beforeUploadAll(_files);
 | 
			
		||||
        if (beforeAll && beforeAll.then) {
 | 
			
		||||
          beforeAll
 | 
			
		||||
            .then(result => {
 | 
			
		||||
              if (result !== false) {
 | 
			
		||||
                this.$$_upload(_files);
 | 
			
		||||
              }
 | 
			
		||||
            })
 | 
			
		||||
            .catch(err => {
 | 
			
		||||
              // console.log("err :>> ", err);
 | 
			
		||||
            });
 | 
			
		||||
        } else if (beforeAll !== false) {
 | 
			
		||||
          this.$$_upload(_files);
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        this.$$_upload(_files);
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @description 上传多个文件
 | 
			
		||||
     * @param {Array.<File>} files 表单文件对象数组
 | 
			
		||||
     */
 | 
			
		||||
    $$_upload(files) {
 | 
			
		||||
      if (this.beforeUpload) {
 | 
			
		||||
        for (const file of files) {
 | 
			
		||||
          const before = this.beforeUpload(file, files);
 | 
			
		||||
          if (before && before.then) {
 | 
			
		||||
            before.then(result => {
 | 
			
		||||
              if (result !== false) {
 | 
			
		||||
                this.$_upload(file, files);
 | 
			
		||||
              }
 | 
			
		||||
            });
 | 
			
		||||
          } else if (before !== false) {
 | 
			
		||||
            this.$_upload(file, files);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        for (const file of files) {
 | 
			
		||||
          this.$_upload(file, files);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @description 上传单个文件
 | 
			
		||||
     * @param {File} file 表单文件对象
 | 
			
		||||
     * @param {Array.<File>} files 表单文件对象数组
 | 
			
		||||
     * @returns {Function} 用于中止上传的函数
 | 
			
		||||
     */
 | 
			
		||||
    $_upload(file, files) {
 | 
			
		||||
      if (this.limit && this.limit > 0) {
 | 
			
		||||
        if (files.length > this.limit) {
 | 
			
		||||
          this.onExceed && this.onExceed(files);
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      let extraData = JSON.parse(JSON.stringify(this.extraData));
 | 
			
		||||
 | 
			
		||||
      extraData[this.fileSizeAttrName] = file.size;
 | 
			
		||||
      extraData[this.fileNameAttrName] = file.name;
 | 
			
		||||
 | 
			
		||||
      // let slicedBlob = file.slice(start);
 | 
			
		||||
 | 
			
		||||
      return UploadAxios.upload(
 | 
			
		||||
        this.action,
 | 
			
		||||
        file,
 | 
			
		||||
        // slicedBlob,
 | 
			
		||||
        extraData,
 | 
			
		||||
        this.onBegin,
 | 
			
		||||
        this.onSuccess,
 | 
			
		||||
        this.onProgress,
 | 
			
		||||
        this.onError,
 | 
			
		||||
        "file"
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.upload {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  outline: 0;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,88 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2020-09-28 13:54:46
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2021-06-06 02:22:29
 | 
			
		||||
 * @Description: 上传拖拽面板
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="upload-pane">
 | 
			
		||||
    <div v-if="showPane" class="drag-area" v-on:dragleave="handleDragleave" v-on:drop="handleDrop">
 | 
			
		||||
      <div class="upload-tip">上传文件到当前目录</div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
// https://stackoverflow.com/questions/10261989/html5-javascript-drag-and-drop-file-from-external-window-windows-explorer
 | 
			
		||||
export default {
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      showPane: false,
 | 
			
		||||
      dropZone: null,
 | 
			
		||||
      onDragenter: e => {
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        e.dataTransfer.dropEffect = "move";
 | 
			
		||||
        this.showPane = true;
 | 
			
		||||
      },
 | 
			
		||||
      onDragover: e => {
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault(); // 此处是取消文件拖拉后另页打开或弹框下载的关键
 | 
			
		||||
        e.dataTransfer.dropEffect = "move";
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.dropZone = document.getElementsByTagName("body")[0];
 | 
			
		||||
    this.dropZone.addEventListener("dragenter", this.onDragenter);
 | 
			
		||||
    this.dropZone.addEventListener("dragover", this.onDragover);
 | 
			
		||||
 | 
			
		||||
    // dropZone.addEventListener("drop", (e) => {
 | 
			
		||||
    //   e.stopPropagation();
 | 
			
		||||
    //   e.preventDefault();
 | 
			
		||||
    //   e.dataTransfer.dropEffect = "move";
 | 
			
		||||
    // });
 | 
			
		||||
  },
 | 
			
		||||
  destroyed() {
 | 
			
		||||
    this.dropZone &&
 | 
			
		||||
      this.dropZone.removeEventListener("dragenter", this.onDragenter);
 | 
			
		||||
    this.dropZone &&
 | 
			
		||||
      this.dropZone.removeEventListener("dragover", this.onDragover);
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    handleDragleave(e) {
 | 
			
		||||
      e.stopPropagation();
 | 
			
		||||
      e.preventDefault();
 | 
			
		||||
      this.showPane = false;
 | 
			
		||||
    },
 | 
			
		||||
    handleDrop(e) {
 | 
			
		||||
      e.stopPropagation();
 | 
			
		||||
      e.preventDefault();
 | 
			
		||||
      this.showPane = false;
 | 
			
		||||
      let files = e.dataTransfer.files; // Array of all files
 | 
			
		||||
      this.$emit("file-dropped", files); // https://developer.mozilla.org/zh-CN/docs/Web/API/DataTransfer/files
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.drag-area {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  z-index: 1000;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
  border: 3px darkgray dashed;
 | 
			
		||||
  background-color: rgba(255, 255, 255, 0.6);
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  .upload-tip {
 | 
			
		||||
    font-size: 36px;
 | 
			
		||||
    color: darkgray;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,208 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2020-10-21 10:00:00
 | 
			
		||||
 * @LastEditors: aqi
 | 
			
		||||
 * @LastEditTime: 2021-09-09 17:13:35
 | 
			
		||||
 * @Description: 视频播放核心组件
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div ref="vContainer" :style="{ height: height + 'px' }" class="videojs">
 | 
			
		||||
    <video
 | 
			
		||||
      ref="videoPlayer"
 | 
			
		||||
      :key="key"
 | 
			
		||||
      class="my-video video-js vjs-default-skin"
 | 
			
		||||
      :style="{ 'object-fit': objectFit }"
 | 
			
		||||
    ></video>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
/**
 | 
			
		||||
 * @see video.js官网 https://videojs.com/
 | 
			
		||||
 * @see 在vue中用法 https://docs.videojs.com/tutorial-vue.html
 | 
			
		||||
 * @see 播放flash,rtmp https://blog.videojs.com/video-js-removes-flash-from-core-player/
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import videojs from "video.js";
 | 
			
		||||
// import videojsFlash from "videojs-flash";
 | 
			
		||||
import "video.js/dist/video-js.css";
 | 
			
		||||
 | 
			
		||||
// import "videojs_snapshot_new/dist/plugins/videojs.imageCapture.js";
 | 
			
		||||
// import "videojs_snapshot_new/dist/videojs-snapshot.js";
 | 
			
		||||
// import "videojs_snapshot_new/src/css/videojs.snapshot.css"
 | 
			
		||||
 | 
			
		||||
let WIDTH_DEFAULT = 800;
 | 
			
		||||
let HEIGHT_DEFAULT = 450;
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  // 视频源src和视频格式type必传
 | 
			
		||||
  props: {
 | 
			
		||||
    // 视频源
 | 
			
		||||
    src: { type: String, required: true },
 | 
			
		||||
    // 视频格式,可传入flv、hls、mp4、ogg、webm等格式,会通过initType转化为source标签里面对应的type
 | 
			
		||||
    type: { type: String, required: true },
 | 
			
		||||
    // 自动播放(非静音状态无法自动播放)
 | 
			
		||||
    autoplay: { type: Boolean, default: true },
 | 
			
		||||
    // 静音
 | 
			
		||||
    muted: { type: Boolean, default: true },
 | 
			
		||||
    // 控制条
 | 
			
		||||
    controls: { type: Boolean, default: true },
 | 
			
		||||
    // 循环播放
 | 
			
		||||
    loop: { type: Boolean, default: false },
 | 
			
		||||
    // 背景图片地址
 | 
			
		||||
    coverAddr: { type: String },
 | 
			
		||||
    // 视频宽度
 | 
			
		||||
    // width: { type: Number, default: WIDTH_DEFAULT },
 | 
			
		||||
    // 视频高度
 | 
			
		||||
    height: { type: Number, default: HEIGHT_DEFAULT },
 | 
			
		||||
    objectFit: { type: String, default: "" },
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      key: "player-" + new Date().getTime(),
 | 
			
		||||
      player: null,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  computed: {
 | 
			
		||||
    exactType: function () {
 | 
			
		||||
      let _exactType;
 | 
			
		||||
      switch (this.type.toUpperCase()) {
 | 
			
		||||
        case "FLV":
 | 
			
		||||
          _exactType = "video/x-flv";
 | 
			
		||||
          break;
 | 
			
		||||
        case "HLS":
 | 
			
		||||
          _exactType = "application/x-mpegURL";
 | 
			
		||||
          break;
 | 
			
		||||
        case "MP4":
 | 
			
		||||
          _exactType = "video/mp4";
 | 
			
		||||
          break;
 | 
			
		||||
        case "OGG":
 | 
			
		||||
          _exactType = "video/ogg";
 | 
			
		||||
          break;
 | 
			
		||||
        case "WEBM":
 | 
			
		||||
          _exactType = "video/webm";
 | 
			
		||||
          break;
 | 
			
		||||
        case "RTMP":
 | 
			
		||||
          _exactType = "rtmp/flv";
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
      return _exactType;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  watch: {
 | 
			
		||||
    // 监听视频源,一旦有变化就刷新播放器
 | 
			
		||||
    src: function () {
 | 
			
		||||
      this.$_video_changeConfig();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 监听视频格式,一旦有变化就刷新播放器
 | 
			
		||||
    type: function () {
 | 
			
		||||
      this.$_video_changeConfig();
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.$_video_init();
 | 
			
		||||
    this.player.on("click", () => {
 | 
			
		||||
      this.$emit("onClickPlayer", this.src);
 | 
			
		||||
    });
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  beforeDestroy() {
 | 
			
		||||
    if (this.player) {
 | 
			
		||||
      this.player.dispose();
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  methods: {
 | 
			
		||||
    // 使用配置对象config初始化播放器
 | 
			
		||||
    // PS:播放器重复初始化时,不会更新配置
 | 
			
		||||
    $_video_init() {
 | 
			
		||||
      let config = {
 | 
			
		||||
        autoplay: this.autoplay,
 | 
			
		||||
        // 没必要传宽高,因下面样式已写宽高为100%
 | 
			
		||||
        // width: this.width,
 | 
			
		||||
        height: this.height,
 | 
			
		||||
        loop: this.loop,
 | 
			
		||||
        controls: this.controls,
 | 
			
		||||
        muted: this.muted,
 | 
			
		||||
        sources: [{ src: this.src, type: this.exactType }],
 | 
			
		||||
        // techOrder: ["html5", "flash"]
 | 
			
		||||
        techOrder: ["html5"],
 | 
			
		||||
        // techOrder: ["html5"]
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      if (this.coverAddr) {
 | 
			
		||||
        config.poster = this.coverAddr;
 | 
			
		||||
      }
 | 
			
		||||
      // console.log(config)
 | 
			
		||||
      // 根据页面上的video标签的id,将播放器挂载到上面
 | 
			
		||||
      this.player = videojs(this.$refs.videoPlayer, config);
 | 
			
		||||
 | 
			
		||||
      // 要触发的自定义事件
 | 
			
		||||
      var eventNames = [
 | 
			
		||||
        "loadstart", // 视频开始加载
 | 
			
		||||
        "canplay", // 视频可以播放
 | 
			
		||||
        "play", // 播放
 | 
			
		||||
        "pause", // 暂停
 | 
			
		||||
        "ended", // 视频播放结束
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
      for (const name of eventNames) {
 | 
			
		||||
        this.player.on(name, () => {
 | 
			
		||||
          this.$emit(name);
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 修改播放器属性,先重置再进行设置
 | 
			
		||||
    $_video_changeConfig() {
 | 
			
		||||
      // console.log(this.src)
 | 
			
		||||
      let config = { src: this.src, type: this.exactType };
 | 
			
		||||
      if (this.coverAddr) {
 | 
			
		||||
        config.poster = this.coverAddr;
 | 
			
		||||
      }
 | 
			
		||||
      this.player.src(config);
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
/*
 | 
			
		||||
在video标签外围div包围盒(video.js默认会生成的)的宽高默认是(有两个)
 | 
			
		||||
.vjs_video_3-dimensions {
 | 
			
		||||
    width: 1920px;
 | 
			
		||||
    height: 1080px;
 | 
			
		||||
}
 | 
			
		||||
.video-js {
 | 
			
		||||
    width: 300px;
 | 
			
		||||
    height: 150px;
 | 
			
		||||
}
 | 
			
		||||
就是说,包围盒一定有px宽高,因此需要改为百分比宽高,从而达到自适应
 | 
			
		||||
 */
 | 
			
		||||
.videojs {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  background-color: transparent;
 | 
			
		||||
  .my-video {
 | 
			
		||||
    // 这里设置宽高,
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    ::v-deep .vjs-poster {
 | 
			
		||||
      background-position: center;
 | 
			
		||||
      background-size: cover;
 | 
			
		||||
      background-repeat: no-repeat;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*不显示截图按扭*/
 | 
			
		||||
.my-video ::v-deep .vjs-camera-button {
 | 
			
		||||
  display: none;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,62 @@
 | 
			
		|||
/*
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2020-09-10 09:49:13
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-10 20:46:06
 | 
			
		||||
 * @Description: 配置文件
 | 
			
		||||
 */
 | 
			
		||||
export const PROJECT_NAME = '销售';
 | 
			
		||||
export const TOKEY_ATTR_NAME = 'X-Access-Token'; // TOKEN的属性名(即客户端传token到服务端,token的属性名)
 | 
			
		||||
export const IS_LOGIN_NEEDED = true; // 系统是否需要登录后使用
 | 
			
		||||
 | 
			
		||||
let baseUrl; // 基础API地址
 | 
			
		||||
switch (process.env.NODE_ENV) {
 | 
			
		||||
    case "production": // 发布到服务器 npm run build
 | 
			
		||||
        baseUrl = 'http://ty.y68.fun/sales-expo-saas'; // swagger http://ty.y68.fun/sales-expo-saas/#/
 | 
			
		||||
        break;
 | 
			
		||||
    case "preview": // 发布前预览 npm run preview
 | 
			
		||||
        baseUrl = 'http://ty.y68.fun/sales-expo-saas'; // swagger http://ty.y68.fun/sales-expo-saas/#/
 | 
			
		||||
        break;
 | 
			
		||||
    case "development": // 本机前端开发调试 npm run serve
 | 
			
		||||
    default:
 | 
			
		||||
        baseUrl = 'http://ty.y68.fun/sales-show-3'; // swagger http://ty.y68.fun/sales-expo-saas/#/
 | 
			
		||||
        // baseUrl = '/api';
 | 
			
		||||
        break;
 | 
			
		||||
}
 | 
			
		||||
export const BASE_URL = baseUrl;
 | 
			
		||||
 | 
			
		||||
export const TIMEOUT = 10000; // 一般请求超时时间
 | 
			
		||||
export const UPLOAD_TIMEOUT = 0; // 指定文件上传请求超时的毫秒数(0 表示无超时时间)
 | 
			
		||||
 | 
			
		||||
// ---------- EIM --------------------------------------------------
 | 
			
		||||
// export const BASE_BOS_URL = 'http://eim.beta.i3vgroup.cn:8080';
 | 
			
		||||
// export const BASE_3D_URL = 'http://eim.beta.i3vgroup.cn:82';
 | 
			
		||||
// export const APP_KEY = 'k4718b198e0b41c2abf4069dcb47eecf';
 | 
			
		||||
// export const EIM_USER_NAME = 'test01';
 | 
			
		||||
// export const EIM_PASSWORD = '123456';
 | 
			
		||||
 | 
			
		||||
// export const IFRAME_NAME = "iframe-viewer-eim-0225.html"; // iframe的html的名称
 | 
			
		||||
// export const IFRAME_2D_NAME = "iframe-viewer-2d.html"; // iframe的html的名称
 | 
			
		||||
 | 
			
		||||
// export const UPLOAD_MODEL_MAX_SIZE = 100; // 上传模型的最大允许大小,单位mb
 | 
			
		||||
// export const DOWNLOAD_CANCEL = "DOWNLOAD_CANCEL"; // 用于专门指示axios的cancel token取消下载的操作
 | 
			
		||||
 | 
			
		||||
// // 示例模型的信息
 | 
			
		||||
// export const EXAMPLE_MODEL_INFO = {
 | 
			
		||||
//     base3dUrl: 'http://eim.i3vgroup.cn:8081',
 | 
			
		||||
//     appKey: 'fd5ba6a10c4b429faef65117aba2a25a',
 | 
			
		||||
//     projectKey: 'p30faf7851d042a08290fb8ea5e18841',
 | 
			
		||||
//     modelKeys: ['M1616334467556'],
 | 
			
		||||
// }
 | 
			
		||||
// ---------- EIM --------------------------------------------------
 | 
			
		||||
 | 
			
		||||
export const DEFAULT_HOME_LAYOUT = ['top-bottom', 'left-right'][0]; // 网站前台是上下布局还是左右布局
 | 
			
		||||
export const DEFAULT_BACK_LAYOUT = ['top-bottom', 'left-right'][0]; // 网站后台是上下布局还是左右布局
 | 
			
		||||
export const HEADER_THICKNESS = 48; // 首页头部尺寸(单位:像素)
 | 
			
		||||
export const HIDE_DRAWER_WHEN_PUSH = false; // 当跳转页面时,抽屉是否隐藏
 | 
			
		||||
 | 
			
		||||
// 弹框类型
 | 
			
		||||
export const DIALOG_TYPE = {
 | 
			
		||||
    ADD: 'add', // 添加(新建)
 | 
			
		||||
    EDIT: 'edit', // 编辑(更新)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,25 @@
 | 
			
		|||
/*
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-12-29 17:46:53
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-05 15:43:38
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import Parent from '../_Common/Parent.js'
 | 
			
		||||
 | 
			
		||||
class Organization extends Parent {
 | 
			
		||||
    /**
 | 
			
		||||
     * @description 组织架构的节点
 | 
			
		||||
     * @param {string} name 部门名称
 | 
			
		||||
     */
 | 
			
		||||
    constructor({
 | 
			
		||||
        name,
 | 
			
		||||
        ...rest
 | 
			
		||||
    }) {
 | 
			
		||||
        super(rest);
 | 
			
		||||
        this.name = name;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Organization
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,28 @@
 | 
			
		|||
/*
 | 
			
		||||
 * @Author: Guanghao
 | 
			
		||||
 * @Date: 2022-01-04 18:07:13
 | 
			
		||||
 * @LastEditors: Guanghao
 | 
			
		||||
 * @LastEditTime: 2022-01-04 18:36:29
 | 
			
		||||
 * @Description: 角色类定义
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
class Role {
 | 
			
		||||
  /**
 | 
			
		||||
   * @description 创建角色类
 | 
			
		||||
   * @param {number} id 角色id
 | 
			
		||||
   * @param {string} name 角色名称
 | 
			
		||||
   * @param {string} description 角色说明
 | 
			
		||||
   * @param {string} auths 角色权限
 | 
			
		||||
   */
 | 
			
		||||
  constructor({
 | 
			
		||||
    id,
 | 
			
		||||
    name,
 | 
			
		||||
    description
 | 
			
		||||
  }) {
 | 
			
		||||
    this.id = id
 | 
			
		||||
    this.name = name
 | 
			
		||||
    this.description = description
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Role
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,32 @@
 | 
			
		|||
/*
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-07-11 22:41:51
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-05 00:45:33
 | 
			
		||||
 * @Description: Json Web Token 的 payload 的格式(v1.0)
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
class TokenPayload {
 | 
			
		||||
    /**
 | 
			
		||||
     * @description Json Web Token 的 payload 的格式
 | 
			
		||||
     * @param {string} uid 用户的id
 | 
			
		||||
     * @param {Array.<number>} roles 角色id的数组
 | 
			
		||||
     * @param {Array.<Array.<string>>} auths 权限code的二维数组,内层数组与角色id的数组元素一一对应
 | 
			
		||||
     */
 | 
			
		||||
    constructor(uid, roles = [], auths = []) {
 | 
			
		||||
        this.uid = uid;
 | 
			
		||||
        this.roles = roles;
 | 
			
		||||
        this.auths = auths;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 二维数组转一维,并去重
 | 
			
		||||
     */
 | 
			
		||||
    getCleanAuths() {
 | 
			
		||||
        let arr = [];
 | 
			
		||||
        this.auths.forEach(_auths => arr = arr.concat(_auths));
 | 
			
		||||
        return [...new Set(arr)];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = TokenPayload
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,34 @@
 | 
			
		|||
/*
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-06-20 01:04:22
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-05 15:40:14
 | 
			
		||||
 * @Description: 用户类
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import Parent from '../_Common/Parent.js'
 | 
			
		||||
 | 
			
		||||
class User extends Parent {
 | 
			
		||||
    /**
 | 
			
		||||
     * @description 创建用户类
 | 
			
		||||
     * @param {string} token 登录凭据
 | 
			
		||||
     * @param {string} username 登录用户名
 | 
			
		||||
     * @param {string} realname 登录用户名
 | 
			
		||||
     * @param {boolean} isRememberMe 是否记住我
 | 
			
		||||
     */
 | 
			
		||||
    constructor({
 | 
			
		||||
        token,
 | 
			
		||||
        username,
 | 
			
		||||
        realname,
 | 
			
		||||
        isRememberMe = true, // 是否记住我,是则记在localStorage,否则记在sessionStorage
 | 
			
		||||
        ...rest
 | 
			
		||||
    }) {
 | 
			
		||||
        super(rest);
 | 
			
		||||
        this.token = token;
 | 
			
		||||
        this.username = username;
 | 
			
		||||
        this.realname = realname;
 | 
			
		||||
        this.isRememberMe = isRememberMe;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default User
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,45 @@
 | 
			
		|||
/*
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-12-18 01:56:48
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-07 16:39:27
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
class MenuItem {
 | 
			
		||||
    /**
 | 
			
		||||
     * 菜单栏按钮对象类
 | 
			
		||||
     * @param {string} id 全局唯一标记
 | 
			
		||||
     * @param {string} title 标题
 | 
			
		||||
     * @param {string} iconSrc 主图标图片的加载地址
 | 
			
		||||
     * @param {string} iconSrcInactive 非激活图标图片的加载地址
 | 
			
		||||
     * @param {string} iconClass 图标的类名(包括 element ui 的图标的类名)
 | 
			
		||||
     * @param {string} routerName 按钮对应的路由名称(点击后跳转的目标路由名称)
 | 
			
		||||
     * @param {string} drawerName 按钮对应的抽屉名称(点击后加载的目标抽屉名称)
 | 
			
		||||
     * @param {object} params 路由param
 | 
			
		||||
     * @param {Array.<MenuItem>} children 子菜单栏对象数组
 | 
			
		||||
     */
 | 
			
		||||
    constructor({
 | 
			
		||||
        id,
 | 
			
		||||
        title,
 | 
			
		||||
        iconSrc,
 | 
			
		||||
        iconSrcInactive,
 | 
			
		||||
        iconClass,
 | 
			
		||||
        routerName,
 | 
			
		||||
        drawerName,
 | 
			
		||||
        params,
 | 
			
		||||
        children = []
 | 
			
		||||
    }) {
 | 
			
		||||
        this.id = id;
 | 
			
		||||
        this.title = title;
 | 
			
		||||
        this.iconSrc = iconSrc;
 | 
			
		||||
        this.iconSrcInactive = iconSrcInactive;
 | 
			
		||||
        this.iconClass = iconClass;
 | 
			
		||||
        this.routerName = routerName;
 | 
			
		||||
        this.drawerName = drawerName;
 | 
			
		||||
        this.params = params;
 | 
			
		||||
        this.children = children;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default MenuItem
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
<!--
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-12-18 01:55:39
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2021-12-18 01:55:41
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
本文件夹存放与系统的菜单栏有关的对象类型
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
/*
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2021-12-23 01:22:12
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-04 22:02:35
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
class Tab {
 | 
			
		||||
    /**
 | 
			
		||||
     * @description 标签栏的每个标签
 | 
			
		||||
     * @param {string} title 标题
 | 
			
		||||
     */
 | 
			
		||||
    constructor({
 | 
			
		||||
        title
 | 
			
		||||
    }) {
 | 
			
		||||
        this.title = title;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Tab;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
/*
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2022-01-10 17:00:37
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-10 17:05:18
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
class CommonResult {
 | 
			
		||||
    /**
 | 
			
		||||
     * @description 通用数据格式类
 | 
			
		||||
     * @param {object|Array.<object>} data 数据
 | 
			
		||||
     * @param {string} message 操作信息(一般由后端返回)
 | 
			
		||||
     */
 | 
			
		||||
    constructor(data, message) {
 | 
			
		||||
        this.data = data;
 | 
			
		||||
        this.message = message;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default CommonResult;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
/*
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2022-01-05 01:14:29
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-05 10:29:12
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
class PageResult {
 | 
			
		||||
    /**
 | 
			
		||||
     * @description 分页获取数据的格式类
 | 
			
		||||
     * @param {Array.<object>} rows 数据集合
 | 
			
		||||
     * @param {number} count 数据总条数
 | 
			
		||||
     */
 | 
			
		||||
    constructor(rows, count) {
 | 
			
		||||
        this.rows = rows;
 | 
			
		||||
        this.count = count;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default PageResult;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
/*
 | 
			
		||||
 * @Author: Billy
 | 
			
		||||
 * @Date: 2022-01-05 01:21:58
 | 
			
		||||
 * @LastEditors: Billy
 | 
			
		||||
 * @LastEditTime: 2022-01-05 10:41:46
 | 
			
		||||
 * @Description: 请输入
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
class Parent {
 | 
			
		||||
    /**
 | 
			
		||||
     * @description 所有业务对象的父类
 | 
			
		||||
     * @param {string} id 对象的全局唯一id
 | 
			
		||||
     * @param {Date|string} createTime 对象数据的创建时间
 | 
			
		||||
     * @param {Date|string} modifyTime 对象数据的最近一次修改时间
 | 
			
		||||
     * @param {boolean} softDelete 是否软删除
 | 
			
		||||
     * @param {string} createUserId 对象的创建者id
 | 
			
		||||
     */
 | 
			
		||||
    constructor({
 | 
			
		||||
        id,
 | 
			
		||||
        createTime,
 | 
			
		||||
        modifyTime,
 | 
			
		||||
        softDelete,
 | 
			
		||||
        createUserId
 | 
			
		||||
    }) {
 | 
			
		||||
        this.id = id;
 | 
			
		||||
        this.createTime = typeof (createTime) === 'string' ? Date(createTime) : createTime;
 | 
			
		||||
        this.modifyTime = typeof (modifyTime) === 'string' ? Date(modifyTime) : modifyTime;
 | 
			
		||||
        this.softDelete = softDelete;
 | 
			
		||||
        this.createUserId = createUserId;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Parent;
 | 
			
		||||