@tsingroc/tsingroc-components 3.14.1 → 4.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tsingroc/tsingroc-components",
3
- "version": "3.14.1",
3
+ "version": "4.1.0",
4
4
  "author": "",
5
5
  "license": "ISC",
6
6
  "description": "",
@@ -38,7 +38,7 @@
38
38
  "jwt-decode": "^4.0.0",
39
39
  "react-icons": "^5.5.0",
40
40
  "rollup-plugin-worker-factory": "^0.5.7",
41
- "weatherlayers-gl": "^2025.5.1"
41
+ "weatherlayers-gl": "^2025.7.1"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "@ant-design/icons": "^6.0.0",
@@ -53,29 +53,29 @@
53
53
  "devDependencies": {
54
54
  "@ant-design/icons": "^6.0.0",
55
55
  "@eslint/js": "^9.21.0",
56
- "@rsbuild/core": "^1.3.21",
57
- "@rsbuild/plugin-react": "^1.3.1",
56
+ "@rsbuild/core": "^1.4.3",
57
+ "@rsbuild/plugin-react": "^1.3.3",
58
58
  "@rspress/plugin-preview": "2.0.0-beta.2",
59
- "@types/node": "^22.15.21",
60
- "@types/react": "^18.3.22",
59
+ "@types/node": "^22.16.0",
60
+ "@types/react": "^18.3.23",
61
61
  "@types/react-dom": "^18.3.7",
62
- "antd": "^5.25.2",
62
+ "antd": "^5.26.3",
63
63
  "antd-style": "^3.7.1",
64
64
  "echarts": "^5.6.0",
65
65
  "echarts-for-react": "^3.0.2",
66
66
  "echarts-gl": "^2.0.9",
67
- "eslint": "^9.27.0",
67
+ "eslint": "^9.30.1",
68
68
  "eslint-plugin-react-hooks": "^5.2.0",
69
- "globals": "^16.1.0",
70
- "prettier": "^3.5.3",
69
+ "globals": "^16.3.0",
70
+ "prettier": "^3.6.2",
71
71
  "react": "^18.3.1",
72
72
  "react-dom": "^18.3.1",
73
73
  "react-markdown": "^10.1.0",
74
74
  "remark-gfm": "^4.0.1",
75
75
  "rspress": "2.0.0-beta.2",
76
- "typedoc": "^0.28.4",
76
+ "typedoc": "^0.28.7",
77
77
  "typescript": "^5.8.3",
78
- "typescript-eslint": "^8.32.1"
78
+ "typescript-eslint": "^8.35.1"
79
79
  },
