// ==UserScript== // @name 随手记 // @namespace http://tampermonkey.net/ // @version 1.1.0 // @description 随手记 用来记录网站常用数据减少查找时间 // @author wangshiwei // @match https://*/* // @match http://*/* // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @grant GM_setValue // @grant GM_getValue // ==/UserScript== (function() { 'use strict'; const defaultData = [ { name: 'test', value: 'test', note: 'test' } ]; const defaultAllowedDomains = []; function getAllowedDomains() { const saved = GM_getValue('planMappingAllowedDomains', null); if (saved) { return JSON.parse(saved); } return defaultAllowedDomains; } function saveAllowedDomains(domains) { GM_setValue('planMappingAllowedDomains', JSON.stringify(domains)); } function isDomainAllowed() { const allowedDomains = getAllowedDomains(); if (allowedDomains.length === 0) { return true; } const currentHostname = window.location.hostname; if (allowedDomains.includes(currentHostname)) { return true; } return allowedDomains.some(domain => { if (domain.startsWith('*.')) { const baseDomain = domain.substring(2); return currentHostname === baseDomain || currentHostname.endsWith('.' + baseDomain); } return false; }); } function getData() { const saved = GM_getValue('planMappingData', null); if (saved) { return JSON.parse(saved); } GM_setValue('planMappingData', JSON.stringify(defaultData)); return defaultData; } function parseArrayData(inputText) { try { const data = JSON.parse(inputText); if (!Array.isArray(data)) { throw new Error('数据必须是数组格式'); } const validData = data.filter(item => { return item && typeof item === 'object' && item.name && item.value && item.note; }); if (validData.length === 0) { throw new Error('未找到有效的数据项。每项必须包含 name、value 和 note 字段'); } if (validData.length < data.length) { console.warn(`部分数据无效,已过滤 ${data.length - validData.length} 条无效数据`); } return validData; } catch (error) { try { let cleaned = inputText.trim(); const arrayMatch = cleaned.match(/\[[\s\S]*\]/); if (arrayMatch) { cleaned = arrayMatch[0]; } const parsedData = new Function('return ' + cleaned)(); if (!Array.isArray(parsedData)) { throw new Error('数据必须是数组格式'); } const validData = parsedData.filter(item => { return item && typeof item === 'object' && item.name && item.value && item.note; }); if (validData.length === 0) { throw new Error('未找到有效的数据项。每项必须包含 name、value 和 note 字段'); } return validData; } catch (parseError) { throw new Error(`解析失败: ${error.message || parseError.message}`); } } } function saveData(data) { GM_setValue('planMappingData', JSON.stringify(data)); } function copyToClipboard(text) { const textarea = document.createElement('textarea'); textarea.value = text; textarea.style.position = 'fixed'; textarea.style.opacity = '0'; document.body.appendChild(textarea); textarea.select(); try { document.execCommand('copy'); return true; } catch (err) { console.error('复制失败:', err); return false; } finally { document.body.removeChild(textarea); } } function createTable(data, onDelete) { const tableWrapper = document.createElement('div'); tableWrapper.style.cssText = ` position: relative; width: 100%; margin-top: 8px; border: 1px solid #ddd; border-radius: 4px; overflow: hidden; background-color: white; isolation: isolate; `; const scrollContainer = document.createElement('div'); scrollContainer.style.cssText = ` height: 360px; overflow-y: auto; overflow-x: hidden; position: relative; `; const style = document.createElement('style'); style.textContent = ` #plan-mapping-modal [data-table-wrapper] > div::-webkit-scrollbar { width: 8px; } #plan-mapping-modal [data-table-wrapper] > div::-webkit-scrollbar-track { background: #f1f1f1; } #plan-mapping-modal [data-table-wrapper] > div::-webkit-scrollbar-thumb { background: #888; border-radius: 4px; } #plan-mapping-modal [data-table-wrapper] > div::-webkit-scrollbar-thumb:hover { background: #555; } #plan-mapping-modal [data-table-wrapper] thead { position: sticky !important; top: 0 !important; z-index: 100 !important; background-color: #f2f2f2 !important; } #plan-mapping-modal [data-table-wrapper] thead th { background-color: #f2f2f2 !important; position: relative !important; z-index: 101 !important; box-shadow: 0 2px 0 #f2f2f2; } #plan-mapping-modal [data-table-wrapper] thead tr { background-color: #f2f2f2 !important; position: relative !important; z-index: 100 !important; } #plan-mapping-modal [data-table-wrapper] tbody { position: relative; z-index: 1; } #plan-mapping-modal [data-table-wrapper] tbody tr { position: relative; z-index: 1; } #plan-mapping-modal [data-table-wrapper] tbody td { position: relative; z-index: 1; } #plan-mapping-modal [data-table-wrapper] tbody button { position: relative; z-index: 1; } #plan-mapping-modal [data-table-wrapper] thead { box-shadow: 0 2px 4px rgba(242, 242, 242, 1); } #plan-mapping-modal [data-table-wrapper] thead tr { box-shadow: 0 2px 4px rgba(242, 242, 242, 1); } #plan-mapping-modal [data-table-wrapper] table { background-color: white; } #plan-mapping-modal [data-table-wrapper] tbody td { background-color: inherit; } #plan-mapping-modal [data-table-wrapper] tbody { display: table-row-group; } #plan-mapping-modal [data-table-wrapper] table { display: table; } `; if (!document.getElementById('plan-mapping-scrollbar-style')) { style.id = 'plan-mapping-scrollbar-style'; document.head.appendChild(style); } const table = document.createElement('table'); table.style.cssText = ` width: 100%; border-collapse: collapse; margin: 0; background-color: white; table-layout: fixed; `; const thead = document.createElement('thead'); thead.style.cssText = ` position: sticky; top: 0; z-index: 100; background-color: #f2f2f2; display: table-header-group; `; const headerRow = document.createElement('tr'); headerRow.style.cssText = ` background-color: #f2f2f2 !important; position: relative; z-index: 100; box-shadow: 0 2px 4px rgba(242, 242, 242, 1); `; const headers = ['名称', '值', '备注', '操作']; headers.forEach(headerText => { const th = document.createElement('th'); th.textContent = headerText; th.style.cssText = ` padding: 12px; text-align: left; border: 1px solid #ddd; background-color: #f2f2f2 !important; position: relative; z-index: 101; box-shadow: 0 2px 0 #f2f2f2; `; headerRow.appendChild(th); }); thead.appendChild(headerRow); table.appendChild(thead); const tbody = document.createElement('tbody'); tbody.style.cssText = ` position: relative; z-index: 1; min-height: 300px; `; if (data.length === 0) { const emptyRow = document.createElement('tr'); emptyRow.style.cssText = ` height: 300px; `; const emptyCell = document.createElement('td'); emptyCell.colSpan = 4; emptyCell.style.cssText = ` padding: 0; text-align: center; vertical-align: middle; border: none; height: 300px; `; const emptyState = document.createElement('div'); emptyState.style.cssText = ` display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: #999; `; const emptyIcon = document.createElement('div'); emptyIcon.style.cssText = ` font-size: 48px; margin-bottom: 16px; opacity: 0.5; `; emptyIcon.textContent = '📭'; const emptyText = document.createElement('div'); emptyText.style.cssText = ` font-size: 14px; color: #999; `; emptyText.textContent = '暂无数据,请点击"导入数据"或"+ 新增"添加'; emptyState.appendChild(emptyIcon); emptyState.appendChild(emptyText); emptyCell.appendChild(emptyState); emptyRow.appendChild(emptyCell); tbody.appendChild(emptyRow); } else { data.forEach((item, index) => { const row = document.createElement('tr'); row.style.backgroundColor = index % 2 === 0 ? '#fff' : '#f9f9f9'; row.style.position = 'relative'; row.style.zIndex = '1'; const td1 = document.createElement('td'); td1.textContent = item.name; td1.style.padding = '10px'; td1.style.border = '1px solid #ddd'; row.appendChild(td1); const td2 = document.createElement('td'); td2.textContent = item.value; td2.style.padding = '10px'; td2.style.border = '1px solid #ddd'; row.appendChild(td2); const td3 = document.createElement('td'); td3.textContent = item.note; td3.style.padding = '10px'; td3.style.border = '1px solid #ddd'; row.appendChild(td3); const td4 = document.createElement('td'); td4.style.padding = '10px'; td4.style.border = '1px solid #ddd'; td4.style.position = 'relative'; td4.style.zIndex = '1'; const btnContainer = document.createElement('div'); btnContainer.style.display = 'flex'; btnContainer.style.gap = '8px'; btnContainer.style.position = 'relative'; btnContainer.style.zIndex = '1'; const copyBtn = document.createElement('button'); copyBtn.textContent = '复制'; copyBtn.style.padding = '5px 15px'; copyBtn.style.cursor = 'pointer'; copyBtn.style.backgroundColor = '#4CAF50'; copyBtn.style.color = 'white'; copyBtn.style.border = 'none'; copyBtn.style.borderRadius = '4px'; copyBtn.onmouseover = () => copyBtn.style.backgroundColor = '#45a049'; copyBtn.onmouseout = () => copyBtn.style.backgroundColor = '#4CAF50'; copyBtn.onclick = () => { if (copyToClipboard(item.value)) { const originalText = copyBtn.textContent; copyBtn.textContent = '已复制!'; copyBtn.style.backgroundColor = '#2196F3'; setTimeout(() => { copyBtn.textContent = originalText; copyBtn.style.backgroundColor = '#4CAF50'; }, 2000); } }; btnContainer.appendChild(copyBtn); const deleteBtn = document.createElement('button'); deleteBtn.textContent = '删除'; deleteBtn.style.padding = '5px 15px'; deleteBtn.style.cursor = 'pointer'; deleteBtn.style.backgroundColor = '#f44336'; deleteBtn.style.color = 'white'; deleteBtn.style.border = 'none'; deleteBtn.style.borderRadius = '4px'; deleteBtn.onmouseover = () => deleteBtn.style.backgroundColor = '#da190b'; deleteBtn.onmouseout = () => deleteBtn.style.backgroundColor = '#f44336'; deleteBtn.onclick = () => { if (confirm(`确定要删除 "${item.name} - ${item.value}" 这条记录吗?`)) { onDelete(index); } }; btnContainer.appendChild(deleteBtn); td4.appendChild(btnContainer); row.appendChild(td4); tbody.appendChild(row); }); } table.appendChild(tbody); scrollContainer.appendChild(table); tableWrapper.appendChild(scrollContainer); return tableWrapper; } function createAddModal(onAdd) { const overlay = document.createElement('div'); overlay.id = 'plan-mapping-add-overlay'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 100000; display: none; `; const modal = document.createElement('div'); modal.id = 'plan-mapping-add-modal'; modal.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90%; max-width: 500px; background-color: white; border-radius: 8px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); z-index: 100001; display: none; flex-direction: column; `; const header = document.createElement('div'); header.style.cssText = ` display: flex; justify-content: space-between; align-items: center; padding: 20px; border-bottom: 1px solid #ddd; background-color: #f5f5f5; `; const title = document.createElement('h2'); title.textContent = '添加新映射'; title.style.margin = '0'; title.style.fontSize = '18px'; header.appendChild(title); const closeBtn = document.createElement('button'); closeBtn.textContent = '×'; closeBtn.style.cssText = ` background: none; border: none; font-size: 28px; cursor: pointer; color: #666; padding: 0; width: 30px; height: 30px; line-height: 30px; text-align: center; `; closeBtn.onmouseover = () => closeBtn.style.color = '#000'; closeBtn.onmouseout = () => closeBtn.style.color = '#666'; closeBtn.onclick = () => { overlay.style.display = 'none'; modal.style.display = 'none'; }; header.appendChild(closeBtn); modal.appendChild(header); const content = document.createElement('div'); content.style.cssText = ` padding: 20px; `; const fields = [ { label: '名称', id: 'name', placeholder: '请输入名称' }, { label: '值', id: 'value', placeholder: '请输入值' }, { label: '备注', id: 'note', placeholder: '请输入备注' } ]; const inputs = {}; fields.forEach(field => { const label = document.createElement('label'); label.textContent = field.label + ': '; label.style.display = 'block'; label.style.marginTop = '15px'; label.style.marginBottom = '5px'; label.style.fontWeight = '500'; content.appendChild(label); const input = document.createElement('input'); input.type = 'text'; input.id = field.id; input.placeholder = field.placeholder; input.style.width = '100%'; input.style.padding = '10px'; input.style.marginBottom = '5px'; input.style.border = '1px solid #ddd'; input.style.borderRadius = '4px'; input.style.boxSizing = 'border-box'; input.style.fontSize = '14px'; content.appendChild(input); inputs[field.id] = input; }); const btnContainer = document.createElement('div'); btnContainer.style.cssText = ` display: flex; justify-content: flex-end; gap: 10px; margin-top: 25px; `; const cancelBtn = document.createElement('button'); cancelBtn.textContent = '取消'; cancelBtn.style.cssText = ` padding: 8px 20px; cursor: pointer; background-color: #f5f5f5; color: #333; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; `; cancelBtn.onmouseover = () => cancelBtn.style.backgroundColor = '#e0e0e0'; cancelBtn.onmouseout = () => cancelBtn.style.backgroundColor = '#f5f5f5'; cancelBtn.onclick = () => { overlay.style.display = 'none'; modal.style.display = 'none'; inputs.name.value = ''; inputs.value.value = ''; inputs.note.value = ''; }; btnContainer.appendChild(cancelBtn); const addBtn = document.createElement('button'); addBtn.textContent = '添加'; addBtn.style.cssText = ` padding: 8px 20px; cursor: pointer; background-color: #2196F3; color: white; border: none; border-radius: 4px; font-size: 14px; `; addBtn.onmouseover = () => addBtn.style.backgroundColor = '#0b7dda'; addBtn.onmouseout = () => addBtn.style.backgroundColor = '#2196F3'; addBtn.onclick = () => { const newItem = { name: inputs.name.value.trim(), value: inputs.value.value.trim(), note: inputs.note.value.trim() }; if (!newItem.name || !newItem.value || !newItem.note) { alert('请填写所有字段'); return; } onAdd(newItem); inputs.name.value = ''; inputs.value.value = ''; inputs.note.value = ''; overlay.style.display = 'none'; modal.style.display = 'none'; }; btnContainer.appendChild(addBtn); content.appendChild(btnContainer); modal.appendChild(content); overlay.onclick = (e) => { if (e.target === overlay) { overlay.style.display = 'none'; modal.style.display = 'none'; inputs.name.value = ''; inputs.value.value = ''; inputs.note.value = ''; } }; document.body.appendChild(overlay); document.body.appendChild(modal); return { show: () => { overlay.style.display = 'block'; modal.style.display = 'flex'; setTimeout(() => { inputs.name.focus(); }, 100); }, hide: () => { overlay.style.display = 'none'; modal.style.display = 'none'; } }; } function createImportModal(onImport) { const overlay = document.createElement('div'); overlay.id = 'plan-mapping-import-overlay'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 100000; display: none; `; const modal = document.createElement('div'); modal.id = 'plan-mapping-import-modal'; modal.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90%; max-width: 600px; background-color: white; border-radius: 8px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); z-index: 100001; display: none; flex-direction: column; max-height: 80vh; `; const header = document.createElement('div'); header.style.cssText = ` display: flex; justify-content: space-between; align-items: center; padding: 20px; border-bottom: 1px solid #ddd; background-color: #f5f5f5; `; const title = document.createElement('h2'); title.textContent = '导入数据'; title.style.margin = '0'; title.style.fontSize = '18px'; header.appendChild(title); const closeBtn = document.createElement('button'); closeBtn.textContent = '×'; closeBtn.style.cssText = ` background: none; border: none; font-size: 28px; cursor: pointer; color: #666; padding: 0; width: 30px; height: 30px; line-height: 30px; text-align: center; `; closeBtn.onmouseover = () => closeBtn.style.color = '#000'; closeBtn.onmouseout = () => closeBtn.style.color = '#666'; closeBtn.onclick = () => { overlay.style.display = 'none'; modal.style.display = 'none'; }; header.appendChild(closeBtn); modal.appendChild(header); const content = document.createElement('div'); content.style.cssText = ` padding: 20px; overflow-y: auto; flex: 1; `; const description = document.createElement('div'); description.style.cssText = ` margin-bottom: 15px; padding: 10px; background-color: #f0f7ff; border-left: 3px solid #2196F3; border-radius: 4px; `; description.innerHTML = `

