@lark-apaas/client-toolkit 1.2.54-alpha.4 → 1.2.54-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/apis/components/UniversalLink.d.ts +3 -0
- package/lib/apis/components/UniversalLink.js +42 -3
- package/lib/auth.d.ts +2 -0
- package/lib/auth.js +2 -2
- package/lib/components/AppContainer/Watermark.d.ts +60 -0
- package/lib/components/AppContainer/Watermark.js +364 -0
- package/lib/components/AppContainer/__test__/Watermark.test.d.ts +1 -0
- package/lib/components/AppContainer/__test__/Watermark.test.js +121 -0
- package/lib/components/AppContainer/__test__/safety-error-boundary.test.d.ts +1 -0
- package/lib/components/AppContainer/__test__/safety-error-boundary.test.js +53 -0
- package/lib/components/AppContainer/doubao-watermark-asset.d.ts +2 -0
- package/lib/components/AppContainer/doubao-watermark-asset.js +4 -0
- package/lib/components/AppContainer/index.js +7 -3
- package/lib/components/AppContainer/safety-error-boundary.d.ts +23 -0
- package/lib/components/AppContainer/safety-error-boundary.js +29 -0
- package/lib/components/AppContainer/safety.js +50 -0
- package/lib/components/User/UserSelect.js +3 -5
- package/lib/hooks/useCurrentUserProfile.d.ts +5 -15
- package/lib/hooks/useCurrentUserProfile.js +17 -12
- package/lib/hooks/useLogout.js +2 -3
- package/lib/index.d.ts +40 -0
- package/lib/index.js +32 -2
- package/lib/integrations/__test__/dataloom.test.d.ts +1 -0
- package/lib/integrations/__test__/dataloom.test.js +83 -0
- package/lib/integrations/dataloom.d.ts +1 -1
- package/lib/integrations/dataloom.js +11 -12
- package/lib/integrations/getCurrentUserProfile.d.ts +3 -4
- package/lib/integrations/services/types.d.ts +0 -34
- package/lib/locales/messages.js +4 -0
- package/lib/types/iframe-events.d.ts +12 -1
- package/lib/utils/postMessage.d.ts +1 -0
- package/lib/utils/postMessage.js +1 -1
- package/lib/utils/requestManager.js +2 -4
- package/package.json +5 -4
|
@@ -8,5 +8,8 @@ export interface UniversalLinkProps extends Omit<React.AnchorHTMLAttributes<HTML
|
|
|
8
8
|
* - 内部路由(/dashboard)→ react-router Link
|
|
9
9
|
* - Hash 锚点(#section)→ <a>
|
|
10
10
|
* - 外链(https://...)→ <a target="_blank">
|
|
11
|
+
*
|
|
12
|
+
* 预览态(iframe 内)时,通过 postMessage 通知父页面处理链接跳转,
|
|
13
|
+
* 避免浏览器安全策略限制导致链接无法正常打开。
|
|
11
14
|
*/
|
|
12
15
|
export declare const UniversalLink: React.ForwardRefExoticComponent<UniversalLinkProps & React.RefAttributes<HTMLAnchorElement>>;
|
|
@@ -1,23 +1,62 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import react from "react";
|
|
3
3
|
import { Link } from "react-router-dom";
|
|
4
|
+
import { isPreview } from "../../utils/utils.js";
|
|
5
|
+
import { getParentOriginFromParams, submitPostMessage } from "../../utils/postMessage.js";
|
|
4
6
|
function isInternalRoute(to) {
|
|
5
7
|
return !to.startsWith('#') && !to.startsWith('http://') && !to.startsWith('https://') && !to.startsWith('//');
|
|
6
8
|
}
|
|
7
9
|
function isExternalLink(to) {
|
|
8
10
|
return to.startsWith('http://') || to.startsWith('https://') || to.startsWith('//');
|
|
9
11
|
}
|
|
10
|
-
|
|
12
|
+
function isIframeUnderMainHost() {
|
|
13
|
+
if ('undefined' == typeof window) return false;
|
|
14
|
+
if (window.parent === window) return false;
|
|
15
|
+
if (getParentOriginFromParams()) return true;
|
|
16
|
+
const envOrigin = process.env.FORCE_FRAMEWORK_DOMAIN_MAIN;
|
|
17
|
+
if (!envOrigin || !document.referrer) return false;
|
|
18
|
+
try {
|
|
19
|
+
return new URL(document.referrer).origin === envOrigin;
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const UniversalLink_UniversalLink = /*#__PURE__*/ react.forwardRef(function({ to, onClick, ...props }, ref) {
|
|
25
|
+
const isExternal = isExternalLink(to);
|
|
26
|
+
const preview = (isPreview() || isIframeUnderMainHost()) && isExternal;
|
|
27
|
+
if (preview) {
|
|
28
|
+
const handlePreviewClick = (e)=>{
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
onClick?.(e);
|
|
31
|
+
submitPostMessage({
|
|
32
|
+
type: 'OpenIframeLink',
|
|
33
|
+
data: {
|
|
34
|
+
href: to,
|
|
35
|
+
external: true
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
return /*#__PURE__*/ jsx("a", {
|
|
40
|
+
href: to,
|
|
41
|
+
ref: ref,
|
|
42
|
+
...props,
|
|
43
|
+
onClick: handlePreviewClick,
|
|
44
|
+
target: props.target ?? '_blank',
|
|
45
|
+
rel: props.rel ?? 'noopener noreferrer'
|
|
46
|
+
});
|
|
47
|
+
}
|
|
11
48
|
if (isInternalRoute(to)) return /*#__PURE__*/ jsx(Link, {
|
|
12
49
|
to: to,
|
|
13
50
|
ref: ref,
|
|
14
|
-
...props
|
|
51
|
+
...props,
|
|
52
|
+
onClick: onClick
|
|
15
53
|
});
|
|
16
54
|
return /*#__PURE__*/ jsx("a", {
|
|
17
55
|
href: to,
|
|
18
56
|
ref: ref,
|
|
19
57
|
...props,
|
|
20
|
-
|
|
58
|
+
onClick: onClick,
|
|
59
|
+
...isExternal && {
|
|
21
60
|
target: props.target ?? '_blank',
|
|
22
61
|
rel: props.rel ?? 'noopener noreferrer'
|
|
23
62
|
}
|
package/lib/auth.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { AuthProvider, useAuth, Can, CanRole, useCan, AbilityContext, useUserPermissions, PermissionClient, ROLE_SUBJECT, } from '@lark-apaas/auth-sdk';
|
|
2
2
|
export type { AuthProviderProps, PermissionApiResponse, PermissionApiConfig, PermissionPointData, AuthSdkConfig, } from '@lark-apaas/auth-sdk';
|
|
3
|
+
export { authClient, AccountClient } from '@lark-apaas/auth-sdk';
|
|
4
|
+
export type { AccountClient as AuthClient, AccountServiceResponse, AccountServiceError, SearchParams, User, UserResponse, SignInRedirectionOptions, UserBaseInfo, UserInfoResponse, Avatar, } from '@lark-apaas/auth-sdk';
|
package/lib/auth.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { AbilityContext, AuthProvider, Can, CanRole, PermissionClient, ROLE_SUBJECT, useAuth, useCan, useUserPermissions } from "@lark-apaas/auth-sdk";
|
|
2
|
-
export { AbilityContext, AuthProvider, Can, CanRole, PermissionClient, ROLE_SUBJECT, useAuth, useCan, useUserPermissions };
|
|
1
|
+
import { AbilityContext, AccountClient, AuthProvider, Can, CanRole, PermissionClient, ROLE_SUBJECT, authClient, useAuth, useCan, useUserPermissions } from "@lark-apaas/auth-sdk";
|
|
2
|
+
export { AbilityContext, AccountClient, AuthProvider, Can, CanRole, PermissionClient, ROLE_SUBJECT, authClient, useAuth, useCan, useUserPermissions };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* 是否豆包来源应用。来源由 coding/canvas 构建链编译期把 MIAODA_APP_SOURCE 注入 process.env
|
|
4
|
+
* (define 整体替换 `process.env.MIAODA_APP_SOURCE` 这个 key)。但完整版 toolkit 同时被其它
|
|
5
|
+
* 全栈构建链(fullstack-vite/rspack-preset)消费——它们不注入该 key,且浏览器没有 process 全局,
|
|
6
|
+
* 此时裸读会抛 `ReferenceError: process is not defined`,把整个宿主应用白屏。故 try/catch 兜底:
|
|
7
|
+
* 未注入 → 返回 false(非豆包,回落 Safety),绝不让水印判定拖垮宿主应用。
|
|
8
|
+
*
|
|
9
|
+
* 注意:只能裸引用完整 key `process.env.MIAODA_APP_SOURCE`(define 替换的就是它);不能写
|
|
10
|
+
* `typeof process` 之类——那段不会被 define 替换,注入构建里 process 仍可能未定义,反而误判。
|
|
11
|
+
*/
|
|
12
|
+
export declare const isDoubaoSource: () => boolean;
|
|
13
|
+
/**
|
|
14
|
+
* 打开「投诉与举报」页:scene/entity_type 固定,entity_id 取当前妙搭 appId,域名按环境选,
|
|
15
|
+
* params 整体 encode。appId 为空(异常环境)时直接不跳转,避免带空 entity_id 打开举报中心
|
|
16
|
+
* (与 safety.tsx openReport 同口径)。
|
|
17
|
+
*/
|
|
18
|
+
export declare const openDoubaoReport: () => void;
|
|
19
|
+
export declare const FeedbackIcon: React.FC;
|
|
20
|
+
export declare const CloseIcon: React.FC<{
|
|
21
|
+
size: number;
|
|
22
|
+
}>;
|
|
23
|
+
export declare const Avatar: React.FC<{
|
|
24
|
+
size: number;
|
|
25
|
+
}>;
|
|
26
|
+
export declare const Pill: React.FC<{
|
|
27
|
+
variant: 'desktop' | 'mobile';
|
|
28
|
+
onClose?: () => void;
|
|
29
|
+
showClose?: boolean;
|
|
30
|
+
}>;
|
|
31
|
+
export declare const MenuRow: React.FC<{
|
|
32
|
+
icon: 'feedback' | 'close';
|
|
33
|
+
label: string;
|
|
34
|
+
fontSize: number;
|
|
35
|
+
onClick?: () => void;
|
|
36
|
+
}>;
|
|
37
|
+
type MenuItem = {
|
|
38
|
+
icon: 'feedback' | 'close';
|
|
39
|
+
label: string;
|
|
40
|
+
onClick?: () => void;
|
|
41
|
+
};
|
|
42
|
+
export declare const MenuCard: React.FC<{
|
|
43
|
+
items: MenuItem[];
|
|
44
|
+
fontSize: number;
|
|
45
|
+
footerFontSize: number;
|
|
46
|
+
minWidth: number;
|
|
47
|
+
style?: React.CSSProperties;
|
|
48
|
+
}>;
|
|
49
|
+
/**
|
|
50
|
+
* 豆包水印:仅当应用来源为豆包时,在视口右下角展示收起态药丸(头像 +「豆包AI」)。
|
|
51
|
+
*
|
|
52
|
+
* 设计稿:Figma 1582:41560(桌面)/ 1582:41777(移动)。
|
|
53
|
+
* - 药丸 / 卡片本体用代码画(投影走 CSS box-shadow、图标用无滤镜小 SVG),高分屏清晰。
|
|
54
|
+
* - 桌面端点关闭叉 → 隐藏整个水印;hover 徽标 → 上方弹白色卡片(投诉与举报 + 由飞书妙搭提供支持)。
|
|
55
|
+
* - 移动端 tap 徽标 → 上方弹卡片(投诉与举报 / 不再展示 / 由飞书妙搭提供支持)。
|
|
56
|
+
* - 「投诉与举报」点击跳飞书风控举报中心(tns,域名随环境);后端链接未就绪时由 REPORT_ENABLED 整体隐藏。
|
|
57
|
+
* - 不加 NODE_ENV 判断——预览态与运行态都要展示。
|
|
58
|
+
*/
|
|
59
|
+
declare const Watermark: React.FC;
|
|
60
|
+
export default Watermark;
|
|
@@ -0,0 +1,364 @@
|
|
|
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 { getEnv } from "../../utils/getParentOrigin.js";
|
|
7
|
+
import { useIsMobile } from "../../hooks/index.js";
|
|
8
|
+
const DOUBAO_SOURCE = '5';
|
|
9
|
+
const isDoubaoSource = ()=>{
|
|
10
|
+
try {
|
|
11
|
+
return String(process.env.MIAODA_APP_SOURCE) === DOUBAO_SOURCE;
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
const REPORT_ENABLED = true;
|
|
17
|
+
const BRAND_TEXT = '豆包 AI 生成';
|
|
18
|
+
const POWERED_BY_TEXT = '由飞书妙搭提供支持';
|
|
19
|
+
const FONT_FAMILY = "'PingFang SC', -apple-system, BlinkMacSystemFont, sans-serif";
|
|
20
|
+
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)';
|
|
21
|
+
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)';
|
|
22
|
+
const REPORT_DOMAIN = {
|
|
23
|
+
BOE: 'tns.feishu-boe.cn',
|
|
24
|
+
PRE: 'tns.feishu-pre.cn',
|
|
25
|
+
ONLINE: 'tns.feishu.cn'
|
|
26
|
+
};
|
|
27
|
+
const openDoubaoReport = ()=>{
|
|
28
|
+
const appId = getAppId();
|
|
29
|
+
if (!appId) return;
|
|
30
|
+
const params = JSON.stringify({
|
|
31
|
+
scene: 'miaoda_app_report',
|
|
32
|
+
entity_id: appId,
|
|
33
|
+
entity_type: 'miaoda_app',
|
|
34
|
+
extra: ''
|
|
35
|
+
});
|
|
36
|
+
const domain = REPORT_DOMAIN[getEnv()] ?? REPORT_DOMAIN.ONLINE;
|
|
37
|
+
const url = `https://${domain}/cust/lark_report/?type=common¶ms=${encodeURIComponent(params)}&lang=zh-CN`;
|
|
38
|
+
window.open(url, '_blank', 'noopener,noreferrer');
|
|
39
|
+
};
|
|
40
|
+
const FeedbackIcon = ()=>/*#__PURE__*/ jsxs("svg", {
|
|
41
|
+
width: 16,
|
|
42
|
+
height: 16,
|
|
43
|
+
viewBox: "0 0 16 16",
|
|
44
|
+
fill: "none",
|
|
45
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
46
|
+
style: {
|
|
47
|
+
display: 'block',
|
|
48
|
+
flexShrink: 0
|
|
49
|
+
},
|
|
50
|
+
children: [
|
|
51
|
+
/*#__PURE__*/ jsx("path", {
|
|
52
|
+
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",
|
|
53
|
+
fill: "currentColor"
|
|
54
|
+
}),
|
|
55
|
+
/*#__PURE__*/ jsx("path", {
|
|
56
|
+
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",
|
|
57
|
+
fill: "currentColor"
|
|
58
|
+
}),
|
|
59
|
+
/*#__PURE__*/ jsx("path", {
|
|
60
|
+
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",
|
|
61
|
+
fill: "currentColor"
|
|
62
|
+
}),
|
|
63
|
+
/*#__PURE__*/ jsx("path", {
|
|
64
|
+
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",
|
|
65
|
+
fill: "currentColor"
|
|
66
|
+
}),
|
|
67
|
+
/*#__PURE__*/ jsx("path", {
|
|
68
|
+
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",
|
|
69
|
+
fill: "currentColor"
|
|
70
|
+
})
|
|
71
|
+
]
|
|
72
|
+
});
|
|
73
|
+
const CloseIcon = ({ size })=>/*#__PURE__*/ jsx("svg", {
|
|
74
|
+
width: size,
|
|
75
|
+
height: size,
|
|
76
|
+
viewBox: "0 0 10 10",
|
|
77
|
+
fill: "none",
|
|
78
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
79
|
+
style: {
|
|
80
|
+
display: 'block',
|
|
81
|
+
flexShrink: 0
|
|
82
|
+
},
|
|
83
|
+
children: /*#__PURE__*/ jsx("path", {
|
|
84
|
+
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",
|
|
85
|
+
fill: "currentColor"
|
|
86
|
+
})
|
|
87
|
+
});
|
|
88
|
+
const Avatar = ({ size })=>/*#__PURE__*/ jsx("img", {
|
|
89
|
+
src: DOUBAO_LOGO_SRC,
|
|
90
|
+
width: size,
|
|
91
|
+
height: size,
|
|
92
|
+
alt: "",
|
|
93
|
+
style: {
|
|
94
|
+
width: size,
|
|
95
|
+
height: size,
|
|
96
|
+
maxWidth: 'none',
|
|
97
|
+
maxHeight: 'none',
|
|
98
|
+
borderRadius: 100,
|
|
99
|
+
flexShrink: 0,
|
|
100
|
+
display: 'block'
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
const Pill = ({ variant, onClose, showClose = false })=>{
|
|
104
|
+
const isDesktop = 'desktop' === variant;
|
|
105
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
106
|
+
style: {
|
|
107
|
+
boxSizing: 'border-box',
|
|
108
|
+
display: 'inline-flex',
|
|
109
|
+
alignItems: 'center',
|
|
110
|
+
gap: isDesktop ? 4 : 6,
|
|
111
|
+
padding: isDesktop ? '6px 12px 6px 8px' : '4px 12px 4px 8px',
|
|
112
|
+
background: '#363636',
|
|
113
|
+
border: '0.5px solid rgba(255,255,255,0.1)',
|
|
114
|
+
borderRadius: 100,
|
|
115
|
+
boxShadow: PILL_SHADOW,
|
|
116
|
+
cursor: 'pointer'
|
|
117
|
+
},
|
|
118
|
+
children: [
|
|
119
|
+
/*#__PURE__*/ jsx(Avatar, {
|
|
120
|
+
size: isDesktop ? 16 : 18
|
|
121
|
+
}),
|
|
122
|
+
/*#__PURE__*/ jsx("span", {
|
|
123
|
+
style: {
|
|
124
|
+
fontFamily: FONT_FAMILY,
|
|
125
|
+
fontSize: isDesktop ? 12 : 14,
|
|
126
|
+
lineHeight: isDesktop ? '18px' : '22px',
|
|
127
|
+
color: 'rgba(255,255,255,0.85)',
|
|
128
|
+
whiteSpace: 'nowrap'
|
|
129
|
+
},
|
|
130
|
+
children: BRAND_TEXT
|
|
131
|
+
}),
|
|
132
|
+
isDesktop && onClose && showClose && /*#__PURE__*/ jsx("span", {
|
|
133
|
+
role: "button",
|
|
134
|
+
"aria-label": "关闭",
|
|
135
|
+
onClick: (e)=>{
|
|
136
|
+
e.stopPropagation();
|
|
137
|
+
onClose();
|
|
138
|
+
},
|
|
139
|
+
style: {
|
|
140
|
+
display: 'inline-flex',
|
|
141
|
+
alignItems: 'center',
|
|
142
|
+
cursor: 'pointer',
|
|
143
|
+
flexShrink: 0,
|
|
144
|
+
color: 'rgba(255,255,255,0.5)'
|
|
145
|
+
},
|
|
146
|
+
children: /*#__PURE__*/ jsx(CloseIcon, {
|
|
147
|
+
size: 10
|
|
148
|
+
})
|
|
149
|
+
})
|
|
150
|
+
]
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
const MenuRow = ({ icon, label, fontSize, onClick })=>{
|
|
154
|
+
const [hover, setHover] = useState(false);
|
|
155
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
156
|
+
role: "menuitem",
|
|
157
|
+
"data-label": label,
|
|
158
|
+
onClick: onClick,
|
|
159
|
+
onMouseEnter: ()=>setHover(true),
|
|
160
|
+
onMouseLeave: ()=>setHover(false),
|
|
161
|
+
style: {
|
|
162
|
+
boxSizing: 'border-box',
|
|
163
|
+
display: 'flex',
|
|
164
|
+
alignItems: 'center',
|
|
165
|
+
gap: 8,
|
|
166
|
+
padding: '4px 10px',
|
|
167
|
+
borderRadius: 6,
|
|
168
|
+
cursor: 'pointer',
|
|
169
|
+
color: '#000000',
|
|
170
|
+
background: hover ? 'rgba(0,0,0,0.04)' : 'transparent'
|
|
171
|
+
},
|
|
172
|
+
children: [
|
|
173
|
+
/*#__PURE__*/ jsx("span", {
|
|
174
|
+
style: {
|
|
175
|
+
display: 'inline-flex',
|
|
176
|
+
width: 16,
|
|
177
|
+
height: 16,
|
|
178
|
+
flexShrink: 0,
|
|
179
|
+
color: '#000000'
|
|
180
|
+
},
|
|
181
|
+
children: 'feedback' === icon ? /*#__PURE__*/ jsx(FeedbackIcon, {}) : /*#__PURE__*/ jsx(CloseIcon, {
|
|
182
|
+
size: 16
|
|
183
|
+
})
|
|
184
|
+
}),
|
|
185
|
+
/*#__PURE__*/ jsx("span", {
|
|
186
|
+
style: {
|
|
187
|
+
fontFamily: FONT_FAMILY,
|
|
188
|
+
fontSize,
|
|
189
|
+
lineHeight: `${fontSize + 8}px`,
|
|
190
|
+
color: '#000000',
|
|
191
|
+
whiteSpace: 'nowrap'
|
|
192
|
+
},
|
|
193
|
+
children: label
|
|
194
|
+
})
|
|
195
|
+
]
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
const MenuCard = ({ items, fontSize, footerFontSize, minWidth, style })=>/*#__PURE__*/ jsxs("div", {
|
|
199
|
+
style: {
|
|
200
|
+
boxSizing: 'border-box',
|
|
201
|
+
display: 'flex',
|
|
202
|
+
flexDirection: 'column',
|
|
203
|
+
minWidth,
|
|
204
|
+
background: '#ffffff',
|
|
205
|
+
border: '0.5px solid rgba(0,0,0,0.1)',
|
|
206
|
+
borderRadius: 10,
|
|
207
|
+
boxShadow: CARD_SHADOW,
|
|
208
|
+
overflow: 'hidden',
|
|
209
|
+
outline: 'none',
|
|
210
|
+
...style
|
|
211
|
+
},
|
|
212
|
+
children: [
|
|
213
|
+
items.length > 0 && /*#__PURE__*/ jsx("div", {
|
|
214
|
+
style: {
|
|
215
|
+
boxSizing: 'border-box',
|
|
216
|
+
padding: 4
|
|
217
|
+
},
|
|
218
|
+
children: items.map((it)=>/*#__PURE__*/ jsx(MenuRow, {
|
|
219
|
+
icon: it.icon,
|
|
220
|
+
label: it.label,
|
|
221
|
+
fontSize: fontSize,
|
|
222
|
+
onClick: it.onClick
|
|
223
|
+
}, it.label))
|
|
224
|
+
}),
|
|
225
|
+
/*#__PURE__*/ jsx("div", {
|
|
226
|
+
style: {
|
|
227
|
+
boxSizing: 'border-box',
|
|
228
|
+
background: '#f9f9f9',
|
|
229
|
+
padding: '8px 14px',
|
|
230
|
+
fontFamily: FONT_FAMILY,
|
|
231
|
+
fontSize: footerFontSize,
|
|
232
|
+
lineHeight: `${footerFontSize + 8}px`,
|
|
233
|
+
color: 'rgba(0,0,0,0.35)',
|
|
234
|
+
whiteSpace: 'nowrap'
|
|
235
|
+
},
|
|
236
|
+
children: POWERED_BY_TEXT
|
|
237
|
+
})
|
|
238
|
+
]
|
|
239
|
+
});
|
|
240
|
+
const MobileWatermark = ({ onDontShow })=>{
|
|
241
|
+
const [open, setOpen] = useState(false);
|
|
242
|
+
const items = [];
|
|
243
|
+
if (REPORT_ENABLED) items.push({
|
|
244
|
+
icon: 'feedback',
|
|
245
|
+
label: '投诉与举报',
|
|
246
|
+
onClick: openDoubaoReport
|
|
247
|
+
});
|
|
248
|
+
items.push({
|
|
249
|
+
icon: 'close',
|
|
250
|
+
label: '不再展示',
|
|
251
|
+
onClick: ()=>{
|
|
252
|
+
setOpen(false);
|
|
253
|
+
onDontShow();
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
return /*#__PURE__*/ jsxs(Root, {
|
|
257
|
+
open: open,
|
|
258
|
+
onOpenChange: setOpen,
|
|
259
|
+
children: [
|
|
260
|
+
/*#__PURE__*/ jsx(Trigger, {
|
|
261
|
+
asChild: true,
|
|
262
|
+
children: /*#__PURE__*/ jsx("div", {
|
|
263
|
+
"data-custom-element": "doubao-watermark-mobile",
|
|
264
|
+
style: {
|
|
265
|
+
position: 'fixed',
|
|
266
|
+
right: 12,
|
|
267
|
+
bottom: 12,
|
|
268
|
+
width: 122,
|
|
269
|
+
height: 30,
|
|
270
|
+
zIndex: 9999,
|
|
271
|
+
cursor: 'pointer'
|
|
272
|
+
},
|
|
273
|
+
children: /*#__PURE__*/ jsx("img", {
|
|
274
|
+
src: DOUBAO_PILL_MOBILE_PNG,
|
|
275
|
+
width: 166,
|
|
276
|
+
height: 94,
|
|
277
|
+
alt: "豆包 AI 生成",
|
|
278
|
+
style: {
|
|
279
|
+
position: 'absolute',
|
|
280
|
+
right: -12,
|
|
281
|
+
bottom: -40,
|
|
282
|
+
width: 166,
|
|
283
|
+
height: 94,
|
|
284
|
+
maxWidth: 'none',
|
|
285
|
+
maxHeight: 'none',
|
|
286
|
+
display: 'block',
|
|
287
|
+
pointerEvents: 'none'
|
|
288
|
+
}
|
|
289
|
+
})
|
|
290
|
+
})
|
|
291
|
+
}),
|
|
292
|
+
/*#__PURE__*/ jsx(Portal, {
|
|
293
|
+
children: /*#__PURE__*/ jsx(Content, {
|
|
294
|
+
side: "top",
|
|
295
|
+
align: "end",
|
|
296
|
+
sideOffset: 8,
|
|
297
|
+
style: {
|
|
298
|
+
outline: 'none'
|
|
299
|
+
},
|
|
300
|
+
children: /*#__PURE__*/ jsx(MenuCard, {
|
|
301
|
+
items: items,
|
|
302
|
+
fontSize: 16,
|
|
303
|
+
footerFontSize: 14,
|
|
304
|
+
minWidth: 160
|
|
305
|
+
})
|
|
306
|
+
})
|
|
307
|
+
})
|
|
308
|
+
]
|
|
309
|
+
});
|
|
310
|
+
};
|
|
311
|
+
const Watermark_Watermark = ()=>{
|
|
312
|
+
const [closed, setClosed] = useState(false);
|
|
313
|
+
const [open, setOpen] = useState(false);
|
|
314
|
+
const timer = useRef(null);
|
|
315
|
+
const isMobile = useIsMobile();
|
|
316
|
+
if (!isDoubaoSource()) return null;
|
|
317
|
+
if (closed) return null;
|
|
318
|
+
if (isMobile) return /*#__PURE__*/ jsx(MobileWatermark, {
|
|
319
|
+
onDontShow: ()=>setClosed(true)
|
|
320
|
+
});
|
|
321
|
+
const enter = ()=>{
|
|
322
|
+
if (timer.current) clearTimeout(timer.current);
|
|
323
|
+
setOpen(true);
|
|
324
|
+
};
|
|
325
|
+
const leave = ()=>{
|
|
326
|
+
timer.current = setTimeout(()=>setOpen(false), 100);
|
|
327
|
+
};
|
|
328
|
+
const desktopItems = [];
|
|
329
|
+
if (REPORT_ENABLED) desktopItems.push({
|
|
330
|
+
icon: 'feedback',
|
|
331
|
+
label: '投诉与举报',
|
|
332
|
+
onClick: openDoubaoReport
|
|
333
|
+
});
|
|
334
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
335
|
+
"data-custom-element": "doubao-watermark",
|
|
336
|
+
onMouseEnter: enter,
|
|
337
|
+
onMouseLeave: leave,
|
|
338
|
+
style: {
|
|
339
|
+
position: 'fixed',
|
|
340
|
+
right: 12,
|
|
341
|
+
bottom: 12,
|
|
342
|
+
zIndex: 9999,
|
|
343
|
+
display: 'flex',
|
|
344
|
+
flexDirection: 'column',
|
|
345
|
+
alignItems: 'flex-end',
|
|
346
|
+
gap: 8
|
|
347
|
+
},
|
|
348
|
+
children: [
|
|
349
|
+
open && /*#__PURE__*/ jsx(MenuCard, {
|
|
350
|
+
items: desktopItems,
|
|
351
|
+
fontSize: 14,
|
|
352
|
+
footerFontSize: 12,
|
|
353
|
+
minWidth: 144
|
|
354
|
+
}),
|
|
355
|
+
/*#__PURE__*/ jsx(Pill, {
|
|
356
|
+
variant: "desktop",
|
|
357
|
+
onClose: ()=>setClosed(true),
|
|
358
|
+
showClose: open
|
|
359
|
+
})
|
|
360
|
+
]
|
|
361
|
+
});
|
|
362
|
+
};
|
|
363
|
+
const Watermark = Watermark_Watermark;
|
|
364
|
+
export { Avatar, CloseIcon, FeedbackIcon, MenuCard, MenuRow, Pill, Watermark as default, isDoubaoSource, openDoubaoReport };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,121 @@
|
|
|
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
|
+
delete globalThis.ENVIRONMENT;
|
|
15
|
+
delete window.appId;
|
|
16
|
+
setViewportWidth(1024);
|
|
17
|
+
});
|
|
18
|
+
describe('Watermark(豆包来源水印)', ()=>{
|
|
19
|
+
it('豆包来源(MIAODA_APP_SOURCE=5)时展示收起态药丸(头像 + 豆包 AI 生成)', ()=>{
|
|
20
|
+
vi.stubEnv('MIAODA_APP_SOURCE', '5');
|
|
21
|
+
const out = html();
|
|
22
|
+
expect(out).toContain('data-custom-element="doubao-watermark"');
|
|
23
|
+
expect(out).toContain('豆包 AI 生成');
|
|
24
|
+
expect(out).toContain(DOUBAO_LOGO_SRC);
|
|
25
|
+
expect(out).not.toContain('aria-label="关闭"');
|
|
26
|
+
expect(out).not.toContain('投诉与举报');
|
|
27
|
+
expect(out).not.toContain('由飞书妙搭提供支持');
|
|
28
|
+
});
|
|
29
|
+
it('移动端视口(<768)时展示移动端药丸(doubao-watermark-mobile)而非桌面药丸', ()=>{
|
|
30
|
+
vi.stubEnv('MIAODA_APP_SOURCE', '5');
|
|
31
|
+
setViewportWidth(375);
|
|
32
|
+
const out = html();
|
|
33
|
+
expect(out).toContain('data-custom-element="doubao-watermark-mobile"');
|
|
34
|
+
expect(out).toContain(DOUBAO_PILL_MOBILE_PNG);
|
|
35
|
+
expect(out).toContain('豆包 AI 生成');
|
|
36
|
+
expect(out).not.toContain('aria-label="关闭"');
|
|
37
|
+
});
|
|
38
|
+
it('非豆包来源(其它 enum)时不渲染', ()=>{
|
|
39
|
+
vi.stubEnv('MIAODA_APP_SOURCE', '3');
|
|
40
|
+
expect(html()).toBe('');
|
|
41
|
+
});
|
|
42
|
+
it('来源未注入时不渲染(String(undefined) !== "5")', ()=>{
|
|
43
|
+
expect(html()).toBe('');
|
|
44
|
+
});
|
|
45
|
+
describe('展开态展示组件(SSR 覆盖)', ()=>{
|
|
46
|
+
it('Pill 桌面态:头像 + 豆包 AI 生成;关闭叉仅 showClose 时显示', ()=>{
|
|
47
|
+
const out = renderToStaticMarkup(/*#__PURE__*/ react.createElement(Pill, {
|
|
48
|
+
variant: 'desktop',
|
|
49
|
+
onClose: ()=>{}
|
|
50
|
+
}));
|
|
51
|
+
expect(out).toContain('豆包 AI 生成');
|
|
52
|
+
expect(out).toContain(DOUBAO_LOGO_SRC);
|
|
53
|
+
expect(out).not.toContain('aria-label="关闭"');
|
|
54
|
+
const hovered = renderToStaticMarkup(/*#__PURE__*/ react.createElement(Pill, {
|
|
55
|
+
variant: 'desktop',
|
|
56
|
+
onClose: ()=>{},
|
|
57
|
+
showClose: true
|
|
58
|
+
}));
|
|
59
|
+
expect(hovered).toContain('aria-label="关闭"');
|
|
60
|
+
});
|
|
61
|
+
it('Pill 移动态:头像 + 豆包 AI 生成,无关闭叉', ()=>{
|
|
62
|
+
const out = renderToStaticMarkup(/*#__PURE__*/ react.createElement(Pill, {
|
|
63
|
+
variant: 'mobile'
|
|
64
|
+
}));
|
|
65
|
+
expect(out).toContain('豆包 AI 生成');
|
|
66
|
+
expect(out).not.toContain('aria-label="关闭"');
|
|
67
|
+
});
|
|
68
|
+
it('MenuCard:投诉与举报 + 不再展示 行 + 由飞书妙搭提供支持 页脚', ()=>{
|
|
69
|
+
const out = renderToStaticMarkup(/*#__PURE__*/ react.createElement(MenuCard, {
|
|
70
|
+
items: [
|
|
71
|
+
{
|
|
72
|
+
icon: 'feedback',
|
|
73
|
+
label: '投诉与举报',
|
|
74
|
+
onClick: ()=>{}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
icon: 'close',
|
|
78
|
+
label: '不再展示',
|
|
79
|
+
onClick: ()=>{}
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
fontSize: 16,
|
|
83
|
+
footerFontSize: 14,
|
|
84
|
+
minWidth: 160
|
|
85
|
+
}));
|
|
86
|
+
expect(out).toContain('data-label="投诉与举报"');
|
|
87
|
+
expect(out).toContain('data-label="不再展示"');
|
|
88
|
+
expect(out).toContain('由飞书妙搭提供支持');
|
|
89
|
+
});
|
|
90
|
+
it('图标:FeedbackIcon / CloseIcon / Avatar 渲染', ()=>{
|
|
91
|
+
expect(renderToStaticMarkup(/*#__PURE__*/ react.createElement(FeedbackIcon))).toContain('<svg');
|
|
92
|
+
expect(renderToStaticMarkup(/*#__PURE__*/ react.createElement(CloseIcon, {
|
|
93
|
+
size: 16
|
|
94
|
+
}))).toContain('<svg');
|
|
95
|
+
expect(renderToStaticMarkup(/*#__PURE__*/ react.createElement(Avatar, {
|
|
96
|
+
size: 18
|
|
97
|
+
}))).toContain(DOUBAO_LOGO_SRC);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
describe('openDoubaoReport(举报跳转,后端就绪后启用)', ()=>{
|
|
101
|
+
it('按当前环境(getEnv)拼出飞书风控举报中心(tns) URL 并以新窗口打开', ()=>{
|
|
102
|
+
const openSpy = vi.spyOn(window, 'open').mockReturnValue(null);
|
|
103
|
+
window.appId = 'app_abc';
|
|
104
|
+
globalThis.ENVIRONMENT = 'staging';
|
|
105
|
+
openDoubaoReport();
|
|
106
|
+
expect(openSpy).toHaveBeenCalledTimes(1);
|
|
107
|
+
const [url, target, features] = openSpy.mock.calls[0];
|
|
108
|
+
expect(String(url)).toContain('tns.feishu-boe.cn/cust/lark_report/');
|
|
109
|
+
expect(String(url)).toContain(encodeURIComponent('miaoda_app_report'));
|
|
110
|
+
expect(String(url)).toContain('app_abc');
|
|
111
|
+
expect(target).toBe('_blank');
|
|
112
|
+
expect(features).toBe('noopener,noreferrer');
|
|
113
|
+
});
|
|
114
|
+
it('无 appId 时不跳转(避免带空 entity_id 打开举报中心)', ()=>{
|
|
115
|
+
const openSpy = vi.spyOn(window, 'open').mockReturnValue(null);
|
|
116
|
+
globalThis.ENVIRONMENT = 'online';
|
|
117
|
+
openDoubaoReport();
|
|
118
|
+
expect(openSpy).not.toHaveBeenCalled();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|