Files
2026-08-26 17:38:18 +08:00

183 lines
5.9 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Panel Content Script —— 商品页右下角悬浮按钮 + 页内悬浮面板
//
// 面板 = iframe 加载插件内置 sidepanel.html(扩展页面在 iframe 里仍有 chrome.* 权限,
// 采集/生成/导出逻辑零改动);按钮与面板容器渲染在独立 Shadow DOM 中,
// 不受商品页全局 CSS 影响,面板悬浮覆盖页面、不挤压原页面布局。
import { matchProfile } from '../src/profiles/index';
/** 面板内 App.tsx → 宿主页的收起消息 */
const PANEL_CLOSE_MSG = 'sc-panel-close';
const STYLES = `
:host { all: initial; }
.fab {
position: fixed;
right: 24px;
bottom: 24px;
width: 48px;
height: 48px;
border-radius: 50%;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font: 700 20px/1 -apple-system, 'PingFang SC', 'Microsoft YaHei', sans-serif;
color: #fff;
background: linear-gradient(135deg, #8b5cf6, #6d28d9);
box-shadow: 0 4px 16px rgba(109, 40, 217, 0.45);
z-index: 3;
transition: transform 0.15s ease;
}
.fab:hover { transform: scale(1.08); }
.fab.hidden { display: none; }
/* 收起按钮:骑在面板左上边缘(一半在面板外)。必须在 iframe 外渲染——
iframe 裁剪内容,iframe 内的元素永远溢不出面板边界 */
.panel-close {
position: fixed;
top: 20px;
right: min(880px, 100vw); /* 面板左边缘 = 视口右沿 - 面板宽度 */
transform: translateX(50%);
width: 28px;
height: 28px;
border-radius: 50%;
border: 1px solid rgba(0, 0, 0, 0.12);
background: #fff;
color: #555;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.18);
z-index: 4;
opacity: 0;
pointer-events: none;
transition: opacity 0.25s ease, transform 0.15s ease;
}
.panel-close.visible { opacity: 1; pointer-events: auto; }
.panel-close:hover { color: #6d28d9; border-color: #6d28d9; transform: translateX(50%) scale(1.08); }
.panel {
position: fixed;
top: 0;
bottom: 0;
right: 0;
width: min(880px, 100vw);
border-radius: 14px 0 0 14px;
overflow: hidden;
background: #fff;
box-shadow: -8px 0 32px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(0, 0, 0, 0.06);
transform: translateX(100%);
transition: transform 0.25s ease;
z-index: 2;
pointer-events: none;
}
.panel.open { transform: translateX(0); pointer-events: auto; }
.panel iframe {
width: 100%;
height: 100%;
border: 0;
display: block;
background: #fff;
}
`;
export default defineContentScript({
matches: [
// Ozon
'https://*.ozon.ru/*',
'https://*.ozon.kz/*',
'https://*.ozon.by/*',
// 1688
'https://detail.1688.com/*',
// 淘宝 / 天猫
'https://item.taobao.com/*',
'https://detail.tmall.com/*',
],
async main(ctx) {
const ui = await createShadowRootUi(ctx, {
name: 'suite-studio-panel',
position: 'overlay',
anchor: 'body',
alignment: 'bottom-right',
zIndex: 2147483646,
css: STYLES,
isolateEvents: true,
onMount(container) {
const fab = document.createElement('button');
fab.className = 'fab hidden';
fab.title = '电商套图工作台';
fab.textContent = '套';
const panel = document.createElement('div');
panel.className = 'panel';
// iframe 懒加载:首次展开才设 src,避免每个商品页都加载整个面板应用
const iframe = document.createElement('iframe');
iframe.title = '电商套图工作台';
panel.append(iframe);
// 外置收起按钮(骑在面板左上边缘,一半在面板外)
const closeBtn = document.createElement('button');
closeBtn.className = 'panel-close';
closeBtn.title = '收起面板(Esc';
closeBtn.innerHTML =
'<svg width="12" height="12" viewBox="0 0 12 12" fill="none">' +
'<path d="M2 2l8 8M10 2l-8 8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>';
container.append(fab, panel, closeBtn);
const open = () => {
if (!iframe.src) iframe.src = chrome.runtime.getURL('/sidepanel.html');
panel.classList.add('open');
closeBtn.classList.add('visible');
fab.classList.add('hidden');
// 聚焦进面板,键盘操作(Esc 关闭 / 预览翻页)直接可用
iframe.focus();
};
const close = () => {
panel.classList.remove('open');
closeBtn.classList.remove('visible');
if (isProductPage()) fab.classList.remove('hidden');
};
fab.addEventListener('click', open);
closeBtn.addEventListener('click', close);
// 面板内 App(Esc)→ 收起;✕ 按钮已外置到宿主层(closeBtn)
window.addEventListener('message', (e) => {
if (e.source === iframe.contentWindow && (e.data as any)?.type === PANEL_CLOSE_MSG) close();
});
// 工具栏图标点击 → 开关面板
chrome.runtime.onMessage.addListener((msg: any) => {
if (msg?.action === 'toggle-suite-panel') {
panel.classList.contains('open') ? close() : open();
}
});
// 仅商品详情页显示按钮;站内软导航后重判(WXT 内置事件,自动拦截 pushState/replaceState/popState
const refresh = () => {
if (isProductPage()) {
if (!panel.classList.contains('open')) fab.classList.remove('hidden');
} else {
fab.classList.add('hidden');
close();
}
};
ctx.addEventListener(window, 'wxt:locationchange', refresh);
refresh();
return { open, close };
},
});
ui.mount();
},
});
/** 是否为四站点支持的商品详情页(与采集 profile 一致) */
function isProductPage(): boolean {
return matchProfile(location.href) !== null;
}