@skyfox2000/webui 1.0.13 → 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/lib/assets/modules/file-upload-CBUcsUnR.js +170 -0
- package/lib/assets/modules/form-validate-CgX7aR7T.js +297 -0
- package/lib/assets/modules/index-Civhd8xG.js +112 -0
- package/lib/assets/modules/index-DQMdt51R.js +726 -0
- package/lib/assets/modules/{index-BEWJ_qAH.js → index-DmWrkTXX.js} +1 -1
- package/lib/assets/modules/{menuTabs-BXdbFZor.js → menuTabs-BRYvFWA-.js} +131 -121
- package/lib/assets/modules/settingInfo-BZakNKIN.js +999 -0
- package/lib/assets/modules/uploadList-B7XoxGOh.js +278 -0
- package/lib/components/common/icon/index.vue.d.ts +1 -1
- package/lib/components/content/dialog/index.vue.d.ts +1 -1
- package/lib/components/content/drawer/index.vue.d.ts +1 -1
- package/lib/components/content/form/index.vue.d.ts +1 -1
- package/lib/components/content/search/index.vue.d.ts +1 -1
- package/lib/components/content/table/index.vue.d.ts +1 -1
- package/lib/components/content/table/tableOperate.vue.d.ts +1 -1
- package/lib/components/content/toolbar/icontool.vue.d.ts +1 -1
- package/lib/components/content/toolbar/index.vue.d.ts +1 -1
- package/lib/components/content/tree/index.vue.d.ts +1 -1
- package/lib/components/form/transfer/transferTable.vue.d.ts +1 -1
- package/lib/components/form/treeSelect/index.vue.d.ts +1 -1
- package/lib/components/form/upload/uploadList.vue.d.ts +1 -1
- package/lib/const/options.d.ts +32 -0
- package/lib/directives/enter-submit.d.ts +4 -0
- package/lib/directives/index.d.ts +2 -0
- package/lib/directives/permission.d.ts +5 -0
- package/lib/es/AceEditor/index.js +9 -8
- package/lib/es/BasicLayout/index.js +28 -24
- package/lib/es/Error403/index.js +15 -10
- package/lib/es/Error404/index.js +15 -10
- package/lib/es/ExcelForm/index.js +380 -175
- package/lib/es/UploadForm/index.js +23 -20
- package/lib/index.d.ts +42 -2
- package/lib/router/index.d.ts +16 -0
- package/lib/stores/appInfo.d.ts +34 -0
- package/lib/stores/hostInfo.d.ts +9 -0
- package/lib/stores/pageInfo.d.ts +18 -0
- package/lib/stores/pinia.d.ts +3 -0
- package/lib/stores/settingInfo.d.ts +8 -0
- package/lib/stores/userInfo.d.ts +21 -0
- package/lib/typings/data.d.ts +80 -0
- package/lib/typings/form.d.ts +171 -0
- package/lib/typings/menu.d.ts +7 -0
- package/lib/typings/option.d.ts +175 -0
- package/lib/typings/page.d.ts +69 -0
- package/lib/typings/table.d.ts +181 -0
- package/lib/typings/tools.d.ts +130 -0
- package/lib/typings/tree.d.ts +72 -0
- package/lib/typings/upload.d.ts +161 -0
- package/lib/typings/urls.d.ts +69 -0
- package/lib/utils/cache.d.ts +23 -0
- package/lib/utils/data.d.ts +6 -0
- package/lib/utils/download.d.ts +4 -0
- package/lib/utils/eventbus.d.ts +16 -0
- package/lib/utils/export-table.d.ts +12 -0
- package/lib/utils/file-upload.d.ts +15 -0
- package/lib/utils/form-excel.d.ts +30 -0
- package/lib/utils/form-validate.d.ts +29 -0
- package/lib/utils/form.d.ts +9 -0
- package/lib/utils/icon-loader.d.ts +125 -0
- package/lib/utils/isEmpty.d.ts +1 -0
- package/lib/utils/main-openapis.d.ts +9 -0
- package/lib/utils/menu.d.ts +6 -0
- package/lib/utils/options.d.ts +10 -0
- package/lib/utils/page.d.ts +25 -0
- package/lib/utils/table.d.ts +21 -0
- package/lib/utils/tools.d.ts +18 -0
- package/lib/utils/tree.d.ts +3 -0
- package/lib/vite-env.d.ts +8 -0
- package/lib/webui.css +1 -1
- package/lib/webui.es.js +1020 -854
- package/package.json +7 -6
- package/src/components/common/icon/appicon.vue +1 -1
- package/src/components/common/icon/fullscreen.vue +2 -1
- package/src/components/common/icon/index.vue +1 -1
- package/src/components/common/icon/layoutIcon.vue +1 -1
- package/src/components/common/icon/projectIcon.vue +1 -1
- package/src/components/common/icon/toolIcon.vue +1 -1
- package/src/components/content/dialog/excelForm.vue +2 -2
- package/src/components/content/dialog/index.vue +1 -1
- package/src/components/content/dialog/uploadForm.vue +7 -6
- package/src/components/content/drawer/index.vue +43 -18
- package/src/components/content/form/formItem.vue +1 -1
- package/src/components/content/form/index.vue +1 -1
- package/src/components/content/search/index.vue +1 -1
- package/src/components/content/search/searchItem.vue +1 -1
- package/src/components/content/table/index.vue +8 -5
- package/src/components/content/table/tableOperate.vue +8 -4
- package/src/components/content/toolbar/icontool.vue +2 -2
- package/src/components/content/toolbar/index.vue +9 -5
- package/src/components/content/tree/index.vue +1 -1
- package/src/components/error/error403.vue +2 -2
- package/src/components/error/error404.vue +2 -2
- package/src/components/form/autoComplete/index.vue +1 -1
- package/src/components/form/cascader/index.vue +1 -2
- package/src/components/form/checkbox/index.vue +11 -5
- package/src/components/form/datePicker/index.vue +1 -1
- package/src/components/form/input/index.vue +1 -1
- package/src/components/form/input/inputNumber.vue +1 -1
- package/src/components/form/input/inputPassword.vue +1 -1
- package/src/components/form/radio/index.vue +1 -1
- package/src/components/form/radio/radioStatus.vue +1 -1
- package/src/components/form/rangePicker/index.vue +1 -1
- package/src/components/form/select/index.vue +1 -1
- package/src/components/form/switch/index.vue +7 -3
- package/src/components/form/textarea/index.vue +1 -1
- package/src/components/form/transfer/index.vue +1 -1
- package/src/components/form/transfer/transferTable.vue +42 -22
- package/src/components/form/treeSelect/index.vue +2 -3
- package/src/components/form/upload/uploadList.vue +1 -1
- package/src/components/layout/breadcrumb/index.vue +1 -1
- package/src/components/layout/header/headerExits.vue +1 -1
- package/src/components/layout/header/index.vue +1 -1
- package/src/components/layout/header/user.vue +2 -1
- package/src/components/layout/menu/index.vue +9 -3
- package/src/components/layout/menu/menuTabs.vue +10 -12
- package/src/components/layout/page/basicLayout.vue +1 -1
- package/src/const/options.ts +114 -0
- package/src/directives/enter-submit.ts +13 -0
- package/src/directives/index.ts +26 -0
- package/src/directives/permission.ts +144 -0
- package/src/index.ts +201 -0
- package/src/router/index.ts +196 -0
- package/src/stores/appInfo.ts +471 -0
- package/src/stores/hostInfo.ts +117 -0
- package/src/stores/pageInfo.ts +131 -0
- package/src/stores/pinia.ts +10 -0
- package/src/stores/settingInfo.ts +53 -0
- package/src/stores/userInfo.ts +392 -0
- package/src/typings/data.d.ts +81 -0
- package/src/typings/form.d.ts +172 -0
- package/src/typings/menu.d.ts +7 -0
- package/src/typings/option.d.ts +177 -0
- package/src/typings/page.d.ts +70 -0
- package/src/typings/table.d.ts +182 -0
- package/src/typings/tools.d.ts +131 -0
- package/src/typings/tree.d.ts +73 -0
- package/src/typings/upload.d.ts +162 -0
- package/src/typings/urls.d.ts +70 -0
- package/src/utils/cache.ts +175 -0
- package/src/utils/data.ts +189 -0
- package/src/utils/download.ts +80 -0
- package/src/utils/eventbus.ts +78 -0
- package/src/utils/export-table.ts +155 -0
- package/src/utils/file-upload.ts +304 -0
- package/src/utils/form-excel.ts +523 -0
- package/src/utils/form-validate.ts +368 -0
- package/src/utils/form.ts +188 -0
- package/src/utils/icon-loader.ts +412 -0
- package/src/utils/isEmpty.ts +18 -0
- package/src/utils/main-openapis.ts +72 -0
- package/src/utils/menu.ts +89 -0
- package/src/utils/options.ts +324 -0
- package/src/utils/page.ts +262 -0
- package/src/utils/table.ts +274 -0
- package/src/utils/tools.ts +362 -0
- package/src/utils/tree.ts +28 -0
- package/tsconfig.json +1 -8
- package/vite.config.ts +7 -4
- package/lib/assets/modules/index-BahGnrAq.js +0 -415
- package/lib/assets/modules/index-BoKIa2sr.js +0 -109
- package/lib/assets/modules/index-D47Ci-T3.js +0 -107
- package/lib/assets/modules/uploadList-Dzlg47V0.js +0 -182
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { defineStore } from 'pinia';
|
|
2
|
+
import { useAppInfo } from './appInfo';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 标签页面属性
|
|
6
|
+
*/
|
|
7
|
+
interface TabPaneProps {
|
|
8
|
+
/**
|
|
9
|
+
* 唯一键,为url
|
|
10
|
+
* 如允许重复,则增加编号
|
|
11
|
+
*/
|
|
12
|
+
key: string;
|
|
13
|
+
/**
|
|
14
|
+
* 标签标题
|
|
15
|
+
*/
|
|
16
|
+
title: string;
|
|
17
|
+
/**
|
|
18
|
+
* 是否可关闭
|
|
19
|
+
* 默认:首页不可关闭
|
|
20
|
+
*/
|
|
21
|
+
closable?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* 是否缓存页面
|
|
24
|
+
*/
|
|
25
|
+
keepAlive?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* 内容组件
|
|
28
|
+
*/
|
|
29
|
+
content: any;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface PageInfo {
|
|
33
|
+
/**
|
|
34
|
+
* 标签页激活的key
|
|
35
|
+
*/
|
|
36
|
+
TabActive: string;
|
|
37
|
+
/**
|
|
38
|
+
* 标签页列表
|
|
39
|
+
*/
|
|
40
|
+
TabPanes: TabPaneProps[];
|
|
41
|
+
/**
|
|
42
|
+
* 是否开启多标签页
|
|
43
|
+
*/
|
|
44
|
+
TabEnabled: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 内部调用,添加Tab,页面缓存不卸载
|
|
49
|
+
* @param key 路由地址:/子应用名/#路由地址
|
|
50
|
+
*/
|
|
51
|
+
const addTabPane = (tabInfo: TabPaneProps): void => {
|
|
52
|
+
const pageInfoStore = usePageInfo();
|
|
53
|
+
if (pageInfoStore.TabEnabled) {
|
|
54
|
+
// 允许添加标签页
|
|
55
|
+
if (findTabPane(tabInfo.key, pageInfoStore.TabPanes)) {
|
|
56
|
+
/// 已存在标签
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
pageInfoStore.TabPanes.push(tabInfo);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const findTabPane = (key: string, tabs: TabPaneProps[]) => {
|
|
64
|
+
return tabs.find((item) => item.key === key);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const usePageInfo = defineStore('pageInfo', {
|
|
68
|
+
state: (): PageInfo => ({
|
|
69
|
+
TabActive: '',
|
|
70
|
+
TabPanes: [],
|
|
71
|
+
TabEnabled: true,
|
|
72
|
+
}),
|
|
73
|
+
actions: {
|
|
74
|
+
/**
|
|
75
|
+
* 删除Tab,页面缓存不卸载
|
|
76
|
+
* @param path 路由地址:路由地址
|
|
77
|
+
*/
|
|
78
|
+
removeTabPane(path: string): void {
|
|
79
|
+
if (this.TabEnabled) {
|
|
80
|
+
// 删除当前标签缓存
|
|
81
|
+
const appInfoStore = useAppInfo();
|
|
82
|
+
appInfoStore.excludeComponent(path);
|
|
83
|
+
// 删除标签页
|
|
84
|
+
let curIndex = 0;
|
|
85
|
+
let tabPanes = [...this.TabPanes];
|
|
86
|
+
for (let i = 0; i < tabPanes.length; i++) {
|
|
87
|
+
if (tabPanes[i].key === path) {
|
|
88
|
+
tabPanes.splice(i, 1);
|
|
89
|
+
curIndex = i - 1 < 0 ? 0 : i - 1;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (let i = 0; i < tabPanes.length; i++) {
|
|
95
|
+
if (tabPanes[i].key === this.TabActive) {
|
|
96
|
+
curIndex = i;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
this.TabPanes = tabPanes;
|
|
102
|
+
this.setTabActive(tabPanes[curIndex].key);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 新增或激活Tab
|
|
108
|
+
* @param path 路由地址:路由地址
|
|
109
|
+
* @returns
|
|
110
|
+
*/
|
|
111
|
+
async setTabActive(path: string): Promise<void> {
|
|
112
|
+
const appInfoStore = useAppInfo();
|
|
113
|
+
let menuItem = appInfoStore.findRoute(path);
|
|
114
|
+
if (!menuItem) return;
|
|
115
|
+
|
|
116
|
+
const tabInfo = {
|
|
117
|
+
key: path,
|
|
118
|
+
title: menuItem.name!.toString(),
|
|
119
|
+
content: '',
|
|
120
|
+
closable: true,
|
|
121
|
+
// 根据路由的meta.keepAlive确定是否加入对应缓存
|
|
122
|
+
// 默认为true
|
|
123
|
+
keepAlive: !(menuItem.meta && !menuItem.meta.keepAlive),
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
addTabPane(tabInfo);
|
|
127
|
+
|
|
128
|
+
this.TabActive = path;
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { SettingInfo } from '@skyfox2000/microbase';
|
|
2
|
+
|
|
3
|
+
import { defineStore } from 'pinia';
|
|
4
|
+
|
|
5
|
+
const SETTINGINFO_STORE_KEY = 'settingInfoStore';
|
|
6
|
+
|
|
7
|
+
export const useSettingInfo = defineStore('settingInfo', {
|
|
8
|
+
state: (): SettingInfo => ({
|
|
9
|
+
fullscreen: false,
|
|
10
|
+
menuCollapse: false,
|
|
11
|
+
tableColumns: {},
|
|
12
|
+
}),
|
|
13
|
+
actions: {
|
|
14
|
+
/**
|
|
15
|
+
* 设置全屏
|
|
16
|
+
*/
|
|
17
|
+
setFullscreen(status: boolean) {
|
|
18
|
+
this.$patch({
|
|
19
|
+
fullscreen: status,
|
|
20
|
+
});
|
|
21
|
+
},
|
|
22
|
+
/**
|
|
23
|
+
* 设置主菜单区折叠
|
|
24
|
+
*/
|
|
25
|
+
setMenuCollapse(status: boolean) {
|
|
26
|
+
this.$patch({
|
|
27
|
+
menuCollapse: status,
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
/**
|
|
31
|
+
* 获取表格头配置
|
|
32
|
+
*/
|
|
33
|
+
getTableColumns(pageUrl: string) {
|
|
34
|
+
return this.tableColumns[pageUrl];
|
|
35
|
+
},
|
|
36
|
+
/**
|
|
37
|
+
* 设置表格头配置
|
|
38
|
+
*/
|
|
39
|
+
setTableColumns(pageUrl: string, columns: Record<string, any>[]) {
|
|
40
|
+
this.$patch({
|
|
41
|
+
tableColumns: {
|
|
42
|
+
...this.tableColumns,
|
|
43
|
+
[pageUrl]: columns,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
persist: {
|
|
49
|
+
key: SETTINGINFO_STORE_KEY,
|
|
50
|
+
storage: localStorage,
|
|
51
|
+
pick: ['fullscreen', 'menuCollapse', 'tableColumns'],
|
|
52
|
+
},
|
|
53
|
+
});
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import { defineStore } from 'pinia';
|
|
2
|
+
import { ApiResponse, httpPost, IUrlInfo, ReqParams, ResStatus, setToken } from '@skyfox2000/fapi';
|
|
3
|
+
import message from 'vue-m-message';
|
|
4
|
+
import { LoginInfo, UserInfo } from '@skyfox2000/microbase';
|
|
5
|
+
import { ref } from 'vue';
|
|
6
|
+
import { useAppInfo } from './appInfo';
|
|
7
|
+
|
|
8
|
+
const USERINFO_STORE_KEY = 'userInfoStore';
|
|
9
|
+
|
|
10
|
+
const TokenError = 'Token解析失败';
|
|
11
|
+
const LoginExpired = '登录过期,请重新登录';
|
|
12
|
+
|
|
13
|
+
/** 登录相关接口 */
|
|
14
|
+
const LoginUrlList: {
|
|
15
|
+
/** 登录信息 */
|
|
16
|
+
login: IUrlInfo;
|
|
17
|
+
/** 获取界面授权 */
|
|
18
|
+
auth: IUrlInfo;
|
|
19
|
+
/** 退出登录 */
|
|
20
|
+
logout: IUrlInfo;
|
|
21
|
+
} = {
|
|
22
|
+
login: {
|
|
23
|
+
api: 'PLATFORM_API',
|
|
24
|
+
url: '/openapi/LoginSrv/login',
|
|
25
|
+
loadingText: false,
|
|
26
|
+
},
|
|
27
|
+
auth: {
|
|
28
|
+
api: 'PLATFORM_API',
|
|
29
|
+
authorize: true,
|
|
30
|
+
url: '/api/RCAccountOpSrv/getPermits',
|
|
31
|
+
loadingText: false,
|
|
32
|
+
},
|
|
33
|
+
logout: {
|
|
34
|
+
api: 'PLATFORM_API',
|
|
35
|
+
authorize: true,
|
|
36
|
+
url: '/api/LoginSrv/logout',
|
|
37
|
+
loadingText: '正在退出...',
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 登录接口操作
|
|
43
|
+
* @param loginInfo 登录信息
|
|
44
|
+
* @returns
|
|
45
|
+
*/
|
|
46
|
+
const loginApi = <T>(loginInfo: LoginInfo): Promise<ApiResponse<T> | null | undefined> => {
|
|
47
|
+
let loginParams: ReqParams = {
|
|
48
|
+
Option: {},
|
|
49
|
+
Query: loginInfo,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return httpPost<T>(LoginUrlList.login, loginParams).then((result: ApiResponse<T> | null) => {
|
|
53
|
+
if (result?.status === ResStatus.SUCCESS) {
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 界面授权信息接口
|
|
62
|
+
*/
|
|
63
|
+
interface AuthInfo {
|
|
64
|
+
/** 应用ID */
|
|
65
|
+
appId: string;
|
|
66
|
+
/** 用户角色级别 */
|
|
67
|
+
level: string;
|
|
68
|
+
/** 权限信息 */
|
|
69
|
+
permits: Record<string, string[]>;
|
|
70
|
+
/** 最后更新时间 */
|
|
71
|
+
lastTime: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 获取用户界面授权
|
|
76
|
+
* @param appId 应用ID
|
|
77
|
+
* @returns 授权信息
|
|
78
|
+
*/
|
|
79
|
+
const getRolePermitsApi = <T>(appId: string): Promise<T | null | undefined> => {
|
|
80
|
+
let authParams: ReqParams = {
|
|
81
|
+
Query: {
|
|
82
|
+
AppId: appId,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return httpPost<T>(LoginUrlList.auth, authParams).then((result) => {
|
|
87
|
+
if (result?.status === ResStatus.SUCCESS) {
|
|
88
|
+
return result.data as T;
|
|
89
|
+
}
|
|
90
|
+
message.error('获取授权信息失败,' + result?.msg!);
|
|
91
|
+
return null;
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 退出登录接口
|
|
97
|
+
* @returns 退出结果
|
|
98
|
+
*/
|
|
99
|
+
const logoutApi = <T>(): Promise<T | null | undefined> => {
|
|
100
|
+
let logoutParams: ReqParams = {
|
|
101
|
+
Option: {},
|
|
102
|
+
Query: {},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return httpPost<T>(LoginUrlList.logout, logoutParams).then((result: ApiResponse<T> | null) => {
|
|
106
|
+
if (result?.status === ResStatus.SUCCESS) {
|
|
107
|
+
return result.data as T;
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 解析JWT token并检查是否过期
|
|
115
|
+
* @param token JWT token字符串
|
|
116
|
+
* @returns 检查结果
|
|
117
|
+
*/
|
|
118
|
+
const isTokenExpired = (token: string): boolean => {
|
|
119
|
+
try {
|
|
120
|
+
const payload = JSON.parse(atob(token.split('.')[1])); // 解析payload部分
|
|
121
|
+
const exp = payload.exp; // 获取过期时间
|
|
122
|
+
|
|
123
|
+
const now = Date.now() / 1000; // 当前时间戳转换为秒
|
|
124
|
+
return now > exp; // 检查是否过期
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error(TokenError, error);
|
|
127
|
+
return true; // 如果解析失败,认为token已过期
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 检查token是否过期
|
|
133
|
+
* @returns 检查结果
|
|
134
|
+
*/
|
|
135
|
+
const checkToken = (token: string, onClose?: () => void): boolean => {
|
|
136
|
+
if (token == '') {
|
|
137
|
+
message.error(LoginExpired, {
|
|
138
|
+
duration: 3000,
|
|
139
|
+
onClose,
|
|
140
|
+
});
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (isTokenExpired(token)) {
|
|
145
|
+
message.error(LoginExpired, {
|
|
146
|
+
duration: 3000,
|
|
147
|
+
onClose,
|
|
148
|
+
});
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return true;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const expandUser = (loginInfo: LoginInfo): boolean => {
|
|
156
|
+
try {
|
|
157
|
+
const payload = JSON.parse(atob(loginInfo.token!.split('.')[1])); // 解析payload部分
|
|
158
|
+
payload.user = JSON.parse(payload.user); // 解析payload部分
|
|
159
|
+
|
|
160
|
+
loginInfo.UserInfo = payload.user;
|
|
161
|
+
|
|
162
|
+
return true; // 检查是否过期
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error(TokenError, error);
|
|
165
|
+
return false; // 如果解析失败,认为token已过期
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Store状态接口
|
|
171
|
+
*/
|
|
172
|
+
interface UserState {
|
|
173
|
+
/** 是否登录 */
|
|
174
|
+
isLogin: boolean;
|
|
175
|
+
/** 用户信息 */
|
|
176
|
+
userInfo: UserInfo;
|
|
177
|
+
/** 用户Token */
|
|
178
|
+
token?: string;
|
|
179
|
+
/** 刷新Token */
|
|
180
|
+
refreshToken?: string;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** 授权信息(私有) */
|
|
184
|
+
const auth = ref<AuthInfo | null>(null);
|
|
185
|
+
/**
|
|
186
|
+
* 当前登录用户信息
|
|
187
|
+
*/
|
|
188
|
+
export const useUserInfo = defineStore('userInfo', {
|
|
189
|
+
state: (): UserState => ({
|
|
190
|
+
isLogin: false,
|
|
191
|
+
userInfo: {
|
|
192
|
+
Id: '',
|
|
193
|
+
Name: '',
|
|
194
|
+
Code: '',
|
|
195
|
+
TenantId: null,
|
|
196
|
+
UserLevel: 'Guest',
|
|
197
|
+
},
|
|
198
|
+
token: '',
|
|
199
|
+
refreshToken: '',
|
|
200
|
+
}),
|
|
201
|
+
actions: {
|
|
202
|
+
/**
|
|
203
|
+
* 启动时初始化用户信息
|
|
204
|
+
*/
|
|
205
|
+
init() {
|
|
206
|
+
if (this.token && this.isLogin) {
|
|
207
|
+
setToken(this.token);
|
|
208
|
+
checkToken(this.token, () => this.clean());
|
|
209
|
+
} else {
|
|
210
|
+
this.clean();
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
/**
|
|
214
|
+
* 登录操作
|
|
215
|
+
* @param loginInfo 登录信息
|
|
216
|
+
* @returns
|
|
217
|
+
*/
|
|
218
|
+
async login(loginInfo: LoginInfo, redirect?: boolean): Promise<ApiResponse<LoginInfo> | void> {
|
|
219
|
+
const result = await loginApi<LoginInfo>(loginInfo);
|
|
220
|
+
if (result) {
|
|
221
|
+
const errInfo = result as ApiResponse<LoginInfo>;
|
|
222
|
+
if (errInfo.errno) {
|
|
223
|
+
return errInfo;
|
|
224
|
+
} else {
|
|
225
|
+
const loginInfo = result.data as LoginInfo;
|
|
226
|
+
this.token = loginInfo.token!;
|
|
227
|
+
setToken(this.token);
|
|
228
|
+
this.isLogin = true;
|
|
229
|
+
|
|
230
|
+
if (expandUser(loginInfo)) {
|
|
231
|
+
this.userInfo = {
|
|
232
|
+
...this.userInfo,
|
|
233
|
+
...loginInfo.UserInfo!,
|
|
234
|
+
};
|
|
235
|
+
} else {
|
|
236
|
+
message.error('用户信息解析失败');
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// 加载子应用
|
|
241
|
+
if (redirect) {
|
|
242
|
+
const appInfoStore = await useAppInfo();
|
|
243
|
+
appInfoStore.toDefaultApp();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* 获取应用授权信息
|
|
251
|
+
* @param appId 应用ID
|
|
252
|
+
* @returns 是否获取成功
|
|
253
|
+
*/
|
|
254
|
+
async getRolePermits(appId: string): Promise<boolean> {
|
|
255
|
+
if (!this.isLogin || !this.token) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// 如果授权信息存在且应用ID相同,即已获取过授权信息
|
|
260
|
+
if (auth.value && auth.value.appId === appId) {
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const result = await getRolePermitsApi<AuthInfo>(appId);
|
|
266
|
+
if (result) {
|
|
267
|
+
// 保存授权信息到内存中
|
|
268
|
+
auth.value = {
|
|
269
|
+
appId: appId,
|
|
270
|
+
level: this.userInfo.UserLevel,
|
|
271
|
+
permits: (result as AuthInfo).permits || [],
|
|
272
|
+
lastTime: (result as AuthInfo).lastTime,
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
return true;
|
|
276
|
+
} else {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
} catch (error) {
|
|
280
|
+
console.error('获取授权信息出错', error);
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* 检查角色权限
|
|
287
|
+
* @param role 资源角色编码需求
|
|
288
|
+
* @returns 是否有角色权限
|
|
289
|
+
*/
|
|
290
|
+
hasRole(role: string | string[]): boolean {
|
|
291
|
+
if (!auth.value || !auth.value.level) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
if (Array.isArray(role)) {
|
|
295
|
+
return role.includes(auth.value.level);
|
|
296
|
+
}
|
|
297
|
+
return auth.value.level === role;
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* 检查功能权限
|
|
302
|
+
* @param permitCode 资源权限编码
|
|
303
|
+
* @returns 是否有功能权限
|
|
304
|
+
*/
|
|
305
|
+
hasPermit(url: string, permitCode: string): boolean {
|
|
306
|
+
if (!auth.value || !auth.value.permits) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
return auth.value.permits[url]?.includes(permitCode) ?? false;
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* 获取用户信息
|
|
314
|
+
* @returns 用户Token
|
|
315
|
+
*/
|
|
316
|
+
getUserInfo(): UserInfo {
|
|
317
|
+
return this.userInfo;
|
|
318
|
+
},
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* 获取用户Token
|
|
322
|
+
* @returns 用户Token
|
|
323
|
+
*/
|
|
324
|
+
getToken(): string {
|
|
325
|
+
if (
|
|
326
|
+
checkToken(this.token!, () => {
|
|
327
|
+
this.clean();
|
|
328
|
+
this.logout();
|
|
329
|
+
})
|
|
330
|
+
) {
|
|
331
|
+
return this.token!;
|
|
332
|
+
}
|
|
333
|
+
return '';
|
|
334
|
+
},
|
|
335
|
+
|
|
336
|
+
/*
|
|
337
|
+
* 清理登录信息
|
|
338
|
+
*/
|
|
339
|
+
async clean(): Promise<void> {
|
|
340
|
+
this.userInfo = {
|
|
341
|
+
Id: '',
|
|
342
|
+
Name: '',
|
|
343
|
+
Code: '',
|
|
344
|
+
TenantId: '',
|
|
345
|
+
UserLevel: '',
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
this.token = '';
|
|
349
|
+
setToken(this.token);
|
|
350
|
+
this.isLogin = false;
|
|
351
|
+
// 清理授权信息
|
|
352
|
+
auth.value = null;
|
|
353
|
+
|
|
354
|
+
// 动态导入并使用appInfo
|
|
355
|
+
const appInfoStore = await useAppInfo();
|
|
356
|
+
appInfoStore.clear();
|
|
357
|
+
return Promise.resolve();
|
|
358
|
+
},
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* 登出操作
|
|
362
|
+
* @returns
|
|
363
|
+
*/
|
|
364
|
+
async logout(): Promise<void> {
|
|
365
|
+
try {
|
|
366
|
+
// 调用后端登出接口
|
|
367
|
+
await logoutApi<any>();
|
|
368
|
+
} catch (error) {
|
|
369
|
+
console.error('调用登出接口失败', error);
|
|
370
|
+
} finally {
|
|
371
|
+
// 无论登出接口是否成功,都执行本地登出操作
|
|
372
|
+
await this.clean();
|
|
373
|
+
|
|
374
|
+
// 跳转到登录页
|
|
375
|
+
setTimeout(async () => {
|
|
376
|
+
message.success('已退出登录');
|
|
377
|
+
}, 1000);
|
|
378
|
+
setTimeout(async () => {
|
|
379
|
+
const appInfoStore = await useAppInfo();
|
|
380
|
+
appInfoStore.logout();
|
|
381
|
+
}, 2000);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return Promise.resolve();
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
persist: {
|
|
388
|
+
key: USERINFO_STORE_KEY,
|
|
389
|
+
storage: localStorage,
|
|
390
|
+
pick: ['isLogin', 'userInfo', 'token', 'refreshToken'],
|
|
391
|
+
},
|
|
392
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Ref } from 'vue';
|
|
2
|
+
import { PageControl } from './page';
|
|
3
|
+
import { AnyData, ApiResponse, IUrlInfo, ReqParams } from '@skyfox2000/fapi';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 任何控制对象的通用接口
|
|
7
|
+
* 包括TreeControl和GridControl等
|
|
8
|
+
*/
|
|
9
|
+
export interface AnyControl {
|
|
10
|
+
/**
|
|
11
|
+
* 所属页面控制器
|
|
12
|
+
*/
|
|
13
|
+
page: PageControl<AnyData>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* URL信息
|
|
17
|
+
*/
|
|
18
|
+
url?: IUrlInfo;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 是否重新加载相关数据
|
|
22
|
+
*/
|
|
23
|
+
reload?: Ref<boolean>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 通用选项
|
|
28
|
+
*/
|
|
29
|
+
export interface PostOptions<T> {
|
|
30
|
+
/**
|
|
31
|
+
* Page页面URL的Key
|
|
32
|
+
*/
|
|
33
|
+
urlKey: string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 自定义URL,优先使用
|
|
37
|
+
*/
|
|
38
|
+
url?: IUrlInfo;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 执行提示文字
|
|
42
|
+
* - false时,不显示提示文字
|
|
43
|
+
* - 空时,显示默认文字
|
|
44
|
+
*/
|
|
45
|
+
loadingText?: string | boolean;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 是否隐藏错误提示
|
|
49
|
+
*/
|
|
50
|
+
hideErrorToast?: boolean;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 执行参数
|
|
54
|
+
*/
|
|
55
|
+
params?: ReqParams;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 处理参数的函数
|
|
59
|
+
*/
|
|
60
|
+
processParams?: (params: ReqParams) => ReqParams;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 加载状态引用
|
|
64
|
+
*/
|
|
65
|
+
loadingState?: Ref<boolean>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 执行选项
|
|
70
|
+
*/
|
|
71
|
+
export interface ExecuteOptions<T> extends PostOptions<T> {
|
|
72
|
+
/**
|
|
73
|
+
* 主键字段名
|
|
74
|
+
*/
|
|
75
|
+
primaryKey?: string;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 状态字段名
|
|
79
|
+
*/
|
|
80
|
+
statusKey?: string;
|
|
81
|
+
}
|