// ==UserScript== // @name SHEIN登录管理器5.1 // @namespace http://tampermonkey.net/ // @version 5.1 // @description 按钮横向排布+导入导出不乱码+只检测login+排序开关+【Vue真实输入修复】+新增标记列 // @match https://sso.geiwohuo.com/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @require https://cdn.jsdelivr.net/npm/file-saver@2.0.5/dist/FileSaver.min.js // ==/UserScript== (function() { 'use strict'; const SELECTOR = { username: '#container > div > div.style__login_form_container--vs4UNdFH > div:nth-child(3) > form > div:nth-child(1) > div.c-dqgsow.soui-form-item-control > div > div > input', password: '#container > div > div.style__login_form_container--vs4UNdFH > div:nth-child(3) > form > div:nth-child(2) > div.c-dqgsow.soui-form-item-control > div > div > input', loginBtn: '#container > div > div.style__login_form_container--vs4UNdFH > div:nth-child(3) > form > button' }; GM_addStyle(` #accountManager{position:fixed;top:50%;left:50%;transform: translate(-120%, -50%);background:#fff;border-radius:14px;box-shadow:0 8px 30px rgba(0,0,0,.12);z-index:99999;padding:20px;font-size:14px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;border:1px solid #f0f0f0} .manager-header{display:flex;gap:10px;margin-bottom:16px;flex-wrap:wrap} .manager-header button{padding:8px 14px;border:none;border-radius:8px;background:#3370ff;color:#fff;cursor:pointer;font-weight:500} .manager-header button.success{background:#00c48c} .manager-header button.sort-mode{background:#722ED1} .account-table{width:100%;border-collapse:collapse;border-radius:10px;overflow:hidden} .account-table th{background:#f7f8fa;padding:10px;text-align:center;font-weight:600} .account-table td{padding:10px;text-align:center;border-bottom:1px solid #f2f2f2;min-width:90px} .account-table td:nth-child(2){padding:10px;text-align:center;border-bottom:1px solid #f2f2f2;min-width:120px} .account-table tr:hover{background:#fafbfc} /* 标记行高亮样式 */ .account-table tr.marked-row {background-color: #e6f7ff !important; border-bottom: 1px solid #91caff;} .btn-group{display:flex;gap:4px;justify-content:center;flex-wrap:nowrap} .table-btn{padding:4px 6px;border:none;border-radius:6px;color:#fff;cursor:pointer;font-size:12px;white-space:nowrap} .login-btn{background:#3370ff} .edit-btn{background:#ffab00} .del-btn{background:#ff4d4f} .sort-btn{display:none;background:#13C2C2} .show-sort .sort-btn{display:inline-block} /* 标记圆形按钮样式 */ .mark-btn { width: 18px; height: 18px; border-radius: 50%; border: 2px solid #d9d9d9; background: #fff; cursor: pointer; transition: all 0.2s; } .mark-btn.active { background: #1890ff; border-color: #1890ff; } .modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:#fff;padding:24px;border-radius:14px;box-shadow:0 10px 40px rgba(0,0,0,.15);z-index:100000;width:340px} .modal input{width:100%;margin:8px 0;padding:10px 12px;border:1px solid #e5e6eb;border-radius:8px;box-sizing:border-box} .modal-btns{display:flex;justify-content:flex-end;gap:10px;margin-top:20px} .modal-btns button{padding:7px 14px;border-radius:8px;border:none;cursor:pointer} .modal-btns button:nth-child(1){background:#f2f3f5;color:#333} .modal-btns button:nth-child(2){background:#3370ff;color:#fff} `); // 排序模式开关 let sortMode = false; const STORAGE_KEY = 'geiwohuo_account_list'; function getAccounts() { return GM_getValue(STORAGE_KEY, []); } function saveAccounts(list) { GM_setValue(STORAGE_KEY, list); renderTable(); } // 渲染表格 function renderTable() { const list = getAccounts(); const tbody = document.querySelector('#accountTable tbody'); const table = document.querySelector('#accountTable'); if (!tbody) return; if (sortMode) { table.classList.add('show-sort'); } else { table.classList.remove('show-sort'); } tbody.innerHTML = ''; list.forEach((item, index) => { const tr = document.createElement('tr'); // 标记行:如果marked为true,添加高亮class if (item.marked) tr.classList.add('marked-row'); tr.innerHTML = ` ${item.type || '-'} ${item.shop || '-'} ${item.account}
`; tbody.appendChild(tr); }); } // ============================================== // 【终极修复】完全照搬你提供的 Vue 真实输入方法 // 100% 解决假输入 / 回弹 / 不触发事件 // ============================================== function triggerEvent(el, type) { if (!el) return; const e = new Event(type, { bubbles: true, cancelable: true }); el.dispatchEvent(e); } function setVueInputValue(input, value) { if (!input) return; input.focus(); input.value = ''; triggerEvent(input, 'input'); triggerEvent(input, 'change'); const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set; if (nativeInputValueSetter) { nativeInputValueSetter.call(input, value); } else { input.value = value; } triggerEvent(input, 'input'); triggerEvent(input, 'change'); triggerEvent(input, 'blur'); } // 登录逻辑(使用真实Vue输入) function doLogin(index) { const list = getAccounts(); const data = list[index]; if (!data) return; const userInput = document.querySelector(SELECTOR.username); const pwdInput = document.querySelector(SELECTOR.password); const loginBtn = document.querySelector(SELECTOR.loginBtn); // 真实填入,不会假输入! setVueInputValue(userInput, data.account); setVueInputValue(pwdInput, data.password); setTimeout(() => { loginBtn?.click(); }, 200); } // 上移 function moveUp(index) { if (index <= 0) return; const list = getAccounts(); [list[index], list[index - 1]] = [list[index - 1], list[index]]; saveAccounts(list); } // 下移 function moveDown(index) { const list = getAccounts(); if (index >= list.length - 1) return; [list[index], list[index + 1]] = [list[index + 1], list[index]]; saveAccounts(list); } // 标记切换功能 function toggleMark(index) { const list = getAccounts(); if (!list[index]) return; // 切换marked状态 list[index].marked = !list[index].marked; saveAccounts(list); } // 弹窗 function showModal(editIndex = -1) { const list = getAccounts(); const isEdit = editIndex >= 0; const data = isEdit ? list[editIndex] : { type: '', shop: '', account: '', password: '', marked: false }; const modal = document.createElement('div'); modal.className = 'modal'; modal.innerHTML = `

