基本搭建完毕
|
@ -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;
|