// ==UserScript==
// @name 常用邮箱/手机号/密码 一键填写助手(可隐藏+快捷键)
// @namespace https://example.com
// @version 0.7.3
// @description 支持邮箱、手机号、密码快速填写;Delete 键 切换面板显隐;导出使用 clipboard API + fallback
// @author You
// @match *://*/*
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
// ─────────────── 数据结构 ───────────────
let data = GM_getValue('fastfill_data_v2', {
emails: ["example@gmail.com", "test@outlook.com"],
phones: ["13812345678", "13900002222"],
passwords: ["Abc123456!", "Passw0rd2025", "MySecret2026"]
});
let panelPos = GM_getValue('fastfill_panel_pos', { bottom: 24, right: 24 });
let panelVisible = GM_getValue('fastfill_panel_visible', false); // 默认隐藏
let panel = null;
// ─────────────── CSS ───────────────
GM_addStyle(`
#fastfill-panel {
position: fixed;
z-index: 999998;
width: 260px;
max-height: 82vh;
overflow-y: auto;
background: rgba(28, 28, 36, 0.96);
backdrop-filter: blur(12px);
color: #e0e0ff;
border-radius: 16px;
box-shadow: 0 12px 44px rgba(0,0,0,0.65);
font-family: system-ui, sans-serif;
font-size: 14px;
padding: 14px 16px;
border: 1px solid rgba(100,100,160,0.35);
user-select: none;
transition: all 0.2s ease, opacity 0.3s, transform 0.3s;
}
#fastfill-panel.hidden {
opacity: 0;
transform: scale(0.88) translateY(20px);
pointer-events: none;
}
#fastfill-header {
font-weight: 600;
color: #a5b4fc;
margin-bottom: 12px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: move;
}
.fastfill-section-title {
color: #c7d2fe;
font-size: 13.5px;
font-weight: 600;
margin: 14px 0 6px;
}
.fastfill-item {
padding: 8px 12px;
margin: 5px 0;
background: rgba(45,45,65,0.7);
border-radius: 8px;
cursor: pointer;
transition: all 0.14s;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.fastfill-item:hover {
background: rgba(70,70,110,0.9);
transform: translateX(3px);
}
.fastfill-item.email { color: #a5b4fc; }
.fastfill-item.phone { color: #86efac; }
.fastfill-item.password { color: #fbbf24; }
#fastfill-btn-area {
margin-top: 18px;
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.fastfill-btn {
flex: 1;
min-width: 78px;
padding: 8px 10px;
border-radius: 8px;
text-align: center;
cursor: pointer;
font-size: 13px;
background: #4b5563;
color: #e0e0ff;
}
.fastfill-btn:hover { background: #6366f1; color: white; }
.fastfill-btn.primary { background: linear-gradient(135deg, #6366f1, #8b5cf6); }
`);
// ─────────────── 工具函数 ───────────────
function saveData() {
GM_setValue('fastfill_data_v2', data);
}
function fillField(value, matcher, preferEmpty = true) {
document.querySelectorAll('input:not([type="hidden"]):not([type="submit"]):not([type="button"])').forEach(el => {
if (!matcher(el) || !el.offsetParent) return;
if (preferEmpty && el.value) return;
if (!preferEmpty && el.value && !confirm(`是否覆盖?\n原: ${el.value}\n新: ${value}`)) return;
el.value = value;
el.dispatchEvent(new Event('input', {bubbles: true}));
el.dispatchEvent(new Event('change', {bubbles: true}));
});
}
function fillEmail(v) { fillField(v, el => /email|mail|邮箱|account|user/i.test((el.name||'')+(el.id||'')+(el.placeholder||''))); }
function fillPhone(v) { fillField(v, el => /phone|mobile|tel|手机|电话/i.test((el.name||'')+(el.id||'')+(el.placeholder||''))); }
function fillPassword(v) { fillField(v, el => /pass|pwd|password|密码|确认密码|confirm/i.test((el.name||'')+(el.id||'')+(el.placeholder||'')), false); }
// ─────────────── 面板创建/更新 ───────────────
function createOrUpdatePanel() {
if (panel) panel.remove();
if (!panelVisible) return;
panel = document.createElement('div');
panel.id = 'fastfill-panel';
panel.style.bottom = panelPos.bottom + 'px';
panel.style.right = panelPos.right + 'px';
panel.innerHTML = `
邮箱
手机号
常用密码
`;
document.body.appendChild(panel);
// 渲染列表
const sections = [
{ id: 'email-list', arr: data.emails, cls: 'email', fill: fillEmail },
{ id: 'phone-list', arr: data.phones, cls: 'phone', fill: fillPhone },
{ id: 'password-list', arr: data.passwords, cls: 'password', fill: fillPassword }
];
sections.forEach(sec => {
const container = panel.querySelector('#' + sec.id);
container.innerHTML = sec.arr.map(v =>
`${v}
`
).join('');
container.querySelectorAll('.fastfill-item').forEach(item => {
item.onclick = () => sec.fill(item.textContent.trim());
});
});
// 关闭 → 隐藏
panel.querySelector('#fastfill-close').onclick = hidePanel;
// 拖拽(省略不变,保持原样)
let drag = { active: false, sx:0, sy:0, sr:0, sb:0 };
const header = panel.querySelector('#fastfill-header');
header.onmousedown = e => {
if (e.target.id === 'fastfill-close') return;
drag.active = true;
drag.sx = e.clientX; drag.sy = e.clientY;
drag.sr = parseFloat(panel.style.right || 24);
drag.sb = parseFloat(panel.style.bottom || 24);
e.preventDefault();
};
document.onmousemove = e => {
if (!drag.active) return;
const dx = e.clientX - drag.sx;
const dy = e.clientY - drag.sy;
panelPos.right = Math.max(10, drag.sr - dx);
panelPos.bottom = Math.max(10, drag.sb - dy);
panel.style.right = panelPos.right + 'px';
panel.style.bottom = panelPos.bottom + 'px';
};
document.onmouseup = () => {
if (drag.active) {
drag.active = false;
GM_setValue('fastfill_panel_pos', panelPos);
}
};
// 导出(使用 navigator.clipboard + fallback)
panel.querySelector('#fastfill-export').onclick = () => {
const json = JSON.stringify(data, null, 2);
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(json)
.then(() => {
alert("已复制到剪贴板!\n可粘贴到记事本、微信、邮箱等地方保存");
})
.catch(err => {
console.error('Clipboard API 失败:', err);
fallbackCopy(json);
});
} else {
fallbackCopy(json);
}
function fallbackCopy(text) {
prompt(
"自动复制失败,请手动复制以下全部内容(全选 → Ctrl+C)\n完成后点确定:\n\n",
text
);
}
};
// 导入(省略不变)
panel.querySelector('#fastfill-import').onclick = () => {
const txt = prompt("请粘贴之前导出的完整 JSON:", "");
if (!txt) return;
try {
const obj = JSON.parse(txt);
['emails','phones','passwords'].forEach(k => {
if (Array.isArray(obj[k])) {
data[k] = obj[k].filter(v => typeof v === 'string' && v.trim());
}
});
saveData();
alert("导入成功");
createOrUpdatePanel();
} catch(e) {
alert("JSON 格式错误,请检查是否完整复制");
}
};
// 管理
panel.querySelector('#fastfill-manage').onclick = showManageModal;
}
function showPanel() {
panelVisible = true;
GM_setValue('fastfill_panel_visible', true);
createOrUpdatePanel();
}
function hidePanel() {
panelVisible = false;
GM_setValue('fastfill_panel_visible', false);
if (panel) {
panel.classList.add('hidden');
setTimeout(() => { panel.remove(); panel = null; }, 320);
}
}
function togglePanel() {
panelVisible ? hidePanel() : showPanel();
}
// ─────────────── 快捷键:Delete 键 ───────────────
document.addEventListener('keydown', e => {
if (e.key === 'Delete' || e.key === 'Del') {
// 防止在输入框中误触发(可选:如果你希望在输入框里也能用,可注释掉下面这行)
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable) return;
e.preventDefault();
togglePanel();
}
});
// ─────────────── 管理弹窗(保持不变) ───────────────
function showManageModal() {
const old = document.getElementById('fastfill-modal');
if (old) old.remove();
const modal = document.createElement('div');
modal.id = 'fastfill-modal';
modal.innerHTML = `
`;
document.body.appendChild(modal);
function renderTab(type) {
const cont = document.getElementById(type + '-edit');
const arr = data[type + 's'] || [];
cont.innerHTML = arr.map((v,i) => `
`).join('');
}
['email','phone','password'].forEach(t => renderTab(t));
// tab 切换
modal.querySelectorAll('.tab').forEach(tab => {
tab.onclick = () => {
modal.querySelectorAll('.tab').forEach(t => {
t.classList.remove('active');
t.style.borderBottomColor = 'transparent';
});
modal.querySelectorAll('[id^="tab-"]').forEach(c => c.style.display = 'none');
tab.classList.add('active');
tab.style.borderBottomColor = '#6366f1';
document.getElementById('tab-' + tab.dataset.tab).style.display = 'block';
};
});
// 添加、保存、删除逻辑(保持不变)
modal.querySelectorAll('.add-btn').forEach(btn => {
btn.onclick = () => {
const input = btn.previousElementSibling;
const val = input.value.trim();
if (!val) return;
const type = btn.closest('[id^="tab-"]').id.replace('tab-', '');
const arr = data[type + 's'];
if (arr.includes(val)) return alert('已存在');
arr.push(val);
saveData();
input.value = '';
renderTab(type);
};
});
modal.addEventListener('click', e => {
if (!e.target.classList.contains('save-btn') && !e.target.classList.contains('del-btn')) return;
const row = e.target.closest('div');
const inp = row.querySelector('input');
const idx = +inp.dataset.i;
const typ = inp.dataset.t;
const arr = data[typ + 's'];
if (e.target.classList.contains('save-btn')) {
const nv = inp.value.trim();
if (nv && nv !== arr[idx]) {
arr[idx] = nv;
saveData();
renderTab(typ);
}
} else if (e.target.classList.contains('del-btn')) {
if (!confirm('确认删除?')) return;
arr.splice(idx, 1);
saveData();
renderTab(typ);
}
});
modal.querySelector('#modal-close').onclick = () => {
modal.remove();
};
}
// ─────────────── 自动检测表单 ───────────────
function hasTargetInput() {
return !!document.querySelector(`
input[type="email" i], input[type="tel" i], input[type="password" i],
input[name*="email" i], input[name*="phone" i], input[name*="pass" i],
input[placeholder*="邮箱" i], input[placeholder*="手机" i], input[placeholder*="密码" i]
`);
}
function autoCheck() {
if (panelVisible && hasTargetInput() && !panel) {
createOrUpdatePanel();
} else if (panel && !hasTargetInput()) {
hidePanel();
}
}
setTimeout(autoCheck, 1200);
const observer = new MutationObserver(autoCheck);
observer.observe(document.body, { childList: true, subtree: true });
// 菜单辅助
GM_registerMenuCommand("显示/隐藏面板 (Delete 键)", togglePanel);
GM_registerMenuCommand("强制刷新面板", createOrUpdatePanel);
// 首次提示(更新快捷键说明)
if (!GM_getValue('fastfill_seen_tip', false)) {
setTimeout(() => {
alert("快捷键:Delete 键(del键) → 显示/隐藏面板\n\n在输入框内按 Del 不会触发(避免误操作)");
GM_setValue('fastfill_seen_tip', true);
}, 2000);
}
})();