${isEdit ? '修改账号' : '添加账号'}

`; document.body.appendChild(modal); modal.querySelector('#m_cancel').onclick = () => modal.remove(); modal.querySelector('#m_save').onclick = () => { const type = modal.querySelector('#m_type').value.trim(); const shop = modal.querySelector('#m_shop').value.trim(); const account = modal.querySelector('#m_account').value.trim(); const password = modal.querySelector('#m_pwd').value.trim(); if (!account || !password) { alert('账号密码必填'); return; } // 保存时保留marked状态 const newData = { type, shop, account, password, marked: data.marked || false }; if (isEdit) list[editIndex] = newData; else list.push(newData); saveAccounts(list); modal.remove(); }; } // 导出 function exportCSV() { const list = getAccounts(); if (list.length === 0) { alert('无账号可导出'); return; } const header = '标记状态,类型,店名,账号,密码\n'; const rows = list.map(i => `${i.marked ? '已标记' : '未标记'},${i.type},${i.shop},${i.account},${i.password}`).join('\n'); const BOM = new Uint8Array([0xEF, 0xBB, 0xBF]); const blob = new Blob([BOM, header + rows], { type: 'text/csv;charset=utf-8' }); saveAs(blob, `给我火账号_${Date.now()}.csv`); } // 导入 function importCSV() { const input = document.createElement('input'); input.type = 'file'; input.accept = '.csv'; input.onchange = e => { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = evt => { try { let buffer = evt.target.result; let decoder = new TextDecoder('utf-8'); let str = decoder.decode(buffer).replace(/^\ufeff/, ''); if (/[\u4e00-\u9fa5]/.test(str) === false && /[�]/.test(str)) { decoder = new TextDecoder('gbk'); str = decoder.decode(buffer).replace(/^\ufeff/, ''); } const lines = str.split(/\r?\n/).filter(i => i.trim()); lines.shift(); const accounts = lines.map(line => { const [markStatus = '', type = '', shop = '', account = '', password = ''] = line.split(','); return { type: type.trim(), shop: shop.trim(), account: account.trim(), password: password.trim(), marked: markStatus.trim() === '已标记' // 导入时识别标记状态 }; }).filter(i => i.account && i.password); if (accounts.length === 0) { alert('未读取到有效账号'); return; } if (confirm(`导入 ${accounts.length} 个账号,是否覆盖本地?`)) { saveAccounts(accounts); } } catch (err) { alert('导入失败:格式错误'); } }; reader.readAsArrayBuffer(file); }; input.click(); } // 绑定事件 function bindTableEvents() { document.querySelector('#accountTable').onclick = e => { const index = parseInt(e.target.dataset.index); if (e.target.classList.contains('mark-btn')) toggleMark(index); else if (e.target.classList.contains('login-btn')) doLogin(index); else if (e.target.classList.contains('edit-btn')) showModal(index); else if (e.target.classList.contains('up-btn')) moveUp(index); else if (e.target.classList.contains('down-btn')) moveDown(index); else if (e.target.classList.contains('del-btn')) { if (confirm('确定删除?')) { const list = getAccounts(); list.splice(index, 1); saveAccounts(list); } } }; } let managerInstance = null; function initUI() { if (managerInstance) return; const ui = document.createElement('div'); ui.id = 'accountManager'; ui.innerHTML = `
标记 类型 店名 账号 操作
`; document.body.appendChild(ui); managerInstance = ui; document.querySelector('#addAccount').onclick = () => showModal(); document.querySelector('#exportCSV').onclick = exportCSV; document.querySelector('#importCSV').onclick = importCSV; // 排序模式开关 document.querySelector('#sortBtn').onclick = () => { sortMode = !sortMode; document.querySelector('#sortBtn').textContent = sortMode ? '✅ 退出排序' : '🔼 排序模式'; renderTable(); }; renderTable(); bindTableEvents(); } function destroyUI() { if (managerInstance) { managerInstance.remove(); managerInstance = null; } } function checkUrl() { return location.href.includes('login') || location.hash.includes('login'); } function start() { if (checkUrl()) initUI(); else destroyUI(); } window.addEventListener('hashchange', start); start(); })();