@lark-apaas/client-toolkit 1.2.10-alpha.3 → 1.2.10-alpha.31

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.
@@ -0,0 +1 @@
1
+ export * from '../../hooks/useScrollReveal';
@@ -0,0 +1 @@
1
+ export * from "../../hooks/useScrollReveal.js";
@@ -0,0 +1 @@
1
+ export { scopedStorage } from '../../utils/scopedStorage';
@@ -0,0 +1,2 @@
1
+ import { scopedStorage } from "../../utils/scopedStorage.js";
2
+ export { scopedStorage };
@@ -139,7 +139,8 @@ class ApiProxy {
139
139
  ...config,
140
140
  headers: {
141
141
  ...this.defaultConfig.headers,
142
- ...config.headers
142
+ ...config.headers,
143
+ 'X-Page-Route': 'undefined' != typeof window ? window.location?.pathname || '/' : '/'
143
144
  }
144
145
  };
145
146
  const requestKey = this.generateRequestKey(mergedConfig);
@@ -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);
@@ -16,7 +16,7 @@ async function createTracker() {
16
16
  const userIDEncrypt = userID && '0' !== userID ? encryptTea(userID) : void 0;
17
17
  const tenantIDEncrypt = tenantID && '0' !== tenantID ? encryptTea(tenantID) : void 0;
18
18
  window.collectEvent('init', {
19
- app_id: 672575,
19
+ app_id: 788827,
20
20
  channel: 'cn',
21
21
  disable_auto_pv: false,
22
22
  enable_ab_test: false,
@@ -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
  ]);
@@ -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)=>{
@@ -0,0 +1,2 @@
1
+ /** 显示 toast 提示(兼容 PC/移动端) */
2
+ export declare function showToast(message: string, duration?: number): Promise<void>;
@@ -0,0 +1,53 @@
1
+ function showToast(message, duration = 3000) {
2
+ return new Promise((resolve)=>{
3
+ const toast = document.createElement('div');
4
+ const iconSvg = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
5
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M8 1.333a6.667 6.667 0 1 1 0 13.334A6.667 6.667 0 0 1 8 1.333ZM8 10a.667.667 0 1 0 0 1.333A.667.667 0 0 0 8 10Zm0-5.333a.667.667 0 0 0-.667.666v3.334a.667.667 0 0 0 1.334 0V5.333A.667.667 0 0 0 8 4.667Z" fill="#ff811a"/>
6
+ </svg>`;
7
+ const iconWrapper = document.createElement('span');
8
+ iconWrapper.innerHTML = iconSvg;
9
+ Object.assign(iconWrapper.style, {
10
+ display: 'flex',
11
+ alignItems: 'center',
12
+ marginRight: '8px',
13
+ flexShrink: '0'
14
+ });
15
+ const textWrapper = document.createElement('span');
16
+ textWrapper.textContent = message;
17
+ toast.appendChild(iconWrapper);
18
+ toast.appendChild(textWrapper);
19
+ Object.assign(toast.style, {
20
+ position: 'fixed',
21
+ top: '32px',
22
+ left: '50%',
23
+ transform: 'translate(-50%, -50%)',
24
+ display: 'flex',
25
+ alignItems: 'center',
26
+ padding: '12px 16px',
27
+ backgroundColor: '#fff',
28
+ color: '#1f2329',
29
+ border: '1px solid #dee0e3',
30
+ borderRadius: '6px',
31
+ fontSize: '14px',
32
+ lineHeight: '1.5',
33
+ zIndex: '99999',
34
+ maxWidth: '80vw',
35
+ wordBreak: 'break-word',
36
+ boxShadow: '0 4px 8px rgba(0, 0, 0, 0.03), 0 3px 6px rgba(0, 0, 0, 0.05), 0 6px 18px rgba(0, 0, 0, 0.03)',
37
+ opacity: '0',
38
+ transition: 'opacity 0.3s ease'
39
+ });
40
+ document.body.appendChild(toast);
41
+ requestAnimationFrame(()=>{
42
+ toast.style.opacity = '1';
43
+ });
44
+ setTimeout(()=>{
45
+ toast.style.opacity = '0';
46
+ setTimeout(()=>{
47
+ document.body.removeChild(toast);
48
+ resolve();
49
+ }, 300);
50
+ }, duration);
51
+ });
52
+ }
53
+ export { showToast };
@@ -2,3 +2,4 @@ export * from './useAppInfo';
2
2
  export * from './useCurrentUserProfile';
3
3
  export * from './useIsMobile';
4
4
  export * from './useLogout';
5
+ export * from './useScrollReveal';
@@ -2,3 +2,4 @@ export * from "./useAppInfo.js";
2
2
  export * from "./useCurrentUserProfile.js";
3
3
  export * from "./useIsMobile.js";
4
4
  export * from "./useLogout.js";
5
+ export * from "./useScrollReveal.js";
@@ -0,0 +1,61 @@
1
+ import { RefObject } from 'react';
2
+ /**
3
+ * Options for useScrollReveal hook
4
+ */
5
+ export interface UseScrollRevealOptions<T extends HTMLElement = HTMLElement> {
6
+ /** Ref to the container element to scope the query (default: document.body) */
7
+ containerRef?: RefObject<T | null>;
8
+ /** IntersectionObserver threshold - element visibility percentage to trigger (default: 0.1) */
9
+ threshold?: number;
10
+ /** IntersectionObserver rootMargin - margin around root (default: '0px 0px -50px 0px') */
11
+ rootMargin?: string;
12
+ /** CSS class to add when element becomes visible (default: 'visible') */
13
+ visibleClass?: string;
14
+ /** CSS selector for elements to observe (default: '.scroll-reveal') */
15
+ selector?: string;
16
+ }
17
+ /**
18
+ * Hook to enable scroll reveal animations using IntersectionObserver.
19
+ *
20
+ * Observes elements with the specified selector and adds a visibility class
21
+ * when they enter the viewport.
22
+ *
23
+ * @param options - Configuration options
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * // Usage without parameters (queries entire document.body)
28
+ * import { useScrollReveal } from '@lark-apaas/client-toolkit/hooks/useScrollReveal';
29
+ *
30
+ * function MyComponent() {
31
+ * useScrollReveal();
32
+ *
33
+ * return <div className="scroll-reveal">I will fade in on scroll!</div>;
34
+ * }
35
+ * ```
36
+ *
37
+ * @example
38
+ * ```tsx
39
+ * // Usage with container ref
40
+ * import { useRef } from 'react';
41
+ * import { useScrollReveal } from '@lark-apaas/client-toolkit/hooks/useScrollReveal';
42
+ *
43
+ * function MyComponent() {
44
+ * const containerRef = useRef<HTMLDivElement>(null);
45
+ * useScrollReveal({ containerRef });
46
+ *
47
+ * return (
48
+ * <div ref={containerRef}>
49
+ * <div className="scroll-reveal">I will fade in on scroll!</div>
50
+ * </div>
51
+ * );
52
+ * }
53
+ * ```
54
+ *
55
+ * @example
56
+ * ```tsx
57
+ * // Usage with custom options
58
+ * useScrollReveal({ threshold: 0.2, visibleClass: 'animate-in' });
59
+ * ```
60
+ */
61
+ export declare function useScrollReveal<T extends HTMLElement = HTMLElement>(options?: UseScrollRevealOptions<T>): void;
@@ -0,0 +1,37 @@
1
+ import { useEffect } from "react";
2
+ const DEFAULT_OPTIONS = {
3
+ threshold: 0.1,
4
+ rootMargin: '0px 0px -50px 0px',
5
+ visibleClass: 'visible',
6
+ selector: '.scroll-reveal'
7
+ };
8
+ function useScrollReveal(options) {
9
+ const { containerRef, threshold, rootMargin, visibleClass, selector } = {
10
+ ...DEFAULT_OPTIONS,
11
+ ...options
12
+ };
13
+ useEffect(()=>{
14
+ const container = containerRef?.current ?? document.body;
15
+ if (!container) return;
16
+ const observer = new IntersectionObserver((entries)=>{
17
+ entries.forEach((entry)=>{
18
+ if (entry.isIntersecting) entry.target.classList.add(visibleClass);
19
+ });
20
+ }, {
21
+ threshold,
22
+ rootMargin
23
+ });
24
+ const elements = container.querySelectorAll(selector);
25
+ elements.forEach((el)=>observer.observe(el));
26
+ return ()=>{
27
+ observer.disconnect();
28
+ };
29
+ }, [
30
+ containerRef,
31
+ threshold,
32
+ rootMargin,
33
+ visibleClass,
34
+ selector
35
+ ]);
36
+ }
37
+ export { useScrollReveal };
package/lib/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createClient } from "@lark-apaas/client-capability";
2
2
  import { normalizeBasePath } from "./utils/utils.js";
3
3
  import { logger } from "./logger/index.js";
4
+ import { showToast } from "./components/ui/toast.js";
4
5
  import { version } from "../package.json";
5
6
  const capabilityClient = createClient({
6
7
  baseURL: normalizeBasePath(process.env.CLIENT_BASE_PATH),
@@ -9,7 +10,10 @@ const capabilityClient = createClient({
9
10
  'X-Suda-Csrf-Token': window.csrfToken ?? ''
10
11
  }
11
12
  },
12
- logger: logger
13
+ logger: logger,
14
+ onRateLimitError: ()=>{
15
+ showToast('应用额度已耗尽,请联系应用开发者');
16
+ }
13
17
  });
14
18
  const src = {
15
19
  version: version
@@ -3,8 +3,10 @@ import { createClient } from "@data-loom/js";
3
3
  import { getAppId } from "../utils/getAppId.js";
4
4
  import { getInitialInfo } from "../utils/getInitialInfo.js";
5
5
  const createDataLoomClient = (url, pat)=>{
6
- if (!url) return null;
7
- const { baseUrl, workspace } = splitWorkspaceUrl(url);
6
+ const { baseUrl, workspace } = url ? splitWorkspaceUrl(url) : {
7
+ baseUrl: '',
8
+ workspace: ''
9
+ };
8
10
  const appId = getAppId(window.location.pathname);
9
11
  return createClient(baseUrl, pat, workspace, {
10
12
  global: {
@@ -1,4 +1,4 @@
1
- import { createApplyHandle, getModuleHot } from "../utils/module-hot.js";
1
+ import { getHmrApi } from "../utils/hmr-api.js";
2
2
  import { submitPostMessage, submitSlardarEvent } from "../utils/postMessage.js";
3
3
  import { levelSchema } from "./log-types.js";
4
4
  import { logger } from "./logger.js";
@@ -63,26 +63,28 @@ function processDevServerLog(log) {
63
63
  }
64
64
  }
65
65
  function listenModuleHmr() {
66
- const hot = getModuleHot();
67
- if (hot) hot.addStatusHandler(createApplyHandle((success, status)=>{
68
- if (success) submitPostMessage({
69
- type: 'DevServerMessage',
70
- data: {
71
- type: 'devServer-status',
72
- status: 'hmr-apply-success'
73
- }
66
+ const hmr = getHmrApi();
67
+ if (hmr) {
68
+ hmr.onSuccess(()=>{
69
+ submitPostMessage({
70
+ type: 'DevServerMessage',
71
+ data: {
72
+ type: 'devServer-status',
73
+ status: 'hmr-apply-success'
74
+ }
75
+ });
74
76
  });
75
- else {
76
- console.warn('hmr apply failed', status);
77
+ hmr.onError((error)=>{
78
+ console.warn('hmr apply failed', error);
77
79
  submitSlardarEvent({
78
80
  name: 'sandbox-devServer',
79
81
  categories: {
80
82
  type: 'hmr-apply-failed',
81
- status
83
+ error: String(error)
82
84
  }
83
85
  });
84
- }
85
- }));
86
+ });
87
+ }
86
88
  }
87
89
  function interceptErrors() {
88
90
  window.addEventListener('error', (event)=>{
@@ -15,6 +15,7 @@
15
15
  * - 使用 __FULLSTACK_RUNTIME_INITIALIZED__ 标志位防止重复初始化
16
16
  * - 旧版 AppContainer 和新版 runtime 可以共存
17
17
  */
18
+ import './react-devtools-hook';
18
19
  import './styles';
19
20
  declare global {
20
21
  interface Window {
@@ -1,3 +1,4 @@
1
+ import "./react-devtools-hook.js";
1
2
  import "./styles.js";
2
3
  import { registerDayjsPlugins } from "./dayjs.js";
3
4
  import { initAxiosConfig } from "./axios.js";
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Inject minimal __REACT_DEVTOOLS_GLOBAL_HOOK__ before React loads.
3
+ * React checks for this hook during module initialization and registers its
4
+ * renderer via hook.inject(). This gives us access to renderer.overrideProps()
5
+ * for live component prop modification in the inspector.
6
+ *
7
+ * Must execute before React's module-level code runs.
8
+ */
9
+ export {};
10
+ declare global {
11
+ interface Window {
12
+ __REACT_DEVTOOLS_GLOBAL_HOOK__?: any;
13
+ }
14
+ }
@@ -0,0 +1,11 @@
1
+ if ('undefined' != typeof window && !window.__REACT_DEVTOOLS_GLOBAL_HOOK__) window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
2
+ renderers: new Map(),
3
+ supportsFiber: true,
4
+ inject (renderer) {
5
+ const id = this.renderers.size + 1;
6
+ this.renderers.set(id, renderer);
7
+ return id;
8
+ },
9
+ onCommitFiberRoot () {},
10
+ onCommitFiberUnmount () {}
11
+ };
@@ -1,3 +1,4 @@
1
1
  @layer theme, base, antd, components, utilities;
2
- @import "tailwindcss";
2
+ @import 'tailwindcss' source(none);
3
+ @source "../**/*.{js,jsx,ts,tsx}";
3
4
 
@@ -54,7 +54,7 @@ async function logResponse(ok, responseOrError) {
54
54
  logTraceID
55
55
  };
56
56
  if (stacktrace) logMeta.stacktrace = stacktrace;
57
- logger.debug({
57
+ logger.log({
58
58
  level: ok,
59
59
  args: [
60
60
  parts.join(''),
@@ -110,7 +110,7 @@ async function logResponse(ok, responseOrError) {
110
110
  const stacktrace = await requestStacktraceMap.get(requestUUID);
111
111
  const logMeta = {};
112
112
  if (stacktrace) logMeta.stacktrace = stacktrace;
113
- logger.debug({
113
+ logger.log({
114
114
  level: 'error',
115
115
  args: [
116
116
  parts.join(''),
@@ -120,7 +120,7 @@ async function logResponse(ok, responseOrError) {
120
120
  });
121
121
  return;
122
122
  }
123
- logger.debug({
123
+ logger.log({
124
124
  level: 'error',
125
125
  args: [
126
126
  '请求失败:无响应对象或配置信息'
@@ -234,6 +234,7 @@ function initAxiosConfig(axiosInstance) {
234
234
  config._startTime = config._startTime || Date.now();
235
235
  const csrfToken = window.csrfToken;
236
236
  if (csrfToken) config.headers['X-Suda-Csrf-Token'] = csrfToken;
237
+ if ('undefined' != typeof window && window.location?.pathname) config.headers['X-Page-Route'] = window.location.pathname;
237
238
  return config;
238
239
  }, (error)=>Promise.reject(error));
239
240
  instance.interceptors.response.use((response)=>response, (error)=>{
@@ -241,8 +242,8 @@ function initAxiosConfig(axiosInstance) {
241
242
  if (error.response?.status === 403) {
242
243
  const method = (error.config?.method || 'GET').toUpperCase();
243
244
  const url = (error.config?.url || '').split('?')[0];
244
- logger.debug({
245
- level: 'warning',
245
+ logger.log({
246
+ level: 'warn',
246
247
  args: [
247
248
  `请求被拒绝(403):${method} ${url}`,
248
249
  {
@@ -0,0 +1,39 @@
1
+ /**
2
+ * 统一 HMR API
3
+ *
4
+ * 提供统一的 HMR 成功/失败回调接口,屏蔽 Webpack 和 Vite 的差异。
5
+ *
6
+ * - Webpack/Rspack: 通过 import.meta.webpackHot 或 module.hot
7
+ * - Vite: 通过 error-overlay 注入的 window.__VITE_HMR__
8
+ *
9
+ * @see docs/RFC_HMR_API.md
10
+ */
11
+ export interface HmrApi {
12
+ /**
13
+ * 注册 HMR 成功回调
14
+ * @param callback 成功回调函数
15
+ * @returns cleanup 函数,用于取消注册
16
+ */
17
+ onSuccess(callback: () => void): () => void;
18
+ /**
19
+ * 注册 HMR 失败回调
20
+ * @param callback 失败回调函数,参数为错误信息
21
+ * @returns cleanup 函数,用于取消注册
22
+ */
23
+ onError(callback: (error: unknown) => void): () => void;
24
+ }
25
+ declare global {
26
+ interface Window {
27
+ __VITE_HMR__?: HmrApi;
28
+ }
29
+ }
30
+ /**
31
+ * 获取 HMR API 实例
32
+ *
33
+ * 自动检测当前构建工具(Webpack/Vite)并返回对应实现:
34
+ * - Webpack/Rspack: 使用内置适配器
35
+ * - Vite: 使用 error-overlay 注入的 window.__VITE_HMR__
36
+ *
37
+ * @returns HmrApi 实例,如果不在开发环境或不支持 HMR 则返回 null
38
+ */
39
+ export declare function getHmrApi(): HmrApi | null;
@@ -0,0 +1,36 @@
1
+ function createWebpackHmrApi(hot) {
2
+ return {
3
+ onSuccess (callback) {
4
+ let lastStatus = null;
5
+ const handler = (status)=>{
6
+ if ('idle' === status && 'apply' === lastStatus) try {
7
+ callback();
8
+ } catch (e) {
9
+ console.error('[HMR] Success callback error:', e);
10
+ }
11
+ lastStatus = status;
12
+ };
13
+ hot.addStatusHandler(handler);
14
+ return ()=>hot.removeStatusHandler(handler);
15
+ },
16
+ onError (callback) {
17
+ const handler = (status)=>{
18
+ if ('fail' === status || 'abort' === status) try {
19
+ callback(new Error(`HMR ${status}`));
20
+ } catch (e) {
21
+ console.error('[HMR] Error callback error:', e);
22
+ }
23
+ };
24
+ hot.addStatusHandler(handler);
25
+ return ()=>hot.removeStatusHandler(handler);
26
+ }
27
+ };
28
+ }
29
+ function getHmrApi() {
30
+ if ('production' === process.env.NODE_ENV) return null;
31
+ if (import.meta.webpackHot) return createWebpackHmrApi(import.meta.webpackHot);
32
+ 'undefined' != typeof module && module.hot;
33
+ if (window.__VITE_HMR__) return window.__VITE_HMR__;
34
+ return null;
35
+ }
36
+ export { getHmrApi };
@@ -15,10 +15,15 @@ export interface ModuleHotInstance {
15
15
  addStatusHandler: (handler: (status: ModuleHotType) => void) => void;
16
16
  removeStatusHandler: (handler: (status: ModuleHotType) => void) => void;
17
17
  }
18
+ /**
19
+ * 获取 Webpack HMR 实例
20
+ * 仅支持 Webpack/Rspack 的 HMR API
21
+ */
18
22
  export declare function getModuleHot(): ModuleHotInstance | null;
19
23
  /**
20
24
  * 创建模块热更成功处理函数
21
- * 监听模块热更状态,当状态为apply时,调用回调函数并传入true
22
- * @param callback 热更成功回调函数,参数为是否成功
25
+ * 监听模块热更状态,检测 apply -> idle 转换表示热更成功
26
+ *
27
+ * @param callback 热更回调函数,参数为是否成功和当前状态
23
28
  */
24
29
  export declare function createApplyHandle(callback: (success: boolean, status: ModuleHotType) => void): (status: ModuleHotType) => void;
@@ -1,66 +1,21 @@
1
- function createViteHmrAdapter(viteHot) {
2
- const handlers = new Set();
3
- const notifyHandlers = (status)=>{
4
- handlers.forEach((handler)=>{
5
- try {
6
- handler(status);
7
- } catch (e) {
8
- console.error('[HMR] Status handler error:', e);
9
- }
10
- });
11
- };
12
- const beforeUpdateHandler = ()=>{
13
- notifyHandlers('prepare');
14
- setTimeout(()=>notifyHandlers('apply'), 0);
15
- };
16
- const afterUpdateHandler = ()=>{
17
- notifyHandlers('idle');
18
- };
19
- const errorHandler = ()=>{
20
- notifyHandlers('fail');
21
- };
22
- const beforeFullReloadHandler = ()=>{
23
- notifyHandlers('check');
24
- };
25
- viteHot.on('vite:beforeUpdate', beforeUpdateHandler);
26
- viteHot.on('vite:afterUpdate', afterUpdateHandler);
27
- viteHot.on('vite:error', errorHandler);
28
- viteHot.on('vite:beforeFullReload', beforeFullReloadHandler);
29
- return {
30
- addStatusHandler (handler) {
31
- handlers.add(handler);
32
- },
33
- removeStatusHandler (handler) {
34
- handlers.delete(handler);
35
- }
36
- };
37
- }
38
- let viteAdapterInstance = null;
39
1
  function getModuleHot() {
40
2
  if ('production' === process.env.NODE_ENV) return null;
41
- if (import.meta.hot) {
42
- if (!viteAdapterInstance) viteAdapterInstance = createViteHmrAdapter(import.meta.hot);
43
- return viteAdapterInstance;
44
- }
45
3
  if (import.meta.webpackHot) return import.meta.webpackHot;
46
4
  'undefined' != typeof module && module.hot;
47
5
  return null;
48
6
  }
49
7
  function createApplyHandle(callback) {
50
- let hasApply = false;
8
+ let lastStatus = null;
51
9
  return (status)=>{
52
10
  if ('fail' === status || 'abort' === status) {
53
- hasApply = false;
11
+ lastStatus = status;
54
12
  return callback(false, status);
55
13
  }
56
- if (hasApply) {
57
- if ('idle' === status) {
58
- hasApply = false;
59
- callback(true, status);
60
- }
61
- return;
14
+ if ('idle' === status && 'apply' === lastStatus) {
15
+ lastStatus = status;
16
+ return callback(true, status);
62
17
  }
63
- hasApply = 'apply' === status;
18
+ lastStatus = status;
64
19
  };
65
20
  }
66
21
  export { createApplyHandle, getModuleHot };
@@ -4,9 +4,7 @@ async function fetchUserProfilesByIds(ids) {
4
4
  try {
5
5
  const dataloom = await getDataloom();
6
6
  if (!dataloom) throw new Error('Dataloom client is unavailable');
7
- const numericIds = ids.map(Number).filter(Number.isFinite);
8
- if (0 === numericIds.length) return [];
9
- const response = await dataloom.service.user.getByIds(numericIds);
7
+ const response = await dataloom.service.user.getByIds(ids);
10
8
  return Array.isArray(response?.data) ? response.data : response?.data?.user_list || [];
11
9
  } catch (error) {
12
10
  console.error(`Failed to fetch profiles for user IDs ${ids.join(', ')}:`, error);
@@ -0,0 +1,5 @@
1
+ /**
2
+ * 按 appId 隔离的 localStorage 封装
3
+ * API 与原生 localStorage 保持一致
4
+ */
5
+ export declare const scopedStorage: Storage;
@@ -0,0 +1,46 @@
1
+ import { getAppId } from "./getAppId.js";
2
+ function getPrefix() {
3
+ const appId = getAppId(window.location.pathname);
4
+ const namespace = appId || '__global__';
5
+ return `__miaoda_${namespace}__:`;
6
+ }
7
+ function getScopedKeys() {
8
+ const prefix = getPrefix();
9
+ const keys = [];
10
+ for(let i = 0; i < localStorage.length; i++){
11
+ const key = localStorage.key(i);
12
+ if (key && key.startsWith(prefix)) keys.push(key.substring(prefix.length));
13
+ }
14
+ return keys;
15
+ }
16
+ const scopedStorage = {
17
+ getItem (key) {
18
+ const prefixedKey = getPrefix() + key;
19
+ return localStorage.getItem(prefixedKey);
20
+ },
21
+ setItem (key, value) {
22
+ const prefixedKey = getPrefix() + key;
23
+ localStorage.setItem(prefixedKey, value);
24
+ },
25
+ removeItem (key) {
26
+ const prefixedKey = getPrefix() + key;
27
+ localStorage.removeItem(prefixedKey);
28
+ },
29
+ clear () {
30
+ const prefix = getPrefix();
31
+ const keysToRemove = [];
32
+ for(let i = 0; i < localStorage.length; i++){
33
+ const key = localStorage.key(i);
34
+ if (key && key.startsWith(prefix)) keysToRemove.push(key);
35
+ }
36
+ keysToRemove.forEach((key)=>localStorage.removeItem(key));
37
+ },
38
+ key (index) {
39
+ const keys = getScopedKeys();
40
+ return keys[index] || null;
41
+ },
42
+ get length () {
43
+ return getScopedKeys().length;
44
+ }
45
+ };
46
+ export { scopedStorage };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/client-toolkit",
3
- "version": "1.2.10-alpha.3",
3
+ "version": "1.2.10-alpha.31",
4
4
  "types": "./lib/index.d.ts",
5
5
  "main": "./lib/index.js",
6
6
  "files": [
@@ -93,10 +93,10 @@
93
93
  "dependencies": {
94
94
  "@ant-design/colors": "^7.2.1",
95
95
  "@ant-design/cssinjs": "^1.24.0",
96
- "@data-loom/js": "0.4.4-alpha.10",
96
+ "@data-loom/js": "0.4.6",
97
97
  "@lark-apaas/auth-sdk": "^0.1.0",
98
- "@lark-apaas/client-capability": "^0.1.3",
99
- "@lark-apaas/miaoda-inspector": "^1.0.14-alpha.4",
98
+ "@lark-apaas/client-capability": "^0.1.4",
99
+ "@lark-apaas/miaoda-inspector": "1.0.14-alpha.14",
100
100
  "@lark-apaas/observable-web": "^1.0.1",
101
101
  "@radix-ui/react-avatar": "^1.1.10",
102
102
  "@radix-ui/react-popover": "^1.1.15",