xs-common-plugins 1.1.5 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -0
- package/package.json +1 -1
- package/src/common/common.js +81 -8
- package/src/router/index.js +16 -1
- package/src/store/index.js +2 -0
- package/src/store/modules/tagsView.js +160 -0
- package/src/utils/filterRules.js +10 -5
- package/src/utils/getMenu.js +2 -1
- package/src/views/layout/components/AppMain.vue +2 -2
- package/src/views/layout/components/Navbar.vue +42 -2
- package/src/views/layout/components/TagsView/ScrollPane.vue +94 -0
- package/src/views/layout/components/TagsView/index.vue +291 -0
- package/src/views/layout/components/index.js +1 -0
- package/src/views/layout/index.vue +4 -2
- package/src/views/redirect/index.vue +12 -0
package/README.md
CHANGED
|
@@ -268,4 +268,24 @@
|
|
|
268
268
|
```
|
|
269
269
|
1.1.5
|
|
270
270
|
1. common.js common.exportExcel (报表导出) 方法, 移除空条件
|
|
271
|
+
```
|
|
272
|
+
```
|
|
273
|
+
1.1.6
|
|
274
|
+
1. 增加多租户却换
|
|
275
|
+
2. 兼容相同路由跳转警告问题
|
|
276
|
+
3. common.js 增加公用方法 this.$common.dic() 用于查询字典单条/多条数据
|
|
277
|
+
```
|
|
278
|
+
```
|
|
279
|
+
1.1.7
|
|
280
|
+
1. 多余显示移除
|
|
281
|
+
```
|
|
282
|
+
```
|
|
283
|
+
1.1.8
|
|
284
|
+
1. common.js 增加 方法
|
|
285
|
+
2. common.js commom.format() 拓展类型枚举
|
|
286
|
+
3. 表单校验增加 min, max 参数
|
|
287
|
+
```
|
|
288
|
+
```
|
|
289
|
+
1.2.0
|
|
290
|
+
1. 项目增加多页签功能, 方便切换
|
|
271
291
|
```
|
package/package.json
CHANGED
package/src/common/common.js
CHANGED
|
@@ -4,7 +4,8 @@ import router from '@/router/index'
|
|
|
4
4
|
import store from '@/store/index'
|
|
5
5
|
import {OrgEnum} from '@/utils/enum'
|
|
6
6
|
import filterRules from '@/utils/filterRules'
|
|
7
|
-
|
|
7
|
+
import request from "xs-request";
|
|
8
|
+
import moduleCfg from '@/modules/module.config.js'
|
|
8
9
|
const common = {}
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -399,28 +400,45 @@ common.requiredList = (list) => {
|
|
|
399
400
|
/**
|
|
400
401
|
* 格式化数据样式
|
|
401
402
|
* @param {*} value 值
|
|
402
|
-
* @param {*} type 要格式化的类型 [date, price]
|
|
403
|
+
* @param {*} type 要格式化的类型 [date, price, price, money, enum, list(前端自定义枚举)]
|
|
403
404
|
* @returns
|
|
404
405
|
*/
|
|
405
406
|
common.format = (value, type="date") => {
|
|
406
|
-
|
|
407
|
+
let newType = ['money', 'date', 'mdate'].includes(type) ? type : type.split('.').length > 1 ? 'enum' : 'list'
|
|
408
|
+
switch (newType) {
|
|
409
|
+
case 'money':
|
|
410
|
+
return Number(value).toFixed(4);
|
|
407
411
|
case 'date':
|
|
408
|
-
if(value) return
|
|
409
|
-
|
|
410
|
-
|
|
412
|
+
if(value === '1970-01-01T00:00:00') return '/'
|
|
413
|
+
return value ? value.replace('T', ' ').substr(0, 19) : ''
|
|
414
|
+
case 'mdate':
|
|
415
|
+
if(value === '1970-01-01T00:00:00') return '/'
|
|
416
|
+
return value ? value.replace('T', ' ').substr(5, 19) : ''
|
|
411
417
|
case 'price':
|
|
412
418
|
if(typeof(value) === 'number') {
|
|
413
419
|
return value.toFixed(2).toString().replace(/,/g,'').replace(/\d+/, function (n) { // 先提取整数部分
|
|
414
420
|
return n.replace(/(\d)(?=(\d{3})+$)/g, function ($1) { // 对整数部分添加分隔符
|
|
415
|
-
|
|
421
|
+
return $1 + ",";
|
|
416
422
|
});
|
|
417
423
|
});
|
|
418
424
|
}
|
|
419
425
|
else return ''
|
|
420
|
-
|
|
426
|
+
case 'enum':
|
|
427
|
+
return getValueByType('enum', type, value)
|
|
428
|
+
case 'list':
|
|
429
|
+
return getValueByType('list', type, value)
|
|
421
430
|
default:
|
|
422
431
|
break;
|
|
423
432
|
}
|
|
433
|
+
function getValueByType (type, name, value) {
|
|
434
|
+
if(type === 'enum') {
|
|
435
|
+
let row = OrgEnum[name].find(item => item.Id == value)
|
|
436
|
+
return row ? row.Text : ''
|
|
437
|
+
} else if(type === 'list') {
|
|
438
|
+
let row = moduleCfg.format ? moduleCfg.format[name].find(item => item.id == value) : null
|
|
439
|
+
return row ? row.name: ''
|
|
440
|
+
}
|
|
441
|
+
}
|
|
424
442
|
}
|
|
425
443
|
|
|
426
444
|
/**
|
|
@@ -473,4 +491,59 @@ common.getReportList = (that, url, query, callBack) => {
|
|
|
473
491
|
})
|
|
474
492
|
}
|
|
475
493
|
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* 字典表 里通过Id 查 name, 或者查询多条数据 (单条返回 name, 多条返回数组)
|
|
497
|
+
* @param {Array} props ['服务名', '表名', '字段名']
|
|
498
|
+
* @param {String, Number, Array} val 值, 传入id, 或者 [id1, id2, ...]
|
|
499
|
+
*
|
|
500
|
+
* 使用案例 (异步方法)
|
|
501
|
+
* async getValue () {
|
|
502
|
+
* await this.$common.dic(['qccuser', 'Brand'], '10010')
|
|
503
|
+
* }
|
|
504
|
+
*
|
|
505
|
+
*/
|
|
506
|
+
|
|
507
|
+
common.dic = async (props, val) => {
|
|
508
|
+
let isArray = Array.isArray(val)
|
|
509
|
+
let server = props[0] || ''
|
|
510
|
+
let tableName = props[1] || ''
|
|
511
|
+
let prop = props[2] || 'name'
|
|
512
|
+
let key = server + tableName
|
|
513
|
+
if(store.getters.dic[key]) {
|
|
514
|
+
if(isArray) return await getDicVal()
|
|
515
|
+
let row = store.getters.dic[key].find(item => item.id == val)
|
|
516
|
+
if(row && (row.isNull || row[prop] === null)) { return '' }
|
|
517
|
+
if(row) return row[prop]
|
|
518
|
+
if(!row) return await getDicVal()
|
|
519
|
+
|
|
520
|
+
} else {
|
|
521
|
+
return await getDicVal()
|
|
522
|
+
}
|
|
523
|
+
async function getDicVal () {
|
|
524
|
+
let servers = props[0] || "";
|
|
525
|
+
let tableName = props[1] || "";
|
|
526
|
+
let key = servers + tableName;
|
|
527
|
+
let url = servers + "/UserAll/Search/Dic/" + tableName;
|
|
528
|
+
let res = await request.post(url, isArray ? val : [val])
|
|
529
|
+
if (res.code !== 0) return;
|
|
530
|
+
store.commit("dic/SET_ASK", { key, val: res.data });
|
|
531
|
+
if(isArray) return res.data
|
|
532
|
+
else {
|
|
533
|
+
let row = res.data.find(item => item.id == val)
|
|
534
|
+
return row ? row[prop] : ''
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* 快速给页面变量赋值
|
|
540
|
+
* @param {*} url 请求接口的地址
|
|
541
|
+
* @param {*} query 接口参数
|
|
542
|
+
* @param {*} prop 要为哪个字段名赋值
|
|
543
|
+
*/
|
|
544
|
+
common.getLabelList = (that, prop, url, query) => {
|
|
545
|
+
common.getObject(url)(query).then(res => {
|
|
546
|
+
that[prop] = res.data
|
|
547
|
+
})
|
|
548
|
+
}
|
|
476
549
|
export default common
|
package/src/router/index.js
CHANGED
|
@@ -3,7 +3,22 @@ import VueRouter from 'vue-router'
|
|
|
3
3
|
import autoRouter from '../automatically/router'
|
|
4
4
|
Vue.use(VueRouter)
|
|
5
5
|
|
|
6
|
+
const originalPush = VueRouter.prototype.push;
|
|
7
|
+
VueRouter.prototype.push = function push(location) {
|
|
8
|
+
return originalPush.call(this, location).catch(err => err)
|
|
9
|
+
}
|
|
6
10
|
const routes = [
|
|
11
|
+
{
|
|
12
|
+
path: '/redirect',
|
|
13
|
+
component: () => import('../views/layout/index'),
|
|
14
|
+
hidden: true,
|
|
15
|
+
children: [
|
|
16
|
+
{
|
|
17
|
+
path: '/redirect/:path(.*)',
|
|
18
|
+
component: () => import('@/views/redirect/index')
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
},
|
|
7
22
|
{
|
|
8
23
|
path: '/',
|
|
9
24
|
name: 'layout',
|
|
@@ -16,7 +31,7 @@ const routes = [
|
|
|
16
31
|
path: '/home',
|
|
17
32
|
name: 'home',
|
|
18
33
|
component: () => import('../views/layout/index'),
|
|
19
|
-
meta: { title: '首页', icon: 'example' },
|
|
34
|
+
meta: { title: '首页', icon: 'example', affix: true,},
|
|
20
35
|
children: [{
|
|
21
36
|
path:'/',
|
|
22
37
|
component: () => import('../views/home/index'),
|
package/src/store/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import app from './modules/app'
|
|
|
5
5
|
import user from './modules/user'
|
|
6
6
|
import widgetdata from './modules/widgetdata'
|
|
7
7
|
import dic from './modules/dic'
|
|
8
|
+
import tagsView from './modules/tagsView'
|
|
8
9
|
import oss from './modules/oss'
|
|
9
10
|
|
|
10
11
|
const globalCfg = require("../modules/module.config");
|
|
@@ -44,6 +45,7 @@ let storeCfg = {
|
|
|
44
45
|
|
|
45
46
|
modules: {
|
|
46
47
|
app,
|
|
48
|
+
tagsView,
|
|
47
49
|
user,
|
|
48
50
|
widgetdata,
|
|
49
51
|
dic,
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
const state = {
|
|
2
|
+
visitedViews: [],
|
|
3
|
+
cachedViews: []
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const mutations = {
|
|
7
|
+
ADD_VISITED_VIEW: (state, view) => {
|
|
8
|
+
if (state.visitedViews.some(v => v.path === view.path)) return
|
|
9
|
+
state.visitedViews.push(
|
|
10
|
+
Object.assign({}, view, {
|
|
11
|
+
title: view.meta.title || 'no-name'
|
|
12
|
+
})
|
|
13
|
+
)
|
|
14
|
+
},
|
|
15
|
+
ADD_CACHED_VIEW: (state, view) => {
|
|
16
|
+
if (state.cachedViews.includes(view.name)) return
|
|
17
|
+
if (!view.meta.noCache) {
|
|
18
|
+
state.cachedViews.push(view.name)
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
DEL_VISITED_VIEW: (state, view) => {
|
|
23
|
+
for (const [i, v] of state.visitedViews.entries()) {
|
|
24
|
+
if (v.path === view.path) {
|
|
25
|
+
state.visitedViews.splice(i, 1)
|
|
26
|
+
break
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
DEL_CACHED_VIEW: (state, view) => {
|
|
31
|
+
const index = state.cachedViews.indexOf(view.name)
|
|
32
|
+
index > -1 && state.cachedViews.splice(index, 1)
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
DEL_OTHERS_VISITED_VIEWS: (state, view) => {
|
|
36
|
+
state.visitedViews = state.visitedViews.filter(v => {
|
|
37
|
+
return v.meta.affix || v.path === view.path
|
|
38
|
+
})
|
|
39
|
+
},
|
|
40
|
+
DEL_OTHERS_CACHED_VIEWS: (state, view) => {
|
|
41
|
+
const index = state.cachedViews.indexOf(view.name)
|
|
42
|
+
if (index > -1) {
|
|
43
|
+
state.cachedViews = state.cachedViews.slice(index, index + 1)
|
|
44
|
+
} else {
|
|
45
|
+
// if index = -1, there is no cached tags
|
|
46
|
+
state.cachedViews = []
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
DEL_ALL_VISITED_VIEWS: state => {
|
|
51
|
+
// keep affix tags
|
|
52
|
+
const affixTags = state.visitedViews.filter(tag => tag.meta.affix)
|
|
53
|
+
state.visitedViews = affixTags
|
|
54
|
+
},
|
|
55
|
+
DEL_ALL_CACHED_VIEWS: state => {
|
|
56
|
+
state.cachedViews = []
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
UPDATE_VISITED_VIEW: (state, view) => {
|
|
60
|
+
for (let v of state.visitedViews) {
|
|
61
|
+
if (v.path === view.path) {
|
|
62
|
+
v = Object.assign(v, view)
|
|
63
|
+
break
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const actions = {
|
|
70
|
+
addView({ dispatch }, view) {
|
|
71
|
+
dispatch('addVisitedView', view)
|
|
72
|
+
dispatch('addCachedView', view)
|
|
73
|
+
},
|
|
74
|
+
addVisitedView({ commit }, view) {
|
|
75
|
+
commit('ADD_VISITED_VIEW', view)
|
|
76
|
+
},
|
|
77
|
+
addCachedView({ commit }, view) {
|
|
78
|
+
commit('ADD_CACHED_VIEW', view)
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
delView({ dispatch, state }, view) {
|
|
82
|
+
return new Promise(resolve => {
|
|
83
|
+
dispatch('delVisitedView', view)
|
|
84
|
+
dispatch('delCachedView', view)
|
|
85
|
+
resolve({
|
|
86
|
+
visitedViews: [...state.visitedViews],
|
|
87
|
+
cachedViews: [...state.cachedViews]
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
},
|
|
91
|
+
delVisitedView({ commit, state }, view) {
|
|
92
|
+
return new Promise(resolve => {
|
|
93
|
+
commit('DEL_VISITED_VIEW', view)
|
|
94
|
+
resolve([...state.visitedViews])
|
|
95
|
+
})
|
|
96
|
+
},
|
|
97
|
+
delCachedView({ commit, state }, view) {
|
|
98
|
+
return new Promise(resolve => {
|
|
99
|
+
commit('DEL_CACHED_VIEW', view)
|
|
100
|
+
resolve([...state.cachedViews])
|
|
101
|
+
})
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
delOthersViews({ dispatch, state }, view) {
|
|
105
|
+
return new Promise(resolve => {
|
|
106
|
+
dispatch('delOthersVisitedViews', view)
|
|
107
|
+
dispatch('delOthersCachedViews', view)
|
|
108
|
+
resolve({
|
|
109
|
+
visitedViews: [...state.visitedViews],
|
|
110
|
+
cachedViews: [...state.cachedViews]
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
},
|
|
114
|
+
delOthersVisitedViews({ commit, state }, view) {
|
|
115
|
+
return new Promise(resolve => {
|
|
116
|
+
commit('DEL_OTHERS_VISITED_VIEWS', view)
|
|
117
|
+
resolve([...state.visitedViews])
|
|
118
|
+
})
|
|
119
|
+
},
|
|
120
|
+
delOthersCachedViews({ commit, state }, view) {
|
|
121
|
+
return new Promise(resolve => {
|
|
122
|
+
commit('DEL_OTHERS_CACHED_VIEWS', view)
|
|
123
|
+
resolve([...state.cachedViews])
|
|
124
|
+
})
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
delAllViews({ dispatch, state }, view) {
|
|
128
|
+
return new Promise(resolve => {
|
|
129
|
+
dispatch('delAllVisitedViews', view)
|
|
130
|
+
dispatch('delAllCachedViews', view)
|
|
131
|
+
resolve({
|
|
132
|
+
visitedViews: [...state.visitedViews],
|
|
133
|
+
cachedViews: [...state.cachedViews]
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
},
|
|
137
|
+
delAllVisitedViews({ commit, state }) {
|
|
138
|
+
return new Promise(resolve => {
|
|
139
|
+
commit('DEL_ALL_VISITED_VIEWS')
|
|
140
|
+
resolve([...state.visitedViews])
|
|
141
|
+
})
|
|
142
|
+
},
|
|
143
|
+
delAllCachedViews({ commit, state }) {
|
|
144
|
+
return new Promise(resolve => {
|
|
145
|
+
commit('DEL_ALL_CACHED_VIEWS')
|
|
146
|
+
resolve([...state.cachedViews])
|
|
147
|
+
})
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
updateVisitedView({ commit }, view) {
|
|
151
|
+
commit('UPDATE_VISITED_VIEW', view)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export default {
|
|
156
|
+
namespaced: true,
|
|
157
|
+
state,
|
|
158
|
+
mutations,
|
|
159
|
+
actions
|
|
160
|
+
}
|
package/src/utils/filterRules.js
CHANGED
|
@@ -2,11 +2,16 @@ const filterRules = ({ required, type, min, max }) => {
|
|
|
2
2
|
let validate = [];
|
|
3
3
|
if (required) {
|
|
4
4
|
// 必填项
|
|
5
|
-
validate.push({
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
validate.push({ required: true, message: "该输入项为必填项", trigger: "change",});
|
|
6
|
+
}
|
|
7
|
+
if(min&&max){
|
|
8
|
+
validate.push({ min:min,max:max, message: '字符长度在'+min+'至'+max+'之间!', trigger: 'change' })
|
|
9
|
+
}
|
|
10
|
+
if(min){
|
|
11
|
+
validate.push({ min:min, message: '最少输入'+min+'个字符!', trigger: 'change' })
|
|
12
|
+
}
|
|
13
|
+
if(max){
|
|
14
|
+
validate.push({ min:1,max:max, message: '最多输入'+max+'个字符!', trigger: 'change' })
|
|
10
15
|
}
|
|
11
16
|
if (type) {
|
|
12
17
|
let message = "";
|
package/src/utils/getMenu.js
CHANGED
|
@@ -25,10 +25,10 @@ export default {
|
|
|
25
25
|
.app-main {
|
|
26
26
|
display: flex;
|
|
27
27
|
/*50 = navbar */
|
|
28
|
-
height: calc(100vh -
|
|
28
|
+
height: calc(100vh - 85px);
|
|
29
29
|
width: 100%;
|
|
30
30
|
position: relative;
|
|
31
|
-
overflow:
|
|
31
|
+
overflow: auto;
|
|
32
32
|
}
|
|
33
33
|
.footer {
|
|
34
34
|
position: absolute;
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
<AllSearch />
|
|
32
32
|
<div class="is-dot">
|
|
33
33
|
<el-badge
|
|
34
|
+
style="margin: 0 3px;"
|
|
34
35
|
v-if="isShowKF === 'isv' || isShowKF === 'erp'"
|
|
35
36
|
is-dot
|
|
36
37
|
class="item"
|
|
@@ -45,7 +46,7 @@
|
|
|
45
46
|
</el-badge>
|
|
46
47
|
</div>
|
|
47
48
|
<div>
|
|
48
|
-
<el-tag>{{
|
|
49
|
+
<el-tag style="margin: 0 3px;">{{
|
|
49
50
|
userProfile &&
|
|
50
51
|
userProfile.hasOwnProperty("real_name") &&
|
|
51
52
|
userProfile.real_name
|
|
@@ -61,6 +62,17 @@
|
|
|
61
62
|
: "--未绑定--"
|
|
62
63
|
}}
|
|
63
64
|
</el-tag>
|
|
65
|
+
</div>
|
|
66
|
+
<!-- 租户 -->
|
|
67
|
+
<div class="tenant" v-if="userProfile && userProfile.TId">
|
|
68
|
+
<el-dropdown @command="changeTenant">
|
|
69
|
+
<span class="el-dropdown-link">
|
|
70
|
+
{{tenantList && tenantList[userProfile.TId]}}
|
|
71
|
+
</span>
|
|
72
|
+
<el-dropdown-menu slot="dropdown">
|
|
73
|
+
<el-dropdown-item v-for="(value, key) in tenantList" :key="key" :command="key">{{value}}</el-dropdown-item>
|
|
74
|
+
</el-dropdown-menu>
|
|
75
|
+
</el-dropdown>
|
|
64
76
|
</div>
|
|
65
77
|
<el-dropdown class="avatar-container" trigger="click">
|
|
66
78
|
<div class="avatar-wrapper">
|
|
@@ -107,7 +119,9 @@ import AllSearch from "./AllSearch";
|
|
|
107
119
|
import { getConfig } from "@/utils/global-config";
|
|
108
120
|
import { getToken, removeToken, removeLocalToken } from "@/utils/auth"; // get token from cookie
|
|
109
121
|
import ImCom from "@/components/im";
|
|
110
|
-
import { getLocalStorage } from "@/utils/localStorage";
|
|
122
|
+
import { getLocalStorage, setLocalStorage } from "@/utils/localStorage";
|
|
123
|
+
import { setToken } from "@/utils/auth"; // get token from cookie
|
|
124
|
+
import { initMenu } from '@/utils/getMenu'
|
|
111
125
|
|
|
112
126
|
export default {
|
|
113
127
|
components: {
|
|
@@ -136,6 +150,7 @@ export default {
|
|
|
136
150
|
LoginName: false,
|
|
137
151
|
isShowKF: getConfig("GROUP"),
|
|
138
152
|
defaultParentId: "887904812217cca9bc2b9adb875daf42",
|
|
153
|
+
tenantList: [], // 租户列表
|
|
139
154
|
};
|
|
140
155
|
},
|
|
141
156
|
computed: {
|
|
@@ -146,8 +161,29 @@ export default {
|
|
|
146
161
|
if (userName === "admin") {
|
|
147
162
|
this.LoginName = true;
|
|
148
163
|
}
|
|
164
|
+
if(this.userProfile &&this.userProfile.TId) this.getTenantList()
|
|
149
165
|
},
|
|
150
166
|
methods: {
|
|
167
|
+
changeTenant (id) {
|
|
168
|
+
if(id === this.userProfile.TId) return
|
|
169
|
+
this.$router.push("/home")
|
|
170
|
+
this.$ask.ucmng.api.sysmng.Tenant.SwitchTenant({clientId: getConfig('CLIENT_ID'), newTId: id}).then(res => {
|
|
171
|
+
let newuser = this.userProfile
|
|
172
|
+
newuser.TId = id
|
|
173
|
+
setLocalStorage('userProfile', newuser)
|
|
174
|
+
this.userProfile = getLocalStorage('userProfile')
|
|
175
|
+
let user = res.data
|
|
176
|
+
setToken(user.accessToken);
|
|
177
|
+
localStorage.setItem("token", user.accessToken);
|
|
178
|
+
initMenu()
|
|
179
|
+
|
|
180
|
+
})
|
|
181
|
+
},
|
|
182
|
+
getTenantList () {
|
|
183
|
+
this.$ask.ucmng.api.sysmng.Tenant.GetTenants().then(res => {
|
|
184
|
+
this.tenantList = res.data
|
|
185
|
+
})
|
|
186
|
+
},
|
|
151
187
|
toggleSideBar() {
|
|
152
188
|
this.$store.dispatch("app/toggleSideBar");
|
|
153
189
|
},
|
|
@@ -279,6 +315,10 @@ export default {
|
|
|
279
315
|
}
|
|
280
316
|
}
|
|
281
317
|
|
|
318
|
+
.tenant {
|
|
319
|
+
padding: 0 10px;
|
|
320
|
+
}
|
|
321
|
+
|
|
282
322
|
.breadcrumb-container {
|
|
283
323
|
float: left;
|
|
284
324
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-scrollbar ref="scrollContainer" :vertical="false" class="scroll-container" @wheel.native.prevent="handleScroll">
|
|
3
|
+
<slot />
|
|
4
|
+
</el-scrollbar>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script>
|
|
8
|
+
const tagAndTagSpacing = 4 // tagAndTagSpacing
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
name: 'ScrollPane',
|
|
12
|
+
data() {
|
|
13
|
+
return {
|
|
14
|
+
left: 0
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
computed: {
|
|
18
|
+
scrollWrapper() {
|
|
19
|
+
return this.$refs.scrollContainer.$refs.wrap
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
mounted() {
|
|
23
|
+
this.scrollWrapper.addEventListener('scroll', this.emitScroll, true)
|
|
24
|
+
},
|
|
25
|
+
beforeDestroy() {
|
|
26
|
+
this.scrollWrapper.removeEventListener('scroll', this.emitScroll)
|
|
27
|
+
},
|
|
28
|
+
methods: {
|
|
29
|
+
handleScroll(e) {
|
|
30
|
+
const eventDelta = e.wheelDelta || -e.deltaY * 40
|
|
31
|
+
const $scrollWrapper = this.scrollWrapper
|
|
32
|
+
$scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
|
|
33
|
+
},
|
|
34
|
+
emitScroll() {
|
|
35
|
+
this.$emit('scroll')
|
|
36
|
+
},
|
|
37
|
+
moveToTarget(currentTag) {
|
|
38
|
+
const $container = this.$refs.scrollContainer.$el
|
|
39
|
+
const $containerWidth = $container.offsetWidth
|
|
40
|
+
const $scrollWrapper = this.scrollWrapper
|
|
41
|
+
const tagList = this.$parent.$refs.tag
|
|
42
|
+
|
|
43
|
+
let firstTag = null
|
|
44
|
+
let lastTag = null
|
|
45
|
+
|
|
46
|
+
// find first tag and last tag
|
|
47
|
+
if (tagList.length > 0) {
|
|
48
|
+
firstTag = tagList[0]
|
|
49
|
+
lastTag = tagList[tagList.length - 1]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (firstTag === currentTag) {
|
|
53
|
+
$scrollWrapper.scrollLeft = 0
|
|
54
|
+
} else if (lastTag === currentTag) {
|
|
55
|
+
$scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth
|
|
56
|
+
} else {
|
|
57
|
+
// find preTag and nextTag
|
|
58
|
+
const currentIndex = tagList.findIndex(item => item === currentTag)
|
|
59
|
+
const prevTag = tagList[currentIndex - 1]
|
|
60
|
+
const nextTag = tagList[currentIndex + 1]
|
|
61
|
+
|
|
62
|
+
// the tag's offsetLeft after of nextTag
|
|
63
|
+
const afterNextTagOffsetLeft = nextTag.$el.offsetLeft + nextTag.$el.offsetWidth + tagAndTagSpacing
|
|
64
|
+
|
|
65
|
+
// the tag's offsetLeft before of prevTag
|
|
66
|
+
const beforePrevTagOffsetLeft = prevTag.$el.offsetLeft - tagAndTagSpacing
|
|
67
|
+
|
|
68
|
+
if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
|
|
69
|
+
$scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth
|
|
70
|
+
} else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
|
|
71
|
+
$scrollWrapper.scrollLeft = beforePrevTagOffsetLeft
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<style lang="scss" scoped>
|
|
80
|
+
.scroll-container {
|
|
81
|
+
white-space: nowrap;
|
|
82
|
+
position: relative;
|
|
83
|
+
overflow: hidden;
|
|
84
|
+
width: 100%;
|
|
85
|
+
::v-deep {
|
|
86
|
+
.el-scrollbar__bar {
|
|
87
|
+
bottom: 0px;
|
|
88
|
+
}
|
|
89
|
+
.el-scrollbar__wrap {
|
|
90
|
+
height: 49px;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
</style>
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div id="tags-view-container" class="tags-view-container">
|
|
3
|
+
<scroll-pane ref="scrollPane" class="tags-view-wrapper" @scroll="handleScroll">
|
|
4
|
+
<router-link
|
|
5
|
+
v-for="tag in visitedViews"
|
|
6
|
+
ref="tag"
|
|
7
|
+
:key="tag.path"
|
|
8
|
+
:class="isActive(tag)?'active':''"
|
|
9
|
+
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
|
|
10
|
+
tag="span"
|
|
11
|
+
class="tags-view-item"
|
|
12
|
+
@click.middle.native="!isAffix(tag)?closeSelectedTag(tag):''"
|
|
13
|
+
@contextmenu.prevent.native="openMenu(tag,$event)"
|
|
14
|
+
>
|
|
15
|
+
{{ tag.title }}
|
|
16
|
+
<span v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
|
|
17
|
+
</router-link>
|
|
18
|
+
</scroll-pane>
|
|
19
|
+
<ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
|
|
20
|
+
<li @click="refreshSelectedTag(selectedTag)">刷新</li>
|
|
21
|
+
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">关闭</li>
|
|
22
|
+
<li @click="closeOthersTags">关闭其他</li>
|
|
23
|
+
<li @click="closeAllTags(selectedTag)">关闭全部</li>
|
|
24
|
+
</ul>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script>
|
|
29
|
+
import ScrollPane from './ScrollPane'
|
|
30
|
+
import path from 'path'
|
|
31
|
+
import router from '../../../../router/index'
|
|
32
|
+
export default {
|
|
33
|
+
components: { ScrollPane },
|
|
34
|
+
data() {
|
|
35
|
+
return {
|
|
36
|
+
visible: false,
|
|
37
|
+
top: 0,
|
|
38
|
+
left: 0,
|
|
39
|
+
selectedTag: {},
|
|
40
|
+
affixTags: []
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
computed: {
|
|
44
|
+
visitedViews() {
|
|
45
|
+
return this.$store.state.tagsView.visitedViews
|
|
46
|
+
},
|
|
47
|
+
routes() {
|
|
48
|
+
return router.options.routes
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
watch: {
|
|
52
|
+
$route() {
|
|
53
|
+
this.addTags()
|
|
54
|
+
this.moveToCurrentTag()
|
|
55
|
+
},
|
|
56
|
+
visible(value) {
|
|
57
|
+
if (value) {
|
|
58
|
+
document.body.addEventListener('click', this.closeMenu)
|
|
59
|
+
} else {
|
|
60
|
+
document.body.removeEventListener('click', this.closeMenu)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
mounted() {
|
|
65
|
+
this.initTags()
|
|
66
|
+
this.addTags()
|
|
67
|
+
},
|
|
68
|
+
methods: {
|
|
69
|
+
isActive(route) {
|
|
70
|
+
return route.path === this.$route.path
|
|
71
|
+
},
|
|
72
|
+
isAffix(tag) {
|
|
73
|
+
return tag.meta && tag.meta.affix
|
|
74
|
+
},
|
|
75
|
+
filterAffixTags(routes, basePath = '/') {
|
|
76
|
+
let tags = []
|
|
77
|
+
routes.forEach(route => {
|
|
78
|
+
if (route.meta && route.meta.affix) {
|
|
79
|
+
const tagPath = path.resolve(basePath, route.path)
|
|
80
|
+
tags.push({
|
|
81
|
+
fullPath: tagPath,
|
|
82
|
+
path: tagPath,
|
|
83
|
+
name: route.name,
|
|
84
|
+
meta: { ...route.meta }
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
if (route.children) {
|
|
88
|
+
const tempTags = this.filterAffixTags(route.children, route.path)
|
|
89
|
+
if (tempTags.length >= 1) {
|
|
90
|
+
tags = [...tags, ...tempTags]
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
return tags
|
|
95
|
+
},
|
|
96
|
+
initTags() {
|
|
97
|
+
const affixTags = this.affixTags = this.filterAffixTags(this.routes)
|
|
98
|
+
for (const tag of affixTags) {
|
|
99
|
+
if (tag.name) {
|
|
100
|
+
this.$store.dispatch('tagsView/addVisitedView', tag)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
addTags() {
|
|
105
|
+
const { meta } = this.$route
|
|
106
|
+
if (meta && meta.title) {
|
|
107
|
+
this.$store.dispatch('tagsView/addView', this.$route)
|
|
108
|
+
}
|
|
109
|
+
return false
|
|
110
|
+
},
|
|
111
|
+
moveToCurrentTag() {
|
|
112
|
+
const tags = this.$refs.tag
|
|
113
|
+
this.$nextTick(() => {
|
|
114
|
+
for (const tag of tags) {
|
|
115
|
+
if (tag.to.path === this.$route.path) {
|
|
116
|
+
this.$refs.scrollPane.moveToTarget(tag)
|
|
117
|
+
// when query is different then update
|
|
118
|
+
if (tag.to.fullPath !== this.$route.fullPath) {
|
|
119
|
+
this.$store.dispatch('tagsView/updateVisitedView', this.$route)
|
|
120
|
+
}
|
|
121
|
+
break
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
},
|
|
126
|
+
refreshSelectedTag(view) {
|
|
127
|
+
this.$store.dispatch('tagsView/delCachedView', view).then(() => {
|
|
128
|
+
const { fullPath } = view
|
|
129
|
+
this.$nextTick(() => {
|
|
130
|
+
this.$router.replace({
|
|
131
|
+
path: '/redirect' + fullPath
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
},
|
|
136
|
+
closeSelectedTag(view) {
|
|
137
|
+
this.$store.dispatch('tagsView/delView', view).then(({ visitedViews }) => {
|
|
138
|
+
if (this.isActive(view)) {
|
|
139
|
+
this.toLastView(visitedViews, view)
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
},
|
|
143
|
+
closeOthersTags() {
|
|
144
|
+
this.$router.push(this.selectedTag)
|
|
145
|
+
this.$store.dispatch('tagsView/delOthersViews', this.selectedTag).then(() => {
|
|
146
|
+
this.moveToCurrentTag()
|
|
147
|
+
})
|
|
148
|
+
},
|
|
149
|
+
closeAllTags(view) {
|
|
150
|
+
this.$store.dispatch('tagsView/delAllViews').then(({ visitedViews }) => {
|
|
151
|
+
if (this.affixTags.some(tag => tag.path === view.path)) {
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
this.toLastView(visitedViews, view)
|
|
155
|
+
})
|
|
156
|
+
},
|
|
157
|
+
toLastView(visitedViews, view) {
|
|
158
|
+
const latestView = visitedViews.slice(-1)[0]
|
|
159
|
+
if (latestView) {
|
|
160
|
+
this.$router.push(latestView.fullPath)
|
|
161
|
+
} else {
|
|
162
|
+
// now the default is to redirect to the home page if there is no tags-view,
|
|
163
|
+
// you can adjust it according to your needs.
|
|
164
|
+
if (view.name === 'Dashboard') {
|
|
165
|
+
// to reload home page
|
|
166
|
+
this.$router.replace({ path: '/redirect' + view.fullPath })
|
|
167
|
+
} else {
|
|
168
|
+
this.$router.push('/')
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
openMenu(tag, e) {
|
|
173
|
+
const menuMinWidth = 105
|
|
174
|
+
const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
|
|
175
|
+
const offsetWidth = this.$el.offsetWidth // container width
|
|
176
|
+
const maxLeft = offsetWidth - menuMinWidth // left boundary
|
|
177
|
+
const left = e.clientX - offsetLeft + 15 // 15: margin right
|
|
178
|
+
|
|
179
|
+
if (left > maxLeft) {
|
|
180
|
+
this.left = maxLeft
|
|
181
|
+
} else {
|
|
182
|
+
this.left = left
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
this.top = e.clientY
|
|
186
|
+
this.visible = true
|
|
187
|
+
this.selectedTag = tag
|
|
188
|
+
},
|
|
189
|
+
closeMenu() {
|
|
190
|
+
this.visible = false
|
|
191
|
+
},
|
|
192
|
+
handleScroll() {
|
|
193
|
+
this.closeMenu()
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
</script>
|
|
198
|
+
|
|
199
|
+
<style lang="scss" scoped>
|
|
200
|
+
.tags-view-container {
|
|
201
|
+
height: 34px;
|
|
202
|
+
width: 100%;
|
|
203
|
+
background: #fff;
|
|
204
|
+
border-bottom: 1px solid #d8dce5;
|
|
205
|
+
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
|
|
206
|
+
.tags-view-wrapper {
|
|
207
|
+
.tags-view-item {
|
|
208
|
+
display: inline-block;
|
|
209
|
+
position: relative;
|
|
210
|
+
cursor: pointer;
|
|
211
|
+
height: 26px;
|
|
212
|
+
line-height: 26px;
|
|
213
|
+
border: 1px solid #d8dce5;
|
|
214
|
+
color: #495060;
|
|
215
|
+
background: #fff;
|
|
216
|
+
padding: 0 8px;
|
|
217
|
+
font-size: 12px;
|
|
218
|
+
margin-left: 5px;
|
|
219
|
+
margin-top: 4px;
|
|
220
|
+
&:first-of-type {
|
|
221
|
+
margin-left: 15px;
|
|
222
|
+
}
|
|
223
|
+
&:last-of-type {
|
|
224
|
+
margin-right: 15px;
|
|
225
|
+
}
|
|
226
|
+
&.active {
|
|
227
|
+
background-color: #409EFF;
|
|
228
|
+
color: #fff;
|
|
229
|
+
border-color: #409EFF;
|
|
230
|
+
&::before {
|
|
231
|
+
content: '';
|
|
232
|
+
background: #fff;
|
|
233
|
+
display: inline-block;
|
|
234
|
+
width: 8px;
|
|
235
|
+
height: 8px;
|
|
236
|
+
border-radius: 50%;
|
|
237
|
+
position: relative;
|
|
238
|
+
margin-right: 2px;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
.contextmenu {
|
|
244
|
+
margin: 0;
|
|
245
|
+
background: #fff;
|
|
246
|
+
z-index: 3000;
|
|
247
|
+
position: absolute;
|
|
248
|
+
list-style-type: none;
|
|
249
|
+
padding: 5px 0;
|
|
250
|
+
border-radius: 4px;
|
|
251
|
+
font-size: 12px;
|
|
252
|
+
font-weight: 400;
|
|
253
|
+
color: #333;
|
|
254
|
+
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
|
|
255
|
+
li {
|
|
256
|
+
margin: 0;
|
|
257
|
+
padding: 7px 16px;
|
|
258
|
+
cursor: pointer;
|
|
259
|
+
&:hover {
|
|
260
|
+
background: #eee;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
</style>
|
|
266
|
+
|
|
267
|
+
<style lang="scss">
|
|
268
|
+
//reset element css of el-icon-close
|
|
269
|
+
.tags-view-wrapper {
|
|
270
|
+
.tags-view-item {
|
|
271
|
+
.el-icon-close {
|
|
272
|
+
width: 16px;
|
|
273
|
+
height: 16px;
|
|
274
|
+
vertical-align: 2px;
|
|
275
|
+
border-radius: 50%;
|
|
276
|
+
text-align: center;
|
|
277
|
+
transition: all .3s cubic-bezier(.645, .045, .355, 1);
|
|
278
|
+
transform-origin: 100% 50%;
|
|
279
|
+
&:before {
|
|
280
|
+
transform: scale(.6);
|
|
281
|
+
display: inline-block;
|
|
282
|
+
vertical-align: -3px;
|
|
283
|
+
}
|
|
284
|
+
&:hover {
|
|
285
|
+
background-color: #b4bccc;
|
|
286
|
+
color: #fff;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
</style>
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<div class="main-container">
|
|
5
5
|
<div :class="{'fixed-header':fixedHeader}">
|
|
6
6
|
<navbar />
|
|
7
|
+
<tags-view />
|
|
7
8
|
</div>
|
|
8
9
|
<app-main />
|
|
9
10
|
</div>
|
|
@@ -11,14 +12,15 @@
|
|
|
11
12
|
</template>
|
|
12
13
|
|
|
13
14
|
<script>
|
|
14
|
-
import { Navbar, Sidebar, AppMain } from "./components";
|
|
15
|
+
import { Navbar, Sidebar, AppMain, TagsView } from "./components";
|
|
15
16
|
import { main } from '@/utils/getMenu'
|
|
16
17
|
export default {
|
|
17
18
|
name: "Layout",
|
|
18
19
|
components: {
|
|
19
20
|
Navbar,
|
|
20
21
|
Sidebar,
|
|
21
|
-
AppMain
|
|
22
|
+
AppMain,
|
|
23
|
+
TagsView
|
|
22
24
|
},
|
|
23
25
|
data() {
|
|
24
26
|
return {
|