@lark-apaas/client-toolkit 1.2.9-beta.43 → 1.2.9-beta.45

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.
@@ -1,4 +1,5 @@
1
1
  function getDefaultBucketId() {
2
- return window._bucket_id;
2
+ const w = window;
3
+ return w._bucket_id ?? w.__platform__?.appPublished?.app_runtime_extra?.bucket?.default_bucket_id;
3
4
  }
4
5
  export { getDefaultBucketId };
@@ -1,5 +1,5 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import { useEffect, useState } from "react";
2
+ import react, { Suspense, useEffect, useMemo, 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";
@@ -10,20 +10,33 @@ import { reportTeaEvent } from "./utils/tea.js";
10
10
  import { useAppInfo } from "../../hooks/index.js";
11
11
  import { TrackKey } from "../../types/tea.js";
12
12
  import { slardar } from "@lark-apaas/internal-slardar";
13
- import safety from "./safety.js";
14
13
  import { getAppId } from "../../utils/getAppId.js";
15
14
  import { isNewPathEnabled } from "../../utils/apiPath.js";
16
15
  import QueryProvider from "../QueryProvider/index.js";
17
16
  import { AuthProvider } from "@lark-apaas/auth-sdk";
18
17
  import "../../runtime/index.js";
18
+ const Safety = /*#__PURE__*/ react.lazy(()=>import("./safety.js"));
19
19
  const isMiaodaPreview = window.IS_MIAODA_PREVIEW;
20
- const readCssVarColor = (varName, fallback)=>{
20
+ const readAllCssVarColors = ()=>{
21
21
  try {
22
- if ('undefined' == typeof document) return fallback;
23
- const value = getComputedStyle(document.documentElement).getPropertyValue(varName).trim();
24
- return value || fallback;
22
+ if ('undefined' == typeof document) return {};
23
+ const styles = getComputedStyle(document.documentElement);
24
+ const read = (name)=>styles.getPropertyValue(name).trim() || void 0;
25
+ return {
26
+ background: read('--background'),
27
+ destructive: read('--destructive'),
28
+ primary: read('--primary'),
29
+ foreground: read('--foreground'),
30
+ warning: read('--warning'),
31
+ success: read('--success'),
32
+ muted: read('--muted'),
33
+ mutedForeground: read('--muted-foreground'),
34
+ border: read('--border'),
35
+ popover: read('--popover'),
36
+ accent: read('--accent')
37
+ };
25
38
  } catch {
26
- return fallback;
39
+ return {};
27
40
  }
28
41
  };
29
42
  const appFlags = process.env.APP_FLAGS || {};
@@ -69,13 +82,17 @@ const App = (props)=>{
69
82
  }, []);
70
83
  const appId = getAppId();
71
84
  const permissionApiUrl = isNewPathEnabled() ? `/app/${appId}/__runtime__/api/v1/permissions/roles` : `/spark/app/${appId}/runtime/api/v1/permissions/roles`;
72
- return /*#__PURE__*/ jsxs(AuthProvider, {
73
- config: {
85
+ const authConfig = useMemo(()=>({
74
86
  enable: enableAuth,
75
87
  permissionApi: {
76
88
  url: permissionApiUrl
77
89
  }
78
- },
90
+ }), [
91
+ enableAuth,
92
+ permissionApiUrl
93
+ ]);
94
+ return /*#__PURE__*/ jsxs(AuthProvider, {
95
+ config: authConfig,
79
96
  children: [
80
97
  !disableToaster && true !== appFlags.customToaster && /*#__PURE__*/ jsx(Toaster, {}),
81
98
  'production' !== process.env.NODE_ENV && /*#__PURE__*/ jsx(MiaodaInspector, {}),
@@ -88,19 +105,7 @@ const App = (props)=>{
88
105
  };
89
106
  const AppContainer_AppContainer = (props)=>{
90
107
  const { children } = props;
91
- const [cssColors, setCssColors] = useState(()=>({
92
- background: readCssVarColor('--background'),
93
- destructive: readCssVarColor('--destructive'),
94
- primary: readCssVarColor('--primary'),
95
- foreground: readCssVarColor('--foreground'),
96
- warning: readCssVarColor('--warning'),
97
- success: readCssVarColor('--success'),
98
- muted: readCssVarColor('--muted'),
99
- mutedForeground: readCssVarColor('--muted-foreground'),
100
- border: readCssVarColor('--border'),
101
- popover: readCssVarColor('--popover'),
102
- accent: readCssVarColor('--accent')
103
- }));
108
+ const [cssColors, setCssColors] = useState(readAllCssVarColors);
104
109
  useEffect(()=>{
105
110
  if ('production' === process.env.NODE_ENV) return ()=>{};
106
111
  const observer = new MutationObserver((mutations)=>{
@@ -119,19 +124,7 @@ const AppContainer_AppContainer = (props)=>{
119
124
  const maxRetries = 10;
120
125
  const retryDelay = 400;
121
126
  const updateColors = ()=>{
122
- const newColors = {
123
- background: readCssVarColor('--background'),
124
- destructive: readCssVarColor('--destructive'),
125
- primary: readCssVarColor('--primary'),
126
- foreground: readCssVarColor('--foreground'),
127
- warning: readCssVarColor('--warning'),
128
- success: readCssVarColor('--success'),
129
- muted: readCssVarColor('--muted'),
130
- mutedForeground: readCssVarColor('--muted-foreground'),
131
- border: readCssVarColor('--border'),
132
- popover: readCssVarColor('--popover'),
133
- accent: readCssVarColor('--accent')
134
- };
127
+ const newColors = readAllCssVarColors();
135
128
  setCssColors((currentColors)=>{
136
129
  if (JSON.stringify(currentColors) !== JSON.stringify(newColors)) {
137
130
  retryCount = 0;
@@ -203,7 +196,10 @@ const AppContainer_AppContainer = (props)=>{
203
196
  };
204
197
  return /*#__PURE__*/ jsxs(Fragment, {
205
198
  children: [
206
- /*#__PURE__*/ jsx(safety, {}),
199
+ /*#__PURE__*/ jsx(Suspense, {
200
+ fallback: null,
201
+ children: /*#__PURE__*/ jsx(Safety, {})
202
+ }),
207
203
  /*#__PURE__*/ jsx(QueryProvider, {
208
204
  children: /*#__PURE__*/ jsx(ConfigProvider, {
209
205
  locale: zh_CN,
@@ -1,8 +1,8 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, useState } from "react";
3
3
  import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover.js";
4
- import { getCsrfToken } from "../../utils/getCsrfToken.js";
5
4
  import { getAppId } from "../../utils/getAppId.js";
5
+ import { getCsrfToken } from "../../utils/getCsrfToken.js";
6
6
  import { isNewPathEnabled } from "../../utils/apiPath.js";
7
7
  import { useIsMobile } from "../../hooks/index.js";
8
8
  import { X } from "lucide-react";
@@ -13,35 +13,34 @@ const Component = ()=>{
13
13
  const [visible, setVisible] = useState(!window.localStorage?.getItem(HasClosedKey));
14
14
  const [open, setOpen] = useState(false);
15
15
  const isMobile = useIsMobile();
16
- const [userinfo, setUserinfo] = useState(null);
17
- const [isInternetVisible, setIsInternetVisible] = useState(false);
18
- const [showBadge, setShowBadge] = useState(true);
19
- const [badgeLoaded, setBadgeLoaded] = useState(false);
20
16
  const timeoutRef = useRef(null);
17
+ const platformData = window.__platform_data__ || window.__platform__ || {};
18
+ const appId = getAppId() || platformData.appId;
19
+ const showBadge = false !== platformData.showBadge;
20
+ const [tenantName, setTenantName] = useState('');
21
+ const [isInternetVisible, setIsInternetVisible] = useState(false);
21
22
  useEffect(()=>{
22
- const appId = getAppId();
23
+ if (!showBadge || !visible) return;
24
+ const csrfToken = getCsrfToken() ?? '';
23
25
  const csrfHeaders = {
24
- 'X-Suda-Csrf-Token': getCsrfToken()
26
+ 'X-Suda-Csrf-Token': csrfToken
25
27
  };
26
28
  const tenantInfoUrl = isNewPathEnabled() ? `/app/${appId}/__runtime__/api/v1/studio/tenant_info` : `/spark/b/${appId}/tenant_info`;
27
29
  fetch(tenantInfoUrl, {
28
30
  headers: csrfHeaders
29
31
  }).then((res)=>res.json()).then((data)=>{
30
- setUserinfo(data?.data?.tenant_info);
31
- setIsInternetVisible(data?.data?.is_internet_visible);
32
- }).catch(()=>{});
33
- const getPublishedUrl = isNewPathEnabled() ? `/app/${appId}/__runtime__/api/v1/studio/get_published` : `/spark/b/${appId}/get_published`;
34
- fetch(getPublishedUrl, {
35
- headers: csrfHeaders
36
- }).then((res)=>res.json()).then((data)=>{
37
- const badge = data?.data?.app_info?.show_badge;
38
- setShowBadge(false !== badge);
39
- }).catch(()=>{
40
- setShowBadge(true);
41
- }).finally(()=>{
42
- setBadgeLoaded(true);
32
+ if (data?.status_code === '0' || data?.code === 0) {
33
+ setTenantName(data?.data?.tenant_info?.name || '');
34
+ setIsInternetVisible(data?.data?.is_internet_visible || false);
35
+ }
36
+ }).catch((e)=>{
37
+ console.warn('Failed to fetch tenant info:', e);
43
38
  });
44
- }, []);
39
+ }, [
40
+ appId,
41
+ showBadge,
42
+ visible
43
+ ]);
45
44
  useEffect(()=>{
46
45
  if (isMobile) {
47
46
  const link = document.createElement('link');
@@ -54,7 +53,6 @@ const Component = ()=>{
54
53
  isMobile
55
54
  ]);
56
55
  if ('production' !== process.env.NODE_ENV) return null;
57
- if (!badgeLoaded) return null;
58
56
  if (!showBadge) return null;
59
57
  if (!visible) return null;
60
58
  if (isMobile) return /*#__PURE__*/ jsxs(Sheet, {
@@ -114,7 +112,7 @@ const Component = ()=>{
114
112
  /*#__PURE__*/ jsx("p", {
115
113
  className: "shrink-0 min-w-[96px] m-0! text-[#646A73] text-sm",
116
114
  children: t('safety.tenant.operated', {
117
- name: userinfo?.name ?? ''
115
+ name: tenantName
118
116
  })
119
117
  })
120
118
  ]
@@ -233,7 +231,7 @@ const Component = ()=>{
233
231
  /*#__PURE__*/ jsx("p", {
234
232
  className: "shrink-0 min-w-[96px] m-0! text-[#a6a6a6]",
235
233
  children: t('safety.tenant.operated', {
236
- name: userinfo?.name ?? ''
234
+ name: tenantName
237
235
  })
238
236
  })
239
237
  ]
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  declare const RenderError: React.FC<{
3
- error: Error;
4
- resetErrorBoundary?: () => void;
3
+ error: unknown;
4
+ resetErrorBoundary?: (...args: unknown[]) => void;
5
5
  }>;
6
6
  export default RenderError;
@@ -3,6 +3,7 @@ import { useEffect } from "react";
3
3
  import { logger } from "../../logger/index.js";
4
4
  import { getHmrApi } from "../../utils/hmr-api.js";
5
5
  import { submitPostMessage } from "../../utils/postMessage.js";
6
+ import { t } from "../../locales/index.js";
6
7
  const RenderError = (props)=>{
7
8
  const { error, resetErrorBoundary } = props;
8
9
  useEffect(()=>{
@@ -46,7 +47,7 @@ const RenderError = (props)=>{
46
47
  }),
47
48
  /*#__PURE__*/ jsx("p", {
48
49
  className: "text-l/[22px] text-[14px] text-[#1F2329] font-medium",
49
- children: "页面出错了"
50
+ children: t('errorRender.title')
50
51
  })
51
52
  ]
52
53
  })
@@ -2,16 +2,21 @@ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useLocation } from "react-router-dom";
3
3
  import { useEffect } from "react";
4
4
  import { logger } from "../../logger/index.js";
5
+ import { t } from "../../locales/index.js";
5
6
  const NotFound = ()=>{
6
7
  const location = useLocation();
7
8
  useEffect(()=>{
8
9
  logger.log({
9
10
  level: 'error',
10
11
  args: [
11
- `页面 ${location.pathname} 不存在`
12
+ t('notFound.logger.pageNotExist', {
13
+ path: location.pathname
14
+ })
12
15
  ],
13
16
  meta: {
14
- repairMessage: `页面 ${location.pathname} 不存在,帮我创建对应页面,并配置到"client/src/app.tsx"路由表中`,
17
+ repairMessage: t('notFound.logger.repairMessage', {
18
+ path: location.pathname
19
+ }),
15
20
  noStacktrace: true,
16
21
  stacktrace: [],
17
22
  type: 'not-found'
@@ -30,7 +35,7 @@ const NotFound = ()=>{
30
35
  }),
31
36
  /*#__PURE__*/ jsx("p", {
32
37
  className: "text-l/[22px] text-[14px] text-[#1F2329] font-medium",
33
- children: "页面不存在"
38
+ children: t('notFound.pageNotExist')
34
39
  })
35
40
  ]
36
41
  });
@@ -1,6 +1,10 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import "react";
3
- const PagePlaceholder = ({ title = '页面待开发', description = '页面暂未开发,请耐心等待...' })=>/*#__PURE__*/ jsxs("div", {
3
+ import { t } from "../../locales/index.js";
4
+ const PagePlaceholder_PagePlaceholder = ({ title, description })=>{
5
+ const defaultTitle = t('pagePlaceholder.title');
6
+ const defaultDescription = t("pagePlaceholder.description");
7
+ return /*#__PURE__*/ jsxs("div", {
4
8
  className: "flex flex-col items-center justify-center px-6 w-full h-[calc(100vh-64px)] text-center",
5
9
  children: [
6
10
  /*#__PURE__*/ jsx("img", {
@@ -12,13 +16,14 @@ const PagePlaceholder = ({ title = '页面待开发', description = '页面暂
12
16
  }),
13
17
  /*#__PURE__*/ jsx("div", {
14
18
  className: "text-center text-foreground text-base font-medium mb-1 leading-6",
15
- children: title
19
+ children: title ?? defaultTitle
16
20
  }),
17
21
  /*#__PURE__*/ jsx("div", {
18
22
  className: "text-center text-muted-foreground text-base leading-6 line-clamp-2 sm:max-w-[480px]",
19
- children: description
23
+ children: description ?? defaultDescription
20
24
  })
21
25
  ]
22
26
  });
23
- const components_PagePlaceholder = PagePlaceholder;
24
- export { components_PagePlaceholder as default };
27
+ };
28
+ const PagePlaceholder = PagePlaceholder_PagePlaceholder;
29
+ export { PagePlaceholder as default };
@@ -3,6 +3,7 @@ import { useState } from "react";
3
3
  import { clsxWithTw } from "../../../utils/utils.js";
4
4
  import { getChatAppLink } from "./utils.js";
5
5
  import { OverflowTooltipText } from "../../ui/overflow-tooltip-text.js";
6
+ import { t } from "../../../locales/index.js";
6
7
  function UserProfileUI({ user }) {
7
8
  const [avatarError, setAvatarError] = useState(false);
8
9
  const canShowAvatar = Boolean(user.avatar) && !avatarError;
@@ -52,7 +53,7 @@ function UserProfileUI({ user }) {
52
53
  }),
53
54
  'inactive' === user.status && /*#__PURE__*/ jsx("span", {
54
55
  className: "shrink-0 inline-flex items-center rounded-sm bg-destructive px-2 py-0.5 text-xs font-medium text-white",
55
- children: "暂停使用"
56
+ children: t('userProfile.status.inactive')
56
57
  })
57
58
  ]
58
59
  }),
@@ -75,7 +76,7 @@ function UserProfileUI({ user }) {
75
76
  fill: "currentColor"
76
77
  })
77
78
  }),
78
- "消息"
79
+ t('userProfile.button.message')
79
80
  ]
80
81
  })
81
82
  }) : null
@@ -90,7 +91,7 @@ function UserProfileUI({ user }) {
90
91
  children: [
91
92
  /*#__PURE__*/ jsx("span", {
92
93
  className: "min-w-[74px] text-sm text-muted-foreground",
93
- children: "部门"
94
+ children: t('userProfile.label.department')
94
95
  }),
95
96
  /*#__PURE__*/ jsx("span", {
96
97
  className: clsxWithTw('flex-1 break-all text-sm text-foreground', {
@@ -105,7 +106,7 @@ function UserProfileUI({ user }) {
105
106
  children: [
106
107
  /*#__PURE__*/ jsx("span", {
107
108
  className: "min-w-[74px] text-sm text-muted-foreground",
108
- children: "邮箱"
109
+ children: t('userProfile.label.email')
109
110
  }),
110
111
  user.email ? /*#__PURE__*/ jsx("a", {
111
112
  href: `mailto:${user.email}`,
@@ -7,7 +7,9 @@ import { MultipleSelectionTags } from "./MultipleSelectionTags.js";
7
7
  import { SingleSelectionPreview } from "./SingleSelectionPreview.js";
8
8
  import { ActionButtons } from "./ActionButtons.js";
9
9
  import { Dropdown } from "./Dropdown.js";
10
- const UserSelectUI = ({ mode = 'single', value, onChange, placeholder = '请选择用户', disabled = false, allowClear = true, onSearch, loading = false, options = [] })=>{
10
+ import { t } from "../../../locales/index.js";
11
+ const UserSelectUI = ({ mode = 'single', value, onChange, placeholder, disabled = false, allowClear = true, onSearch, loading = false, options = [] })=>{
12
+ const defaultPlaceholder = t('userSelect.placeholder');
11
13
  const [isOpen, setIsOpen] = useState(false);
12
14
  const [searchText, setSearchText] = useState('');
13
15
  const [searchResults, setSearchResults] = useState([]);
@@ -135,7 +137,7 @@ const UserSelectUI = ({ mode = 'single', value, onChange, placeholder = '请选
135
137
  const singleSelectedUser = 'single' === mode && hasSelection ? selectedUsers[0] : void 0;
136
138
  const showSingleSelection = !!singleSelectedUser && 0 === searchText.length;
137
139
  const canClear = allowClear && hasSelection && !disabled;
138
- const inputPlaceholder = hasSelection ? '' : placeholder;
140
+ const inputPlaceholder = hasSelection ? '' : placeholder ?? defaultPlaceholder;
139
141
  const focusInputAtStart = ()=>{
140
142
  const input = inputRef.current;
141
143
  if (!input) return;
@@ -4,11 +4,15 @@ import { createRoot } from "react-dom/client";
4
4
  import { Content, Description, Overlay, Portal, Root, Title } from "@radix-ui/react-dialog";
5
5
  import { Button } from "./button.js";
6
6
  import { clsxWithTw } from "../../utils/utils.js";
7
+ import { t } from "../../locales/index.js";
7
8
  function showConfirm(options) {
8
9
  const opts = 'string' == typeof options ? {
9
10
  message: options
10
11
  } : options;
11
- const { title = '提示', message, confirmText = '确认', cancelText = '取消', variant = 'default' } = opts;
12
+ const { title, message, confirmText, cancelText, variant = 'default' } = opts;
13
+ const defaultTitle = t('confirm.title');
14
+ const defaultConfirmText = t('confirm.confirmText');
15
+ const defaultCancelText = t('confirm.cancelText');
12
16
  return new Promise((resolve)=>{
13
17
  const container = document.createElement('div');
14
18
  document.body.appendChild(container);
@@ -26,10 +30,10 @@ function showConfirm(options) {
26
30
  resolve(false);
27
31
  }
28
32
  root.render(/*#__PURE__*/ jsx(ConfirmDialog, {
29
- title: title,
33
+ title: title ?? defaultTitle,
30
34
  message: message,
31
- confirmText: confirmText,
32
- cancelText: cancelText,
35
+ confirmText: confirmText ?? defaultConfirmText,
36
+ cancelText: cancelText ?? defaultCancelText,
33
37
  variant: variant,
34
38
  onConfirm: handleConfirm,
35
39
  onCancel: handleCancel
package/lib/index.js CHANGED
@@ -4,6 +4,7 @@ import { getAppId } from "./utils/getAppId.js";
4
4
  import { isNewPathEnabled } from "./utils/apiPath.js";
5
5
  import { logger } from "./logger/index.js";
6
6
  import { showToast } from "./components/ui/toast.js";
7
+ import { t } from "./locales/index.js";
7
8
  import { version } from "../package.json";
8
9
  import { showConfirm } from "./components/ui/confirm.js";
9
10
  const _appId = getAppId();
@@ -20,7 +21,7 @@ const capabilityClient = createClient({
20
21
  },
21
22
  logger: logger,
22
23
  onRateLimitError: ()=>{
23
- showToast('应用额度已耗尽,请联系应用开发者');
24
+ showToast(t('index.rateLimitError'));
24
25
  }
25
26
  });
26
27
  const src = {
@@ -1,7 +1,7 @@
1
1
  import { splitWorkspaceUrl } from "../utils/url.js";
2
2
  import { createClient } from "@data-loom/js";
3
3
  import { getAppId } from "../utils/getAppId.js";
4
- import { getInitialInfo } from "../utils/getInitialInfo.js";
4
+ import { getAppPublished } from "../utils/getInitialInfo.js";
5
5
  const createDataLoomClient = (url, pat)=>{
6
6
  const { baseUrl, workspace } = url ? splitWorkspaceUrl(url) : {
7
7
  baseUrl: '',
@@ -27,7 +27,7 @@ let pendingPromise = null;
27
27
  function getDataloom() {
28
28
  if (dataloom) return Promise.resolve(dataloom);
29
29
  if (pendingPromise) return pendingPromise;
30
- pendingPromise = getInitialInfo().then((info)=>{
30
+ pendingPromise = getAppPublished().then((info)=>{
31
31
  const DATALOOM_CLIENT_URL = info?.app_runtime_extra?.url;
32
32
  const DATALOOM_PAT = info?.app_runtime_extra?.token;
33
33
  dataloom = createDataLoomClient(DATALOOM_CLIENT_URL, DATALOOM_PAT);
@@ -25,11 +25,107 @@ const messages_messages = {
25
25
  },
26
26
  'safety.cover.pc': {
27
27
  zh: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/LMfspH/ljhwZthlaukjlkulzlp/logo/miaodacover.png',
28
- en: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/LMfspH/ljhwZthlaukjlkulzlp/logo/miaodacover-pcen.png'
28
+ en: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/LMfspH/ljhwZthlaukjlkulzlp/logo/miaodacover-weben.png'
29
29
  },
30
30
  'safety.cover.mobile': {
31
31
  zh: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/LMfspH/ljhwZthlaukjlkulzlp/logo/miaodacover-mobile.png',
32
- en: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/LMfspH/ljhwZthlaukjlkulzlp/logo/miaodacover-weben.png'
32
+ en: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/LMfspH/ljhwZthlaukjlkulzlp/logo/miaodacover-mobileen.png'
33
+ },
34
+ 'index.rateLimitError': {
35
+ zh: '应用额度已耗尽,请联系应用开发者',
36
+ en: 'Application quota exhausted, please contact the app developer'
37
+ },
38
+ 'theme.themeColors.title': {
39
+ zh: '主题色',
40
+ en: 'Theme Color'
41
+ },
42
+ 'theme.themeRadius.title': {
43
+ zh: '圆角',
44
+ en: 'Border Radius'
45
+ },
46
+ 'theme.themeSpaces.title': {
47
+ zh: '间距',
48
+ en: 'Spacing'
49
+ },
50
+ 'errorRender.title': {
51
+ zh: '页面出错了',
52
+ en: 'Something went wrong'
53
+ },
54
+ 'notFound.pageNotExist': {
55
+ zh: '页面不存在',
56
+ en: 'Page not found'
57
+ },
58
+ 'notFound.logger.pageNotExist': {
59
+ zh: '页面 {path} 不存在',
60
+ en: 'Page {path} does not exist'
61
+ },
62
+ 'notFound.logger.repairMessage': {
63
+ zh: '页面 {path} 不存在,帮我创建对应页面,并配置到"client/src/app.tsx"路由表中',
64
+ en: 'Page {path} does not exist, help me create the corresponding page and configure it in the "client/src/app.tsx" routing table'
65
+ },
66
+ 'pagePlaceholder.title': {
67
+ zh: '页面待开发',
68
+ en: 'Page coming soon'
69
+ },
70
+ "pagePlaceholder.description": {
71
+ zh: '页面暂未开发,请耐心等待...',
72
+ en: 'This page is under development, please wait...'
73
+ },
74
+ 'confirm.title': {
75
+ zh: '提示',
76
+ en: 'Confirm'
77
+ },
78
+ 'confirm.confirmText': {
79
+ zh: '确认',
80
+ en: 'Confirm'
81
+ },
82
+ 'confirm.cancelText': {
83
+ zh: '取消',
84
+ en: 'Cancel'
85
+ },
86
+ 'userSelect.placeholder': {
87
+ zh: '请选择用户',
88
+ en: 'Select user'
89
+ },
90
+ 'userProfile.status.inactive': {
91
+ zh: '暂停使用',
92
+ en: 'Inactive'
93
+ },
94
+ 'userProfile.button.message': {
95
+ zh: '消息',
96
+ en: 'Message'
97
+ },
98
+ 'userProfile.label.department': {
99
+ zh: '部门',
100
+ en: 'Department'
101
+ },
102
+ 'userProfile.label.email': {
103
+ zh: '邮箱',
104
+ en: 'Email'
105
+ },
106
+ 'axios.log.requestSuccess': {
107
+ zh: '请求成功:',
108
+ en: 'Request succeeded: '
109
+ },
110
+ 'axios.log.requestFailed': {
111
+ zh: '请求失败:',
112
+ en: 'Request failed: '
113
+ },
114
+ 'axios.log.networkFailed': {
115
+ zh: '网络请求失败:',
116
+ en: 'Network request failed: '
117
+ },
118
+ 'axios.log.noResponseOrConfig': {
119
+ zh: '请求失败:无响应对象或配置信息',
120
+ en: 'Request failed: no response object or config'
121
+ },
122
+ 'axios.log.forbidden': {
123
+ zh: '请求被拒绝(403):{method} {url}',
124
+ en: 'Request rejected (403): {method} {url}'
125
+ },
126
+ 'axios.log.unknownError': {
127
+ zh: '未知错误',
128
+ en: 'Unknown error'
33
129
  }
34
130
  };
35
131
  const messages = messages_messages;
@@ -5,6 +5,7 @@ import { getStacktrace } from "../logger/selected-logs.js";
5
5
  import { safeStringify } from "./safeStringify.js";
6
6
  import { slardar } from "@lark-apaas/internal-slardar";
7
7
  import { normalizeBasePath } from "./utils.js";
8
+ import { t } from "../locales/index.js";
8
9
  const APP_CLIENT_API_LOG_TYPE = 'app_client_api_log';
9
10
  function stripBasePath(urlPath) {
10
11
  const base = normalizeBasePath(process.env.CLIENT_BASE_PATH);
@@ -95,8 +96,8 @@ async function logResponse(ok, responseOrError) {
95
96
  if (isValidResponse(responseOrError)) {
96
97
  const response = responseOrError;
97
98
  const okToTextMap = {
98
- success: '请求成功:',
99
- error: '请求失败:'
99
+ success: t('axios.log.requestSuccess'),
100
+ error: t('axios.log.requestFailed')
100
101
  };
101
102
  const realOK = 'success' === ok && response.status >= 200 && response.status < 300;
102
103
  const method = (response.config.method || 'GET').toUpperCase();
@@ -154,13 +155,13 @@ async function logResponse(ok, responseOrError) {
154
155
  const error = responseOrError;
155
156
  if (error && error.config && 'object' == typeof error.config) {
156
157
  const errorType = error.code || 'UNKNOWN_ERROR';
157
- const errorMessage = error.message || '未知错误';
158
+ const errorMessage = error.message || t('axios.log.unknownError');
158
159
  const method = (error.config.method || 'GET').toUpperCase();
159
160
  const url = error.config.url || '';
160
161
  const startTime = error.config._startTime;
161
162
  const duration = startTime ? Date.now() - startTime : 0;
162
163
  const parts = [
163
- '网络请求失败:',
164
+ t('axios.log.networkFailed'),
164
165
  method,
165
166
  ' ',
166
167
  url.split('?')[0].replace(/^\/spark\/p\/app_\w+/, ''),
@@ -210,7 +211,7 @@ async function logResponse(ok, responseOrError) {
210
211
  logger.log({
211
212
  level: 'error',
212
213
  args: [
213
- '请求失败:无响应对象或配置信息'
214
+ t('axios.log.noResponseOrConfig')
214
215
  ],
215
216
  meta: {}
216
217
  });
@@ -360,7 +361,10 @@ function initAxiosConfig(axiosInstance) {
360
361
  logger.log({
361
362
  level: 'warn',
362
363
  args: [
363
- `请求被拒绝(403):${method} ${url}`,
364
+ t('axios.log.forbidden', {
365
+ method,
366
+ url
367
+ }),
364
368
  {
365
369
  状态码: 403,
366
370
  返回数据: error.response.data
@@ -13,6 +13,13 @@ interface AppRuntimePublished {
13
13
  interface BucketConfig {
14
14
  default_bucket_id?: string;
15
15
  }
16
+ /**
17
+ * 从API获取已发布的应用信息(仅全栈沙箱模式下使用)
18
+ */
19
+ export declare function getAppPublished(): Promise<{
20
+ app_info?: AppRuntimePublished;
21
+ app_runtime_extra?: AppRuntimeExtra;
22
+ } | undefined>;
16
23
  declare let initialInfo: {
17
24
  app_info?: AppRuntimePublished;
18
25
  app_runtime_extra?: AppRuntimeExtra;
@@ -1,6 +1,11 @@
1
1
  import { getAppId } from "./getAppId.js";
2
2
  import { getCsrfToken } from "./getCsrfToken.js";
3
3
  import { isNewPathEnabled } from "./apiPath.js";
4
+ function syncBucketIdToWindow(info) {
5
+ if (!info) return;
6
+ const bucketId = info.app_runtime_extra?.bucket?.default_bucket_id;
7
+ if (bucketId) window._bucket_id = bucketId;
8
+ }
4
9
  async function getAppPublished() {
5
10
  try {
6
11
  const headers = {
@@ -18,24 +23,40 @@ async function getAppPublished() {
18
23
  credentials: 'include'
19
24
  });
20
25
  const res = await response.json();
21
- if (0 === res.code || '0' === res.status_code) return res.data;
26
+ if (0 === res.code || '0' === res.status_code) {
27
+ syncBucketIdToWindow(res.data);
28
+ return res.data;
29
+ }
22
30
  console.error('Error fetching published app info:', res);
23
31
  } catch (error) {
24
32
  console.error('Error fetching published app info:', error);
25
33
  }
26
34
  }
35
+ function getPreloadedInfo() {
36
+ try {
37
+ const platformData = window.__platform__?.appPublished;
38
+ if (platformData && 'object' == typeof platformData) return platformData;
39
+ } catch {}
40
+ }
27
41
  let initialInfo;
28
42
  let pendingPromise = null;
29
43
  function getInitialInfo(refresh = false) {
30
44
  if (initialInfo && !refresh) return Promise.resolve(initialInfo);
45
+ if (!initialInfo && !refresh) {
46
+ const preloaded = getPreloadedInfo();
47
+ if (preloaded) {
48
+ initialInfo = preloaded;
49
+ syncBucketIdToWindow(initialInfo);
50
+ return Promise.resolve(initialInfo);
51
+ }
52
+ }
31
53
  if (pendingPromise && !refresh) return pendingPromise;
32
54
  pendingPromise = getAppPublished().then((info)=>{
33
55
  initialInfo = info;
34
- if (initialInfo) window._bucket_id = initialInfo.app_runtime_extra?.bucket?.default_bucket_id;
35
56
  return initialInfo;
36
57
  }).finally(()=>{
37
58
  pendingPromise = null;
38
59
  });
39
60
  return pendingPromise;
40
61
  }
41
- export { getInitialInfo };
62
+ export { getAppPublished, getInitialInfo };
@@ -1,9 +1,17 @@
1
1
  /**
2
2
  * 统一的语言判断方法。
3
3
  *
4
- * 基于浏览器 Accept-Language(navigator.language)判断。
5
- * 返回标准化的 locale code:'zh' | 'en'。
6
- * 目前只区分中英文,未来需要更多语言时在此扩展。
4
+ * 生产环境(`process.env.NODE_ENV === 'production'`):
5
+ * 只看浏览器容器语言(navigator.language)。
6
+ *
7
+ * 非生产环境(沙箱预览场景):
8
+ * 优先级 query > localStorage > navigator。
9
+ * URL query `locale`(值为 `zh_CN` / `en_US`)由宿主平台(miaoda)
10
+ * 通过 iframe src 透传,确保产物内的语言与宿主一致;
11
+ * 读到 query 时会同步写入 localStorage,便于后续无 query 的跳转/刷新场景使用。
12
+ *
13
+ * 返回标准化 locale code:'zh' | 'en'。
14
+ * 目前只区分中英文,未来扩展时在此调整。
7
15
  */
8
16
  export type Locale = 'zh' | 'en';
9
17
  export declare function getLocale(): Locale;
@@ -1,5 +1,41 @@
1
+ const STORAGE_KEY = 'miaoda:preview-locale';
2
+ function toLocale(value) {
3
+ const lang = value ?? 'zh';
4
+ return lang.toLowerCase().startsWith('zh') ? 'zh' : 'en';
5
+ }
6
+ function readLocaleFromQuery() {
7
+ if ('undefined' == typeof window) return null;
8
+ try {
9
+ const value = new URLSearchParams(window.location.search).get('locale');
10
+ return value && value.length > 0 ? value : null;
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+ function readLocaleFromStorage() {
16
+ if ('undefined' == typeof window) return null;
17
+ try {
18
+ const value = window.localStorage?.getItem(STORAGE_KEY);
19
+ return value && value.length > 0 ? value : null;
20
+ } catch {
21
+ return null;
22
+ }
23
+ }
24
+ function writeLocaleToStorage(value) {
25
+ if ('undefined' == typeof window) return;
26
+ try {
27
+ window.localStorage?.setItem(STORAGE_KEY, value);
28
+ } catch {}
29
+ }
1
30
  function getLocale() {
2
- const lang = navigator.language || 'zh';
3
- return lang.startsWith('zh') ? 'zh' : 'en';
31
+ if ('production' === process.env.NODE_ENV) return toLocale(navigator.language);
32
+ const fromQuery = readLocaleFromQuery();
33
+ if (fromQuery) {
34
+ writeLocaleToStorage(fromQuery);
35
+ return toLocale(fromQuery);
36
+ }
37
+ const fromStorage = readLocaleFromStorage();
38
+ if (fromStorage) return toLocale(fromStorage);
39
+ return toLocale(navigator.language);
4
40
  }
5
41
  export { getLocale };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/client-toolkit",
3
- "version": "1.2.9-beta.43",
3
+ "version": "1.2.9-beta.45",
4
4
  "types": "./lib/index.d.ts",
5
5
  "main": "./lib/index.js",
6
6
  "files": [