数据格式说明:

请粘贴数组格式的数据,每项必须包含 name、value 和 note 字段。
支持 JSON 格式或 JavaScript 数组格式。

`; content.appendChild(description); const exampleText = document.createElement('div'); exampleText.style.cssText = ` margin-bottom: 15px; padding: 10px; background-color: #f9f9f9; border-radius: 4px; font-size: 12px; font-family: monospace; color: #666; max-height: 120px; overflow-y: auto; `; exampleText.textContent = `示例格式:\n[\n {\n name: '示例名称',\n value: '示例值',\n note: '示例备注'\n }\n]`; content.appendChild(exampleText); const label = document.createElement('label'); label.textContent = '粘贴数据:'; label.style.display = 'block'; label.style.marginBottom = '8px'; label.style.fontWeight = '500'; content.appendChild(label); const textarea = document.createElement('textarea'); textarea.id = 'import-data-textarea'; textarea.placeholder = '请粘贴数组数据...'; textarea.style.cssText = ` width: 100%; min-height: 200px; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-family: monospace; font-size: 13px; resize: vertical; box-sizing: border-box; `; content.appendChild(textarea); const errorMsg = document.createElement('div'); errorMsg.id = 'import-error-msg'; errorMsg.style.cssText = ` margin-top: 10px; padding: 10px; background-color: #ffebee; border-left: 3px solid #f44336; border-radius: 4px; color: #c62828; font-size: 13px; display: none; `; content.appendChild(errorMsg); const btnContainer = document.createElement('div'); btnContainer.style.cssText = ` display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; `; const cancelBtn = document.createElement('button'); cancelBtn.textContent = '取消'; cancelBtn.style.cssText = ` padding: 8px 20px; cursor: pointer; background-color: #f5f5f5; color: #333; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; `; cancelBtn.onmouseover = () => cancelBtn.style.backgroundColor = '#e0e0e0'; cancelBtn.onmouseout = () => cancelBtn.style.backgroundColor = '#f5f5f5'; cancelBtn.onclick = () => { overlay.style.display = 'none'; modal.style.display = 'none'; textarea.value = ''; errorMsg.style.display = 'none'; }; btnContainer.appendChild(cancelBtn); const importBtn = document.createElement('button'); importBtn.textContent = '导入'; importBtn.style.cssText = ` padding: 8px 20px; cursor: pointer; background-color: #2196F3; color: white; border: none; border-radius: 4px; font-size: 14px; `; importBtn.onmouseover = () => importBtn.style.backgroundColor = '#0b7dda'; importBtn.onmouseout = () => importBtn.style.backgroundColor = '#2196F3'; importBtn.onclick = () => { const inputText = textarea.value.trim(); if (!inputText) { errorMsg.textContent = '请输入数据'; errorMsg.style.display = 'block'; return; } try { const parsedData = parseArrayData(inputText); if (parsedData.length === 0) { errorMsg.textContent = '未找到有效数据'; errorMsg.style.display = 'block'; return; } onImport(parsedData); overlay.style.display = 'none'; modal.style.display = 'none'; textarea.value = ''; errorMsg.style.display = 'none'; } catch (error) { errorMsg.textContent = error.message || '解析失败,请检查数据格式'; errorMsg.style.display = 'block'; } }; btnContainer.appendChild(importBtn); content.appendChild(btnContainer); modal.appendChild(content); overlay.onclick = (e) => { if (e.target === overlay) { overlay.style.display = 'none'; modal.style.display = 'none'; textarea.value = ''; errorMsg.style.display = 'none'; } }; document.body.appendChild(overlay); document.body.appendChild(modal); return { show: () => { overlay.style.display = 'block'; modal.style.display = 'flex'; setTimeout(() => { textarea.focus(); }, 100); }, hide: () => { overlay.style.display = 'none'; modal.style.display = 'none'; } }; } function createModal(configModal) { const overlay = document.createElement('div'); overlay.id = 'plan-mapping-overlay'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 99998; display: none; `; const modal = document.createElement('div'); modal.id = 'plan-mapping-modal'; modal.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90%; max-width: 1000px; max-height: 90vh; background-color: white; border-radius: 8px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); z-index: 99999; display: none; overflow: hidden; flex-direction: column; `; const header = document.createElement('div'); header.style.cssText = ` display: flex; justify-content: space-between; align-items: center; padding: 20px; border-bottom: 1px solid #ddd; background-color: #f5f5f5; `; const titleContainer = document.createElement('div'); titleContainer.style.cssText = ` display: flex; flex-direction: row; align-items: center; gap: 4px; `; const title = document.createElement('h2'); title.textContent = '随手记列表'; title.style.margin = '0'; title.style.fontSize = '20px'; titleContainer.appendChild(title); const description = document.createElement('div'); description.style.cssText = ` font-size: 12px; color: #666; font-weight: normal; `; description.textContent = '默认匹配所有域名,可以自行在配置按钮中配置域名配置规则'; titleContainer.appendChild(description); header.appendChild(titleContainer); const headerBtnGroup = document.createElement('div'); headerBtnGroup.style.cssText = ` display: flex; align-items: center; gap: 10px; `; if (configModal) { const configBtn = document.createElement('button'); configBtn.textContent = '⚙️ 配置'; configBtn.style.cssText = ` padding: 6px 12px; cursor: pointer; background-color: #f5f5f5; color: #333; border: 1px solid #ddd; border-radius: 4px; font-size: 13px; `; configBtn.onmouseover = () => configBtn.style.backgroundColor = '#e0e0e0'; configBtn.onmouseout = () => configBtn.style.backgroundColor = '#f5f5f5'; configBtn.onclick = () => { configModal.show(); }; headerBtnGroup.appendChild(configBtn); } const closeBtn = document.createElement('button'); closeBtn.textContent = '×'; closeBtn.style.cssText = ` background: none; border: none; font-size: 28px; cursor: pointer; color: #666; padding: 0; width: 30px; height: 30px; line-height: 30px; text-align: center; `; closeBtn.onmouseover = () => closeBtn.style.color = '#000'; closeBtn.onmouseout = () => closeBtn.style.color = '#666'; closeBtn.onclick = () => { overlay.style.display = 'none'; modal.style.display = 'none'; }; headerBtnGroup.appendChild(closeBtn); header.appendChild(headerBtnGroup); modal.appendChild(header); const content = document.createElement('div'); content.id = 'plan-mapping-content'; content.style.cssText = ` padding: 5px 20px 20px 20px; overflow-y: auto; flex: 1; `; const tableContainer = document.createElement('div'); tableContainer.style.cssText = ` position: relative; `; const tableHeader = document.createElement('div'); tableHeader.style.cssText = ` display: flex; justify-content: space-between; align-items: center; `; const tableTitle = document.createElement('h3'); tableTitle.textContent = 'note列表'; tableTitle.style.margin = '0'; tableHeader.appendChild(tableTitle); const btnGroup = document.createElement('div'); btnGroup.style.cssText = ` display: flex; gap: 10px; align-items: center; `; const importBtn = document.createElement('button'); importBtn.textContent = '📥 导入数据'; importBtn.style.cssText = ` padding: 8px 20px; cursor: pointer; background-color: #4CAF50; color: white; border: none; border-radius: 4px; font-size: 14px; `; importBtn.onmouseover = () => importBtn.style.backgroundColor = '#45a049'; importBtn.onmouseout = () => importBtn.style.backgroundColor = '#4CAF50'; const addNewBtn = document.createElement('button'); addNewBtn.textContent = '+ 新增'; addNewBtn.style.cssText = ` padding: 8px 20px; cursor: pointer; background-color: #2196F3; color: white; border: none; border-radius: 4px; font-size: 14px; `; addNewBtn.onmouseover = () => addNewBtn.style.backgroundColor = '#0b7dda'; addNewBtn.onmouseout = () => addNewBtn.style.backgroundColor = '#2196F3'; const addModal = createAddModal((newItem) => { const data = getData(); data.push(newItem); saveData(data); renderTable(); }); const importModal = createImportModal((importedData) => { saveData(importedData); renderTable(); }); importBtn.onclick = () => { importModal.show(); }; addNewBtn.onclick = () => { addModal.show(); }; btnGroup.appendChild(importBtn); btnGroup.appendChild(addNewBtn); tableHeader.appendChild(btnGroup); tableContainer.appendChild(tableHeader); const renderTable = () => { const data = getData(); const oldTableWrapper = tableContainer.querySelector('[data-table-wrapper]'); if (oldTableWrapper) { oldTableWrapper.remove(); } const tableWrapper = createTable(data, (index) => { const data = getData(); data.splice(index, 1); saveData(data); renderTable(); }); tableWrapper.setAttribute('data-table-wrapper', 'true'); tableContainer.appendChild(tableWrapper); }; renderTable(); content.appendChild(tableContainer); modal.appendChild(content); overlay.onclick = (e) => { if (e.target === overlay) { overlay.style.display = 'none'; modal.style.display = 'none'; } }; document.body.appendChild(overlay); document.body.appendChild(modal); return { show: () => { overlay.style.display = 'block'; modal.style.display = 'flex'; }, hide: () => { overlay.style.display = 'none'; modal.style.display = 'none'; } }; } function createFloatingButton(modal, configModal) { const floatBtn = document.createElement('div'); floatBtn.id = 'plan-mapping-float-btn'; floatBtn.textContent = '随手记'; floatBtn.style.cssText = ` position: fixed; right: 90px; top: 15px; width: 60px; height: 30px; color: #fff; background-color: #38D2D2; background-image: radial-gradient(93% 87% at 87% 89%, rgba(0, 0, 0, 0.23) 0%, transparent 86.18%), radial-gradient(66% 66% at 26% 20%, rgba(255, 255, 255, 0.55) 0%, rgba(255, 255, 255, 0) 69.79%, rgba(255, 255, 255, 0) 100%); box-shadow: inset -3px -3px 9px rgba(255, 255, 255, 0.25), inset 0px 3px 9px rgba(255, 255, 255, 0.3), inset 0px 1px 1px rgba(255, 255, 255, 0.6), inset 0px -8px 36px rgba(0, 0, 0, 0.3), inset 0px 1px 5px rgba(255, 255, 255, 0.6), 2px 19px 31px rgba(0, 0, 0, 0.2); border-radius: 14px; font-weight: bold; font-size: 16px; display: flex; align-items: center; justify-content: center; cursor: pointer; z-index: 99997; font-size: 12px; text-align: center; line-height: 1.2; user-select: none; transition: transform 0.2s, box-shadow 0.2s; `; floatBtn.onmouseover = () => { floatBtn.style.transform = 'scale(1.1)'; floatBtn.style.boxShadow = '0 6px 16px rgba(0, 0, 0, 0.4)'; }; floatBtn.onmouseout = () => { floatBtn.style.transform = 'scale(1)'; floatBtn.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.3)'; }; floatBtn.onclick = () => { modal.show(); }; document.body.appendChild(floatBtn); } function createDomainConfigModal() { const overlay = document.createElement('div'); overlay.id = 'plan-mapping-domain-config-overlay'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 100002; display: none; `; const modal = document.createElement('div'); modal.id = 'plan-mapping-domain-config-modal'; modal.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90%; max-width: 600px; background-color: white; border-radius: 8px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); z-index: 100003; display: none; flex-direction: column; max-height: 80vh; `; const header = document.createElement('div'); header.style.cssText = ` display: flex; justify-content: space-between; align-items: center; padding: 20px; border-bottom: 1px solid #ddd; background-color: #f5f5f5; `; const title = document.createElement('h2'); title.textContent = '域名配置'; title.style.margin = '0'; title.style.fontSize = '18px'; header.appendChild(title); const closeBtn = document.createElement('button'); closeBtn.textContent = '×'; closeBtn.style.cssText = ` background: none; border: none; font-size: 28px; cursor: pointer; color: #666; padding: 0; width: 30px; height: 30px; line-height: 30px; text-align: center; `; closeBtn.onmouseover = () => closeBtn.style.color = '#000'; closeBtn.onmouseout = () => closeBtn.style.color = '#666'; closeBtn.onclick = () => { overlay.style.display = 'none'; modal.style.display = 'none'; }; header.appendChild(closeBtn); modal.appendChild(header); const content = document.createElement('div'); content.style.cssText = ` padding: 20px; overflow-y: auto; flex: 1; `; const description = document.createElement('div'); description.style.cssText = ` margin-bottom: 15px; padding: 10px; background-color: #f0f7ff; border-left: 3px solid #2196F3; border-radius: 4px; `; description.innerHTML = `

