@lark-apaas/client-toolkit 1.2.0-alpha.9 → 1.2.1-0.alpha.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.
Files changed (72) hide show
  1. package/README.md +68 -0
  2. package/lib/apis/components/ActiveLink.d.ts +26 -0
  3. package/lib/apis/components/ActiveLink.js +66 -0
  4. package/lib/apis/components/UniversalLink.d.ts +12 -0
  5. package/lib/apis/components/UniversalLink.js +26 -0
  6. package/lib/apis/tools/services.d.ts +1 -0
  7. package/lib/apis/tools/services.js +1 -0
  8. package/lib/apis/trace.d.ts +1 -0
  9. package/lib/apis/trace.js +1 -0
  10. package/lib/apis/utils/getEnv.d.ts +1 -0
  11. package/lib/apis/utils/getEnv.js +2 -0
  12. package/lib/auth.d.ts +1 -0
  13. package/lib/auth.js +2 -0
  14. package/lib/components/AppContainer/IframeBridge.d.ts +0 -1
  15. package/lib/components/AppContainer/IframeBridge.js +4 -22
  16. package/lib/components/AppContainer/index.d.ts +5 -2
  17. package/lib/components/AppContainer/index.js +9 -46
  18. package/lib/components/AppContainer/utils/childApi.js +1 -0
  19. package/lib/components/AppContainer/utils/observable.js +5 -2
  20. package/lib/components/ErrorRender/index.js +5 -11
  21. package/lib/components/PagePlaceholder/index.js +1 -1
  22. package/lib/components/User/UserSelect.js +1 -13
  23. package/lib/index.js +3 -1
  24. package/lib/integrations/services/DepartmentService.d.ts +10 -0
  25. package/lib/integrations/services/DepartmentService.js +29 -0
  26. package/lib/integrations/services/UserProfileService.d.ts +14 -0
  27. package/lib/integrations/services/UserProfileService.js +36 -0
  28. package/lib/integrations/services/UserService.d.ts +12 -0
  29. package/lib/integrations/services/UserService.js +46 -0
  30. package/lib/integrations/services/index.d.ts +4 -0
  31. package/lib/integrations/services/index.js +4 -0
  32. package/lib/integrations/services/types.d.ts +91 -0
  33. package/lib/integrations/services/types.js +0 -0
  34. package/lib/logger/batch-logger.js +3 -2
  35. package/lib/logger/intercept-global-error.js +16 -14
  36. package/lib/logger/log-types.d.ts +4 -4
  37. package/lib/logger/log-types.js +1 -1
  38. package/lib/logger/selected-logs.js +1 -2
  39. package/lib/runtime/axios.d.ts +5 -0
  40. package/lib/runtime/axios.js +2 -0
  41. package/lib/runtime/dayjs.d.ts +5 -0
  42. package/lib/runtime/dayjs.js +2 -0
  43. package/lib/runtime/iframe-bridge.d.ts +11 -0
  44. package/lib/runtime/iframe-bridge.js +29 -0
  45. package/lib/runtime/index.d.ts +23 -0
  46. package/lib/runtime/index.js +16 -0
  47. package/lib/runtime/observable.d.ts +5 -0
  48. package/lib/runtime/observable.js +2 -0
  49. package/lib/runtime/server-log.d.ts +5 -0
  50. package/lib/runtime/server-log.js +41 -0
  51. package/lib/runtime/styles.d.ts +5 -0
  52. package/lib/runtime/styles.js +1 -0
  53. package/lib/theme-layer.css +2 -1
  54. package/lib/trace/index.d.ts +13 -0
  55. package/lib/trace/index.js +6 -0
  56. package/lib/utils/axiosConfig.d.ts +10 -3
  57. package/lib/utils/axiosConfig.js +123 -7
  58. package/lib/utils/getAxiosForBackend.js +1 -1
  59. package/lib/utils/getParentOrigin.d.ts +0 -5
  60. package/lib/utils/getParentOrigin.js +13 -12
  61. package/lib/utils/hmr-api.d.ts +39 -0
  62. package/lib/utils/hmr-api.js +36 -0
  63. package/lib/utils/module-hot.d.ts +9 -5
  64. package/lib/utils/module-hot.js +9 -10
  65. package/lib/utils/postMessage.d.ts +2 -1
  66. package/lib/utils/postMessage.js +19 -5
  67. package/lib/utils/requestManager.js +1 -3
  68. package/package.json +22 -11
  69. package/lib/components/AppContainer/utils/listenHot.d.ts +0 -1
  70. package/lib/components/AppContainer/utils/listenHot.js +0 -57
  71. package/lib/logger/__tests__/batch-logger.test.d.ts +0 -1
  72. package/lib/logger/__tests__/batch-logger.test.js +0 -367