80
80
  "prettier": {
81
81
  "trailingComma": "all"
@@ -11,7 +11,11 @@ import { jwtDecode, type JwtPayload } from "jwt-decode";
11
11
  import Sdk from "casdoor-js-sdk";
12
12
  import type { SdkConfig } from "casdoor-js-sdk/lib/esm/sdk";
13
13
 
14
- export interface AccessTokenPayload extends JwtPayload {
14
+ export type AccessTokenPayload =
15
+ | CasdoorAccessTokenPayload
16
+ | LocalAccessTokenPayload;
17
+
18
+ interface CasdoorAccessTokenPayload extends JwtPayload {
15
19
  /**
16
20
  * Token 的发布者。见 [JWT 标准][1]。
17
21
  *
@@ -67,7 +71,44 @@ export interface AccessTokenPayload extends JwtPayload {
67
71
  scope: "profile";
68
72
  }
69
73
 
70
- export interface UserInfo {
74
+ interface LocalAccessTokenPayload extends JwtPayload {
75
+ /** Token 的过期时间(Unix 时间戳)。*/
76
+ exp: number;
77
+ /** Token 的生效时间(Unix 时间戳)。*/
78
+ nbf: number;
79
+ /** Token 的签发时间(Unix 时间戳)。*/
80
+ iat: number;
81
+ /** 用户信息 */
82
+ User: {
83
+ /** 注册时间。*/
84
+ createTime: string;
85
+ /** 用户的 UUID。*/
86
+ id?: string;
87
+ /** 用户名。*/
88
+ username?: string;
89
+ /** 是否已删除。*/
90
+ deleted?: boolean;
91
+ /** 用户的角色。*/
92
+ role: {
93
+ /** 角色的 UUID。*/
94
+ id: string;
95
+ /** 角色名。*/
96
+ name: string;
97
+ /** 角色的显示名。*/
98
+ display_name: string;
99
+ /** 角色拥有的权限。*/
100
+ functions: string[];
101
+ /** 角色的优先级。*/
102
+ priority: number;
103
+ };
104
+ /** 用户的角色的 UUID。*/
105
+ roleId: string;
106
+ };
107
+ }
108
+
109
+ export type UserInfo = CasdoorUserInfo | LocalUserInfo;
110
+
111
+ interface CasdoorUserInfo {
71
112
  /**
72
113
  * Token 的发布者。见 [JWT 标准][1]。
73
114
  *
@@ -106,27 +147,40 @@ export interface UserInfo {
106
147
  roles: string[];
107
148
  }
108
149
 
109
- interface User {
110
- /** 用户的个人信息,未经过 {@linkcode AuthCheck} 验证时为空。*/
111
- userInfo?: UserInfo;
112
- /** Access Token,未登录时为空。*/
113
- accessToken?: string;
114
- /** 解码后的 Access Token,未登录时为空。这里也会有一些用户的个人信息。*/
115
- decodedAccessToken?: AccessTokenPayload;
150
+ interface LocalUserInfo {
151
+ /** 注册时间。*/
152
+ createTime?: string;
153
+ /** 用户的 UUID。*/
154
+ id?: string;
155
+ /** 用户名。*/
156
+ username?: string;
157
+ /** 用户的角色。*/
158
+ roles: string[];
116
159
  }
117
160
 
118
- export interface Auth extends User {
119
- /** 全局的 Casdoor SDK 对象。*/
120
- sdk: Sdk;
161
+ export type Auth = CasdoorAuth | LocalAuth;
162
+
163
+ interface CommonAuth {
121
164
  /**
122
- * 跳转到登录。
165
+ * 当前使用的登录机制。
166
+ *
167
+ * - `"casdoor"`:基于 Casdoor 的登录系统。
168
+ * - `"local"`:场站端使用的内网登录系统。
169
+ */
170
+ mode: "casdoor" | "local";
171
+ /**
172
+ * 跳转到登录页面。
123
173
  *
124
174
  * 参数 `redirect` 可以填入登录完成后要跳转到的地址。如果省略,那么跳转目标将由
125
175
  * {@linkcode AuthCallback} 的 {@linkcode AuthCallbackProps.onRedirect | onRedirect}
126
176
  * 属性决定,默认为跳转到主页。
127
177
  */
128
178
  login(redirect?: string): void;
129
- /** 从登录页面返回时应该调用的回调。当登录状态无效时会**抛出异常**。*/
179
+ /**
180
+ * 从登录页面返回时应该调用的回调。当登录状态无效时会**抛出异常**。
181
+ *
182
+ * 当 {@linkcode mode | mode === "local"} 时,该函数无效。
183
+ */
130
184
  callback(): Promise<void>;
131
185
  /** 在线检查用户是否登录。用户未登录时会**抛出异常**。*/
132
186
  validate(): Promise<void>;
@@ -145,8 +199,198 @@ export interface Auth extends User {
145
199
  resource: RequestInfo | URL,
146
200
  options?: RequestInit,
147
201
  ): Promise<Response>;
202
+ /** 用户的个人信息,未经过 {@linkcode AuthCheck} 验证时为空。*/
203
+ userInfo?: UserInfo;
204
+ /** Access Token,未登录时为空。*/
205
+ accessToken?: string;
206
+ /** 解码后的 Access Token,未登录时为空。这里也会有一些用户的个人信息。*/
207
+ decodedAccessToken?: AccessTokenPayload;
208
+ }
209
+
210
+ export interface CasdoorAuth extends CommonAuth {
211
+ mode: "casdoor";
212
+ /** 全局的 Casdoor SDK 对象。*/
213
+ sdk: Sdk;
214
+ userInfo?: CasdoorUserInfo;
215
+ decodedAccessToken?: CasdoorAccessTokenPayload;
216
+ }
217
+
218
+ export interface LocalAuth extends CommonAuth {
219
+ mode: "local";
220
+ /**
221
+ * 在登录页面需要通过该函数调用登录 API。
222
+ *
223
+ * 账号或密码错误时会**抛出异常**。
224
+ */
225
+ localLogin(username: string, password: string): Promise<void>;
226
+ userInfo?: LocalUserInfo;
227
+ decodedAccessToken?: LocalAccessTokenPayload;
228
+ }
229
+
230
+ type User = Pick<CommonAuth, "userInfo" | "accessToken" | "decodedAccessToken">;
231
+ type CasdoorUser = Pick<
232
+ CasdoorAuth,
233
+ "userInfo" | "accessToken" | "decodedAccessToken"
234
+ >;
235
+ type LocalUser = Pick<
236
+ LocalAuth,
237
+ "userInfo" | "accessToken" | "decodedAccessToken"
238
+ >;
239
+
240
+ const commonAuth = (
241
+ user: User,
242
+ setUser: (user: User) => void,
243
+ ): Pick<CommonAuth, "logout" | "fetchWithAuth"> => ({
244
+ async logout(redirect) {
245
+ localStorage.removeItem("access_token");
246
+ setUser({});
247
+ if (redirect) {
248
+ location.href = redirect;
249
+ }
250
+ },
251
+ async fetchWithAuth(resource, options) {
252
+ const token = user.accessToken;
253
+ if (!token) {
254
+ throw new Error("未登录时无法使用 fetchWithAuth");
255
+ }
256
+ return await fetch(resource, {
257
+ ...options,
258
+ headers: {
259
+ Authorization: `Bearer ${token}`,
260
+ ...options?.headers,
261
+ },
262
+ });
263
+ },
264
+ });
265
+
266
+ function casdoorAuth(
267
+ user: User,
268
+ setUser: (user: User) => void,
269
+ sdkConfig: SdkConfig,
270
+ signinOrigin?: string,
271
+ signinPath?: string,
272
+ ): CasdoorAuth {
273
+ const sdk = new Sdk(sdkConfig);
274
+ return {
275
+ mode: "casdoor",
276
+ sdk,
277
+ login(redirect?: string) {
278
+ location.href = sdk.getSigninUrl();
279
+ if (redirect) {
280
+ sessionStorage.setItem("login_redirect", redirect);
281
+ }
282
+ },
283
+ async callback() {
284
+ // sdk 的类型声明有误
285
+ const resp = (await sdk.signin(
286
+ signinOrigin ?? location.origin,
287
+ signinPath,
288
+ )) as unknown as { token?: string };
289
+ const token = resp.token;
290
+ if (!token) {
291
+ throw new Error("登录 API 返回了异常的结果:" + JSON.stringify(resp));
292
+ }
293
+ localStorage.setItem("access_token", token);
294
+ setUser({
295
+ accessToken: token,
296
+ get decodedAccessToken() {
297
+ return jwtDecode<AccessTokenPayload>(token);
298
+ },
299
+ });
300
+ },
301
+ async validate() {
302
+ const token = user.accessToken;
303
+ if (!token) {
304
+ throw new Error("未登录!");
305
+ }
306
+ // sdk 的类型声明有误
307
+ const resp = (await sdk.getUserInfo(token)) as unknown as
308
+ | UserInfo
309
+ | { status: "error" };
310
+ if ("status" in resp) {
311
+ localStorage.removeItem("access_token");
312
+ setUser({});
313
+ throw new Error("登陆状态无效!");
314
+ } else {
315
+ setUser({ ...user, userInfo: resp });
316
+ }
317
+ },
318
+ ...commonAuth(user, setUser),
319
+ ...(user as CasdoorUser),
320
+ };
148
321
  }
149
322
 
323
+ const localAuth = (
324
+ user: User,
325
+ setUser: (user: User) => void,
326
+ loginPage: string,
327
+ signinOrigin?: string,
328
+ signinPath?: string,
329
+ ): LocalAuth => ({
330
+ mode: "local",
331
+ login(redirect) {
332
+ location.href = loginPage;
333
+ if (redirect) {
334
+ sessionStorage.setItem("login_redirect", redirect);
335
+ }
336
+ },
337
+ async localLogin(username, password) {
338
+ const resp = await fetch(
339
+ new URL(signinPath ?? "/api/signin", signinOrigin ?? location.origin),
340
+ {
341
+ method: "post",
342
+ headers: { "Content-Type": "application/json" },
343
+ body: JSON.stringify({ username, password }),
344
+ },
345
+ );
346
+ if (resp.status !== 200) {
347
+ throw new Error("账号或密码错误!");
348
+ }
349
+ const json = await resp.json();
350
+ const token = json.token;
351
+ if (typeof token !== "string") {
352
+ throw new Error("登录 API 返回了异常的结果:" + JSON.stringify(json));
353
+ }
354
+ localStorage.setItem("access_token", token);
355
+ setUser({
356
+ accessToken: token,
357
+ get decodedAccessToken() {
358
+ return jwtDecode<AccessTokenPayload>(token);
359
+ },
360
+ });
361
+ const redirect = sessionStorage.getItem("login_redirect");
362
+ sessionStorage.removeItem("login_redirect");
363
+ location.href = redirect ?? "/";
364
+ },
365
+ async callback() {},
366
+ async validate() {
367
+ const token = user.accessToken;
368
+ if (!token) {
369
+ throw new Error("未登录!");
370
+ }
371
+ const resp = await fetch(
372
+ new URL("/api/userInfo", signinOrigin ?? location.origin),
373
+ { headers: { Authorization: `Bearer ${token}` } },
374
+ );
375
+ try {
376
+ if (resp.status !== 200) {
377
+ throw new Error("登陆状态无效!");
378
+ }
379
+ const json = await resp.json();
380
+ if (!json.data) {
381
+ throw new Error("登陆状态无效!");
382
+ }
383
+ setUser({ ...user, userInfo: json.data });
384
+ } catch (err) {
385
+ localStorage.removeItem("access_token");
386
+ setUser({});
387
+ throw err;
388
+ }
389
+ },
390
+ ...commonAuth(user, setUser),
391
+ ...(user as LocalUser),
392
+ });
393
+
150
394
  const AuthContext = createContext<Auth | undefined>(undefined);
151
395
 
152
396
  /**
@@ -163,12 +407,23 @@ export function useAuth(user: string = "useAuth") {
163
407
  }
164
408
 
165
409
  export interface AuthProviderProps {
410
+ /**
411
+ * 要使用的登录机制。
412
+ *
413
+ * - `"casdoor"`:基于 Casdoor 的登录系统。
414
+ * - `"local"`:场站端使用的内网登录系统。
415
+ *
416
+ * @default "casdoor"
417
+ */
418
+ mode?: "casdoor" | "local";
166
419
  /**
167
420
  * 用于初始化 Casdoor SDK 的配置对象。
168
421
  * {@linkcode SdkConfig.redirectPath | sdkConfig.redirectPath} 应该指向一个包含
169
422
  * {@linkcode AuthCallback} 组件的路径。
170
423
  */
171
- sdkConfig: SdkConfig;
424
+ sdkConfig?: SdkConfig;
425
+ /** {@linkcode mode | mode: "local"} 时,登录页面的路径。*/
426
+ loginPage?: string;
172
427
  /**
173
428
  * 登录 API 的源点,末尾不带 `/`。
174
429
  * @default location.origin
@@ -210,73 +465,37 @@ export function AuthProvider(props: AuthProviderProps): ReactNode {
210
465
  }
211
466
  });
212
467
  const auth = useMemo<Auth>(() => {
213
- const sdk = new Sdk(props.sdkConfig);
214
- return {
215
- sdk,
216
- login(redirect) {
217
- location.href = sdk.getSigninUrl();
218
- if (redirect) {
219
- sessionStorage.setItem("login_redirect", redirect);
220
- }
221
- },
222
- async callback() {
223
- // sdk 的类型声明有误
224
- const resp = (await sdk.signin(
225
- props.signinOrigin ?? location.origin,
226
- props.signinPath,
227
- )) as unknown as { token?: string };
228
- const token = resp.token;
229
- if (!token) {
230
- throw new Error("登录 API 返回了异常的结果:" + JSON.stringify(resp));
231
- }
232
- localStorage.setItem("access_token", token);
233
- setUser({
234
- accessToken: token,
235
- get decodedAccessToken() {
236
- return jwtDecode<AccessTokenPayload>(token);
237
- },
238
- });
239
- },
240
- async validate() {
241
- const token = user.accessToken;
242
- if (!token) {
243
- throw new Error("未登录!");
244
- }
245
- // sdk 的类型声明有误
246
- const resp = (await sdk.getUserInfo(token)) as unknown as
247
- | UserInfo
248
- | { status: "error" };
249
- if ("status" in resp) {
250
- localStorage.removeItem("access_token");
251
- setUser({});
252
- throw new Error("登陆状态无效!");
253
- } else {
254
- setUser({ ...user, userInfo: resp });
255
- }
256
- },
257
- logout(redirect) {
258
- localStorage.removeItem("access_token");
259
- setUser({});
260
- if (redirect) {
261
- location.href = redirect;
262
- }
263
- },
264
- async fetchWithAuth(resource, options) {
265
- const token = user.accessToken;
266
- if (!token) {
267
- throw new Error("未登录时无法使用 fetchWithAuth");
268
- }
269
- return await fetch(resource, {
270
- ...options,
271
- headers: {
272
- Authorization: `Bearer ${token}`,
273
- ...options?.headers,
274
- },
275
- });
276
- },
277
- ...user,
278
- };
279
- }, [props.sdkConfig, props.signinOrigin, props.signinPath, user]);
468
+ if ((props.mode ?? "casdoor") === "casdoor") {
469
+ if (!props.sdkConfig) {
470
+ throw new Error(`设置了 mode === "casdoor",却没有设置 sdkConfig!`);
471
+ }
472
+ return casdoorAuth(
473
+ user,
474
+ setUser,
475
+ props.sdkConfig,
476
+ props.signinOrigin,
477
+ props.signinPath,
478
+ );
479
+ } else {
480
+ if (!props.loginPage) {
481
+ throw new Error(`设置了 mode === "local",却没有设置 loginPage!`);
482
+ }
483
+ return localAuth(
484
+ user,
485
+ setUser,
486
+ props.loginPage,
487
+ props.signinOrigin,
488
+ props.signinPath,
489
+ );
490
+ }
491
+ }, [
492
+ props.mode,
493
+ props.sdkConfig,
494
+ props.loginPage,
495
+ props.signinOrigin,
496
+ props.signinPath,
497
+ user,
498
+ ]);
280
499
  return (
281
500
  <AuthContext.Provider value={auth}>{props.children}</AuthContext.Provider>
282
501
  );
@@ -104,70 +104,6 @@ export interface SidebarProps extends MenuProps {
104
104
  style?: CSSProperties;
105
105
  }
106
106
 
107
- const useStyles = createStyles(
108
- ({ token, css, cx, prefixCls }, collapsed: boolean) => {
109
- const sidebarHeader = css`
110
- margin-top: ${token.marginXXS}px;
111
-
112
- > img {
113
- width: 40%;
114
- }
115
-
116
- > h1 {
117
- margin: ${collapsed ? 0 : token.marginXXS}px 0 0 0;
118
- font-size: ${collapsed ? 0 : token.fontSize}px;
119
- font-weight: normal;
120
- line-height: 1;
121
- transition:
122
- margin-top ${token.motionDurationMid},
123
- font-size ${token.motionDurationMid};
124
- }
125
- `;
126
- const sidebarMenu = css`
127
- flex-basis: 100%;
128
- overflow-y: auto;
129
- `;
130
- const sidebarBtn = css`
131
- position: absolute;
132
- top: 75%;
133
- right: 0;
134
- transform: translate(50%, -50%);
135
-
136
- font-size: ${token.sizeSM}px;
137
-
138
- transition: opacity ${token.motionDurationMid};
139
- opacity: 0%;
140
- `;
141
- const sidebar = css`
142
- position: relative;
143
- box-shadow: rgba(0, 0, 0, 0.15) 0 0 8px;
144
-
145
- > .${prefixCls}-layout-sider-children {
146
- display: flex;
147
- flex-direction: column;
148
- }
149
-
150
- /* 为了提高权重 */
151
- & .${cx(sidebarBtn)} {
152
- width: ${token.controlHeightXS}px;
153
- }
154
- & .${cx(sidebarMenu)}.${cx(sidebarMenu)} {
155
- border-inline-end: none;
156
- }
157
-
158
- &:hover .${cx(sidebarBtn)} {
159
- opacity: 100%;
160
- }
161
- `;
162
- return {
163
- sidebar,
164
- sidebarHeader,
165
- sidebarMenu,
166
- sidebarBtn,
167
- };
168
- },
169
- );
170
-
171
107
  /**
172
108
  * 出自 TMS 系统的侧边栏组件。该组件需要放置在 [Ant Design 的 `Layout` 组件][1]内部才能正常工作。
173
109
  * 如果没有指定 {@linkcode SidebarProps.footer | footer} 属性,
@@ -202,6 +138,11 @@ function Sidebar(props: SidebarProps) {
202
138
  } = props;
203
139
 
204
140
  const [collapsed, setCollapsed] = useState(false);
141
+ // 默认展开到 selectedKeys
142
+ const [openKeys, setOpenKeys] = useState<string[]>(
143
+ () =>
144
+ rest.selectedKeys?.flatMap((key) => findKeyPath(items, key) ?? []) ?? [],
145
+ );
205
146
  const { cx, styles, theme: token } = useStyles(collapsed);
206
147
 
207
148
  return (
@@ -232,6 +173,8 @@ function Sidebar(props: SidebarProps) {
232
173
  mode="inline"
233
174
  items={items}
234
175
  inlineCollapsed={collapsible ? collapsed : false}
176
+ openKeys={openKeys}
177
+ onOpenChange={(openKeys) => setOpenKeys(openKeys)}
235
178
  className={cx(styles.sidebarMenu)}
236
179
  {...rest}
237
180
  />
@@ -248,3 +191,85 @@ function Sidebar(props: SidebarProps) {
248
191
  }
249
192
 
250
193
  export default Sidebar;
194
+
195
+ const useStyles = createStyles(
196
+ ({ token, css, cx, prefixCls }, collapsed: boolean) => {
197
+ const sidebarHeader = css`
198
+ margin-top: ${token.marginXXS}px;
199
+
200
+ > img {
201
+ width: 40%;
202
+ }
203
+
204
+ > h1 {
205
+ margin: ${collapsed ? 0 : token.marginXXS}px 0 0 0;
206
+ font-size: ${collapsed ? 0 : token.fontSize}px;
207
+ font-weight: normal;
208
+ line-height: 1;
209
+ transition:
210
+ margin-top ${token.motionDurationMid},
211
+ font-size ${token.motionDurationMid};
212
+ }
213
+ `;
214
+ const sidebarMenu = css`
215
+ flex-basis: 100%;
216
+ overflow-y: auto;
217
+ `;
218
+ const sidebarBtn = css`
219
+ position: absolute;
220
+ top: 75%;
221
+ right: 0;
222
+ transform: translate(50%, -50%);
223
+
224
+ font-size: ${token.sizeSM}px;
225
+
226
+ transition: opacity ${token.motionDurationMid};
227
+ opacity: 0%;
228
+ `;
229
+ const sidebar = css`
230
+ position: relative;
231
+ box-shadow: rgba(0, 0, 0, 0.15) 0 0 8px;
232
+
233
+ > .${prefixCls}-layout-sider-children {
234
+ display: flex;
235
+ flex-direction: column;
236
+ }
237
+
238
+ /* 为了提高权重 */
239
+ & .${cx(sidebarBtn)} {
240
+ width: ${token.controlHeightXS}px;
241
+ }
242
+ & .${cx(sidebarMenu)}.${cx(sidebarMenu)} {
243
+ border-inline-end: none;
244
+ }
245
+
246
+ &:hover .${cx(sidebarBtn)} {
247
+ opacity: 100%;
248
+ }
249
+ `;
250
+ return {
251
+ sidebar,
252
+ sidebarHeader,
253
+ sidebarMenu,
254
+ sidebarBtn,
255
+ };
256
+ },
257
+ );
258
+
259
+ function findKeyPath(items: MenuItem[], key: string): string[] | null {
260
+ for (const item of items) {
261
+ if (!item) {
262
+ continue;
263
+ }
264
+ if (item.key === key) {
265
+ return [];
266
+ }
267
+ if ("children" in item) {
268
+ const childrenResult = findKeyPath(item.children ?? [], key);
269
+ if (childrenResult) {
270
+ return [item.key as string, ...childrenResult];
271
+ }
272
+ }
273
+ }
274
+ return null;
275
+ }