@litianxiang/portal-core 0.1.22 → 0.1.24
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 +320 -269
- package/dist/index.d.ts +79 -1
- package/dist/index.js +8 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,373 +1,424 @@
|
|
|
1
|
-
# @litianxiang/portal-core
|
|
1
|
+
# @litianxiang/portal-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
门户子系统通用内核,统一提供:
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
各子系统只需注入自己的静态路由、用户 Store、(可选)Tab Store 和“第一个页面”的计算函数,即可接入。
|
|
5
|
+
- 路由守卫与动态菜单挂载
|
|
6
|
+
- SSO 登录态恢复与统一退出
|
|
7
|
+
- 认证 HTTP 拦截器(Loading、401、自动刷新 token)
|
|
8
|
+
- 菜单/权限/用户状态工具
|
|
9
|
+
- Tab、Loading、时间、数字、主题、i18n 工具
|
|
12
10
|
|
|
13
11
|
## 安装
|
|
14
12
|
|
|
15
13
|
```bash
|
|
16
14
|
pnpm add @litianxiang/portal-core
|
|
17
15
|
# 或
|
|
18
|
-
|
|
19
|
-
# 或
|
|
20
|
-
npm install @litianxiang/portal-core
|
|
16
|
+
npm i @litianxiang/portal-core
|
|
21
17
|
```
|
|
22
18
|
|
|
23
|
-
|
|
19
|
+
## 快速开始
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
- vue-router 4(hash 模式)
|
|
21
|
+
### 1) 路由接入
|
|
27
22
|
|
|
28
|
-
|
|
23
|
+
```ts
|
|
24
|
+
import { createAppRouter } from '@litianxiang/portal-core'
|
|
25
|
+
import { staticRoutes } from './routes'
|
|
26
|
+
import { useUserStore } from '@/stores/user'
|
|
29
27
|
|
|
30
|
-
|
|
28
|
+
const router = createAppRouter({
|
|
29
|
+
staticRoutes,
|
|
30
|
+
getUserStore: () => useUserStore(),
|
|
31
|
+
getFirstPage: (allMenu) => allMenu.find((i: any) => i.category === 'page')?.path,
|
|
32
|
+
authLoginUrl: import.meta.env.VITE_AUTH_LOGIN_URL
|
|
33
|
+
})
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
import {
|
|
34
|
-
createAppRouter,
|
|
35
|
-
createLogoutToAuth,
|
|
36
|
-
calcInactivityAction,
|
|
37
|
-
createAuthHttpClient,
|
|
38
|
-
createPermissionHelper
|
|
39
|
-
} from '@litianxiang/portal-core'
|
|
35
|
+
export default router
|
|
40
36
|
```
|
|
41
37
|
|
|
42
|
-
|
|
38
|
+
### 2) HTTP 接入
|
|
43
39
|
|
|
44
40
|
```ts
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
41
|
+
import axios from 'axios'
|
|
42
|
+
import { createAuthHttpClient } from '@litianxiang/portal-core'
|
|
43
|
+
import { useUserStore } from '@/stores/user'
|
|
44
|
+
import { useLoadingStore } from '@/stores/loading'
|
|
45
|
+
|
|
46
|
+
export const { http, logoutToAuth } = createAuthHttpClient({
|
|
47
|
+
axios,
|
|
48
|
+
getUserStore: () => useUserStore(),
|
|
49
|
+
getLoadingStore: () => useLoadingStore(),
|
|
50
|
+
onShowError: (msg) => console.error(msg)
|
|
51
|
+
})
|
|
52
|
+
```
|
|
52
53
|
|
|
53
|
-
|
|
54
|
+
## 导出方法说明(逐方法)
|
|
54
55
|
|
|
55
|
-
|
|
56
|
+
### app
|
|
57
|
+
|
|
58
|
+
- `setupPortalApp(options)`:统一执行应用启动编排(插件注册、图标注册、beforeMount、mount)。
|
|
59
|
+
|
|
60
|
+
最小示例:
|
|
56
61
|
|
|
57
62
|
```ts
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
import { setupPortalApp } from '@litianxiang/portal-core'
|
|
64
|
+
|
|
65
|
+
setupPortalApp({
|
|
66
|
+
app,
|
|
67
|
+
plugins: [router, pinia],
|
|
68
|
+
elementIcons: ElementPlusIconsVue,
|
|
69
|
+
mountSelector: '#app'
|
|
70
|
+
})
|
|
71
|
+
```
|
|
64
72
|
|
|
65
|
-
|
|
66
|
-
function createLogoutToAuth(options: LogoutOptions): () => void
|
|
73
|
+
### router
|
|
67
74
|
|
|
68
|
-
|
|
69
|
-
interface InactivityOptions {
|
|
70
|
-
expireMs?: number // token 允许的最大空闲时间,默认 2 小时
|
|
71
|
-
bufferMs?: number // 提前刷新 token 的缓冲时间,默认 5 分钟
|
|
72
|
-
}
|
|
75
|
+
- `createAppRouter(options)`:创建统一路由实例,内置登录态校验、菜单动态挂载、首屏跳转逻辑。
|
|
73
76
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
77
|
+
最小示例:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { createAppRouter } from '@litianxiang/portal-core'
|
|
81
|
+
|
|
82
|
+
const router = createAppRouter({
|
|
83
|
+
staticRoutes,
|
|
84
|
+
getUserStore: () => useUserStore(),
|
|
85
|
+
getFirstPage: (allMenu) => allMenu.find((m: any) => m.category === 'page')?.path
|
|
86
|
+
})
|
|
85
87
|
```
|
|
86
88
|
|
|
87
|
-
|
|
89
|
+
### auth
|
|
90
|
+
|
|
91
|
+
- `createLogoutToAuth(options)`:生成统一退出函数,负责清理用户态并跳转到认证站。
|
|
92
|
+
- `calcInactivityAction(lastActiveTime, now, config)`:根据空闲时长计算是否应刷新 token 或强制退出。
|
|
93
|
+
|
|
94
|
+
最小示例:
|
|
88
95
|
|
|
89
96
|
```ts
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
authLoginUrl?: string
|
|
97
|
-
ssoStorageKey?: string
|
|
98
|
-
refreshUrl: string
|
|
99
|
-
inactiveExpireMs?: number
|
|
100
|
-
inactiveBufferMs?: number
|
|
101
|
-
onShowError?: (msg: string) => void
|
|
102
|
-
}
|
|
97
|
+
import { createLogoutToAuth, calcInactivityAction } from '@litianxiang/portal-core'
|
|
98
|
+
|
|
99
|
+
const logoutToAuth = createLogoutToAuth({
|
|
100
|
+
getUserStore: () => useUserStore(),
|
|
101
|
+
getLoadingStore: () => useLoadingStore()
|
|
102
|
+
})
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
const { shouldLogout, shouldRefresh } = calcInactivityAction(lastActiveTime, Date.now())
|
|
105
|
+
if (shouldLogout) logoutToAuth()
|
|
106
|
+
if (shouldRefresh) {
|
|
107
|
+
// 执行刷新 token
|
|
107
108
|
}
|
|
108
109
|
```
|
|
109
110
|
|
|
110
|
-
|
|
111
|
+
### http
|
|
112
|
+
|
|
113
|
+
- `createAuthHttpClient(options)`:创建带认证拦截器的 HTTP 客户端(Loading、token 注入、401 处理、刷新 token)。
|
|
114
|
+
- `createPortalHttpClient(options)`:业务项目 HTTP 初始化便捷封装,减少重复模板代码。
|
|
115
|
+
|
|
116
|
+
最小示例:
|
|
111
117
|
|
|
112
118
|
```ts
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
getUserStore: () => any
|
|
116
|
-
}
|
|
119
|
+
import axios from 'axios'
|
|
120
|
+
import { createAuthHttpClient, createPortalHttpClient } from '@litianxiang/portal-core'
|
|
117
121
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
// 判断是否拥有给定列表中的任意一个权限
|
|
124
|
-
hasAnyPermission: (codesOrPaths: string[]) => boolean
|
|
125
|
-
}
|
|
122
|
+
const { http } = createAuthHttpClient({
|
|
123
|
+
axios,
|
|
124
|
+
getUserStore: () => useUserStore(),
|
|
125
|
+
getLoadingStore: () => useLoadingStore()
|
|
126
|
+
})
|
|
126
127
|
|
|
127
|
-
|
|
128
|
-
```
|
|
129
|
-
```
|
|
128
|
+
await http.post('/api/demo', {})
|
|
130
129
|
|
|
131
|
-
|
|
130
|
+
// 业务项目推荐直接使用便捷封装
|
|
131
|
+
const { http: portalHttp } = createPortalHttpClient({
|
|
132
|
+
axios,
|
|
133
|
+
getUserStore: () => useUserStore(),
|
|
134
|
+
getLoadingStore: () => useLoadingStore()
|
|
135
|
+
})
|
|
136
|
+
```
|
|
132
137
|
|
|
133
|
-
|
|
138
|
+
### config
|
|
134
139
|
|
|
135
|
-
|
|
140
|
+
- `getDefaultAuthLoginUrl()`:基于当前域名生成默认统一登录地址。
|
|
136
141
|
|
|
137
|
-
|
|
142
|
+
最小示例:
|
|
138
143
|
|
|
139
144
|
```ts
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
export const staticRoutes: RouteRecordRaw[] = [
|
|
144
|
-
{
|
|
145
|
-
path: '/',
|
|
146
|
-
name: 'layout',
|
|
147
|
-
component: () => import('@/views/layout/index.vue'),
|
|
148
|
-
children: [
|
|
149
|
-
{
|
|
150
|
-
path: '',
|
|
151
|
-
name: 'main',
|
|
152
|
-
component: () => import('@/views/main/index.vue')
|
|
153
|
-
}
|
|
154
|
-
]
|
|
155
|
-
}
|
|
156
|
-
]
|
|
145
|
+
import { getDefaultAuthLoginUrl } from '@litianxiang/portal-core'
|
|
146
|
+
|
|
147
|
+
const authLoginUrl = getDefaultAuthLoginUrl()
|
|
157
148
|
```
|
|
158
149
|
|
|
159
|
-
|
|
150
|
+
### permission
|
|
151
|
+
|
|
152
|
+
- `createPermissionHelper(options)`:创建权限判断工具(页面权限、按钮权限、任一权限命中)。
|
|
153
|
+
|
|
154
|
+
最小示例:
|
|
160
155
|
|
|
161
|
-
|
|
156
|
+
```ts
|
|
157
|
+
import { createPermissionHelper } from '@litianxiang/portal-core'
|
|
162
158
|
|
|
163
|
-
|
|
159
|
+
const permission = createPermissionHelper({ getUserStore: () => useUserStore() })
|
|
160
|
+
const canVisit = permission.hasPagePermission('/system/user')
|
|
161
|
+
const canCreate = permission.hasButtonPermission('/system/user:add')
|
|
162
|
+
```
|
|
164
163
|
|
|
165
|
-
|
|
164
|
+
### menu
|
|
166
165
|
|
|
167
|
-
- `
|
|
168
|
-
- `
|
|
169
|
-
- `
|
|
170
|
-
- `
|
|
171
|
-
- `
|
|
172
|
-
- `fetchUserMenu()`:拉取菜单并填充 `getUserALLMenu`、`isMenuLoaded`,返回 boolean 是否成功
|
|
166
|
+
- `createMenuHelper(options)`:创建菜单辅助工具,提供路径查找、标题、父链、面包屑能力。
|
|
167
|
+
- `transformMenuTree(menuList, options)`:将后端菜单转换为标准 `CoreMenuItem` 结构。
|
|
168
|
+
- `buildAllMenuTree(menuList)`:构建完整菜单树(含页面与按钮)。
|
|
169
|
+
- `buildPageMenuList(menuList)`:从完整树裁剪 page/button 结构。
|
|
170
|
+
- `findFirstPagePath(menuList)`:递归查找第一个可访问页面路径。
|
|
173
171
|
|
|
174
|
-
|
|
172
|
+
最小示例:
|
|
175
173
|
|
|
176
174
|
```ts
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
getUserAccessToken: state => state.accessToken,
|
|
185
|
-
isMenuLoaded: state => state.menuLoaded,
|
|
186
|
-
getUserALLMenu: state => state.allMenu
|
|
187
|
-
},
|
|
188
|
-
actions: {
|
|
189
|
-
syncFromAuthStore() {
|
|
190
|
-
// 从统一认证站点恢复 token,成功返回 true
|
|
191
|
-
},
|
|
192
|
-
resetMenuLoaded() {
|
|
193
|
-
this.menuLoaded = false
|
|
194
|
-
this.allMenu = []
|
|
195
|
-
},
|
|
196
|
-
async fetchUserMenu() {
|
|
197
|
-
// 1. 请求后端菜单树
|
|
198
|
-
// 2. 转成拍平的 allMenu
|
|
199
|
-
// 3. this.menuLoaded = true
|
|
200
|
-
// 返回 true / false 表示是否成功
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
})
|
|
204
|
-
```
|
|
175
|
+
import {
|
|
176
|
+
createMenuHelper,
|
|
177
|
+
transformMenuTree,
|
|
178
|
+
buildAllMenuTree,
|
|
179
|
+
buildPageMenuList,
|
|
180
|
+
findFirstPagePath
|
|
181
|
+
} from '@litianxiang/portal-core'
|
|
205
182
|
|
|
206
|
-
|
|
183
|
+
const tree = transformMenuTree(rawMenu)
|
|
184
|
+
const allMenu = buildAllMenuTree(tree)
|
|
185
|
+
const pageMenu = buildPageMenuList(allMenu)
|
|
186
|
+
const firstPath = findFirstPagePath(allMenu)
|
|
207
187
|
|
|
208
|
-
|
|
188
|
+
const helper = createMenuHelper({ getMenuTree: () => allMenu })
|
|
189
|
+
const breadcrumb = helper.getBreadcrumb('/system/user')
|
|
190
|
+
```
|
|
209
191
|
|
|
210
|
-
|
|
192
|
+
### user
|
|
211
193
|
|
|
212
|
-
- `
|
|
194
|
+
- `createBaseUserState()`:创建基础用户状态(不含菜单)。
|
|
195
|
+
- `createBaseUserGetters()`:创建基础用户 getters(工号/姓名/token/最后活跃时间)。
|
|
196
|
+
- `createBaseUserActions(options?)`:创建基础用户 actions(set/clear/syncFromAuthStore)。
|
|
197
|
+
- `createMenuUserState()`:创建管理站点用户状态(含 all_menu/sidebar_menu/menuLoaded)。
|
|
198
|
+
- `createMenuUserGetters()`:创建管理站点用户 getters(含菜单相关 getters)。
|
|
199
|
+
- `createMenuUserActions(options)`:创建管理站点 actions(菜单加载、SSO 恢复、状态维护)。
|
|
200
|
+
- `filterSidebarMenu(menus, options?)`:递归过滤不应在侧边栏展示的菜单类别。
|
|
201
|
+
- `syncFromAuthStoreToStore(store, options?)`:从统一登录缓存恢复用户信息到 store。
|
|
202
|
+
- `fetchUserMenuForStore(store, options)`:请求后端菜单并写入 `all_menu/sidebar_menu`。
|
|
213
203
|
|
|
214
|
-
|
|
204
|
+
最小示例:
|
|
215
205
|
|
|
216
206
|
```ts
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
207
|
+
import {
|
|
208
|
+
createBaseUserState,
|
|
209
|
+
createBaseUserGetters,
|
|
210
|
+
createBaseUserActions,
|
|
211
|
+
createMenuUserState,
|
|
212
|
+
createMenuUserGetters,
|
|
213
|
+
createMenuUserActions,
|
|
214
|
+
filterSidebarMenu,
|
|
215
|
+
syncFromAuthStoreToStore,
|
|
216
|
+
fetchUserMenuForStore
|
|
217
|
+
} from '@litianxiang/portal-core'
|
|
218
|
+
|
|
219
|
+
// 轻量站点
|
|
220
|
+
const baseState = createBaseUserState()
|
|
221
|
+
const baseGetters = createBaseUserGetters()
|
|
222
|
+
const baseActions = createBaseUserActions()
|
|
223
|
+
|
|
224
|
+
// 管理站点
|
|
225
|
+
const menuState = createMenuUserState()
|
|
226
|
+
const menuGetters = createMenuUserGetters()
|
|
227
|
+
const menuActions = createMenuUserActions({ http, site: 'manage', transformMenuData, getALLMenu })
|
|
228
|
+
|
|
229
|
+
const restored = syncFromAuthStoreToStore(menuState)
|
|
230
|
+
const sidebar = filterSidebarMenu(menuState.all_menu)
|
|
231
|
+
await fetchUserMenuForStore(menuState, { http, site: 'manage', transformMenuData, getALLMenu })
|
|
224
232
|
```
|
|
225
233
|
|
|
226
|
-
###
|
|
234
|
+
### tab
|
|
235
|
+
|
|
236
|
+
- `addTab(tabs, tab)`:新增标签页(同路径不重复)。
|
|
237
|
+
- `removeTab(tabs, path)`:按路径删除标签页。
|
|
238
|
+
- `closeOthers(tabs, path, homePath?)`:关闭除当前与首页外的标签页。
|
|
239
|
+
- `closeLeft(tabs, path, homePath?)`:关闭当前左侧标签页(首页保留)。
|
|
240
|
+
- `closeRight(tabs, path, homePath?)`:关闭当前右侧标签页(首页保留)。
|
|
241
|
+
- `closeAll(tabs)`:关闭全部可关闭标签页。
|
|
242
|
+
- `createTabStoreOptions(homePath?)`:生成可直接用于 Pinia 的 Tab Store 配置。
|
|
227
243
|
|
|
228
|
-
|
|
244
|
+
最小示例:
|
|
229
245
|
|
|
230
246
|
```ts
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
247
|
+
import {
|
|
248
|
+
addTab,
|
|
249
|
+
removeTab,
|
|
250
|
+
closeOthers,
|
|
251
|
+
closeLeft,
|
|
252
|
+
closeRight,
|
|
253
|
+
closeAll,
|
|
254
|
+
createTabStoreOptions
|
|
255
|
+
} from '@litianxiang/portal-core'
|
|
256
|
+
|
|
257
|
+
let tabs = addTab([], { title: '首页', path: '/main/home', closable: false, keepAlive: true })
|
|
258
|
+
tabs = addTab(tabs, { title: '用户管理', path: '/system/user', closable: true, keepAlive: true })
|
|
259
|
+
tabs = closeOthers(tabs, '/system/user')
|
|
260
|
+
tabs = closeLeft(tabs, '/system/user')
|
|
261
|
+
tabs = closeRight(tabs, '/system/user')
|
|
262
|
+
tabs = removeTab(tabs, '/system/user')
|
|
263
|
+
tabs = closeAll(tabs)
|
|
264
|
+
|
|
265
|
+
const tabStoreOptions = createTabStoreOptions('/main/home')
|
|
236
266
|
```
|
|
237
267
|
|
|
238
|
-
|
|
268
|
+
### loading
|
|
269
|
+
|
|
270
|
+
- `createLoadingStoreOptions()`:生成可直接用于 Pinia 的 Loading Store 配置。
|
|
239
271
|
|
|
240
|
-
|
|
272
|
+
最小示例:
|
|
241
273
|
|
|
242
274
|
```ts
|
|
243
|
-
|
|
244
|
-
import { createAppRouter } from '@litianxiang/portal-core'
|
|
245
|
-
import { useUserStore } from '@/stores/user'
|
|
246
|
-
import { useTabStore } from '@/stores/tab'
|
|
247
|
-
import { staticRoutes } from './routes'
|
|
248
|
-
import { getFirstPage } from '@/utils/menu'
|
|
275
|
+
import { createLoadingStoreOptions } from '@litianxiang/portal-core'
|
|
249
276
|
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
getUserStore: () => useUserStore(),
|
|
253
|
-
getTabStore: () => useTabStore(),
|
|
254
|
-
getFirstPage,
|
|
255
|
-
authLoginUrl: import.meta.env.VITE_AUTH_LOGIN_URL // 可选
|
|
256
|
-
})
|
|
277
|
+
const loadingStoreOptions = createLoadingStoreOptions()
|
|
278
|
+
```
|
|
257
279
|
|
|
258
|
-
|
|
280
|
+
### time
|
|
281
|
+
|
|
282
|
+
- `formatTime(date, format?)`:按指定格式输出时间字符串。
|
|
283
|
+
- `humanizeTime(date)`:输出相对时间(如“3分钟前”)。
|
|
284
|
+
- `timeDiff(start, end?)`:计算时间差(毫秒)。
|
|
285
|
+
- `formatToMonthDay(date)`:格式化为 `MM-DD`。
|
|
286
|
+
- `createRange(start, end)`:生成有序时间区间(自动处理先后)。
|
|
287
|
+
- `formatRange(start, end, format?)`:格式化时间区间字符串。
|
|
288
|
+
- `getPresetRange(key, now?)`:获取常用预设区间(今日/近7天/本月等)。
|
|
289
|
+
- `isInRange(value, range)`:判断时间是否落在区间内(含边界)。
|
|
290
|
+
- `useTime()`:组合导出以上时间工具。
|
|
291
|
+
|
|
292
|
+
最小示例:
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
import {
|
|
296
|
+
formatTime,
|
|
297
|
+
humanizeTime,
|
|
298
|
+
timeDiff,
|
|
299
|
+
formatToMonthDay,
|
|
300
|
+
createRange,
|
|
301
|
+
formatRange,
|
|
302
|
+
getPresetRange,
|
|
303
|
+
isInRange,
|
|
304
|
+
useTime
|
|
305
|
+
} from '@litianxiang/portal-core'
|
|
306
|
+
|
|
307
|
+
const t1 = formatTime(new Date())
|
|
308
|
+
const t2 = humanizeTime(Date.now() - 60 * 1000)
|
|
309
|
+
const diff = timeDiff('2026-01-01')
|
|
310
|
+
const md = formatToMonthDay(new Date())
|
|
311
|
+
const range = createRange('2026-01-01', '2026-01-31')
|
|
312
|
+
const text = formatRange('2026-01-01', '2026-01-31')
|
|
313
|
+
const preset = getPresetRange('last7days')
|
|
314
|
+
const inRange = isInRange(new Date(), range)
|
|
315
|
+
const timeUtils = useTime()
|
|
259
316
|
```
|
|
260
|
-
### 6. 使用 createAuthHttpClient 统一封装 http(推荐)
|
|
261
317
|
|
|
262
|
-
|
|
318
|
+
### number
|
|
263
319
|
|
|
264
|
-
-
|
|
265
|
-
-
|
|
266
|
-
-
|
|
267
|
-
- 401 统一退出到认证站并弹出错误提示
|
|
320
|
+
- `formatToYi(value, decimals?)`:按“亿元”单位格式化数值。
|
|
321
|
+
- `formatToWanShou(value, decimals?)`:按“万手”单位格式化数值。
|
|
322
|
+
- `formatWanYuanToYi(value, decimals?)`:将“万元”转换并格式化为“亿元”。
|
|
268
323
|
|
|
269
|
-
|
|
324
|
+
最小示例:
|
|
270
325
|
|
|
271
326
|
```ts
|
|
272
|
-
|
|
273
|
-
import axios from 'axios'
|
|
274
|
-
import { ElMessage } from 'element-plus'
|
|
275
|
-
import { createAuthHttpClient } from '@litianxiang/portal-core'
|
|
276
|
-
import { useUserStore } from '@/stores/user'
|
|
277
|
-
import { useLoadingStore } from '@/stores/loading'
|
|
327
|
+
import { formatToYi, formatToWanShou, formatWanYuanToYi } from '@litianxiang/portal-core'
|
|
278
328
|
|
|
279
|
-
const
|
|
280
|
-
|
|
329
|
+
const a = formatToYi(123000000)
|
|
330
|
+
const b = formatToWanShou(560000)
|
|
331
|
+
const c = formatWanYuanToYi(50000)
|
|
332
|
+
```
|
|
281
333
|
|
|
282
|
-
|
|
283
|
-
axios,
|
|
284
|
-
baseURL: '',
|
|
285
|
-
timeout: 10000,
|
|
286
|
-
getUserStore: () => useUserStore(),
|
|
287
|
-
getLoadingStore: () => useLoadingStore(),
|
|
288
|
-
authLoginUrl: AUTH_LOGIN_URL,
|
|
289
|
-
ssoStorageKey: 'user-store',
|
|
290
|
-
refreshUrl: '/proxy/auth/api/token/refresh',
|
|
291
|
-
inactiveExpireMs: 2 * 60 * 60 * 1000,
|
|
292
|
-
inactiveBufferMs: 5 * 60 * 1000,
|
|
293
|
-
onShowError: (msg: string) => {
|
|
294
|
-
ElMessage.error(msg)
|
|
295
|
-
}
|
|
296
|
-
})
|
|
334
|
+
### theme
|
|
297
335
|
|
|
298
|
-
|
|
299
|
-
|
|
336
|
+
- `initTheme()`:从本地缓存读取主题配置并初始化。
|
|
337
|
+
- `applyTheme(config)`:将主题变量应用到根节点 CSS 变量。
|
|
338
|
+
- `useThemeWatcher(config)`:监听主题变化并自动持久化。
|
|
300
339
|
|
|
301
|
-
|
|
302
|
-
|
|
340
|
+
最小示例:
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
import { initTheme, applyTheme, useThemeWatcher } from '@litianxiang/portal-core'
|
|
344
|
+
|
|
345
|
+
const theme = initTheme()
|
|
346
|
+
applyTheme(theme)
|
|
347
|
+
useThemeWatcher(theme)
|
|
303
348
|
```
|
|
304
349
|
|
|
305
|
-
|
|
350
|
+
### i18n
|
|
306
351
|
|
|
307
|
-
|
|
352
|
+
- `createPortalI18n(options)`:创建并返回统一配置的 `vue-i18n` 实例。
|
|
308
353
|
|
|
309
|
-
|
|
354
|
+
最小示例:
|
|
310
355
|
|
|
311
356
|
```ts
|
|
312
|
-
|
|
313
|
-
import { createPermissionHelper } from '@litianxiang/portal-core'
|
|
314
|
-
import { useUserStore } from '@/stores/user'
|
|
357
|
+
import { createPortalI18n } from '@litianxiang/portal-core'
|
|
315
358
|
|
|
316
|
-
const
|
|
317
|
-
|
|
359
|
+
const i18n = createPortalI18n({
|
|
360
|
+
messages: {
|
|
361
|
+
'zh-CN': { common: { ok: '确定' } },
|
|
362
|
+
'en-US': { common: { ok: 'OK' } }
|
|
363
|
+
}
|
|
318
364
|
})
|
|
365
|
+
```
|
|
319
366
|
|
|
320
|
-
|
|
321
|
-
permission.hasPagePermission('/system/menu')
|
|
367
|
+
## 业务方约定
|
|
322
368
|
|
|
323
|
-
|
|
324
|
-
permission.hasButtonPermission('/system/menu:add')
|
|
369
|
+
### userStore(最小能力)
|
|
325
370
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
371
|
+
- `getUserAccessToken`
|
|
372
|
+
- `getUserRefreshToken`
|
|
373
|
+
- `getUserLastActiveTime`
|
|
374
|
+
- `setUserAccessToken(token)`
|
|
375
|
+
- `setUserRefreshToken(token)`
|
|
376
|
+
- `setUserLastActiveTime(ts)`
|
|
377
|
+
- `syncFromAuthStore()`
|
|
378
|
+
- `clearUserInfo()`
|
|
332
379
|
|
|
333
|
-
|
|
334
|
-
> - `createPermissionHelper` 内部会优先使用 `userStore.getUserPageMenu`(包含 page + button),若不存在则退回 `userStore.getUserALLMenu`;
|
|
335
|
-
> - 匹配时仅按基础路径(去掉 `?query`)比较,避免 query 影响权限判断。
|
|
380
|
+
管理站点再补充:
|
|
336
381
|
|
|
337
|
-
|
|
382
|
+
- `isMenuLoaded`
|
|
383
|
+
- `resetMenuLoaded()`
|
|
384
|
+
- `fetchUserMenu()`
|
|
385
|
+
- `getUserALLMenu`
|
|
338
386
|
|
|
339
|
-
|
|
387
|
+
### 路由结构约定
|
|
340
388
|
|
|
341
|
-
|
|
342
|
-
- 若找不到,再从 `/src/views` 根目录查找:`/src/views${path}.vue`
|
|
389
|
+
`createAppRouter` 会把动态菜单路由挂到 `name: 'main'` 的节点下,业务静态路由需提供该节点。
|
|
343
390
|
|
|
344
|
-
|
|
391
|
+
### 视图解析约定
|
|
345
392
|
|
|
346
|
-
|
|
347
|
-
- `/src/views/main/system/user.vue`
|
|
348
|
-
- `/src/views/system/user.vue`
|
|
349
|
-
- 若都不存在,会默认认为在 `/src/views/main/system/user.vue`,方便排查。
|
|
393
|
+
菜单 path 会按以下优先级解析视图:
|
|
350
394
|
|
|
351
|
-
|
|
395
|
+
1. `/src/views/main${path}.vue`
|
|
396
|
+
2. `/src/views${path}.vue`
|
|
352
397
|
|
|
353
|
-
##
|
|
398
|
+
## 常见问题
|
|
354
399
|
|
|
355
|
-
|
|
400
|
+
### 1) 刷新后跳登录
|
|
356
401
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
402
|
+
先检查 `syncFromAuthStore()` 是否正确从统一登录缓存恢复 token。
|
|
403
|
+
|
|
404
|
+
### 2) 401 循环跳转
|
|
405
|
+
|
|
406
|
+
检查是否重复创建多个 http 实例,或在 401 处理中重复手动跳转。
|
|
407
|
+
|
|
408
|
+
### 3) 菜单加载后白屏
|
|
409
|
+
|
|
410
|
+
检查菜单 path 与实际视图文件是否匹配(含 `/main` 约定)。
|
|
411
|
+
|
|
412
|
+
## 本地开发
|
|
413
|
+
|
|
414
|
+
```bash
|
|
415
|
+
pnpm install
|
|
416
|
+
pnpm run build
|
|
417
|
+
pnpm run pack:check
|
|
418
|
+
```
|
|
368
419
|
|
|
369
|
-
|
|
420
|
+
## 发布流程
|
|
370
421
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
422
|
+
1. 更新版本号(patch 递增)
|
|
423
|
+
2. 执行 `pnpm run pack:check`
|
|
424
|
+
3. 发布:`npm publish --access public --registry=https://registry.npmjs.org/`
|
package/dist/index.d.ts
CHANGED
|
@@ -69,6 +69,30 @@ interface CreateAuthHttpClientOptions {
|
|
|
69
69
|
inactiveBufferMs?: number;
|
|
70
70
|
onShowError?: (msg: string) => void;
|
|
71
71
|
}
|
|
72
|
+
interface CreatePortalHttpClientOptions {
|
|
73
|
+
axios?: any;
|
|
74
|
+
baseURL?: string;
|
|
75
|
+
timeout?: number;
|
|
76
|
+
getUserStore: () => any;
|
|
77
|
+
getLoadingStore: () => any;
|
|
78
|
+
authLoginUrl?: string;
|
|
79
|
+
ssoStorageKey?: string;
|
|
80
|
+
refreshUrl?: string;
|
|
81
|
+
inactiveExpireMs?: number;
|
|
82
|
+
inactiveBufferMs?: number;
|
|
83
|
+
onShowError?: (msg: string) => void;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 创建业务项目通用 HTTP 客户端(createAuthHttpClient 的便捷封装)。
|
|
87
|
+
*
|
|
88
|
+
* 作用:
|
|
89
|
+
* - 统一管理端项目中的标准 HTTP 初始化写法
|
|
90
|
+
* - 复用 portal-core 认证拦截器能力
|
|
91
|
+
*/
|
|
92
|
+
declare function createPortalHttpClient(options: CreatePortalHttpClientOptions): {
|
|
93
|
+
http: any;
|
|
94
|
+
logoutToAuth: () => void;
|
|
95
|
+
};
|
|
72
96
|
declare function createAuthHttpClient(options: CreateAuthHttpClientOptions): {
|
|
73
97
|
http: any;
|
|
74
98
|
logoutToAuth: () => void;
|
|
@@ -94,6 +118,9 @@ interface PermissionHelper {
|
|
|
94
118
|
*/
|
|
95
119
|
hasAnyPermission: (codesOrPaths: string[]) => boolean;
|
|
96
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* 创建统一权限判断工具,提供页面权限、按钮权限和任意权限判断。
|
|
123
|
+
*/
|
|
97
124
|
declare function createPermissionHelper(options: PermissionHelperOptions): PermissionHelper;
|
|
98
125
|
|
|
99
126
|
interface MenuHelperOptions {
|
|
@@ -126,6 +153,9 @@ interface MenuHelper {
|
|
|
126
153
|
*/
|
|
127
154
|
getBreadcrumb: (path: string) => BreadcrumbItem[];
|
|
128
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* 创建菜单辅助工具,统一提供查找、标题、链路、父级与面包屑能力。
|
|
158
|
+
*/
|
|
129
159
|
declare function createMenuHelper(options: MenuHelperOptions): MenuHelper;
|
|
130
160
|
/**
|
|
131
161
|
* 标准化后的菜单节点结构,供各前端站点复用
|
|
@@ -212,6 +242,9 @@ declare function getPresetRange(key: PresetRangeKey, now?: TimeInput): TimeRange
|
|
|
212
242
|
* 判断某个时间是否在区间内(含边界)
|
|
213
243
|
*/
|
|
214
244
|
declare function isInRange(value: TimeInput, range: TimeRange<TimeInput>): boolean;
|
|
245
|
+
/**
|
|
246
|
+
* 组合导出时间工具方法,便于在业务中按对象方式使用。
|
|
247
|
+
*/
|
|
215
248
|
declare function useTime(): {
|
|
216
249
|
formatTime: typeof formatTime;
|
|
217
250
|
humanizeTime: typeof humanizeTime;
|
|
@@ -262,11 +295,29 @@ interface TabItem {
|
|
|
262
295
|
keepAlive: boolean;
|
|
263
296
|
}
|
|
264
297
|
declare const DEFAULT_HOME_PATH = "/main/home";
|
|
298
|
+
/**
|
|
299
|
+
* 向标签页列表追加新标签;若路径已存在则保持原列表。
|
|
300
|
+
*/
|
|
265
301
|
declare function addTab(tabs: TabItem[], tab: TabItem): TabItem[];
|
|
302
|
+
/**
|
|
303
|
+
* 根据路径移除指定标签页。
|
|
304
|
+
*/
|
|
266
305
|
declare function removeTab(tabs: TabItem[], path: string): TabItem[];
|
|
306
|
+
/**
|
|
307
|
+
* 关闭除当前与首页外的其他标签页。
|
|
308
|
+
*/
|
|
267
309
|
declare function closeOthers(tabs: TabItem[], path: string, homePath?: string): TabItem[];
|
|
310
|
+
/**
|
|
311
|
+
* 关闭当前标签左侧的标签页(首页始终保留)。
|
|
312
|
+
*/
|
|
268
313
|
declare function closeLeft(tabs: TabItem[], path: string, homePath?: string): TabItem[];
|
|
314
|
+
/**
|
|
315
|
+
* 关闭当前标签右侧的标签页(首页始终保留)。
|
|
316
|
+
*/
|
|
269
317
|
declare function closeRight(tabs: TabItem[], path: string, homePath?: string): TabItem[];
|
|
318
|
+
/**
|
|
319
|
+
* 关闭所有可关闭标签,仅保留不可关闭项。
|
|
320
|
+
*/
|
|
270
321
|
declare function closeAll(tabs: TabItem[]): TabItem[];
|
|
271
322
|
/**
|
|
272
323
|
* 为 Pinia 创建通用的 Tab Store 配置
|
|
@@ -305,7 +356,14 @@ interface BaseUserStoreLike {
|
|
|
305
356
|
refresh_token: string;
|
|
306
357
|
lastActiveTime: number | null;
|
|
307
358
|
}
|
|
359
|
+
/**
|
|
360
|
+
* 创建基础用户状态(不含菜单)。
|
|
361
|
+
* 适用于 portal.web、portal.auth.web 等轻量站点。
|
|
362
|
+
*/
|
|
308
363
|
declare function createBaseUserState(): BaseUserStoreLike;
|
|
364
|
+
/**
|
|
365
|
+
* 创建基础用户 getters(不含菜单)。
|
|
366
|
+
*/
|
|
309
367
|
declare function createBaseUserGetters(): {
|
|
310
368
|
getUserInfo: (state: any) => {
|
|
311
369
|
userno: any;
|
|
@@ -324,6 +382,9 @@ interface CreateBaseUserActionsOptions {
|
|
|
324
382
|
/** SSO 缓存 key,默认读取 AUTH_CONFIG.ssoStorageKey */
|
|
325
383
|
ssoStorageKey?: string;
|
|
326
384
|
}
|
|
385
|
+
/**
|
|
386
|
+
* 创建基础用户 actions(不含菜单)。
|
|
387
|
+
*/
|
|
327
388
|
declare function createBaseUserActions(options?: CreateBaseUserActionsOptions): {
|
|
328
389
|
setUserInfo(this: BaseUserStoreLike, userInfo: any): void;
|
|
329
390
|
setUserAccessToken(this: BaseUserStoreLike, access_token: string): void;
|
|
@@ -341,7 +402,14 @@ interface CoreMenuUserState<TMenu = any> extends CoreUserStoreLike {
|
|
|
341
402
|
all_menu: TMenu[];
|
|
342
403
|
sidebar_menu: TMenu[];
|
|
343
404
|
}
|
|
405
|
+
/**
|
|
406
|
+
* 创建管理站点用户状态(含菜单)。
|
|
407
|
+
* 适用于带动态菜单的后台系统。
|
|
408
|
+
*/
|
|
344
409
|
declare function createMenuUserState<TMenu = any>(): CoreMenuUserState<TMenu>;
|
|
410
|
+
/**
|
|
411
|
+
* 创建管理站点用户 getters(含菜单)。
|
|
412
|
+
*/
|
|
345
413
|
declare function createMenuUserGetters<TUserInfo = any>(): {
|
|
346
414
|
getUserInfo: (state: any) => TUserInfo;
|
|
347
415
|
getUserNo: (state: any) => any;
|
|
@@ -388,6 +456,9 @@ interface CreateMenuUserActionsOptions {
|
|
|
388
456
|
filterOptions?: SidebarMenuFilterOptions;
|
|
389
457
|
ssoStorageKey?: string;
|
|
390
458
|
}
|
|
459
|
+
/**
|
|
460
|
+
* 创建管理站点用户 actions(含菜单请求、菜单状态维护、SSO 恢复)。
|
|
461
|
+
*/
|
|
391
462
|
declare function createMenuUserActions(options: CreateMenuUserActionsOptions): {
|
|
392
463
|
setUserInfo(this: CoreUserStoreLike, userInfo: any): void;
|
|
393
464
|
setUserAccessToken(this: CoreUserStoreLike, access_token: string): void;
|
|
@@ -455,6 +526,13 @@ interface SetupPortalAppOptions {
|
|
|
455
526
|
beforeMount?: () => void;
|
|
456
527
|
mountSelector?: string;
|
|
457
528
|
}
|
|
529
|
+
/**
|
|
530
|
+
* 按 Portal 约定完成应用启动编排:
|
|
531
|
+
* 1. 注册插件
|
|
532
|
+
* 2. 注册 Element Plus 图标
|
|
533
|
+
* 3. 执行挂载前回调
|
|
534
|
+
* 4. 挂载应用
|
|
535
|
+
*/
|
|
458
536
|
declare function setupPortalApp(options: SetupPortalAppOptions): void;
|
|
459
537
|
|
|
460
538
|
interface CreateI18nOptions {
|
|
@@ -473,4 +551,4 @@ interface CreateI18nOptions {
|
|
|
473
551
|
*/
|
|
474
552
|
declare function createPortalI18n(options: CreateI18nOptions): vue_i18n.I18n<Record<string, any>, {}, {}, string, false>;
|
|
475
553
|
|
|
476
|
-
export { AUTH_CONFIG, type AppRouterOptions, type AuthConfig, type BaseUserStoreLike, type BreadcrumbItem, type CoreMenuItem, type CoreMenuUserState, type CoreUserStoreLike, type CreateAuthHttpClientOptions, type CreateBaseUserActionsOptions, type CreateI18nOptions, type CreateMenuUserActionsOptions, DEFAULT_AUTH_LOGIN_ENV_KEY, DEFAULT_AUTH_LOGIN_PATH, DEFAULT_HOME_PATH, DEFAULT_SSO_STORAGE_KEY, type FetchUserMenuForStoreOptions, type FontSize, type InactivityConfig, type InactivityResult, type LoadingState, type LogoutToAuthOptions, type MenuHelper, type MenuHelperOptions, type PermissionHelper, type PermissionHelperOptions, type PresetRangeKey, type SetupPortalAppOptions, type SidebarMenuFilterOptions, type SyncFromAuthStoreOptions, type TabItem, type ThemeConfig, type ThemeMode, type TimeInput, type TimeRange, type TransformMenuOptions, addTab, applyTheme, buildAllMenuTree, buildPageMenuList, calcInactivityAction, closeAll, closeLeft, closeOthers, closeRight, createAppRouter, createAuthHttpClient, createBaseUserActions, createBaseUserGetters, createBaseUserState, createLoadingStoreOptions, createLogoutToAuth, createMenuHelper, createMenuUserActions, createMenuUserGetters, createMenuUserState, createPermissionHelper, createPortalI18n, createRange, createTabStoreOptions, fetchUserMenuForStore, filterSidebarMenu, findFirstPagePath, formatRange, formatTime, formatToMonthDay, formatToWanShou, formatToYi, formatWanYuanToYi, getDefaultAuthLoginUrl, getPresetRange, humanizeTime, initTheme, isInRange, removeTab, setupPortalApp, syncFromAuthStoreToStore, timeDiff, transformMenuTree, useThemeWatcher, useTime };
|
|
554
|
+
export { AUTH_CONFIG, type AppRouterOptions, type AuthConfig, type BaseUserStoreLike, type BreadcrumbItem, type CoreMenuItem, type CoreMenuUserState, type CoreUserStoreLike, type CreateAuthHttpClientOptions, type CreateBaseUserActionsOptions, type CreateI18nOptions, type CreateMenuUserActionsOptions, type CreatePortalHttpClientOptions, DEFAULT_AUTH_LOGIN_ENV_KEY, DEFAULT_AUTH_LOGIN_PATH, DEFAULT_HOME_PATH, DEFAULT_SSO_STORAGE_KEY, type FetchUserMenuForStoreOptions, type FontSize, type InactivityConfig, type InactivityResult, type LoadingState, type LogoutToAuthOptions, type MenuHelper, type MenuHelperOptions, type PermissionHelper, type PermissionHelperOptions, type PresetRangeKey, type SetupPortalAppOptions, type SidebarMenuFilterOptions, type SyncFromAuthStoreOptions, type TabItem, type ThemeConfig, type ThemeMode, type TimeInput, type TimeRange, type TransformMenuOptions, addTab, applyTheme, buildAllMenuTree, buildPageMenuList, calcInactivityAction, closeAll, closeLeft, closeOthers, closeRight, createAppRouter, createAuthHttpClient, createBaseUserActions, createBaseUserGetters, createBaseUserState, createLoadingStoreOptions, createLogoutToAuth, createMenuHelper, createMenuUserActions, createMenuUserGetters, createMenuUserState, createPermissionHelper, createPortalHttpClient, createPortalI18n, createRange, createTabStoreOptions, fetchUserMenuForStore, filterSidebarMenu, findFirstPagePath, formatRange, formatTime, formatToMonthDay, formatToWanShou, formatToYi, formatWanYuanToYi, getDefaultAuthLoginUrl, getPresetRange, humanizeTime, initTheme, isInRange, removeTab, setupPortalApp, syncFromAuthStoreToStore, timeDiff, transformMenuTree, useThemeWatcher, useTime };
|
package/dist/index.js
CHANGED
|
@@ -458,6 +458,13 @@ function calcInactivityAction(lastActiveTime, now, config = {}) {
|
|
|
458
458
|
|
|
459
459
|
// src/http/http.ts
|
|
460
460
|
import axiosLib from "axios";
|
|
461
|
+
function createPortalHttpClient(options) {
|
|
462
|
+
return createAuthHttpClient({
|
|
463
|
+
baseURL: "",
|
|
464
|
+
timeout: 1e4,
|
|
465
|
+
...options
|
|
466
|
+
});
|
|
467
|
+
}
|
|
461
468
|
function createAuthHttpClient(options) {
|
|
462
469
|
const {
|
|
463
470
|
axios,
|
|
@@ -1200,6 +1207,7 @@ export {
|
|
|
1200
1207
|
createMenuUserGetters,
|
|
1201
1208
|
createMenuUserState,
|
|
1202
1209
|
createPermissionHelper,
|
|
1210
|
+
createPortalHttpClient,
|
|
1203
1211
|
createPortalI18n,
|
|
1204
1212
|
createRange,
|
|
1205
1213
|
createTabStoreOptions,
|