您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
飞书在线文档World,Excel批量替换
// ==UserScript== // @name 飞书文档批量替换 // @namespace https://www.gd520.cc // @version 2025-02-19 // @description 飞书在线文档World,Excel批量替换 // @author Guodong // @license GPL // @match https://weboffice.feishu-3rd-party-services.com/office/s/* // @match https://weboffice.feishu-3rd-party-services.com/office/w/* // @icon https://www.feishu.cn/favicon.ico // @grant none // ==/UserScript== (function () { 'use strict'; // 如果 URL 中包含 require_mode=1,则退出脚本 if (window.location.href.includes('require_mode=1')) { console.log('已检测到 require_mode=1,脚本不执行'); return; } const excelDoc = window.location.href.includes('/office/s/'); function setInputValue(input, value) { const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set; nativeInputValueSetter.call(input, value); input.dispatchEvent(new Event('input', { bubbles: true })); } let debounceTimeout; function debounceSave(fn, delay) { clearTimeout(debounceTimeout); debounceTimeout = setTimeout(fn, delay); } function createReplacePanel() { const panel = document.createElement('div'); Object.assign(panel.style, { position: 'fixed', bottom: '65px', right: '30px', width: '515px', maxHeight: '360px', overflowY: 'auto', backgroundColor: '#fff', border: '1px solid #ccc', borderRadius: '8px', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', zIndex: 10000, fontFamily: 'Arial, sans-serif', fontSize: '14px', color: '#333' }); const showIconButton = document.createElement('button'); showIconButton.innerHTML = '☰ 飞书文档助手'; Object.assign(showIconButton.style, { position: 'fixed', bottom: '65px', right: '30px', display: 'none', cursor: 'pointer', zIndex: 10001 }); styleButton(showIconButton, '#795548'); showIconButton.addEventListener('click', () => { panel.style.display = 'block'; showIconButton.style.display = 'none'; }); document.body.appendChild(showIconButton); const headerDiv = document.createElement('div'); Object.assign(headerDiv.style, { position: 'sticky', top: '0', backgroundColor: '#fff', padding: '15px 15px 0 15px' }); panel.appendChild(headerDiv); const title = document.createElement('h2'); title.textContent = '飞书文档批量替换 - Guodong'; title.style.display = 'inline-block'; title.style.marginBottom = '10px'; headerDiv.appendChild(title); const hideButton = document.createElement('button'); hideButton.textContent = '隐藏'; styleButton(hideButton, '#E91E63'); hideButton.style.float = 'right'; hideButton.addEventListener('click', () => { panel.style.display = 'none'; showIconButton.style.display = 'block'; }); headerDiv.appendChild(hideButton); const buttonContainer = document.createElement('div'); buttonContainer.style.marginTop = '10px'; headerDiv.appendChild(buttonContainer); const executeButton = document.createElement('button'); executeButton.textContent = '执行'; styleButton(executeButton, '#2196F3'); buttonContainer.appendChild(executeButton); const addButton = document.createElement('button'); addButton.textContent = '添加'; styleButton(addButton, '#4CAF50'); buttonContainer.appendChild(addButton); const exportButton = document.createElement('button'); exportButton.textContent = '导出'; styleButton(exportButton, '#FF9800'); buttonContainer.appendChild(exportButton); const importButton = document.createElement('button'); importButton.textContent = '导入'; styleButton(importButton, '#9C27B0'); buttonContainer.appendChild(importButton); const docType = document.createElement('h4'); docType.textContent = excelDoc ? 'Excel' : 'Word'; Object.assign(docType.style, { marginLeft: '10px', display: 'inline-block', marginBottom: '5px' }); buttonContainer.appendChild(docType); const replaceListContainer = document.createElement('div'); replaceListContainer.style.marginLeft = '10px'; replaceListContainer.style.marginTop = '5px'; panel.appendChild(replaceListContainer); let replacePairs = []; try { replacePairs = JSON.parse(localStorage.getItem('replacePairs')) || []; } catch (error) { console.log('读取规则失败,可能数据格式错误', error); } console.log('油猴:Loaded replace pairs from localStorage:', replacePairs); function updateReplaceList() { replaceListContainer.innerHTML = ''; replacePairs.forEach((pair, index) => { const pairDiv = document.createElement('div'); pairDiv.style.display = 'flex'; pairDiv.style.alignItems = 'center'; pairDiv.style.margin = '5px'; const findInput = document.createElement('input'); findInput.type = 'text'; findInput.placeholder = '查找'; findInput.value = pair.findText; styleInput(findInput); findInput.addEventListener('input', () => { pair.findText = findInput.value; debounceSave(saveReplacePairs, 300); }); const replaceInput = document.createElement('input'); replaceInput.type = 'text'; replaceInput.placeholder = '替换'; replaceInput.value = pair.replaceText; styleInput(replaceInput); replaceInput.addEventListener('input', () => { pair.replaceText = replaceInput.value; debounceSave(saveReplacePairs, 300); }); const upButton = document.createElement('button'); upButton.textContent = '↑'; styleButton(upButton, '#795548'); upButton.style.marginRight = '4px'; upButton.addEventListener('click', () => { if (index > 0) { [replacePairs[index - 1], replacePairs[index]] = [replacePairs[index], replacePairs[index - 1]]; saveReplacePairs(); updateReplaceList(); } }); const downButton = document.createElement('button'); downButton.textContent = '↓'; styleButton(downButton, '#795548'); downButton.style.marginRight = '4px'; downButton.addEventListener('click', () => { if (index < replacePairs.length - 1) { [replacePairs[index + 1], replacePairs[index]] = [replacePairs[index], replacePairs[index + 1]]; saveReplacePairs(); updateReplaceList(); } }); const deleteButton = document.createElement('button'); deleteButton.textContent = 'X'; styleButton(deleteButton, '#f44336'); deleteButton.addEventListener('click', () => { replacePairs.splice(index, 1); saveReplacePairs(); updateReplaceList(); }); pairDiv.appendChild(findInput); pairDiv.appendChild(replaceInput); pairDiv.appendChild(upButton); pairDiv.appendChild(downButton); pairDiv.appendChild(deleteButton); replaceListContainer.appendChild(pairDiv); }); } function saveReplacePairs() { localStorage.setItem('replacePairs', JSON.stringify(replacePairs)); } addButton.addEventListener('click', () => { replacePairs.push({ findText: '', replaceText: '' }); updateReplaceList(); saveReplacePairs(); }); executeButton.addEventListener('click', async () => { await performMultipleReplaces(replacePairs); }); exportButton.addEventListener('click', () => { const now = new Date(); const fileName = formatTime(now) + 'FeishuRule.txt'; const blob = new Blob([JSON.stringify(replacePairs, null, 2)], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = fileName; link.click(); URL.revokeObjectURL(url); }); importButton.addEventListener('click', () => { const input = document.createElement('input'); input.type = 'file'; input.accept = '.txt'; input.onchange = (e) => { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = () => { try { const data = JSON.parse(reader.result); if (Array.isArray(data)) { replacePairs = data; saveReplacePairs(); updateReplaceList(); } } catch (err) { console.log('导入规则时发生错误:', err); } }; reader.readAsText(file); }; input.click(); }); document.body.appendChild(panel); updateReplaceList(); } function styleButton(btn, bgColor) { btn.style.padding = '5px 10px'; btn.style.marginRight = '10px'; btn.style.backgroundColor = bgColor; btn.style.color = '#fff'; btn.style.border = 'none'; btn.style.cursor = 'pointer'; btn.style.borderRadius = '4px'; } function styleInput(inp) { inp.style.flex = '1'; inp.style.padding = '4px'; inp.style.marginRight = '5px'; } function formatTime(d) { const y = d.getFullYear(); const M = String(d.getMonth() + 1).padStart(2, '0'); const _d = String(d.getDate()).padStart(2, '0'); const h = String(d.getHours()).padStart(2, '0'); const m = String(d.getMinutes()).padStart(2, '0'); const s = String(d.getSeconds()).padStart(2, '0'); return `${y}${M}${_d}_${h}${m}${s}_`; } function waitForElement(selector, timeout = 5000) { return new Promise((resolve, reject) => { const element = document.querySelector(selector); if (element) { resolve(element); return; } const observer = new MutationObserver((mutations, obs) => { const element = document.querySelector(selector); if (element) { resolve(element); obs.disconnect(); } }); observer.observe(document.body, { childList: true, subtree: true }); setTimeout(() => { observer.disconnect(); reject(`油猴:等待元素 ${selector} 超时`); }, timeout); }); } function waitForReplaceCompletion(successSelector, failSelector, timeout = 10000) { return new Promise((resolve, reject) => { const startTime = Date.now(); const checkCompletion = () => { const successMessage = document.querySelector(successSelector); const failMessage = document.querySelector(failSelector); if (successMessage) { console.log('油猴:替换操作成功'); resolve('success'); } else if (failMessage) { console.log('油猴:替换操作失败'); resolve('fail'); } else if (Date.now() - startTime > timeout) { console.log('油猴:替换操作等待超时'); reject('timeout'); } else { requestAnimationFrame(checkCompletion); } }; checkCompletion(); }); } function simulateCtrlH() { const event = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: 'h', code: 'KeyH', keyCode: 72, charCode: 72, ctrlKey: true }); document.dispatchEvent(event); } async function performReplace(findText, replaceText) { try { simulateCtrlH(); if (excelDoc) { const excelFindSelector = '.row:not(.replace) .kd-input-inner'; const excelReplaceSelector = '.row.replace .kd-input-inner'; const findInput = await waitForElement(excelFindSelector); if (findInput) { setInputValue(findInput, findText); console.log(`油猴:查找框已设置为: ${findText}`); } const replaceInput = await waitForElement(excelReplaceSelector); if (replaceInput) { setInputValue(replaceInput, replaceText); console.log(`油猴:替换框已设置为: ${replaceText}`); } document.querySelectorAll('.kd-radio-input')[1]?.click(); const replaceAllButton = document.querySelector('.et-cmd-bar-find-panel .wo-button.narrow.replace-all .kd-button.kd-button-secondary.kd-button-lg'); if (replaceAllButton) { replaceAllButton.click(); } await new Promise(resolve => setTimeout(resolve, 200)); await waitForReplaceCompletion('.kd-button.kd-button-primary.kd-button-lg.confirm-btn', '.cell_shower .error') .then(async (result) => { if (result === 'success') { document.querySelector('.kd-button.kd-button-primary.kd-button-lg.confirm-btn')?.click(); console.log('油猴:替换操作成功'); } else if (result === 'fail') { console.log('油猴:无需替换'); } await new Promise(resolve => setTimeout(resolve, 200)); }); } else { const wordFindSelector = '.component-find-input'; const wordReplaceSelector = '.component-replace-input'; const findInput = await waitForElement(wordFindSelector); if (findInput) { setInputValue(findInput, findText); console.log(`油猴:查找框已设置为: ${findText}`); } const replaceInput = await waitForElement(wordReplaceSelector); if (replaceInput) { setInputValue(replaceInput, replaceText); console.log(`油猴:替换框已设置为: ${replaceText}`); } const replaceButton = await waitForElement('.kd-button.kd-button-primary.kd-button-lg'); if (replaceButton && !replaceButton.disabled) { replaceButton.click(); console.log('油猴:已点击全部替换按钮'); } else { console.log('油猴:全部替换按钮不可点击或未找到'); return; } await new Promise(resolve => setTimeout(resolve, 200)); await waitForReplaceCompletion('.kd-message-text', '.find-result.empty-result'); await new Promise(resolve => setTimeout(resolve, 200)); } } catch (error) { console.log(`油猴:执行替换操作时出现错误: ${error}`); } } async function performMultipleReplaces(replacePairs) { for (const pair of replacePairs) { if (pair.findText) { await performReplace(pair.findText, pair.replaceText); } } } createReplacePanel(); })();