Files
custom-plugin/components/page-marker.ts
2026-05-01 21:13:16 +08:00

145 lines
3.9 KiB
TypeScript
Raw 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.
/**
* 页面标记组件 —— 在网页上显示一个固定位置的“已锁定”标记
*/
export interface PageMarkerOptions {
/** 标记显示的文本,默认为“🔒 已锁定” */
text?: string;
/** 位置预设:'top-right' | 'top-left' | 'bottom-right' | 'bottom-left',默认 'top-right' */
position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
/** 背景色,默认 rgba(0,0,0,0.75) */
backgroundColor?: string;
/** 字体颜色,默认 #fff */
color?: string;
/** 自定义样式,会与默认样式合并 */
customStyle?: Partial<CSSStyleDeclaration>;
/** 几秒后自动消失,设为 0 则永久存在,默认 0 */
autoRemoveSeconds?: number;
/** 是否允许用户点击关闭,默认 false */
closable?: boolean;
}
export class PageMarker {
private element: HTMLDivElement | null = null;
private timer: ReturnType<typeof setTimeout> | null = null;
constructor(private options: PageMarkerOptions = {}) {}
/**
* 在页面上显示标记
*/
show(): void {
if (this.element) this.hide();
const {
text = '🔒 已锁定',
position = 'top-right',
backgroundColor = 'rgba(0,0,0,0.75)',
color = '#fff',
autoRemoveSeconds = 0,
closable = false,
customStyle = {},
} = this.options;
const el = document.createElement('div');
el.textContent = text;
el.title = '此页面已被插件锁定';
const baseStyle: Record<string, string> = {
position: 'fixed',
zIndex: '99999',
padding: '6px 14px',
borderRadius: '4px',
fontSize: '13px',
fontFamily: 'Arial, sans-serif',
fontWeight: '600',
backgroundColor,
color,
boxShadow: '0 2px 8px rgba(0,0,0,0.3)',
pointerEvents: closable ? 'auto' : 'none',
userSelect: 'none',
transition: 'opacity 0.2s ease',
...positionStyles(position),
...(customStyle as Record<string, string>),
};
Object.assign(el.style, baseStyle);
// 可关闭按钮
if (closable) {
const closeBtn = document.createElement('span');
closeBtn.textContent = '×';
closeBtn.style.cssText = 'margin-left:8px;cursor:pointer;font-weight:bold;opacity:0.7';
closeBtn.addEventListener('click', (e) => {
e.stopPropagation();
this.hide();
});
el.appendChild(closeBtn);
}
document.body.appendChild(el);
this.element = el;
if (autoRemoveSeconds > 0) {
this.timer = setTimeout(() => this.hide(), autoRemoveSeconds * 1000);
}
}
/**
* 隐藏/移除标记
*/
hide(): void {
if (this.element) {
this.element.style.opacity = '0';
setTimeout(() => {
this.element?.remove();
this.element = null;
}, 200);
}
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
}
/**
* 更新标记文本
*/
updateText(text: string): void {
if (!this.element) return;
// 保留关闭按钮
const closeBtn = this.element.querySelector('span');
this.element.childNodes.forEach(node => {
if (node !== closeBtn) node.remove();
});
this.element.prepend(document.createTextNode(text));
}
/**
* 标记当前是否已显示
*/
get isVisible(): boolean {
return !!this.element;
}
}
function positionStyles(pos: string): Record<string, string> {
const offset = '12px';
switch (pos) {
case 'top-right': return { top: offset, right: offset };
case 'top-left': return { top: offset, left: offset };
case 'bottom-right': return { bottom: offset, right: offset };
case 'bottom-left': return { bottom: offset, left: offset };
default: return { top: offset, right: offset };
}
}
/**
* 快捷函数:直接显示一个简单标记
*/
export function showPageMarker(options?: PageMarkerOptions): PageMarker {
const marker = new PageMarker(options);
marker.show();
return marker;
}