vue2-client 1.18.47 → 1.18.49
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/.eslintrc.js +84 -84
- package/Components.md +60 -60
- package/docs/index.md +30 -30
- package/index.js +31 -31
- package/jest-transform-stub.js +8 -8
- package/jest.setup.js +7 -7
- package/package.json +1 -1
- package/src/assets/img/querySlotDemo.svg +15 -15
- package/src/base-client/components/common/AmapMarker/AmapPointRendering.vue +120 -120
- package/src/base-client/components/common/CitySelect/index.js +3 -3
- package/src/base-client/components/common/CitySelect/index.md +109 -109
- package/src/base-client/components/common/CreateQuery/CreateQuery.vue +669 -669
- package/src/base-client/components/common/CreateQuery/index.js +3 -3
- package/src/base-client/components/common/CreateQuery/index.md +42 -42
- package/src/base-client/components/common/CreateSimpleFormQuery/index.js +3 -3
- package/src/base-client/components/common/CreateSimpleFormQuery/index.md +42 -42
- package/src/base-client/components/common/FormGroupEdit/index.js +3 -3
- package/src/base-client/components/common/FormGroupEdit/index.md +43 -43
- package/src/base-client/components/common/FormGroupQuery/FormGroupQuery.vue +166 -166
- package/src/base-client/components/common/FormGroupQuery/index.js +3 -3
- package/src/base-client/components/common/FormGroupQuery/index.md +43 -43
- package/src/base-client/components/common/JSONToTree/jsontotree.vue +271 -271
- package/src/base-client/components/common/PersonSetting/PersonSetting.vue +208 -208
- package/src/base-client/components/common/PersonSetting/index.js +3 -3
- package/src/base-client/components/common/Tree/Tree.vue +149 -149
- package/src/base-client/components/common/Tree/index.js +2 -2
- package/src/base-client/components/common/Upload/index.js +3 -3
- package/src/base-client/components/common/XAddNativeForm/index.md +146 -146
- package/src/base-client/components/common/XCard/XCard.vue +64 -64
- package/src/base-client/components/common/XDataDrawer/XDataDrawer.vue +180 -180
- package/src/base-client/components/common/XDataDrawer/index.js +3 -3
- package/src/base-client/components/common/XDataDrawer/index.md +41 -41
- package/src/base-client/components/common/XDescriptions/index.js +3 -3
- package/src/base-client/components/common/XDescriptions/index.md +322 -322
- package/src/base-client/components/common/XForm/XFormItem.vue +464 -407
- package/src/base-client/components/common/XForm/index.md +178 -178
- package/src/base-client/components/common/XFormTable/demo.vue +24 -35
- package/src/base-client/components/common/XStepView/XStepView.vue +252 -252
- package/src/base-client/components/common/XStepView/index.js +3 -3
- package/src/base-client/components/common/XStepView/index.md +31 -31
- package/src/base-client/components/common/XTable/XTable.vue +1715 -1715
- package/src/base-client/components/common/XTable/XTableWrapper.vue +786 -786
- package/src/base-client/components/common/XTable/index.md +255 -255
- package/src/base-client/components/system/DictionaryDetailsView/DictionaryDetailsView.vue +232 -232
- package/src/base-client/plugins/Config.js +19 -19
- package/src/base-client/plugins/tabs-page-plugin.js +39 -39
- package/src/components/Charts/Bar.vue +62 -62
- package/src/components/Charts/ChartCard.vue +134 -134
- package/src/components/Charts/Liquid.vue +67 -67
- package/src/components/Charts/MiniArea.vue +39 -39
- package/src/components/Charts/MiniBar.vue +39 -39
- package/src/components/Charts/MiniProgress.vue +75 -75
- package/src/components/Charts/MiniSmoothArea.vue +40 -40
- package/src/components/Charts/Radar.vue +68 -68
- package/src/components/Charts/RankList.vue +77 -77
- package/src/components/Charts/TagCloud.vue +113 -113
- package/src/components/Charts/TransferBar.vue +64 -64
- package/src/components/Charts/Trend.vue +82 -82
- package/src/components/Charts/chart.less +12 -12
- package/src/components/Charts/smooth.area.less +13 -13
- package/src/components/NumberInfo/NumberInfo.vue +54 -54
- package/src/components/NumberInfo/index.js +3 -3
- package/src/components/NumberInfo/index.less +54 -54
- package/src/components/NumberInfo/index.md +43 -43
- package/src/components/card/ChartCard.vue +79 -79
- package/src/components/chart/Bar.vue +60 -60
- package/src/components/chart/MiniArea.vue +67 -67
- package/src/components/chart/MiniBar.vue +59 -59
- package/src/components/chart/MiniProgress.vue +57 -57
- package/src/components/chart/Radar.vue +80 -80
- package/src/components/chart/RankingList.vue +60 -60
- package/src/components/chart/Trend.vue +79 -79
- package/src/components/chart/index.less +9 -9
- package/src/components/checkbox/ColorCheckbox.vue +157 -157
- package/src/components/input/IInput.vue +66 -66
- package/src/components/menu/SideMenu.vue +75 -75
- package/src/components/menu/menu.js +273 -273
- package/src/components/tool/AStepItem.vue +60 -60
- package/src/layouts/CommonLayout.vue +56 -56
- package/src/layouts/header/HeaderNotice.vue +177 -177
- package/src/lib.js +1 -1
- package/src/mock/extend/index.js +84 -84
- package/src/mock/goods/index.js +108 -108
- package/src/pages/dashboard/workplace/WorkPlace.vue +141 -141
- package/src/pages/system/dictionary/index.vue +44 -44
- package/src/pages/system/monitor/loginInfor/index.vue +37 -37
- package/src/pages/system/monitor/operLog/index.vue +37 -37
- package/src/router/async/router.map.js +9 -14
- package/src/services/api/cas.js +79 -79
- package/src/services/api/common.js +10 -2
- package/src/services/api/restTools.js +11 -0
- package/src/services/v3Api.js +25 -17
- package/src/store/modules/setting.js +119 -119
- package/src/utils/authority-utils.js +85 -85
- package/src/utils/errorCode.js +6 -6
- package/src/utils/request.js +68 -80
- package/src/utils/requestDedupe.js +245 -0
- package//350/277/201/347/247/273/346/227/245/345/277/227.md +15 -15
- package/assets/c__Users_QXLL_AppData_Roaming_Cursor_User_workspaceStorage_4fb3d7e3441d32bc6f2c7358b4188b48_images_e8c132e6dde513250d7f9721712df1f8-f1522a08-1aa0-4866-b4e5-b34cf2e21109.png +0 -0
- package/docs/HChart/351/205/215/347/275/256/346/226/207/346/241/243.md +0 -960
|
@@ -1,119 +1,119 @@
|
|
|
1
|
-
import config from '@vue2-client/config'
|
|
2
|
-
import { ADMIN } from '@vue2-client/config/default'
|
|
3
|
-
import { formatFullPath } from '@vue2-client/utils/i18n'
|
|
4
|
-
import { filterMenu } from '@vue2-client/utils/authority-utils'
|
|
5
|
-
import { getLocalSetting } from '@vue2-client/utils/themeUtil'
|
|
6
|
-
import deepClone from 'lodash.clonedeep'
|
|
7
|
-
|
|
8
|
-
const localSetting = getLocalSetting(true)
|
|
9
|
-
const customTitlesStr = sessionStorage.getItem(process.env.VUE_APP_TBAS_TITLES_KEY)
|
|
10
|
-
const customTitles = (customTitlesStr && JSON.parse(customTitlesStr)) || []
|
|
11
|
-
|
|
12
|
-
export default {
|
|
13
|
-
namespaced: true,
|
|
14
|
-
state: {
|
|
15
|
-
isMobile: false,
|
|
16
|
-
animates: ADMIN.animates,
|
|
17
|
-
palettes: ADMIN.palettes,
|
|
18
|
-
pageMinHeight: 0,
|
|
19
|
-
menuData: [],
|
|
20
|
-
activatedFirst: undefined,
|
|
21
|
-
customTitles,
|
|
22
|
-
...config,
|
|
23
|
-
...localSetting
|
|
24
|
-
},
|
|
25
|
-
getters: {
|
|
26
|
-
menuData (state, getters, rootState) {
|
|
27
|
-
if (state.filterMenu) {
|
|
28
|
-
const { permissions, roles } = rootState.account
|
|
29
|
-
return filterMenu(deepClone(state.menuData), permissions, roles)
|
|
30
|
-
}
|
|
31
|
-
return state.menuData
|
|
32
|
-
},
|
|
33
|
-
firstMenu (state, getters) {
|
|
34
|
-
const { menuData } = getters
|
|
35
|
-
if (menuData.length > 0 && !menuData[0].fullPath) {
|
|
36
|
-
formatFullPath(menuData)
|
|
37
|
-
}
|
|
38
|
-
return menuData.map(item => {
|
|
39
|
-
const menuItem = { ...item }
|
|
40
|
-
delete menuItem.children
|
|
41
|
-
return menuItem
|
|
42
|
-
})
|
|
43
|
-
},
|
|
44
|
-
subMenu (state) {
|
|
45
|
-
const { menuData, activatedFirst } = state
|
|
46
|
-
if (menuData.length > 0 && !menuData[0].fullPath) {
|
|
47
|
-
formatFullPath(menuData)
|
|
48
|
-
}
|
|
49
|
-
const current = menuData.find(menu => menu.fullPath === activatedFirst)
|
|
50
|
-
return current && current.children || []
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
mutations: {
|
|
54
|
-
setDevice (state, isMobile) {
|
|
55
|
-
state.isMobile = isMobile
|
|
56
|
-
},
|
|
57
|
-
setTheme (state, theme) {
|
|
58
|
-
state.theme = theme
|
|
59
|
-
},
|
|
60
|
-
setLayout (state, layout) {
|
|
61
|
-
state.layout = layout
|
|
62
|
-
},
|
|
63
|
-
setMultiPage (state, multiPage) {
|
|
64
|
-
state.multiPage = multiPage
|
|
65
|
-
},
|
|
66
|
-
setAnimate (state, animate) {
|
|
67
|
-
state.animate = animate
|
|
68
|
-
},
|
|
69
|
-
setWeekMode (state, weekMode) {
|
|
70
|
-
state.weekMode = weekMode
|
|
71
|
-
},
|
|
72
|
-
setFixedHeader (state, fixedHeader) {
|
|
73
|
-
state.fixedHeader = fixedHeader
|
|
74
|
-
},
|
|
75
|
-
setFixedSideBar (state, fixedSideBar) {
|
|
76
|
-
state.fixedSideBar = fixedSideBar
|
|
77
|
-
},
|
|
78
|
-
setLang (state, lang) {
|
|
79
|
-
state.lang = lang
|
|
80
|
-
},
|
|
81
|
-
setHideSetting (state, hideSetting) {
|
|
82
|
-
state.hideSetting = hideSetting
|
|
83
|
-
},
|
|
84
|
-
correctPageMinHeight (state, minHeight) {
|
|
85
|
-
state.pageMinHeight += minHeight
|
|
86
|
-
},
|
|
87
|
-
setMenuData (state, menuData) {
|
|
88
|
-
state.menuData = menuData
|
|
89
|
-
},
|
|
90
|
-
setAsyncRoutes (state, asyncRoutes) {
|
|
91
|
-
state.asyncRoutes = asyncRoutes
|
|
92
|
-
},
|
|
93
|
-
setPageWidth (state, pageWidth) {
|
|
94
|
-
state.pageWidth = pageWidth
|
|
95
|
-
},
|
|
96
|
-
setActivatedFirst (state, activatedFirst) {
|
|
97
|
-
state.activatedFirst = activatedFirst
|
|
98
|
-
},
|
|
99
|
-
setFixedTabs (state, fixedTabs) {
|
|
100
|
-
state.fixedTabs = fixedTabs
|
|
101
|
-
},
|
|
102
|
-
setCustomTitle (state, { path, title }) {
|
|
103
|
-
if (title) {
|
|
104
|
-
const obj = state.customTitles.find(item => item.path === path)
|
|
105
|
-
if (obj) {
|
|
106
|
-
obj.title = title
|
|
107
|
-
} else {
|
|
108
|
-
state.customTitles.push({ path, title })
|
|
109
|
-
}
|
|
110
|
-
sessionStorage.setItem(process.env.VUE_APP_TBAS_TITLES_KEY, JSON.stringify(state.customTitles))
|
|
111
|
-
}
|
|
112
|
-
},
|
|
113
|
-
setSetting (state, setting) {
|
|
114
|
-
for (const key in setting) {
|
|
115
|
-
state[key] = setting[key]
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
1
|
+
import config from '@vue2-client/config'
|
|
2
|
+
import { ADMIN } from '@vue2-client/config/default'
|
|
3
|
+
import { formatFullPath } from '@vue2-client/utils/i18n'
|
|
4
|
+
import { filterMenu } from '@vue2-client/utils/authority-utils'
|
|
5
|
+
import { getLocalSetting } from '@vue2-client/utils/themeUtil'
|
|
6
|
+
import deepClone from 'lodash.clonedeep'
|
|
7
|
+
|
|
8
|
+
const localSetting = getLocalSetting(true)
|
|
9
|
+
const customTitlesStr = sessionStorage.getItem(process.env.VUE_APP_TBAS_TITLES_KEY)
|
|
10
|
+
const customTitles = (customTitlesStr && JSON.parse(customTitlesStr)) || []
|
|
11
|
+
|
|
12
|
+
export default {
|
|
13
|
+
namespaced: true,
|
|
14
|
+
state: {
|
|
15
|
+
isMobile: false,
|
|
16
|
+
animates: ADMIN.animates,
|
|
17
|
+
palettes: ADMIN.palettes,
|
|
18
|
+
pageMinHeight: 0,
|
|
19
|
+
menuData: [],
|
|
20
|
+
activatedFirst: undefined,
|
|
21
|
+
customTitles,
|
|
22
|
+
...config,
|
|
23
|
+
...localSetting
|
|
24
|
+
},
|
|
25
|
+
getters: {
|
|
26
|
+
menuData (state, getters, rootState) {
|
|
27
|
+
if (state.filterMenu) {
|
|
28
|
+
const { permissions, roles } = rootState.account
|
|
29
|
+
return filterMenu(deepClone(state.menuData), permissions, roles)
|
|
30
|
+
}
|
|
31
|
+
return state.menuData
|
|
32
|
+
},
|
|
33
|
+
firstMenu (state, getters) {
|
|
34
|
+
const { menuData } = getters
|
|
35
|
+
if (menuData.length > 0 && !menuData[0].fullPath) {
|
|
36
|
+
formatFullPath(menuData)
|
|
37
|
+
}
|
|
38
|
+
return menuData.map(item => {
|
|
39
|
+
const menuItem = { ...item }
|
|
40
|
+
delete menuItem.children
|
|
41
|
+
return menuItem
|
|
42
|
+
})
|
|
43
|
+
},
|
|
44
|
+
subMenu (state) {
|
|
45
|
+
const { menuData, activatedFirst } = state
|
|
46
|
+
if (menuData.length > 0 && !menuData[0].fullPath) {
|
|
47
|
+
formatFullPath(menuData)
|
|
48
|
+
}
|
|
49
|
+
const current = menuData.find(menu => menu.fullPath === activatedFirst)
|
|
50
|
+
return current && current.children || []
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
mutations: {
|
|
54
|
+
setDevice (state, isMobile) {
|
|
55
|
+
state.isMobile = isMobile
|
|
56
|
+
},
|
|
57
|
+
setTheme (state, theme) {
|
|
58
|
+
state.theme = theme
|
|
59
|
+
},
|
|
60
|
+
setLayout (state, layout) {
|
|
61
|
+
state.layout = layout
|
|
62
|
+
},
|
|
63
|
+
setMultiPage (state, multiPage) {
|
|
64
|
+
state.multiPage = multiPage
|
|
65
|
+
},
|
|
66
|
+
setAnimate (state, animate) {
|
|
67
|
+
state.animate = animate
|
|
68
|
+
},
|
|
69
|
+
setWeekMode (state, weekMode) {
|
|
70
|
+
state.weekMode = weekMode
|
|
71
|
+
},
|
|
72
|
+
setFixedHeader (state, fixedHeader) {
|
|
73
|
+
state.fixedHeader = fixedHeader
|
|
74
|
+
},
|
|
75
|
+
setFixedSideBar (state, fixedSideBar) {
|
|
76
|
+
state.fixedSideBar = fixedSideBar
|
|
77
|
+
},
|
|
78
|
+
setLang (state, lang) {
|
|
79
|
+
state.lang = lang
|
|
80
|
+
},
|
|
81
|
+
setHideSetting (state, hideSetting) {
|
|
82
|
+
state.hideSetting = hideSetting
|
|
83
|
+
},
|
|
84
|
+
correctPageMinHeight (state, minHeight) {
|
|
85
|
+
state.pageMinHeight += minHeight
|
|
86
|
+
},
|
|
87
|
+
setMenuData (state, menuData) {
|
|
88
|
+
state.menuData = menuData
|
|
89
|
+
},
|
|
90
|
+
setAsyncRoutes (state, asyncRoutes) {
|
|
91
|
+
state.asyncRoutes = asyncRoutes
|
|
92
|
+
},
|
|
93
|
+
setPageWidth (state, pageWidth) {
|
|
94
|
+
state.pageWidth = pageWidth
|
|
95
|
+
},
|
|
96
|
+
setActivatedFirst (state, activatedFirst) {
|
|
97
|
+
state.activatedFirst = activatedFirst
|
|
98
|
+
},
|
|
99
|
+
setFixedTabs (state, fixedTabs) {
|
|
100
|
+
state.fixedTabs = fixedTabs
|
|
101
|
+
},
|
|
102
|
+
setCustomTitle (state, { path, title }) {
|
|
103
|
+
if (title) {
|
|
104
|
+
const obj = state.customTitles.find(item => item.path === path)
|
|
105
|
+
if (obj) {
|
|
106
|
+
obj.title = title
|
|
107
|
+
} else {
|
|
108
|
+
state.customTitles.push({ path, title })
|
|
109
|
+
}
|
|
110
|
+
sessionStorage.setItem(process.env.VUE_APP_TBAS_TITLES_KEY, JSON.stringify(state.customTitles))
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
setSetting (state, setting) {
|
|
114
|
+
for (const key in setting) {
|
|
115
|
+
state[key] = setting[key]
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -1,85 +1,85 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 判断是否有路由的权限
|
|
3
|
-
* @param authority 路由权限配置
|
|
4
|
-
* @param permissions 用户权限集合
|
|
5
|
-
* @returns {boolean|*}
|
|
6
|
-
*/
|
|
7
|
-
function hasPermission (authority, permissions) {
|
|
8
|
-
let required = '*'
|
|
9
|
-
if (typeof authority === 'string') {
|
|
10
|
-
required = authority
|
|
11
|
-
} else if (Array.isArray(authority)) {
|
|
12
|
-
required = authority
|
|
13
|
-
} else if (typeof authority === 'object') {
|
|
14
|
-
required = authority.permission
|
|
15
|
-
}
|
|
16
|
-
return required === '*' || hasAnyItem(required, permissions, (r, t) => !!(r === t || r === t.id))
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* 判断是否有路由需要的角色
|
|
21
|
-
* @param authority 路由权限配置
|
|
22
|
-
* @param roles 用户角色集合
|
|
23
|
-
*/
|
|
24
|
-
function hasRole (authority, roles) {
|
|
25
|
-
let required
|
|
26
|
-
if (typeof authority === 'object') {
|
|
27
|
-
required = authority.role
|
|
28
|
-
}
|
|
29
|
-
return authority === '*' || hasAnyItem(required, roles, (r, t) => !!(r === t || r === t.id))
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* 判断目标数组是否有所需元素
|
|
34
|
-
* @param {String | String[]}required 所需元素,数组或单个元素
|
|
35
|
-
* @param {String[]|Object[]} source 目标数组
|
|
36
|
-
* @param {Function} filter 匹配条件
|
|
37
|
-
* (r: String, s: String|Object) => boolean
|
|
38
|
-
* @returns {boolean}
|
|
39
|
-
*/
|
|
40
|
-
function hasAnyItem (required, source, filter) {
|
|
41
|
-
if (!required) {
|
|
42
|
-
return false
|
|
43
|
-
}
|
|
44
|
-
const checkedList = Array.isArray(required) ? required : [required]
|
|
45
|
-
return !!source.find(s => checkedList.find(r => filter(r, s)))
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* 路由权限校验
|
|
50
|
-
* @param route 路由
|
|
51
|
-
* @param permissions 用户权限集合
|
|
52
|
-
* @param roles 用户角色集合
|
|
53
|
-
* @returns {boolean}
|
|
54
|
-
*/
|
|
55
|
-
function hasAuthority (route, permissions, roles) {
|
|
56
|
-
const authorities = [...route.meta.pAuthorities, route.meta.authority]
|
|
57
|
-
for (const authority of authorities) {
|
|
58
|
-
if (!hasPermission(authority, permissions) && !hasRole(authority, roles)) {
|
|
59
|
-
return false
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return true
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* 根据权限配置过滤菜单数据
|
|
67
|
-
* @param menuData
|
|
68
|
-
* @param permissions
|
|
69
|
-
* @param roles
|
|
70
|
-
*/
|
|
71
|
-
function filterMenu (menuData, permissions, roles) {
|
|
72
|
-
return menuData.filter(menu => {
|
|
73
|
-
if (menu.meta && menu.meta.invisible === undefined) {
|
|
74
|
-
if (!hasAuthority(menu, permissions, roles)) {
|
|
75
|
-
return false
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
if (menu.children && menu.children.length > 0) {
|
|
79
|
-
menu.children = filterMenu(menu.children, permissions, roles)
|
|
80
|
-
}
|
|
81
|
-
return true
|
|
82
|
-
})
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export { filterMenu, hasAuthority }
|
|
1
|
+
/**
|
|
2
|
+
* 判断是否有路由的权限
|
|
3
|
+
* @param authority 路由权限配置
|
|
4
|
+
* @param permissions 用户权限集合
|
|
5
|
+
* @returns {boolean|*}
|
|
6
|
+
*/
|
|
7
|
+
function hasPermission (authority, permissions) {
|
|
8
|
+
let required = '*'
|
|
9
|
+
if (typeof authority === 'string') {
|
|
10
|
+
required = authority
|
|
11
|
+
} else if (Array.isArray(authority)) {
|
|
12
|
+
required = authority
|
|
13
|
+
} else if (typeof authority === 'object') {
|
|
14
|
+
required = authority.permission
|
|
15
|
+
}
|
|
16
|
+
return required === '*' || hasAnyItem(required, permissions, (r, t) => !!(r === t || r === t.id))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 判断是否有路由需要的角色
|
|
21
|
+
* @param authority 路由权限配置
|
|
22
|
+
* @param roles 用户角色集合
|
|
23
|
+
*/
|
|
24
|
+
function hasRole (authority, roles) {
|
|
25
|
+
let required
|
|
26
|
+
if (typeof authority === 'object') {
|
|
27
|
+
required = authority.role
|
|
28
|
+
}
|
|
29
|
+
return authority === '*' || hasAnyItem(required, roles, (r, t) => !!(r === t || r === t.id))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 判断目标数组是否有所需元素
|
|
34
|
+
* @param {String | String[]}required 所需元素,数组或单个元素
|
|
35
|
+
* @param {String[]|Object[]} source 目标数组
|
|
36
|
+
* @param {Function} filter 匹配条件
|
|
37
|
+
* (r: String, s: String|Object) => boolean
|
|
38
|
+
* @returns {boolean}
|
|
39
|
+
*/
|
|
40
|
+
function hasAnyItem (required, source, filter) {
|
|
41
|
+
if (!required) {
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
const checkedList = Array.isArray(required) ? required : [required]
|
|
45
|
+
return !!source.find(s => checkedList.find(r => filter(r, s)))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 路由权限校验
|
|
50
|
+
* @param route 路由
|
|
51
|
+
* @param permissions 用户权限集合
|
|
52
|
+
* @param roles 用户角色集合
|
|
53
|
+
* @returns {boolean}
|
|
54
|
+
*/
|
|
55
|
+
function hasAuthority (route, permissions, roles) {
|
|
56
|
+
const authorities = [...route.meta.pAuthorities, route.meta.authority]
|
|
57
|
+
for (const authority of authorities) {
|
|
58
|
+
if (!hasPermission(authority, permissions) && !hasRole(authority, roles)) {
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return true
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 根据权限配置过滤菜单数据
|
|
67
|
+
* @param menuData
|
|
68
|
+
* @param permissions
|
|
69
|
+
* @param roles
|
|
70
|
+
*/
|
|
71
|
+
function filterMenu (menuData, permissions, roles) {
|
|
72
|
+
return menuData.filter(menu => {
|
|
73
|
+
if (menu.meta && menu.meta.invisible === undefined) {
|
|
74
|
+
if (!hasAuthority(menu, permissions, roles)) {
|
|
75
|
+
return false
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (menu.children && menu.children.length > 0) {
|
|
79
|
+
menu.children = filterMenu(menu.children, permissions, roles)
|
|
80
|
+
}
|
|
81
|
+
return true
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export { filterMenu, hasAuthority }
|
package/src/utils/errorCode.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
401: '认证失败,无法访问系统资源',
|
|
3
|
-
403: '当前操作没有权限',
|
|
4
|
-
404: '访问资源不存在',
|
|
5
|
-
default: '系统未知错误,请反馈给管理员'
|
|
6
|
-
}
|
|
1
|
+
export default {
|
|
2
|
+
401: '认证失败,无法访问系统资源',
|
|
3
|
+
403: '当前操作没有权限',
|
|
4
|
+
404: '访问资源不存在',
|
|
5
|
+
default: '系统未知错误,请反馈给管理员'
|
|
6
|
+
}
|
package/src/utils/request.js
CHANGED
|
@@ -12,81 +12,34 @@ import { logout, V4RefreshToken } from '@vue2-client/services/user'
|
|
|
12
12
|
import { LOGIN, SEARCH, V4_LOGIN } from '@vue2-client/services/apiService'
|
|
13
13
|
import { setV4AccessToken } from '@vue2-client/utils/login'
|
|
14
14
|
import EncryptUtil from '@vue2-client/utils/EncryptUtil'
|
|
15
|
+
// 引入请求去重模块
|
|
16
|
+
import {
|
|
17
|
+
DEDUPE_STRATEGY,
|
|
18
|
+
generateRequestKey,
|
|
19
|
+
hasPendingRequest,
|
|
20
|
+
addPendingRequest,
|
|
21
|
+
handleDuplicateRequest,
|
|
22
|
+
resolvePendingRequest,
|
|
23
|
+
rejectPendingRequest,
|
|
24
|
+
clearPendingRequests as clearAllPendingRequests,
|
|
25
|
+
shouldEnableDedupe,
|
|
26
|
+
getDedupeStrategy
|
|
27
|
+
} from '@vue2-client/utils/requestDedupe'
|
|
15
28
|
|
|
16
29
|
// 是否显示重新登录
|
|
17
30
|
let isReloginShow
|
|
18
31
|
|
|
19
|
-
// ============ 请求去重管理 ============
|
|
20
|
-
// 存储进行中的请求 Map<requestKey, { promise, controller }>
|
|
21
|
-
const pendingRequests = new Map()
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* 生成请求唯一标识
|
|
25
|
-
* @param {object} config - axios 请求配置
|
|
26
|
-
* @returns {string} 请求标识
|
|
27
|
-
*/
|
|
28
|
-
function generateRequestKey (config) {
|
|
29
|
-
const { method, url, data, params } = config
|
|
30
|
-
let dataStr = ''
|
|
31
|
-
let paramsStr = ''
|
|
32
|
-
try {
|
|
33
|
-
dataStr = data ? JSON.stringify(data) : ''
|
|
34
|
-
paramsStr = params ? JSON.stringify(params) : ''
|
|
35
|
-
} catch (e) {
|
|
36
|
-
// 循环引用或其他序列化问题,使用时间戳保证唯一性
|
|
37
|
-
console.warn('[请求去重] 参数序列化失败,跳过去重')
|
|
38
|
-
dataStr = `_${Date.now()}_${Math.random()}`
|
|
39
|
-
}
|
|
40
|
-
return `${method}:${url}:${dataStr}:${paramsStr}`
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* 添加请求到 pending 列表
|
|
45
|
-
* @param {object} config - axios 请求配置
|
|
46
|
-
*/
|
|
47
|
-
function addPendingRequest (config) {
|
|
48
|
-
const requestKey = generateRequestKey(config)
|
|
49
|
-
config._requestKey = requestKey
|
|
50
|
-
|
|
51
|
-
if (!pendingRequests.has(requestKey)) {
|
|
52
|
-
// 创建 AbortController 用于取消请求
|
|
53
|
-
const controller = new AbortController()
|
|
54
|
-
config.signal = controller.signal
|
|
55
|
-
pendingRequests.set(requestKey, { controller, config })
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* 移除已完成的请求
|
|
61
|
-
* @param {object} config - axios 请求配置
|
|
62
|
-
*/
|
|
63
|
-
function removePendingRequest (config) {
|
|
64
|
-
const requestKey = config._requestKey || generateRequestKey(config)
|
|
65
|
-
if (pendingRequests.has(requestKey)) {
|
|
66
|
-
pendingRequests.delete(requestKey)
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* 检查是否有相同的请求正在进行
|
|
72
|
-
* @param {object} config - axios 请求配置
|
|
73
|
-
* @returns {boolean}
|
|
74
|
-
*/
|
|
75
|
-
function hasPendingRequest (config) {
|
|
76
|
-
const requestKey = generateRequestKey(config)
|
|
77
|
-
return pendingRequests.has(requestKey)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
32
|
/**
|
|
81
33
|
* 清空所有 pending 请求
|
|
34
|
+
* @description 页面切换或需要取消所有请求时调用
|
|
82
35
|
*/
|
|
83
36
|
export function clearPendingRequests () {
|
|
84
|
-
|
|
85
|
-
controller.abort()
|
|
86
|
-
})
|
|
87
|
-
pendingRequests.clear()
|
|
37
|
+
clearAllPendingRequests()
|
|
88
38
|
}
|
|
89
39
|
|
|
40
|
+
// 导出去重策略常量,方便外部使用
|
|
41
|
+
export { DEDUPE_STRATEGY }
|
|
42
|
+
|
|
90
43
|
axios.defaults.timeout = 50000
|
|
91
44
|
axios.defaults.withCredentials = true
|
|
92
45
|
// 如果是microapp
|
|
@@ -219,19 +172,35 @@ function loadInterceptors () {
|
|
|
219
172
|
axios.interceptors.request.use(config => {
|
|
220
173
|
// ============ 请求去重逻辑 ============
|
|
221
174
|
// POST 请求默认开启去重,可通过 config.dedupe = false 关闭
|
|
222
|
-
|
|
175
|
+
// 支持两种策略:reject(拒绝,默认)和 reuse(复用结果)
|
|
176
|
+
const enableDedupe = shouldEnableDedupe(config)
|
|
223
177
|
|
|
224
|
-
if (
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const error = new Error('重复请求被取消')
|
|
228
|
-
error.code = 'ERR_DUPLICATE_REQUEST'
|
|
229
|
-
error.config = config
|
|
230
|
-
return Promise.reject(error)
|
|
231
|
-
}
|
|
178
|
+
if (enableDedupe) {
|
|
179
|
+
const requestKey = generateRequestKey(config)
|
|
180
|
+
config._requestKey = requestKey
|
|
232
181
|
|
|
233
|
-
|
|
234
|
-
|
|
182
|
+
// 检查是否有相同请求正在进行
|
|
183
|
+
if (hasPendingRequest(requestKey)) {
|
|
184
|
+
const result = handleDuplicateRequest(requestKey, config)
|
|
185
|
+
|
|
186
|
+
if (!result.shouldProceed) {
|
|
187
|
+
if (result.isReuse && result.promise) {
|
|
188
|
+
// 复用策略:返回相同的 Promise,标记为复用请求
|
|
189
|
+
config._isReuseRequest = true
|
|
190
|
+
config._reusePromise = result.promise
|
|
191
|
+
// 返回一个特殊标记,让后续逻辑知道这是复用请求
|
|
192
|
+
return config
|
|
193
|
+
} else {
|
|
194
|
+
// 拒绝策略:直接返回错误
|
|
195
|
+
return Promise.reject(result.error)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 添加到 pending 列表
|
|
201
|
+
const strategy = getDedupeStrategy(config)
|
|
202
|
+
const { signal } = addPendingRequest(requestKey, config, strategy)
|
|
203
|
+
config.signal = signal
|
|
235
204
|
}
|
|
236
205
|
// ============ 去重逻辑结束 ============
|
|
237
206
|
|
|
@@ -279,8 +248,12 @@ function loadInterceptors () {
|
|
|
279
248
|
}, errorHandler)
|
|
280
249
|
// 加载响应拦截器
|
|
281
250
|
axios.interceptors.response.use((res) => {
|
|
282
|
-
|
|
283
|
-
|
|
251
|
+
const requestKey = res.config._requestKey
|
|
252
|
+
|
|
253
|
+
// 如果是复用请求,直接返回复用的 Promise 结果
|
|
254
|
+
if (res.config._isReuseRequest && res.config._reusePromise) {
|
|
255
|
+
return res.config._reusePromise
|
|
256
|
+
}
|
|
284
257
|
|
|
285
258
|
// 判断是否需要解密
|
|
286
259
|
if (res.headers && res.headers['x-encrypted'] === '1') {
|
|
@@ -358,10 +331,22 @@ function loadInterceptors () {
|
|
|
358
331
|
description: msg
|
|
359
332
|
})
|
|
360
333
|
} else {
|
|
334
|
+
// 请求成功,通知复用的请求并返回数据
|
|
335
|
+
if (requestKey) {
|
|
336
|
+
resolvePendingRequest(requestKey, res.data)
|
|
337
|
+
}
|
|
361
338
|
return res.data
|
|
362
339
|
}
|
|
340
|
+
// 请求失败,通知复用的请求
|
|
341
|
+
if (requestKey) {
|
|
342
|
+
rejectPendingRequest(requestKey, new Error(msg))
|
|
343
|
+
}
|
|
363
344
|
return Promise.reject(msg)
|
|
364
345
|
} else {
|
|
346
|
+
// 请求成功,通知复用的请求并返回数据
|
|
347
|
+
if (requestKey) {
|
|
348
|
+
resolvePendingRequest(requestKey, res.data)
|
|
349
|
+
}
|
|
365
350
|
return res.data
|
|
366
351
|
}
|
|
367
352
|
}, errorHandler)
|
|
@@ -408,9 +393,12 @@ function loginExpire () {
|
|
|
408
393
|
}
|
|
409
394
|
// 异常拦截处理器
|
|
410
395
|
const errorHandler = (error) => {
|
|
411
|
-
//
|
|
396
|
+
// 请求失败,通知复用的请求并移除 pending 记录
|
|
412
397
|
if (error.config) {
|
|
413
|
-
|
|
398
|
+
const requestKey = error.config._requestKey
|
|
399
|
+
if (requestKey) {
|
|
400
|
+
rejectPendingRequest(requestKey, error)
|
|
401
|
+
}
|
|
414
402
|
}
|
|
415
403
|
|
|
416
404
|
// 如果是被取消的请求(去重导致或 AbortController),静默处理
|