Commit 6a5d07c6 by luoqi

merge: fix/host-embed-popup-and-copy → test(第二批)

- 新标签页改一步 open —— 修宿主加 allow-popups 后的 SecurityError(两步走断 opener 导致)
- 召回池卡片去掉优先级那排五个色点,只留分数
- postMessage 字段名简化 desc / treatments / stage;早期矫治 → 早矫
- 交付文档补 allow-popups-to-escape-sandbox 的说明与完整 sandbox 推荐串
parents b8836580 47c079ef
Pipeline #3488 failed in 0 seconds
...@@ -144,6 +144,12 @@ PAC 侧已经用 `targetOrigin` 定向发送(不广播),但那只防"发错人", ...@@ -144,6 +144,12 @@ PAC 侧已经用 `targetOrigin` 定向发送(不广播),但那只防"发错人",
- **iframe sandbox 别漏权限**。若你们给 iframe 加了 `sandbox`,至少要有 - **iframe sandbox 别漏权限**。若你们给 iframe 加了 `sandbox`,至少要有
`allow-scripts allow-same-origin`;**动作要开新标签页则必须加 `allow-popups`** `allow-scripts allow-same-origin`;**动作要开新标签页则必须加 `allow-popups`**
(否则浏览器直接拦掉,控制台报 `Blocked opening ... 'allow-popups' permission is not set`, (否则浏览器直接拦掉,控制台报 `Blocked opening ... 'allow-popups' permission is not set`,
PAC 会退化成整页跳转)。想让新页不继承沙箱再加 `allow-popups-to-escape-sandbox`。 PAC 会退化成整页跳转)。
**建议再加 `allow-popups-to-escape-sandbox`** —— 只给 `allow-popups` 时新标签页会**继承沙箱**,
你们自己的页面在里面可能功能不全(存储受限、表单提交被挡等)。推荐:
```html
sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-top-navigation"
```
- **认领前点不到**。这些按钮在 PAC 侧过「认领闸」——工单没被客服认领时点了会被拦, - **认领前点不到**。这些按钮在 PAC 侧过「认领闸」——工单没被客服认领时点了会被拦,
是有意为之(没有归属人后面对不上账),不是 bug。 是有意为之(没有归属人后面对不上账),不是 bug。
...@@ -658,23 +658,23 @@ function DoctorPicker({ ...@@ -658,23 +658,23 @@ function DoctorPicker({
); );
} }
// ── 优先级(与列表页 PriorityBar 同款:五格条 + 10 分制)──────── // ── 优先级(只留分数,10 分制)────────────────────────────────
// 业务 2026-07-30:去掉原来那排五个色点。分数本身已经带档位信息(还按档着色),
// 五个点是同一件事的第二种编码 —— 一行里两处讲优先级,反而要人对照着看。
// 档位色保留在数字上:极低/低 绿 → 中/高 琥珀 → 极高 玫红,扫一眼仍分得出轻重。
// hover 的算分明细走外层 PriorityHover,不在这里。
function PriorityBar({ score }: { score: number }) { function PriorityBar({ score }: { score: number }) {
const pct = Math.max(0, Math.min(1, score / 100)); const pct = Math.max(0, Math.min(1, score / 100));
const filled = Math.max(1, Math.round(pct * 5));
const colors = ['bg-emerald-400', 'bg-emerald-500', 'bg-amber-400', 'bg-amber-500', 'bg-rose-500'];
const labelTone = const labelTone =
pct >= 0.6 ? (pct >= 0.8 ? 'text-rose-700' : 'text-amber-700') : pct >= 0.2 ? 'text-emerald-700' : 'text-slate-500'; pct >= 0.6 ? (pct >= 0.8 ? 'text-rose-700' : 'text-amber-700') : pct >= 0.2 ? 'text-emerald-700' : 'text-slate-500';
// 同列表页:展示值 = 排序键 / 10,别再读 breakdown.raw(见 plans-list-app 注释) // 同列表页:展示值 = 排序键 / 10,别再读 breakdown.raw(见 plans-list-app 注释)
const disp = (score / 10).toFixed(2); const disp = (score / 10).toFixed(2);
return ( return (
<span className="inline-flex flex-none items-center gap-1" title={`优先级 ${disp} / 10`}> <span
<span className="inline-flex items-center gap-0.5"> className={cn('flex-none text-[10.5px] font-semibold tabular-nums', labelTone)}
{Array.from({ length: 5 }).map((_, i) => ( title={`优先级 ${disp} / 10`}
<span key={i} className={cn('h-1.5 w-[6px] rounded-sm', i < filled ? colors[i] : 'bg-slate-200')} /> >
))} {disp}
</span>
<span className={cn('text-[10.5px] font-semibold tabular-nums', labelTone)}>{disp}</span>
</span> </span>
); );
} }
......
...@@ -69,22 +69,35 @@ export const HOST_LINK_PROPS = { target: '_blank', rel: 'noopener noreferrer' } ...@@ -69,22 +69,35 @@ export const HOST_LINK_PROPS = { target: '_blank', rel: 'noopener noreferrer' }
* sandbox 若也没给 allow-top-navigation,赋值会抛 SecurityError,继续降级 * sandbox 若也没给 allow-top-navigation,赋值会抛 SecurityError,继续降级
* ③ 本 frame 内跳转 —— 一定成立,PAC 自己被替换掉(客服可用宿主的返回回来) * ③ 本 frame 内跳转 —— 一定成立,PAC 自己被替换掉(客服可用宿主的返回回来)
* *
* ⚠️ 检测被拦**不能靠 window.open 的返回值配 noopener**:features 里带 noopener 时 * ⚠️ **必须一步 open 带上 URL,不能"先开 about:blank 再导航"**(2026-07-30 踩过):
* 规范规定**成功也返回 null**,那样永远分不清是被拦还是开成功了。 * 那一版是为了"开完手工断 opener 以等效 noopener,同时保留可检测性"。宿主加上 allow-popups 之后
* 所以改成先开同源 about:blank、拿到句柄后手工断 opener 再导航 —— 效果同 noopener,但可检测。 * 立刻炸了两条:
* `Unsafe attempt to initiate navigation for frame with URL 'about:blank' … The frame
* attempting navigation is sandboxed and is trying to navigate a popup, but is not the
* popup's opener and is not set to propagate sandboxing to popups.`
* `Uncaught SecurityError: Failed to execute 'replace' on 'Location': The current window
* does not have permission to navigate the target frame to '<host url>'.`
* 因果很直白:**opener 一断,我们就不再是那个弹窗的 opener**,而沙箱化的 frame 只有作为 opener
* 才有权导航它 —— 于是第二步 `location.replace` 被拒,新标签页停在空白页。
* 顺序反过来(先导航后断 opener)也不行:导航到跨源之后再设 opener 会抛。
* *
* 根治仍在宿主侧:请对接方给 iframe 的 sandbox 加上 `allow-popups`(想保留新页则再加 * 所以结论是:**一步到位 `window.open(url, '_blank')`**。
* `allow-popups-to-escape-sandbox`,否则新页会继承沙箱限制)。这里只是让 PAC 别装死。 * · 可检测性还在 —— 被弹窗拦时返回 null(features 里**不写** noopener;写了成功也返回 null,
* 那才是分不清被拦和成功的那种写法)
* · opener 只做**尽力而为**的切断(跨源会抛,catch 掉即可)。这里能接受:目标 URL 是宿主管理页
* 配好的自家地址,不是用户输入的任意站点,tabnabbing 面本来就很窄。
*
* 根治仍在宿主侧:`sandbox` 要有 `allow-popups`;**并且建议加 `allow-popups-to-escape-sandbox`**
* —— 只给 allow-popups 时新标签页会继承沙箱,宿主自己的页面在里面可能功能不全。
*/ */
export function openHostUrl(url: string): void { export function openHostUrl(url: string): void {
const opened = window.open('', '_blank'); const opened = window.open(url, '_blank');
if (opened) { if (opened) {
try { try {
opened.opener = null; // about:blank 同源,能设;等价于 noopener,防 tabnabbing opened.opener = null; // 尽力而为:同源能断,跨源会抛(见上方注释,不影响已经开出来的页)
} catch { } catch {
/* 设不上就算了 —— 比开不出页面轻得多 */ /* 断不掉就算了 —— 目标是宿主自家配置的地址 */
} }
opened.location.replace(url);
return; return;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment