145 lines
3.9 KiB
TypeScript
145 lines
3.9 KiB
TypeScript
/**
|
||
* 页面标记组件 —— 在网页上显示一个固定位置的“已锁定”标记
|
||
*/
|
||
|
||
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;
|
||
}
|