package/README.md CHANGED
@@ -6,3 +6,71 @@
6
6
 
7
7
  * 导出所有对外暴露的 API 和组件,并通过 npm exports 来控制
8
8
  * 其他目录作为 internal 实现,不对外暴露
9
+
10
+ ### auth 目录
11
+
12
+ * 包含与权限相关的 API 和组件
13
+
14
+
15
+ ### 开发组件 - 使用 CanRole 组件 (推荐)
16
+
17
+ ```tsx
18
+ import { CanRole } from '@lark-apaas/client-toolkit/auth';
19
+
20
+ function Home() {
21
+ return (
22
+ <div>
23
+ <CanRole roles={['role_admin']}>
24
+ <div>管理员按钮</div>
25
+ </CanRole>
26
+ <CanRole roles={['role_admin', 'role_editor']}>
27
+ <div>编辑按钮</div>
28
+ </CanRole>
29
+ </div>
30
+ );
31
+ }
32
+ ```
33
+
34
+ ### 开发组件 - 使用 AbilityContext 处理复杂场景
35
+
36
+ ```tsx
37
+ import { useContext } from 'react';
38
+ import { AbilityContext, ROLE_SUBJECT } from '@lark-apaas/client-toolkit/auth';
39
+
40
+ function Home() {
41
+ const ability = useContext(AbilityContext);
42
+ return (
43
+ <div>
44
+ {ability.can('role_admin', ROLE_SUBJECT) || ability.can('role_editor', ROLE_SUBJECT) ? (
45
+ <div>可见的仪表盘</div>
46
+ ) : null}
47
+ </div>
48
+ );
49
+ }
50
+ ```
51
+
52
+ ### 开发组件 - 进阶示例
53
+
54
+ ### 菜单按权限过滤
55
+
56
+ ```tsx
57
+ import { useContext } from 'react';
58
+ import { AbilityContext, ROLE_SUBJECT } from '@lark-apaas/client-toolkit/auth';
59
+
60
+ const menus = [
61
+ { name: 'Dashboard', path: '/dashboard', p: { action: 'role_admin', subject: ROLE_SUBJECT } },
62
+ { name: 'Users', path: '/users', p: { action: 'role_editor', subject: ROLE_SUBJECT } },
63
+ { name: 'Settings', path: '/settings', p: { action: 'role_admin', subject: ROLE_SUBJECT } },
64
+ ];
65
+
66
+ function Nav() {
67
+ const ability = useContext(AbilityContext);
68
+ return (
69
+ <nav>
70
+ {menus.map(m => ability.can(m.p.action, m.p.subject) && (
71
+ <a key={m.path} href={m.path}>{m.name}</a>
72
+ ))}
73
+ </nav>
74
+ );
75
+ }
76
+ ```
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+ import { type NavLinkProps } from 'react-router-dom';
3
+ export interface ActiveLinkProps extends Omit<NavLinkProps, 'to'> {
4
+ to: string;
5
+ }
6
+ /**
7
+ * ActiveLink 组件
8
+ *
9
+ * 替换 react-router-dom 的 NavLink,扩展支持 hash 路由跳转和激活态功能
10
+ *
11
+ * @example
12
+ * // 普通路由跳转
13
+ * <ActiveLink to="/dashboard">Dashboard</ActiveLink>
14
+ *
15
+ * // hash 路由跳转
16
+ * <ActiveLink to="#section1">Section 1</ActiveLink>
17
+ *
18
+ * // 使用函数式 className 显示激活态
19
+ * <ActiveLink
20
+ * to="#features"
21
+ * className={({isActive}) => isActive ? 'active' : ''}
22
+ * >
23
+ * Features
24
+ * </ActiveLink>
25
+ */
26
+ export declare const ActiveLink: React.ForwardRefExoticComponent<ActiveLinkProps & React.RefAttributes<HTMLAnchorElement>>;
@@ -0,0 +1,66 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { forwardRef, useEffect, useState } from "react";
3
+ import { NavLink } from "react-router-dom";
4
+ const ActiveLink = /*#__PURE__*/ forwardRef(({ to, onClick, className, style, children, ...rest }, ref)=>{
5
+ const [currentHash, setCurrentHash] = useState(()=>'undefined' != typeof window ? window.location.hash : '');
6
+ const isHashRoute = 'string' == typeof to && to.startsWith('#');
7
+ useEffect(()=>{
8
+ if (!isHashRoute) return;
9
+ const handleHashChange = ()=>{
10
+ setCurrentHash(window.location.hash);
11
+ };
12
+ window.addEventListener('hashchange', handleHashChange);
13
+ return ()=>window.removeEventListener('hashchange', handleHashChange);
14
+ }, [
15
+ isHashRoute
16
+ ]);
17
+ const isActive = isHashRoute && currentHash === to;
18
+ if (!isHashRoute) return /*#__PURE__*/ jsx(NavLink, {
19
+ ref: ref,
20
+ to: to,
21
+ onClick: onClick,
22
+ className: className,
23
+ style: style,
24
+ ...rest,
25
+ children: children
26
+ });
27
+ const handleHashClick = (e)=>{
28
+ onClick?.(e);
29
+ if (e.defaultPrevented) return;
30
+ e.preventDefault();
31
+ const targetId = to.slice(1);
32
+ const targetElement = document.getElementById(targetId);
33
+ history.pushState(null, '', to);
34
+ setCurrentHash(to);
35
+ window.dispatchEvent(new Event('hashchange'));
36
+ if (targetElement && 'function' == typeof targetElement.scrollIntoView) targetElement.scrollIntoView({
37
+ behavior: 'smooth'
38
+ });
39
+ };
40
+ const computedClassName = 'function' == typeof className ? className({
41
+ isActive,
42
+ isPending: false,
43
+ isTransitioning: false
44
+ }) : className;
45
+ const computedStyle = 'function' == typeof style ? style({
46
+ isActive,
47
+ isPending: false,
48
+ isTransitioning: false
49
+ }) : style;
50
+ const computedChildren = 'function' == typeof children ? children({
51
+ isActive,
52
+ isPending: false,
53
+ isTransitioning: false
54
+ }) : children;
55
+ return /*#__PURE__*/ jsx("a", {
56
+ ref: ref,
57
+ href: to,
58
+ onClick: handleHashClick,
59
+ className: computedClassName,
60
+ style: computedStyle,
61
+ ...rest,
62
+ children: computedChildren
63
+ });
64
+ });
65
+ ActiveLink.displayName = 'ActiveLink';
66
+ export { ActiveLink };
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ export interface UniversalLinkProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
3
+ /** 链接目标:内部路由、hash 锚点或外部 URL */
4
+ to: string;
5
+ }
6
+ /**
7
+ * 统一的链接组件,用于替换原生 <a> 标签
8
+ * - 内部路由(/dashboard)→ react-router Link
9
+ * - Hash 锚点(#section)→ <a>
10
+ * - 外链(https://...)→ <a target="_blank">
11
+ */
12
+ export declare const UniversalLink: React.ForwardRefExoticComponent<UniversalLinkProps & React.RefAttributes<HTMLAnchorElement>>;
@@ -0,0 +1,26 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import react from "react";
3
+ import { Link } from "react-router-dom";
4
+ function isInternalRoute(to) {
5
+ return !to.startsWith('#') && !to.startsWith('http://') && !to.startsWith('https://') && !to.startsWith('//');
6
+ }
7
+ function isExternalLink(to) {
8
+ return to.startsWith('http://') || to.startsWith('https://') || to.startsWith('//');
9
+ }
10
+ const UniversalLink_UniversalLink = /*#__PURE__*/ react.forwardRef(function({ to, ...props }, ref) {
11
+ if (isInternalRoute(to)) return /*#__PURE__*/ jsx(Link, {
12
+ to: to,
13
+ ref: ref,
14
+ ...props
15
+ });
16
+ return /*#__PURE__*/ jsx("a", {
17
+ href: to,
18
+ ref: ref,
19
+ ...props,
20
+ ...isExternalLink(to) && {
21
+ target: props.target ?? '_blank',
22
+ rel: props.rel ?? 'noopener noreferrer'
23
+ }
24
+ });
25
+ });
26
+ export { UniversalLink_UniversalLink as UniversalLink };
@@ -0,0 +1 @@
1
+ export * from '../../integrations/services';
@@ -0,0 +1 @@
1
+ export * from "../../integrations/services/index.js";
@@ -0,0 +1 @@
1
+ export * from '../trace';
@@ -0,0 +1 @@
1
+ export * from "../trace/index.js";
@@ -0,0 +1 @@
1
+ export { getEnv } from '../../utils/getParentOrigin';
@@ -0,0 +1,2 @@
1
+ import { getEnv } from "../../utils/getParentOrigin.js";
2
+ export { getEnv };
package/lib/auth.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { CanRole, AbilityContext, ROLE_SUBJECT } from '@lark-apaas/auth-sdk';
package/lib/auth.js ADDED
@@ -0,0 +1,2 @@
1
+ import { AbilityContext, CanRole, ROLE_SUBJECT } from "@lark-apaas/auth-sdk";
2
+ export { AbilityContext, CanRole, ROLE_SUBJECT };
@@ -1,3 +1,2 @@
1
1
  import React from 'react';