配置说明:

默认匹配所有域名。如需限制脚本生效范围,可配置允许的域名列表,每行一个域名。
支持通配符,如 *.example.com 表示匹配 example.com 及其所有子域名。
留空则匹配所有域名。当前域名:${window.location.hostname}

`; content.appendChild(description); const label = document.createElement('label'); label.textContent = '允许的域名列表(每行一个):'; label.style.display = 'block'; label.style.marginBottom = '8px'; label.style.fontWeight = '500'; content.appendChild(label); const textarea = document.createElement('textarea'); textarea.id = 'domain-config-textarea'; textarea.placeholder = 'example.com\n*.example.com\nsubdomain.example.com'; textarea.style.cssText = ` width: 100%; min-height: 200px; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-family: monospace; font-size: 13px; resize: vertical; box-sizing: border-box; `; const currentDomains = getAllowedDomains(); textarea.value = currentDomains.join('\n'); content.appendChild(textarea); const addCurrentBtn = document.createElement('button'); addCurrentBtn.textContent = '➕ 添加当前域名'; addCurrentBtn.style.cssText = ` margin-top: 10px; padding: 6px 12px; cursor: pointer; background-color: #f5f5f5; color: #333; border: 1px solid #ddd; border-radius: 4px; font-size: 13px; `; addCurrentBtn.onmouseover = () => addCurrentBtn.style.backgroundColor = '#e0e0e0'; addCurrentBtn.onmouseout = () => addCurrentBtn.style.backgroundColor = '#f5f5f5'; addCurrentBtn.onclick = () => { const currentDomain = window.location.hostname; const domains = textarea.value.split('\n').map(d => d.trim()).filter(d => d); if (!domains.includes(currentDomain)) { domains.push(currentDomain); textarea.value = domains.join('\n'); } }; content.appendChild(addCurrentBtn); const errorMsg = document.createElement('div'); errorMsg.id = 'domain-config-error-msg'; errorMsg.style.cssText = ` margin-top: 10px; padding: 10px; background-color: #ffebee; border-left: 3px solid #f44336; border-radius: 4px; color: #c62828; font-size: 13px; display: none; `; content.appendChild(errorMsg); const successMsg = document.createElement('div'); successMsg.id = 'domain-config-success-msg'; successMsg.style.cssText = ` margin-top: 10px; padding: 10px; background-color: #e8f5e9; border-left: 3px solid #4CAF50; border-radius: 4px; color: #2e7d32; font-size: 13px; display: none; `; content.appendChild(successMsg); const btnContainer = document.createElement('div'); btnContainer.style.cssText = ` display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; `; const cancelBtn = document.createElement('button'); cancelBtn.textContent = '取消'; cancelBtn.style.cssText = ` padding: 8px 20px; cursor: pointer; background-color: #f5f5f5; color: #333; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; `; cancelBtn.onmouseover = () => cancelBtn.style.backgroundColor = '#e0e0e0'; cancelBtn.onmouseout = () => cancelBtn.style.backgroundColor = '#f5f5f5'; cancelBtn.onclick = () => { overlay.style.display = 'none'; modal.style.display = 'none'; }; btnContainer.appendChild(cancelBtn); const saveBtn = document.createElement('button'); saveBtn.textContent = '保存'; saveBtn.style.cssText = ` padding: 8px 20px; cursor: pointer; background-color: #2196F3; color: white; border: none; border-radius: 4px; font-size: 14px; `; saveBtn.onmouseover = () => saveBtn.style.backgroundColor = '#0b7dda'; saveBtn.onmouseout = () => saveBtn.style.backgroundColor = '#2196F3'; saveBtn.onclick = () => { const inputText = textarea.value.trim(); const domains = inputText.split('\n') .map(d => d.trim()) .filter(d => d && !d.startsWith('#')); // 过滤空行和注释 saveAllowedDomains(domains); if (domains.length === 0) { successMsg.textContent = '已设置为匹配所有域名,页面将自动刷新'; } else { successMsg.textContent = `已保存 ${domains.length} 个域名配置,页面将自动刷新`; } successMsg.style.display = 'block'; errorMsg.style.display = 'none'; setTimeout(() => { window.location.reload(); }, 1500); }; btnContainer.appendChild(saveBtn); content.appendChild(btnContainer); modal.appendChild(content); overlay.onclick = (e) => { if (e.target === overlay) { overlay.style.display = 'none'; modal.style.display = 'none'; } }; document.body.appendChild(overlay); document.body.appendChild(modal); return { show: () => { overlay.style.display = 'block'; modal.style.display = 'flex'; const currentDomains = getAllowedDomains(); textarea.value = currentDomains.join('\n'); setTimeout(() => { textarea.focus(); }, 100); }, hide: () => { overlay.style.display = 'none'; modal.style.display = 'none'; } }; } function init() { if (!isDomainAllowed()) { return; } function setup() { const configModal = createDomainConfigModal(); const modal = createModal(configModal); const floatBtn = createFloatingButton(modal, configModal); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', setup); } else { setup(); } } init(); })();