@lark-apaas/client-toolkit 1.2.52-alpha.1 → 1.2.52-beta.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.
@@ -1,5 +1,5 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import { useEffect, useMemo, 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";
@@ -7,6 +7,7 @@ import IframeBridge from "./IframeBridge.js";
7
7
  import { Toaster } from "./sonner.js";
8
8
  import { PageHoc } from "./PageHoc.js";
9
9
  import { reportTeaEvent } from "./utils/tea.js";
10
+ import { SafetyErrorBoundary } from "./safety-error-boundary.js";
10
11
  import { useAppInfo } from "../../hooks/index.js";
11
12
  import { TrackKey } from "../../types/tea.js";
12
13
  import { slardar } from "@lark-apaas/internal-slardar";
@@ -14,8 +15,8 @@ import { getAppId } from "../../utils/getAppId.js";
14
15
  import { isNewPathEnabled } from "../../utils/apiPath.js";
15
16
  import QueryProvider from "../QueryProvider/index.js";
16
17
  import { AuthProvider } from "@lark-apaas/auth-sdk";
17
- import Watermark from "./Watermark.js";
18
18
  import "../../runtime/index.js";
19
+ const Safety = /*#__PURE__*/ react.lazy(()=>import("./safety.js"));
19
20
  const isMiaodaPreview = window.IS_MIAODA_PREVIEW;
20
21
  const readAllCssVarColors = ()=>{
21
22
  try {
@@ -196,7 +197,12 @@ const AppContainer_AppContainer = (props)=>{
196
197
  };
197
198
  return /*#__PURE__*/ jsxs(Fragment, {
198
199
  children: [
199
- /*#__PURE__*/ jsx(Watermark, {}),
200
+ /*#__PURE__*/ jsx(SafetyErrorBoundary, {
201
+ children: /*#__PURE__*/ jsx(Suspense, {
202
+ fallback: null,
203
+ children: /*#__PURE__*/ jsx(Safety, {})
204
+ })
205
+ }),
200
206
  /*#__PURE__*/ jsx(QueryProvider, {
201
207
  children: /*#__PURE__*/ jsx(ConfigProvider, {
202
208
  locale: zh_CN,
@@ -1,11 +1,10 @@
1
1
  import { IUserProfile } from '../apis/udt-types';
2
2
  /**
3
3
  * useCurrentUserProfile 的返回类型。
4
- * 初始状态为空对象 `{}`(所有字段均为 undefined),异步获取用户信息后字段才会被填充。
5
- * 使用时必须通过可选链或空值合并进行安全访问,如 `userInfo?.user_id`。
6
- * 判断是否已加载完成应使用 `if (!userInfo?.user_id)` 而非 `if (!userInfo)`(因为空对象是 truthy)。
4
+ * client-toolkit-lite 完全一致:所有字段保证非 undefined(未加载时为空字符串),
5
+ * 使用 `if (!userInfo.user_id)` 判断是否已加载完成。
7
6
  */
8
- export type ICompatibilityUserProfile = Partial<IUserProfile & {
7
+ export interface ICompatibilityUserProfile extends IUserProfile {
9
8
  /**
10
9
  * @deprecated please use `name`
11
10
  */
@@ -14,14 +13,5 @@ export type ICompatibilityUserProfile = Partial<IUserProfile & {
14
13
  * @deprecated please use `avatar`
15
14
  */
16
15
  userAvatar: string;
17
- }>;
18
- export declare const useCurrentUserProfile: () => Partial<IUserProfile & {
19
- /**
20
- * @deprecated please use `name`
21
- */
22
- userName: string;
23
- /**
24
- * @deprecated please use `avatar`
25
- */
26
- userAvatar: string;
27
- }>;
16
+ }
17
+ export declare const useCurrentUserProfile: () => ICompatibilityUserProfile;
@@ -25,9 +25,14 @@ function fetchLarkUserId() {
25
25
  function getCompatibilityUserProfile() {
26
26
  const userInfo = getCurrentUserProfile();
27
27
  return {
28
- ...userInfo,
29
- userName: userInfo.name,
30
- userAvatar: userInfo.avatar
28
+ user_id: userInfo.user_id ?? '',
29
+ email: userInfo.email ?? '',
30
+ name: userInfo.name ?? '',
31
+ avatar: userInfo.avatar ?? '',
32
+ status: userInfo.status,
33
+ lark_user_id: userInfo.lark_user_id,
34
+ userName: userInfo.name ?? '',
35
+ userAvatar: userInfo.avatar ?? ''
31
36
  };
32
37
  }
33
38
  const useCurrentUserProfile = ()=>{
@@ -38,14 +43,15 @@ const useCurrentUserProfile = ()=>{
38
43
  const result = await authClient.session.getUserInfo();
39
44
  if (cancelled) return;
40
45
  const info = result?.data?.user_info;
41
- const userName = getNameFromArray(info?.name);
46
+ const userName = getNameFromArray(info?.name) ?? '';
47
+ const avatar = info?.avatar?.image?.large ?? '';
42
48
  const newUserInfo = {
43
- user_id: info?.user_id?.toString(),
44
- email: info?.email,
49
+ user_id: info?.user_id?.toString() ?? '',
50
+ email: info?.email ?? '',
45
51
  name: userName,
46
- avatar: info?.avatar?.image?.large,
47
- userName: userName,
48
- userAvatar: info?.avatar?.image?.large
52
+ avatar,
53
+ userName,
54
+ userAvatar: avatar
49
55
  };
50
56
  const larkUserId = await fetchLarkUserId();
51
57
  if (larkUserId) newUserInfo.lark_user_id = larkUserId;
package/lib/index.d.ts CHANGED
@@ -1,6 +1,46 @@
1
+ import { getAppId } from './utils/getAppId';
2
+ import { logger } from './logger';
1
3
  export declare const capabilityClient: import("@lark-apaas/client-capability").CapabilityClient;
2
4
  export { showConfirm } from './components/ui/confirm';
3
5
  export type { ConfirmOptions } from './components/ui/confirm';
6
+ export { default as AppContainer } from './components/AppContainer';
7
+ export { default as QueryProvider } from './components/QueryProvider';
8
+ export { default as PagePlaceholder } from './components/PagePlaceholder';
9
+ export { default as Welcome } from './components/PagePlaceholder';
10
+ export { default as ErrorRender } from './components/ErrorRender';
11
+ export { ActiveLink } from './apis/components/ActiveLink';
12
+ export { NavLink } from './apis/components/NavLink';
13
+ export { UniversalLink } from './apis/components/UniversalLink';
14
+ export { useAppInfo } from './hooks/useAppInfo';
15
+ export { useCurrentUserProfile } from './hooks/useCurrentUserProfile';
16
+ export { useIsMobile } from './hooks/useIsMobile';
17
+ export { useLogout } from './hooks/useLogout';
18
+ export { clsxWithTw, isPreview, normalizeBasePath, getWsPath } from './utils/utils';
19
+ export { getCsrfToken } from './utils/getCsrfToken';
20
+ export { scopedStorage } from './utils/scopedStorage';
21
+ export { copyToClipboard } from './utils/copyToClipboard';
22
+ export { isIpad, isMobile, isIOS } from './utils/deviceType';
23
+ export { resolveAppUrl } from './utils/resolveAppUrl';
24
+ export { getEnvPath } from './utils/getEnvPath';
25
+ export { safeStringify } from './utils/safeStringify';
26
+ export { getAxiosForBackend, axiosForBackend } from './utils/getAxiosForBackend';
27
+ export { initAxiosConfig } from './utils/axiosConfig';
28
+ export { getAppId };
29
+ export { getAppInfo } from './integrations/getAppInfo';
30
+ export { getCurrentUserProfile } from './integrations/getCurrentUserProfile';
31
+ export { getDataloom } from './integrations/dataloom';
32
+ export { logger };
33
+ export type { LogLevel, LogWithMeta } from './logger/log-types';
34
+ export { trace } from './trace';
35
+ export { initObservable } from './runtime/observable';
36
+ export type { AppInfoPayload } from './types/common';
37
+ export type { IUserProfile, IJson, IFileAttachment } from './apis/udt-types';
38
+ export type { TrackParams } from './types/tea';
39
+ export { TrackKey } from './types/tea';
40
+ export { reportTeaEvent } from './components/AppContainer/utils/tea';
41
+ export * as avatarImages from './apis/constants/img-resources/avatar';
42
+ export * as bannerImages from './apis/constants/img-resources/banner';
43
+ export * as coverImages from './apis/constants/img-resources/cover';
4
44
  declare const _default: {
5
45
  version: string;
6
46
  };
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createClient } from "@lark-apaas/client-capability";
2
- import { normalizeBasePath } from "./utils/utils.js";
2
+ import { clsxWithTw, getWsPath, isPreview, normalizeBasePath } from "./utils/utils.js";
3
3
  import { getAppId } from "./utils/getAppId.js";
4
4
  import { isNewPathEnabled } from "./utils/apiPath.js";
5
5
  import { logger } from "./logger/index.js";
@@ -7,6 +7,36 @@ import { showToast } from "./components/ui/toast.js";
7
7
  import { t } from "./locales/index.js";
8
8
  import { version } from "../package.json";
9
9
  import { showConfirm } from "./components/ui/confirm.js";
10
+ import AppContainer from "./components/AppContainer/index.js";
11
+ import QueryProvider from "./components/QueryProvider/index.js";
12
+ import PagePlaceholder from "./components/PagePlaceholder/index.js";
13
+ import ErrorRender from "./components/ErrorRender/index.js";
14
+ import { ActiveLink } from "./apis/components/ActiveLink.js";
15
+ import { NavLink } from "./apis/components/NavLink.js";
16
+ import { UniversalLink } from "./apis/components/UniversalLink.js";
17
+ import { useAppInfo } from "./hooks/useAppInfo.js";
18
+ import { useCurrentUserProfile } from "./hooks/useCurrentUserProfile.js";
19
+ import { useIsMobile } from "./hooks/useIsMobile.js";
20
+ import { useLogout } from "./hooks/useLogout.js";
21
+ import { getCsrfToken } from "./utils/getCsrfToken.js";
22
+ import { scopedStorage } from "./utils/scopedStorage.js";
23
+ import { copyToClipboard } from "./utils/copyToClipboard.js";
24
+ import { isIOS, isIpad, isMobile } from "./utils/deviceType.js";
25
+ import { resolveAppUrl } from "./utils/resolveAppUrl.js";
26
+ import { getEnvPath } from "./utils/getEnvPath.js";
27
+ import { safeStringify } from "./utils/safeStringify.js";
28
+ import { axiosForBackend, getAxiosForBackend } from "./utils/getAxiosForBackend.js";
29
+ import { initAxiosConfig } from "./utils/axiosConfig.js";
30
+ import { getAppInfo } from "./integrations/getAppInfo.js";
31
+ import { getCurrentUserProfile } from "./integrations/getCurrentUserProfile.js";
32
+ import { getDataloom } from "./integrations/dataloom.js";
33
+ import { trace } from "./trace/index.js";
34
+ import { initObservable } from "./runtime/observable.js";
35
+ import { TrackKey } from "./types/tea.js";
36
+ import { reportTeaEvent } from "./components/AppContainer/utils/tea.js";
37
+ import * as __WEBPACK_EXTERNAL_MODULE__apis_constants_img_resources_avatar_js_2c154035__ from "./apis/constants/img-resources/avatar.js";
38
+ import * as __WEBPACK_EXTERNAL_MODULE__apis_constants_img_resources_banner_js_72c990bf__ from "./apis/constants/img-resources/banner.js";
39
+ import * as __WEBPACK_EXTERNAL_MODULE__apis_constants_img_resources_cover_js_90a812c2__ from "./apis/constants/img-resources/cover.js";
10
40
  const _appId = getAppId();
11
41
  const _acquireUploadUrl = isNewPathEnabled() ? `/app/${_appId}/__runtime__/api/v1/studio/plugins/tmp_files/acquire_upload_url` : "/af/api/v1/studio/plugins/tmp_files/acquire_upload_url";
12
42
  const _acquireDownloadUrl = isNewPathEnabled() ? `/app/${_appId}/__runtime__/api/v1/studio/plugins/tmp_files/acquire_download_url` : "/af/api/v1/studio/plugins/tmp_files/acquire_download_url";
@@ -27,4 +57,4 @@ const capabilityClient = createClient({
27
57
  const src = {
28
58
  version: version
29
59
  };
30
- export { capabilityClient, src as default, showConfirm };
60
+ export { ActiveLink, AppContainer, ErrorRender, NavLink, PagePlaceholder, QueryProvider, TrackKey, UniversalLink, PagePlaceholder as Welcome, __WEBPACK_EXTERNAL_MODULE__apis_constants_img_resources_avatar_js_2c154035__ as avatarImages, axiosForBackend, __WEBPACK_EXTERNAL_MODULE__apis_constants_img_resources_banner_js_72c990bf__ as bannerImages, capabilityClient, clsxWithTw, copyToClipboard, __WEBPACK_EXTERNAL_MODULE__apis_constants_img_resources_cover_js_90a812c2__ as coverImages, src as default, getAppId, getAppInfo, getAxiosForBackend, getCsrfToken, getCurrentUserProfile, getDataloom, getEnvPath, getWsPath, initAxiosConfig, initObservable, isIOS, isIpad, isMobile, isPreview, logger, normalizeBasePath, reportTeaEvent, resolveAppUrl, safeStringify, scopedStorage, showConfirm, trace, useAppInfo, useCurrentUserProfile, useIsMobile, useLogout };
@@ -1,11 +1,10 @@
1
1
  import { IUserProfile } from '../apis/udt-types';
2
2
  /**
3
3
  * 获取当前用户信息。
4
- * 返回 Partial<IUserProfile>,因为初始值来自 window._userInfo,
5
- * 该值在运行时始终为空对象 {},所有字段均为 undefined。
6
- * 异步获取用户信息后才会被填充为完整的 IUserProfile。
4
+ * client-toolkit-lite 一致返回 IUserProfile(非 Partial);
5
+ * 调用方需自行处理 window._userInfo 未就绪时空对象的情况。
7
6
  */
8
- export declare function getCurrentUserProfile(): Partial<IUserProfile>;
7
+ export declare function getCurrentUserProfile(): IUserProfile;
9
8
  /**
10
9
  * @deprecated 请使用 getCurrentUserProfile 代替
11
10
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/client-toolkit",
3
- "version": "1.2.52-alpha.1",
3
+ "version": "1.2.52-beta.0",
4
4
  "types": "./lib/index.d.ts",
5
5
  "main": "./lib/index.js",
6
6
  "files": [
@@ -26,6 +26,7 @@
26
26
  "types": "./lib/antd-table.d.ts"
27
27
  },
28
28
  "./lib/index.css": "./lib/index.css",
29
+ "./styles.css": "./lib/index.css",
29
30
  "./dataloom": {
30
31
  "import": "./lib/apis/dataloom.js",
31
32
  "require": "./lib/apis/dataloom.js",
@@ -1,48 +0,0 @@
1
- import React from 'react';
2
- /**
3
- * 打开「投诉与举报」页:scene/entity_type 固定,entity_id 取当前妙搭 appId(getAppId →
4
- * window.appId,水印仅豆包应用展示,此时必有 appId),域名按环境选,params 整体 encode。
5
- */
6
- export declare const openDoubaoReport: () => void;
7
- export declare const FeedbackIcon: React.FC;
8
- export declare const CloseIcon: React.FC<{
9
- size: number;
10
- }>;
11
- export declare const Avatar: React.FC<{
12
- size: number;
13
- }>;
14
- export declare const Pill: React.FC<{
15
- variant: 'desktop' | 'mobile';
16
- onClose?: () => void;
17
- showClose?: boolean;
18
- }>;
19
- export declare const MenuRow: React.FC<{
20
- icon: 'feedback' | 'close';
21
- label: string;
22
- fontSize: number;
23
- onClick?: () => void;
24
- }>;
25
- type MenuItem = {
26
- icon: 'feedback' | 'close';
27
- label: string;
28
- onClick?: () => void;
29
- };
30
- export declare const MenuCard: React.FC<{
31
- items: MenuItem[];
32
- fontSize: number;
33
- footerFontSize: number;
34
- minWidth: number;
35
- style?: React.CSSProperties;
36
- }>;
37
- /**
38
- * 豆包水印:仅当应用来源为豆包时,在视口右下角展示收起态药丸(头像 +「豆包AI」)。
39
- *
40
- * 设计稿:Figma 1582:41560(桌面)/ 1582:41777(移动)。
41
- * - 药丸 / 卡片本体用代码画(投影走 CSS box-shadow、图标用无滤镜小 SVG),高分屏清晰。
42
- * - 桌面端点关闭叉 → 隐藏整个水印;hover 徽标 → 上方弹白色卡片(投诉与举报 + 由飞书妙搭提供支持)。
43
- * - 移动端 tap 徽标 → 上方弹卡片(投诉与举报 / 不再展示 / 由飞书妙搭提供支持)。
44
- * - 「投诉与举报」点击跳飞书风控举报中心(tns,域名随环境);后端链接未就绪时由 REPORT_ENABLED 整体隐藏。
45
- * - 不加 NODE_ENV 判断——预览态与运行态都要展示。
46
- */
47
- declare const Watermark: React.FC;
48
- export default Watermark;
@@ -1,352 +0,0 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useRef, useState } from "react";
3
- import { Content, Portal, Root, Trigger } from "@radix-ui/react-popover";
4
- import { DOUBAO_LOGO_SRC, DOUBAO_PILL_MOBILE_PNG } from "./doubao-watermark-asset.js";
5
- import { getAppId } from "../../utils/getAppId.js";
6
- import { useIsMobile } from "../../hooks/index.js";
7
- const REPORT_ENABLED = true;
8
- const BRAND_TEXT = '豆包 AI 生成';
9
- const POWERED_BY_TEXT = '由飞书妙搭提供支持';
10
- const FONT_FAMILY = "'PingFang SC', -apple-system, BlinkMacSystemFont, sans-serif";
11
- const PILL_SHADOW = '0px 8px 32px rgba(0,0,0,0.08), 0px 4px 16px rgba(0,0,0,0.04), 0px 2px 4px rgba(0,0,0,0.02)';
12
- const CARD_SHADOW = '0px 4px 16px rgba(0,0,0,0.03), 0px 4px 10px rgba(0,0,0,0.03), 0px 2px 4px rgba(0,0,0,0.02)';
13
- const reportDomainByEnv = (env)=>{
14
- if ('boe' === env) return 'tns.feishu-boe.cn';
15
- if ('pre' === env) return 'tns.feishu-pre.cn';
16
- return 'tns.feishu.cn';
17
- };
18
- const openDoubaoReport = ()=>{
19
- const params = JSON.stringify({
20
- scene: 'miaoda_app_report',
21
- entity_id: getAppId() ?? '',
22
- entity_type: 'miaoda_app',
23
- extra: ''
24
- });
25
- const domain = reportDomainByEnv(process.env.FORCE_FRAMEWORK_ENVIRONMENT);
26
- const url = `https://${domain}/cust/lark_report/?type=common&params=${encodeURIComponent(params)}&lang=zh-CN`;
27
- window.open(url, '_blank', 'noopener,noreferrer');
28
- };
29
- const FeedbackIcon = ()=>/*#__PURE__*/ jsxs("svg", {
30
- width: 16,
31
- height: 16,
32
- viewBox: "0 0 16 16",
33
- fill: "none",
34
- xmlns: "http://www.w3.org/2000/svg",
35
- style: {
36
- display: 'block',
37
- flexShrink: 0
38
- },
39
- children: [
40
- /*#__PURE__*/ jsx("path", {
41
- d: "M12.6667 2H3.33333V14H6.66667V15.3334H3.33333C2.59695 15.3334 2 14.7364 2 14V2C2 1.26364 2.59695 0.666687 3.33333 0.666687H12.6667C13.403 0.666687 14 1.26364 14 2V7.33335H12.6667V2Z",
42
- fill: "currentColor"
43
- }),
44
- /*#__PURE__*/ jsx("path", {
45
- d: "M5.33333 4.66669C4.96514 4.66669 4.66667 4.96516 4.66667 5.33335C4.66667 5.70154 4.96514 6 5.33333 6H10.6667C11.0349 6 11.3333 5.70154 11.3333 5.33335C11.3333 4.96516 11.0349 4.66669 10.6667 4.66669H5.33333Z",
46
- fill: "currentColor"
47
- }),
48
- /*#__PURE__*/ jsx("path", {
49
- d: "M4.66667 8.33335C4.66667 7.96516 4.96514 7.66669 5.33333 7.66669H8C8.36819 7.66669 8.66667 7.96516 8.66667 8.33335C8.66667 8.70154 8.36819 9 8 9H5.33333C4.96514 9 4.66667 8.70154 4.66667 8.33335Z",
50
- fill: "currentColor"
51
- }),
52
- /*#__PURE__*/ jsx("path", {
53
- d: "M13.5523 8.70017C13.9428 8.30964 14.576 8.30964 14.9665 8.70017C15.357 9.09069 15.357 9.72386 14.9665 10.1144L14.2594 10.8215L12.8452 9.40728L13.5523 8.70017Z",
54
- fill: "currentColor"
55
- }),
56
- /*#__PURE__*/ jsx("path", {
57
- d: "M12.1381 10.1144L13.5523 11.5286L10.2309 14.85C10.0916 14.9892 9.91229 15.0815 9.71798 15.1138L8.45737 15.3234C8.42136 15.3294 8.38467 15.3177 8.35885 15.2918C8.33271 15.2657 8.32099 15.2284 8.32749 15.192L8.55128 13.9374C8.58465 13.7502 8.6746 13.5779 8.80901 13.4435L12.1381 10.1144Z",
58
- fill: "currentColor"
59
- })
60
- ]
61
- });
62
- const CloseIcon = ({ size })=>/*#__PURE__*/ jsx("svg", {
63
- width: size,
64
- height: size,
65
- viewBox: "0 0 10 10",
66
- fill: "none",
67
- xmlns: "http://www.w3.org/2000/svg",
68
- style: {
69
- display: 'block',
70
- flexShrink: 0
71
- },
72
- children: /*#__PURE__*/ jsx("path", {
73
- d: "M8.08312 8.08298C8.2384 7.92769 8.23953 7.67705 8.08424 7.52176L5.3625 4.80002L8.08422 2.07829C8.23951 1.92301 8.23839 1.67236 8.0831 1.51708C7.92781 1.36179 7.67717 1.36066 7.52188 1.51595C7.36659 1.67124 4.80016 4.23767 4.80016 4.23767L2.07841 1.51593C1.92313 1.36064 1.67248 1.36177 1.5172 1.51705C1.36191 1.67234 1.36078 1.92298 1.51607 2.07827L4.23782 4.80002L1.51605 7.52178C1.36076 7.67707 1.36189 7.92771 1.51718 8.083C1.67246 8.23829 1.92311 8.23941 2.07839 8.08412L4.80016 5.36236L7.5219 8.0841C7.67719 8.23939 7.92783 8.23826 8.08312 8.08298Z",
74
- fill: "currentColor"
75
- })
76
- });
77
- const Avatar = ({ size })=>/*#__PURE__*/ jsx("img", {
78
- src: DOUBAO_LOGO_SRC,
79
- width: size,
80
- height: size,
81
- alt: "",
82
- style: {
83
- width: size,
84
- height: size,
85
- maxWidth: 'none',
86
- maxHeight: 'none',
87
- borderRadius: 100,
88
- flexShrink: 0,
89
- display: 'block'
90
- }
91
- });
92
- const Pill = ({ variant, onClose, showClose = false })=>{
93
- const isDesktop = 'desktop' === variant;
94
- return /*#__PURE__*/ jsxs("div", {
95
- style: {
96
- boxSizing: 'border-box',
97
- display: 'inline-flex',
98
- alignItems: 'center',
99
- gap: isDesktop ? 4 : 6,
100
- padding: isDesktop ? '6px 12px 6px 8px' : '4px 12px 4px 8px',
101
- background: '#363636',
102
- border: '0.5px solid rgba(255,255,255,0.1)',
103
- borderRadius: 100,
104
- boxShadow: PILL_SHADOW,
105
- cursor: 'pointer'
106
- },
107
- children: [
108
- /*#__PURE__*/ jsx(Avatar, {
109
- size: isDesktop ? 16 : 18
110
- }),
111
- /*#__PURE__*/ jsx("span", {
112
- style: {
113
- fontFamily: FONT_FAMILY,
114
- fontSize: isDesktop ? 12 : 14,
115
- lineHeight: isDesktop ? '18px' : '22px',
116
- color: 'rgba(255,255,255,0.85)',
117
- whiteSpace: 'nowrap'
118
- },
119
- children: BRAND_TEXT
120
- }),
121
- isDesktop && onClose && showClose && /*#__PURE__*/ jsx("span", {
122
- role: "button",
123
- "aria-label": "关闭",
124
- onClick: (e)=>{
125
- e.stopPropagation();
126
- onClose();
127
- },
128
- style: {
129
- display: 'inline-flex',
130
- alignItems: 'center',
131
- cursor: 'pointer',
132
- flexShrink: 0,
133
- color: 'rgba(255,255,255,0.5)'
134
- },
135
- children: /*#__PURE__*/ jsx(CloseIcon, {
136
- size: 10
137
- })
138
- })
139
- ]
140
- });
141
- };
142
- const MenuRow = ({ icon, label, fontSize, onClick })=>{
143
- const [hover, setHover] = useState(false);
144
- return /*#__PURE__*/ jsxs("div", {
145
- role: "menuitem",
146
- "data-label": label,
147
- onClick: onClick,
148
- onMouseEnter: ()=>setHover(true),
149
- onMouseLeave: ()=>setHover(false),
150
- style: {
151
- boxSizing: 'border-box',
152
- display: 'flex',
153
- alignItems: 'center',
154
- gap: 8,
155
- padding: '4px 10px',
156
- borderRadius: 6,
157
- cursor: 'pointer',
158
- color: '#000000',
159
- background: hover ? 'rgba(0,0,0,0.04)' : 'transparent'
160
- },
161
- children: [
162
- /*#__PURE__*/ jsx("span", {
163
- style: {
164
- display: 'inline-flex',
165
- width: 16,
166
- height: 16,
167
- flexShrink: 0,
168
- color: '#000000'
169
- },
170
- children: 'feedback' === icon ? /*#__PURE__*/ jsx(FeedbackIcon, {}) : /*#__PURE__*/ jsx(CloseIcon, {
171
- size: 16
172
- })
173
- }),
174
- /*#__PURE__*/ jsx("span", {
175
- style: {
176
- fontFamily: FONT_FAMILY,
177
- fontSize,
178
- lineHeight: `${fontSize + 8}px`,
179
- color: '#000000',
180
- whiteSpace: 'nowrap'
181
- },
182
- children: label
183
- })
184
- ]
185
- });
186
- };
187
- const MenuCard = ({ items, fontSize, footerFontSize, minWidth, style })=>/*#__PURE__*/ jsxs("div", {
188
- style: {
189
- boxSizing: 'border-box',
190
- display: 'flex',
191
- flexDirection: 'column',
192
- minWidth,
193
- background: '#ffffff',
194
- border: '0.5px solid rgba(0,0,0,0.1)',
195
- borderRadius: 10,
196
- boxShadow: CARD_SHADOW,
197
- overflow: 'hidden',
198
- outline: 'none',
199
- ...style
200
- },
201
- children: [
202
- items.length > 0 && /*#__PURE__*/ jsx("div", {
203
- style: {
204
- boxSizing: 'border-box',
205
- padding: 4
206
- },
207
- children: items.map((it)=>/*#__PURE__*/ jsx(MenuRow, {
208
- icon: it.icon,
209
- label: it.label,
210
- fontSize: fontSize,
211
- onClick: it.onClick
212
- }, it.label))
213
- }),
214
- /*#__PURE__*/ jsx("div", {
215
- style: {
216
- boxSizing: 'border-box',
217
- background: '#f9f9f9',
218
- padding: '8px 14px',
219
- fontFamily: FONT_FAMILY,
220
- fontSize: footerFontSize,
221
- lineHeight: `${footerFontSize + 8}px`,
222
- color: 'rgba(0,0,0,0.35)',
223
- whiteSpace: 'nowrap'
224
- },
225
- children: POWERED_BY_TEXT
226
- })
227
- ]
228
- });
229
- const MobileWatermark = ({ onDontShow })=>{
230
- const [open, setOpen] = useState(false);
231
- const items = [];
232
- if (REPORT_ENABLED) items.push({
233
- icon: 'feedback',
234
- label: '投诉与举报',
235
- onClick: openDoubaoReport
236
- });
237
- items.push({
238
- icon: 'close',
239
- label: '不再展示',
240
- onClick: ()=>{
241
- setOpen(false);
242
- onDontShow();
243
- }
244
- });
245
- return /*#__PURE__*/ jsxs(Root, {
246
- open: open,
247
- onOpenChange: setOpen,
248
- children: [
249
- /*#__PURE__*/ jsx(Trigger, {
250
- asChild: true,
251
- children: /*#__PURE__*/ jsx("div", {
252
- "data-custom-element": "doubao-watermark-mobile",
253
- style: {
254
- position: 'fixed',
255
- right: 12,
256
- bottom: 12,
257
- width: 122,
258
- height: 30,
259
- zIndex: 9999,
260
- cursor: 'pointer'
261
- },
262
- children: /*#__PURE__*/ jsx("img", {
263
- src: DOUBAO_PILL_MOBILE_PNG,
264
- width: 166,
265
- height: 94,
266
- alt: "豆包 AI 生成",
267
- style: {
268
- position: 'absolute',
269
- right: -12,
270
- bottom: -40,
271
- width: 166,
272
- height: 94,
273
- maxWidth: 'none',
274
- maxHeight: 'none',
275
- display: 'block',
276
- pointerEvents: 'none'
277
- }
278
- })
279
- })
280
- }),
281
- /*#__PURE__*/ jsx(Portal, {
282
- children: /*#__PURE__*/ jsx(Content, {
283
- side: "top",
284
- align: "end",
285
- sideOffset: 8,
286
- style: {
287
- outline: 'none'
288
- },
289
- children: /*#__PURE__*/ jsx(MenuCard, {
290
- items: items,
291
- fontSize: 16,
292
- footerFontSize: 14,
293
- minWidth: 160
294
- })
295
- })
296
- })
297
- ]
298
- });
299
- };
300
- const Watermark_Watermark = ()=>{
301
- const [closed, setClosed] = useState(false);
302
- const [open, setOpen] = useState(false);
303
- const timer = useRef(null);
304
- const isMobile = useIsMobile();
305
- if (closed) return null;
306
- if (isMobile) return /*#__PURE__*/ jsx(MobileWatermark, {
307
- onDontShow: ()=>setClosed(true)
308
- });
309
- const enter = ()=>{
310
- if (timer.current) clearTimeout(timer.current);
311
- setOpen(true);
312
- };
313
- const leave = ()=>{
314
- timer.current = setTimeout(()=>setOpen(false), 100);
315
- };
316
- const desktopItems = [];
317
- if (REPORT_ENABLED) desktopItems.push({
318
- icon: 'feedback',
319
- label: '投诉与举报',
320
- onClick: openDoubaoReport
321
- });
322
- return /*#__PURE__*/ jsxs("div", {
323
- "data-custom-element": "doubao-watermark",
324
- onMouseEnter: enter,
325
- onMouseLeave: leave,
326
- style: {
327
- position: 'fixed',
328
- right: 12,
329
- bottom: 12,
330
- zIndex: 9999,
331
- display: 'flex',
332
- flexDirection: 'column',
333
- alignItems: 'flex-end',
334
- gap: 8
335
- },
336
- children: [
337
- open && /*#__PURE__*/ jsx(MenuCard, {
338
- items: desktopItems,
339
- fontSize: 14,
340
- footerFontSize: 12,
341
- minWidth: 144
342
- }),
343
- /*#__PURE__*/ jsx(Pill, {
344
- variant: "desktop",
345
- onClose: ()=>setClosed(true),
346
- showClose: open
347
- })
348
- ]
349
- });
350
- };
351
- const Watermark = Watermark_Watermark;
352
- export { Avatar, CloseIcon, FeedbackIcon, MenuCard, MenuRow, Pill, Watermark as default, openDoubaoReport };
@@ -1,113 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from "vitest";
2
- import react from "react";
3
- import { renderToStaticMarkup } from "react-dom/server";
4
- import Watermark, { Avatar, CloseIcon, FeedbackIcon, MenuCard, Pill, openDoubaoReport } from "../Watermark.js";
5
- import { DOUBAO_LOGO_SRC, DOUBAO_PILL_MOBILE_PNG } from "../doubao-watermark-asset.js";
6
- const html = ()=>renderToStaticMarkup(/*#__PURE__*/ react.createElement(Watermark));
7
- const setViewportWidth = (w)=>Object.defineProperty(window, 'innerWidth', {
8
- value: w,
9
- configurable: true
10
- });
11
- afterEach(()=>{
12
- vi.unstubAllEnvs();
13
- vi.restoreAllMocks();
14
- setViewportWidth(1024);
15
- });
16
- describe('Watermark(豆包来源水印)', ()=>{
17
- it('豆包来源(MIAODA_APP_SOURCE=5)时展示收起态药丸(头像 + 豆包 AI 生成)', ()=>{
18
- vi.stubEnv('MIAODA_APP_SOURCE', '5');
19
- const out = html();
20
- expect(out).toContain('data-custom-element="doubao-watermark"');
21
- expect(out).toContain('豆包 AI 生成');
22
- expect(out).toContain(DOUBAO_LOGO_SRC);
23
- expect(out).not.toContain('aria-label="关闭"');
24
- expect(out).not.toContain('投诉与举报');
25
- expect(out).not.toContain('由飞书妙搭提供支持');
26
- });
27
- it('移动端视口(<768)时展示移动端药丸(doubao-watermark-mobile)而非桌面药丸', ()=>{
28
- vi.stubEnv('MIAODA_APP_SOURCE', '5');
29
- setViewportWidth(375);
30
- const out = html();
31
- expect(out).toContain('data-custom-element="doubao-watermark-mobile"');
32
- expect(out).toContain(DOUBAO_PILL_MOBILE_PNG);
33
- expect(out).toContain('豆包 AI 生成');
34
- expect(out).not.toContain('aria-label="关闭"');
35
- });
36
- it('非豆包来源(其它 enum)时不渲染', ()=>{
37
- vi.stubEnv('MIAODA_APP_SOURCE', '3');
38
- expect(html()).toBe('');
39
- });
40
- it('来源未注入时不渲染(String(undefined) !== "5")', ()=>{
41
- expect(html()).toBe('');
42
- });
43
- describe('展开态展示组件(SSR 覆盖)', ()=>{
44
- it('Pill 桌面态:头像 + 豆包 AI 生成;关闭叉仅 showClose 时显示', ()=>{
45
- const out = renderToStaticMarkup(/*#__PURE__*/ react.createElement(Pill, {
46
- variant: 'desktop',
47
- onClose: ()=>{}
48
- }));
49
- expect(out).toContain('豆包 AI 生成');
50
- expect(out).toContain(DOUBAO_LOGO_SRC);
51
- expect(out).not.toContain('aria-label="关闭"');
52
- const hovered = renderToStaticMarkup(/*#__PURE__*/ react.createElement(Pill, {
53
- variant: 'desktop',
54
- onClose: ()=>{},
55
- showClose: true
56
- }));
57
- expect(hovered).toContain('aria-label="关闭"');
58
- });
59
- it('Pill 移动态:头像 + 豆包 AI 生成,无关闭叉', ()=>{
60
- const out = renderToStaticMarkup(/*#__PURE__*/ react.createElement(Pill, {
61
- variant: 'mobile'
62
- }));
63
- expect(out).toContain('豆包 AI 生成');
64
- expect(out).not.toContain('aria-label="关闭"');
65
- });
66
- it('MenuCard:投诉与举报 + 不再展示 行 + 由飞书妙搭提供支持 页脚', ()=>{
67
- const out = renderToStaticMarkup(/*#__PURE__*/ react.createElement(MenuCard, {
68
- items: [
69
- {
70
- icon: 'feedback',
71
- label: '投诉与举报',
72
- onClick: ()=>{}
73
- },
74
- {
75
- icon: 'close',
76
- label: '不再展示',
77
- onClick: ()=>{}
78
- }
79
- ],
80
- fontSize: 16,
81
- footerFontSize: 14,
82
- minWidth: 160
83
- }));
84
- expect(out).toContain('data-label="投诉与举报"');
85
- expect(out).toContain('data-label="不再展示"');
86
- expect(out).toContain('由飞书妙搭提供支持');
87
- });
88
- it('图标:FeedbackIcon / CloseIcon / Avatar 渲染', ()=>{
89
- expect(renderToStaticMarkup(/*#__PURE__*/ react.createElement(FeedbackIcon))).toContain('<svg');
90
- expect(renderToStaticMarkup(/*#__PURE__*/ react.createElement(CloseIcon, {
91
- size: 16
92
- }))).toContain('<svg');
93
- expect(renderToStaticMarkup(/*#__PURE__*/ react.createElement(Avatar, {
94
- size: 18
95
- }))).toContain(DOUBAO_LOGO_SRC);
96
- });
97
- });
98
- describe('openDoubaoReport(举报跳转,后端就绪后启用)', ()=>{
99
- it('按当前环境拼出飞书风控举报中心(tns) URL 并以新窗口打开', ()=>{
100
- const openSpy = vi.spyOn(window, 'open').mockReturnValue(null);
101
- window.appId = 'app_abc';
102
- vi.stubEnv('FORCE_FRAMEWORK_ENVIRONMENT', 'boe');
103
- openDoubaoReport();
104
- expect(openSpy).toHaveBeenCalledTimes(1);
105
- const [url, target, features] = openSpy.mock.calls[0];
106
- expect(String(url)).toContain('tns.feishu-boe.cn/cust/lark_report/');
107
- expect(String(url)).toContain(encodeURIComponent('miaoda_app_report'));
108
- expect(String(url)).toContain('app_abc');
109
- expect(target).toBe('_blank');
110
- expect(features).toBe('noopener,noreferrer');
111
- });
112
- });
113
- });
@@ -1,2 +0,0 @@
1
- export declare const DOUBAO_LOGO_SRC = "https://lf3-static.bytednsdoc.com/obj/eden-cn/LMfspH/ljhwZthlaukjlkulzlp/miaoda-ui/setting_voice.svg";
2
- export declare const DOUBAO_PILL_MOBILE_PNG = "https://lf3-static.bytednsdoc.com/obj/eden-cn/LMfspH/ljhwZthlaukjlkulzlp/miaoda-ui/Frame%202147223991%20(3).png";
@@ -1,4 +0,0 @@
1
- const CDN = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/LMfspH/ljhwZthlaukjlkulzlp/miaoda-ui';
2
- const DOUBAO_LOGO_SRC = `${CDN}/setting_voice.svg`;
3
- const DOUBAO_PILL_MOBILE_PNG = `${CDN}/Frame%202147223991%20(3).png`;
4
- export { DOUBAO_LOGO_SRC, DOUBAO_PILL_MOBILE_PNG };