2
- import './utils/listenHot';
3
2
  export default function IframeBridge(): React.JSX.Element;
@@ -1,12 +1,8 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useRef } from "react";
3
3
  import { useLocation, useNavigate } from "react-router-dom";
4
- import { connectToParent } from "penpal";
5
4
  import { useUpdatingRef } from "../../hooks/useUpdatingRef.js";
6
5
  import { submitPostMessage } from "../../utils/postMessage.js";
7
- import { getPreviewParentOrigin } from "../../utils/getParentOrigin.js";
8
- import { childApi } from "./utils/childApi.js";
9
- import "./utils/listenHot.js";
10
6
  var IframeBridge_RouteMessageType = /*#__PURE__*/ function(RouteMessageType) {
11
7
  RouteMessageType["RouteChange"] = "RouteChange";
12
8
  RouteMessageType["RouteBack"] = "RouteBack";
@@ -16,32 +12,18 @@ var IframeBridge_RouteMessageType = /*#__PURE__*/ function(RouteMessageType) {
16
12
  function isRouteMessageType(type) {
17
13
  return Object.values(IframeBridge_RouteMessageType).includes(type);
18
14
  }
19
- async function connectParent() {
20
- submitPostMessage({
21
- type: 'PreviewReady',
22
- data: {}
23
- });
24
- const connection = connectToParent({
25
- parentOrigin: getPreviewParentOrigin(),
26
- methods: {
27
- ...childApi
28
- }
29
- });
30
- await connection.promise;
31
- }
32
- 'production' !== process.env.NODE_ENV && connectParent();
33
15
  function IframeBridge() {
34
16
  const location = useLocation();
35
17
  const navigate = useNavigate();
36
18
  const navigateRef = useUpdatingRef(navigate);
37
19
  const isActive = useRef(false);
38
- const historyBack = useCallback(()=>{
20
+ const historyBack = useCallback((_payload)=>{
39
21
  navigateRef.current(-1);
40
22
  isActive.current = true;
41
23
  }, [
42
24
  navigateRef
43
25
  ]);
44
- const historyForward = useCallback(()=>{
26
+ const historyForward = useCallback((_payload)=>{
45
27
  navigateRef.current(1);
46
28
  isActive.current = true;
47
29
  }, [
@@ -69,8 +51,8 @@ function IframeBridge() {
69
51
  location
70
52
  ]);
71
53
  const handleMessage = useCallback((event)=>{
72
- const { data } = event;
73
- if (isRouteMessageType(data?.type)) operatorMessage[data?.type](data?.data);
54
+ const data = event.data ?? {};
55
+ if ('string' == typeof data.type && isRouteMessageType(data.type)) operatorMessage[data.type](data.data);
74
56
  }, [
75
57
  operatorMessage
76
58
  ]);
@@ -1,7 +1,10 @@
1
1
  import React from 'react';
2
2
  import { IBaseThemeProviderProps } from '../theme';
3
- import '../../index.css';
3
+ import '../../runtime';
4
+ interface IBaseAuthProviderProps {
5
+ enableAuth?: boolean;
6
+ }
4
7
  declare const AppContainer: React.FC<{
5
8
  children: React.ReactNode;
6
- } & IBaseThemeProviderProps>;
9
+ } & IBaseThemeProviderProps & IBaseAuthProviderProps>;
7
10
  export default AppContainer;
@@ -1,5 +1,5 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef, useState } from "react";
2
+ import { useEffect, useState } from "react";
3
3
  import { ConfigProvider } from "antd";
4
4
  import { MiaodaInspector } from "@lark-apaas/miaoda-inspector";
5
5
  import zh_CN from "antd/locale/zh_CN";
@@ -8,20 +8,14 @@ import { defaultUIConfig } from "../theme/ui-config.js";
8
8
  import { Toaster } from "./sonner.js";
9
9
  import { PageHoc } from "./PageHoc.js";
10
10
  import { findValueByPixel, generateTailwindRadiusToken, themeColorTokenMap, themeMetaOptions } from "../theme/index.js";
11
- import { registerDayjsPlugins } from "./dayjsPlugins.js";
12
- import "../../index.css";
13
- import { initAxiosConfig } from "../../utils/axiosConfig.js";
14
11
  import { reportTeaEvent } from "./utils/tea.js";
15
12
  import { useAppInfo } from "../../hooks/index.js";
16
13
  import { TrackKey } from "../../types/tea.js";
17
14
  import safety from "./safety.js";
18
15
  import { getAppId } from "../../utils/getAppId.js";
19
- import { ServerLogSSEClient } from "../../server-log/index.js";
20
16
  import QueryProvider from "../QueryProvider/index.js";
21
- import { initObservable } from "./utils/observable.js";
22
- registerDayjsPlugins();
23
- initAxiosConfig();
24
- initObservable();
17
+ import { AuthProvider } from "@lark-apaas/auth-sdk";
18
+ import "../../runtime/index.js";
25
19
  const isMiaodaPreview = window.IS_MIAODA_PREVIEW;
26
20
  const readCssVarColor = (varName, fallback)=>{
27
21
  try {
@@ -33,9 +27,8 @@ const readCssVarColor = (varName, fallback)=>{
33
27
  }
34
28
  };
35
29
  const App = (props)=>{
36
- const { themeMeta = {} } = props;
30
+ const { themeMeta = {}, enableAuth } = props;
37
31
  useAppInfo();
38
- const serverLogClientRef = useRef(null);
39
32
  const { rem } = findValueByPixel(themeMetaOptions.themeRadius, themeMeta.borderRadius) || {
40
33
  rem: '0.625'
41
34
  };
@@ -47,40 +40,6 @@ const App = (props)=>{
47
40
  borderRadius: radiusToken
48
41
  }
49
42
  };
50
- useEffect(()=>{
51
- if ('production' !== process.env.NODE_ENV && window.parent !== window) {
52
- try {
53
- const backendUrl = window.location.origin;
54
- serverLogClientRef.current = new ServerLogSSEClient({
55
- serverUrl: backendUrl,
56
- sseEndpoint: '/dev/logs/server-logs/stream',
57
- debug: true
58
- });
59
- serverLogClientRef.current.start();
60
- console.log('[AppContainer] Server log SSE client started');
61
- } catch (error) {
62
- console.error('[AppContainer] Failed to start server log SSE client:', error);
63
- }
64
- const handleVisibilityChange = ()=>{
65
- if (!serverLogClientRef.current) return;
66
- if (document.hidden) {
67
- serverLogClientRef.current.pause();
68
- console.log('[AppContainer] Tab hidden, SSE paused');
69
- } else {
70
- serverLogClientRef.current.resume();
71
- console.log('[AppContainer] Tab visible, SSE resumed');
72
- }
73
- };
74
- document.addEventListener('visibilitychange', handleVisibilityChange);
75
- return ()=>{
76
- document.removeEventListener('visibilitychange', handleVisibilityChange);
77
- if (serverLogClientRef.current) {
78
- serverLogClientRef.current.stop();
79
- console.log('[AppContainer] Server log SSE client stopped');
80
- }
81
- };
82
- }
83
- }, []);
84
43
  useEffect(()=>{
85
44
  if (isMiaodaPreview) fetch(`${location.origin}/ai/api/feida_preview/csrf`).then(()=>{
86
45
  setTimeout(()=>{
@@ -99,7 +58,10 @@ const App = (props)=>{
99
58
  }
100
59
  });
101
60
  }, []);
102
- return /*#__PURE__*/ jsxs(Fragment, {
61
+ return /*#__PURE__*/ jsxs(AuthProvider, {
62
+ config: {
63
+ enable: enableAuth
64
+ },
103
65
  children: [
104
66
  /*#__PURE__*/ jsx(Toaster, {}),
105
67
  'production' !== process.env.NODE_ENV && /*#__PURE__*/ jsx(MiaodaInspector, {
@@ -247,6 +209,7 @@ const AppContainer_AppContainer = (props)=>{
247
209
  },
248
210
  children: /*#__PURE__*/ jsx(App, {
249
211
  themeMeta: props.themeMeta,
212
+ enableAuth: props.enableAuth,
250
213
  children: children
251
214
  })
252
215
  })
@@ -16,6 +16,7 @@ async function getRoutes() {
16
16
  return routes;
17
17
  }
18
18
  async function getSourceMap() {
19
+ if ('vite' === process.env.BUILD_TOOL) return '';
19
20
  let sourceMapContent = '';
20
21
  try {
21
22
  const basePath = normalizeBasePath(process.env.CLIENT_BASE_PATH);
@@ -1,12 +1,15 @@
1
1
  import { AppEnv, observable } from "@lark-apaas/observable-web";
2
2
  const initObservable = ()=>{
3
3
  try {
4
+ const appId = window.appId;
4
5
  observable.start({
5
6
  serviceName: "app",
6
7
  env: 'development' === process.env.NODE_ENV ? AppEnv.Dev : AppEnv.Prod,
7
8
  collectorUrl: {
8
- log: `/spark/app/${window.appId}/runtime/api/v1/observability/logs/collect`,
9
- metric: `/spark/app/${window.appId}/runtime/api/v1/observability/metrics/collect`
9
+ log: `/spark/app/${appId}/runtime/api/v1/observability/logs/collect`,
10
+ trace: `/spark/app/${appId}/runtime/api/v1/observability/traces/collect`,
11
+ metric: `/spark/app/${appId}/runtime/api/v1/observability/metrics/collect`,
12
+ time: `/spark/api/v1/observability/app/${appId}/current_server_timestamp`
10
13
  }
11
14
  });
12
15
  } catch (error) {
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useEffect } from "react";
3
3
  import { logger } from "../../logger/index.js";
4
- import { createApplyHandle, getModuleHot } from "../../utils/module-hot.js";
4
+ import { getHmrApi } from "../../utils/hmr-api.js";
5
5
  import { submitPostMessage } from "../../utils/postMessage.js";
6
6
  const RenderError = (props)=>{
7
7
  const { error, resetErrorBoundary } = props;
@@ -27,16 +27,10 @@ const RenderError = (props)=>{
27
27
  ]);
28
28
  useEffect(()=>{
29
29
  if (!resetErrorBoundary) return;
30
- const hot = getModuleHot();
31
- if (hot) {
32
- const handler = createApplyHandle((success)=>{
33
- if (success) resetErrorBoundary();
34
- });
35
- hot.addStatusHandler(handler);
36
- return ()=>{
37
- hot.removeStatusHandler(handler);
38
- };
39
- }
30
+ const hmr = getHmrApi();
31
+ if (hmr) return hmr.onSuccess(()=>{
32
+ resetErrorBoundary();
33
+ });
40
34
  }, [
41
35
  resetErrorBoundary
42
36
  ]);
@@ -15,7 +15,7 @@ const PagePlaceholder = ({ title = '页面待开发', description = '页面暂
15
15
  children: title
16
16
  }),
17
17
  /*#__PURE__*/ jsx("div", {
18
- className: "text-center text-muted-foreground text-base leading-6",
18
+ className: "text-center text-muted-foreground text-base leading-6 line-clamp-2 sm:max-w-[480px]",
19
19
  children: description
20
20
  })
21
21
  ]
@@ -67,20 +67,8 @@ const UserSelect = ({ mode = 'single', defaultValue, value, onChange, placeholde
67
67
  if (!normalizedIds.length) return void setUiValue(void 0);
68
68
  const fetchProfiles = async ()=>{
69
69
  try {
70
- const ids = normalizedIds.map((id)=>Number(id)).filter((id)=>Number.isFinite(id));
71
- if (!ids.length) {
72
- const profiles = normalizedIds.map((id)=>inputProfilesMap.get(id) ?? {
73
- user_id: id,
74
- name: '',
75
- avatar: '',
76
- email: '',
77
- status: 1
78
- });
79
- setUiValue('single' === mode ? profiles[0] : profiles);
80
- return;
81
- }
82
70
  const dataloom = await getDataloom();
83
- const response = await dataloom.service.user.getByIds(ids);
71
+ const response = await dataloom.service.user.getByIds(normalizedIds);
84
72
  const fetchedList = Array.isArray(response?.data) ? response?.data : Array.isArray(response?.data?.user_list) ? response?.data?.user_list : [];
85
73
  const fetchedMap = new Map();
86
74
  fetchedList.forEach((profile)=>{
package/lib/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createClient } from "@lark-apaas/client-capability";
2
2
  import { normalizeBasePath } from "./utils/utils.js";
3
+ import { logger } from "./logger/index.js";
3
4
  import { version } from "../package.json";
4
5
  const capabilityClient = createClient({
5
6
  baseURL: normalizeBasePath(process.env.CLIENT_BASE_PATH),
@@ -7,7 +8,8 @@ const capabilityClient = createClient({
7
8
  headers: {
8
9
  'X-Suda-Csrf-Token': window.csrfToken ?? ''
9
10
  }
10
- }
11
+ },
12
+ logger: logger
11
13
  });
12
14
  const src = {
13
15
  version: version
@@ -0,0 +1,10 @@
1
+ import type { SearchDepartmentsParams, SearchDepartmentsResponse } from './types';
2
+ export type DepartmentServiceConfig = {
3
+ getAppId?: () => string | null | undefined;
4
+ searchDepartmentUrl?: (appId: string) => string;
5
+ };
6
+ export declare class DepartmentService {
7
+ private config;
8
+ constructor(config?: DepartmentServiceConfig);
9
+ searchDepartments(params: SearchDepartmentsParams): Promise<SearchDepartmentsResponse>;
10
+ }
@@ -0,0 +1,29 @@
1
+ import { getAppId } from "../../utils/getAppId.js";
2
+ const DEFAULT_CONFIG = {
3
+ getAppId: ()=>getAppId(window.location.pathname),
4
+ searchDepartmentUrl: (appId)=>`/af/app/${appId}/runtime/api/v1/account/search_department`
5
+ };
6
+ class DepartmentService {
7
+ config;
8
+ constructor(config = {}){
9
+ this.config = {
10
+ ...DEFAULT_CONFIG,
11
+ ...config
12
+ };
13
+ }
14
+ async searchDepartments(params) {
15
+ const appId = this.config.getAppId();
16
+ if (!appId) throw new Error('Failed to get appId');
17
+ const response = await fetch(this.config.searchDepartmentUrl(appId), {
18
+ method: 'POST',
19
+ headers: {
20
+ 'Content-Type': 'application/json'
21
+ },
22
+ body: JSON.stringify(params),
23
+ credentials: 'include'
24
+ });
25
+ if (!response.ok) throw new Error('Failed to search departments');
26
+ return response.json();
27
+ }
28
+ }
29
+ export { DepartmentService };
@@ -0,0 +1,14 @@
1
+ import type { AccountType, UserProfileData } from './types';
2
+ /**
3
+ * 获取 CDN 资源 URL
4
+ */
5
+ export declare function getAssetsUrl(path: string): string;
6
+ export type UserProfileServiceConfig = {
7
+ getAppId?: () => string | null | undefined;
8
+ userProfileUrl?: (appId: string) => string;
9
+ };
10
+ export declare class UserProfileService {
11
+ private config;
12
+ constructor(config?: UserProfileServiceConfig);
13
+ getUserProfile(userId: string, accountType?: AccountType, signal?: AbortSignal): Promise<UserProfileData>;
14
+ }
@@ -0,0 +1,36 @@
1
+ import { getAppId } from "../../utils/getAppId.js";
2
+ const CDN_HOST = 'https://lf3-static.bytednsdoc.com';
3
+ function getAssetsUrl(path) {
4
+ return `${CDN_HOST}${path}`;
5
+ }
6
+ const DEFAULT_CONFIG = {
7
+ getAppId: ()=>getAppId(window.location.pathname),
8
+ userProfileUrl: (appId)=>`/af/app/${appId}/runtime/api/v1/account/user_profile`
9
+ };
10
+ class UserProfileService {
11
+ config;
12
+ constructor(config = {}){
13
+ this.config = {
14
+ ...DEFAULT_CONFIG,
15
+ ...config
16
+ };
17
+ }
18
+ async getUserProfile(userId, accountType = 'apaas', signal) {
19
+ const appId = this.config.getAppId();
20
+ if (!appId) throw new Error('Failed to get appId');
21
+ const params = new URLSearchParams();
22
+ if ('lark' === accountType) params.append('larkUserID', userId);
23
+ else params.append('userID', userId);
24
+ const response = await fetch(`${this.config.userProfileUrl(appId)}?${params.toString()}`, {
25
+ signal,
26
+ headers: {
27
+ 'Content-Type': 'application/json'
28
+ },
29
+ credentials: 'include'
30
+ });
31
+ if (!response.ok) throw new Error(`Failed to fetch user profile: ${response.status}`);
32
+ const data = await response.json();
33
+ return data.data;
34
+ }
35
+ }
36
+ export { UserProfileService, getAssetsUrl };
@@ -0,0 +1,12 @@
1
+ import type { BatchGetUsersResponse, SearchUsersParams, SearchUsersResponse } from './types';
2
+ export type UserServiceConfig = {
3
+ getAppId?: () => string | null | undefined;
4
+ searchUserUrl?: (appId: string) => string;
5
+ listUsersUrl?: (appId: string) => string;
6
+ };
7
+ export declare class UserService {
8
+ private config;
9
+ constructor(config?: UserServiceConfig);
10
+ searchUsers(params: SearchUsersParams): Promise<SearchUsersResponse>;
11
+ listUsersByIds(userIds: string[]): Promise<BatchGetUsersResponse>;
12
+ }