Commit 951fb26d by luoqi

feat(web): 宿主槽位跳转一律新开标签页,不再顶掉当前页

业务约定:客户档案 / 病历 / 预约等宿主页面「打开新页,不在当前页面做跳转」。

原来四处槽位跳转全用 `_top` —— PAC 嵌在宿主 iframe 里时会把整个宿主页顶掉,
客服看完档案回不到刚才的工单(召回上下文、已填的通话结果全丢)。

四处统一收口到 action-url.ts 的两个出口,免得以后再各写各的 target:
  a 标签  → HOST_LINK_PROPS (target=_blank + rel=noopener noreferrer)
  JS 派发 → openHostUrl()

覆盖:VIEW_PATIENT(原始档案)、VIEW_MEDICAL_RECORD(原始病历)、
CREATE_APPOINTMENT(新建预约)、OPEN_POTENTIAL_TREATMENT / OPEN_RETURN_VISIT
的 URL 模式(postMessage 模式不涉及跳转,不动)。

本地实测:两个 a 标签渲染成 target="_blank" rel="noopener noreferrer";
新建预约走 openHostUrl 打开宿主页。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
parent b5fdb1b9
...@@ -28,7 +28,7 @@ import { IdentityCluster } from '@/components/identity-cluster'; ...@@ -28,7 +28,7 @@ import { IdentityCluster } from '@/components/identity-cluster';
import { isEmbedded } from '@/lib/embed'; import { isEmbedded } from '@/lib/embed';
import { hostActionMode, openHostAction } from '@/lib/host-message'; import { hostActionMode, openHostAction } from '@/lib/host-message';
import { CloseOpportunityDialog } from './close-opportunity-dialog'; import { CloseOpportunityDialog } from './close-opportunity-dialog';
import { resolveActionUrl } from '@/lib/action-url'; import { resolveActionUrl, openHostUrl, HOST_LINK_PROPS } from '@/lib/action-url';
import { import {
cn, cn,
formatGender, formatGender,
...@@ -433,8 +433,8 @@ export function PlanDetailApp({ ...@@ -433,8 +433,8 @@ export function PlanDetailApp({
showToast('amber', '未配置新建预约', '请在宿主管理页配置 actionUrls.CREATE_APPOINTMENT'); showToast('amber', '未配置新建预约', '请在宿主管理页配置 actionUrls.CREATE_APPOINTMENT');
return; return;
} }
window.open(url, '_top'); openHostUrl(url);
showToast('emerald', '已打开宿主预约页', '带上患者 id'); showToast('emerald', '已在新标签页打开宿主预约页', '带上患者 id');
}; };
// ── 宠物引导 1:话术已生成 + 在本患者页停留 15s → 发话引导给话术打「是否好用」评价 ── // ── 宠物引导 1:话术已生成 + 在本患者页停留 15s → 发话引导给话术打「是否好用」评价 ──
...@@ -1339,8 +1339,7 @@ function IdentityCard({ ...@@ -1339,8 +1339,7 @@ function IdentityCard({
{originalArchiveUrl ? ( {originalArchiveUrl ? (
<a <a
href={originalArchiveUrl} href={originalArchiveUrl}
target="_top" {...HOST_LINK_PROPS}
rel="noopener"
className="text-[10.5px] text-teal-700 hover:underline" className="text-[10.5px] text-teal-700 hover:underline"
> >
原始档案 → 原始档案 →
...@@ -1909,8 +1908,7 @@ function TreatmentHistoryCard({ ...@@ -1909,8 +1908,7 @@ function TreatmentHistoryCard({
// 宿主电子病历原文(占位插槽)—— 替换原「详情」时间轴抽屉 // 宿主电子病历原文(占位插槽)—— 替换原「详情」时间轴抽屉
<a <a
href={emrUrl} href={emrUrl}
target="_top" {...HOST_LINK_PROPS}
rel="noopener"
className="text-[10.5px] text-teal-700 hover:underline" className="text-[10.5px] text-teal-700 hover:underline"
> >
原始病历 → 原始病历 →
......
...@@ -37,3 +37,19 @@ export function resolveActionUrl( ...@@ -37,3 +37,19 @@ export function resolveActionUrl(
const tpl = actionTemplate(key); const tpl = actionTemplate(key);
return tpl ? fillActionUrl(tpl, ctx) : undefined; return tpl ? fillActionUrl(tpl, ctx) : undefined;
} }
/**
* ⭐ 宿主槽位跳转一律**新开标签页** —— 所有槽位统一,别再各写各的 target。
*
* 2026-07-29 业务约定:客户档案 / 病历 / 预约等宿主页面「打开新页,不在当前页面做跳转」。
* 原来用的是 `_top`:PAC 嵌在宿主 iframe 里时会把**整个宿主页顶掉**,客服看完档案回不到
* 刚才的工单(召回上下文、已填的通话结果全丢)。新标签页则两边都在。
*
* 用 a 标签的地方用 HOST_LINK_PROPS,用 JS 派发的地方用 openHostUrl(),两条路同一口径。
* `noopener`:新页拿不到 window.opener,防宿主页被反向导航(tabnabbing)。
*/
export const HOST_LINK_PROPS = { target: '_blank', rel: 'noopener noreferrer' } as const;
export function openHostUrl(url: string): void {
window.open(url, '_blank', 'noopener,noreferrer');
}
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import { toast } from 'sonner'; import { toast } from 'sonner';
import { useAuthStore } from '@/stores/auth-store'; import { useAuthStore } from '@/stores/auth-store';
import { fillActionUrl } from '@/lib/action-url'; import { fillActionUrl, openHostUrl } from '@/lib/action-url';
/** /**
* 宿主动作派发(潜在治疗 / 回访)—— 模式由 actionUrls[key] 的值形态决定: * 宿主动作派发(潜在治疗 / 回访)—— 模式由 actionUrls[key] 的值形态决定:
...@@ -63,7 +63,7 @@ export function openHostAction( ...@@ -63,7 +63,7 @@ export function openHostAction(
return false; return false;
} }
if (mode === 'url') { if (mode === 'url') {
window.open(fillActionUrl(raw, ctx), '_top'); openHostUrl(fillActionUrl(raw, ctx));
return true; return true;
} }
// 信封:source + type + action + payload(契约约定;无 version / doctorId / meta) // 信封:source + type + action + payload(契约约定;无 version / doctorId / meta)
......
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