生物一键快速添加
// ==UserScript== // @name Bio // @namespace http://mng.jjvip.net/ // @version 0.2.1 // @description 生物一键快速添加 // @author batcom // @match *://*.aoyinbio.cn/* // @match *://mng.jjvip.net/* // @match *://ssrcc.com.cn/* // @match https://noancell.cn/bio/create* // @icon https://www.zhihupe.com/favicon.ico // @require https://code.jquery.com/jquery-3.2.1.min.js // @require https://www.layuicdn.com/layui-v2.8.15/layui.js // @require https://unpkg.com/[email protected]/lib/template-web.js // @run-at document-end // @resource layui.css https://gitee.com/layui/layui/raw/main/dist/css/layui.css // @resource layui64.css https://gitee.com/batcom/layui/raw/main/dist/css/layui64.css // @resource woff2.css https://gitee.com/batcom/layui/raw/main/dist/css/woff2.css // @connect mng.jjvip.net // @connect noancell.cn // @connect www.ssrcc.com.cn // @connect www.aoyinbio.cn // @grant unsafeWindow // @grant GM_getResourceText // @grant GM_getResourceURL // @grant GM_addStyle // @grant GM_openInTab // @grant GM_log // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_download // @grant GM_xmlhttpRequest // @grant GM_setClipboard // @license GPL License // ==/UserScript== (function () { 'use strict'; // HTTP工具类 - 使用GM_xmlhttpRequest解决跨域问题 const HttpUtil = { /** * 发送HTTP请求 * @param {Object} options 请求配置 * @returns {Promise} 返回Promise对象 */ request (options) { return new Promise((resolve, reject) => { console.log('GM_xmlhttpRequest 请求配置:'); console.log('- Method:', options.method || 'GET'); console.log('- URL:', options.url); console.log('- Headers:', options.headers || {}); console.log('- Data:', options.data || null); GM_xmlhttpRequest({ method: options.method || 'GET', url: options.url, headers: options.headers || {}, data: options.data || null, timeout: options.timeout || 30000, onload: function (response) { console.log('GM_xmlhttpRequest 响应状态:', response.status); console.log('GM_xmlhttpRequest 响应文本:', response.responseText); if (response.status >= 200 && response.status < 300) { try { const data = options.responseType === 'json' ? JSON.parse(response.responseText) : response.responseText; resolve({ status: response.status, statusText: response.statusText, data: data, headers: response.responseHeaders }); } catch (e) { console.error('JSON解析失败:', e); console.error('原始响应文本:', response.responseText); reject(new Error('解析响应数据失败: ' + e.message)); } } else { console.error('HTTP错误响应:', response.status, response.statusText); console.error('错误响应内容:', response.responseText); reject(new Error(`HTTP错误: ${response.status} ${response.statusText}`)); } }, onerror: function (error) { reject(new Error('网络请求失败: ' + error.message)); }, ontimeout: function () { reject(new Error('请求超时')); } }); }); }, /** * 发送POST请求 * @param {string} url 请求URL * @param {Object} data 请求数据 * @param {Object} headers 请求头 * @returns {Promise} 返回Promise对象 */ post (url, data, headers = {}) { const requestData = typeof data === 'object' ? JSON.stringify(data) : data; console.log('HttpUtil POST - 发送的数据:', requestData); // 如果headers中没有content-type,则添加默认的 const finalHeaders = { ...headers }; if (!finalHeaders['content-type'] && !finalHeaders['Content-Type']) { finalHeaders['Content-Type'] = 'application/json'; } return this.request({ method: 'POST', url: url, data: requestData, headers: finalHeaders, responseType: 'json' }); }, /** * 发送GET请求 * @param {string} url 请求URL * @param {Object} headers 请求头 * @returns {Promise} 返回Promise对象 */ get (url, headers = {}) { return this.request({ method: 'GET', url: url, headers: headers, responseType: 'json' }); } }; // 创建UI界面 function createUI () { // 创建容器 const container = document.createElement('div'); container.id = 'bio-fetcher-container'; container.style.cssText = ` position: fixed; top: 20px; right: 20px; width: 400px; background: white; border: 2px solid #007bff; border-radius: 8px; padding: 15px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 10000; font-family: Arial, sans-serif; `; // 创建标题 const title = document.createElement('h3'); title.textContent = '数据抓取工具'; title.style.cssText = ` margin: 0 0 15px 0; color: #007bff; font-size: 16px; `; // 创建输入框 const input = document.createElement('input'); input.type = 'text'; input.placeholder = '请输入要抓取的URL'; input.id = 'fetch-url-input'; input.style.cssText = ` width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; margin-bottom: 10px; box-sizing: border-box; `; // 创建按钮 const button = document.createElement('button'); button.textContent = '一键抓取'; button.id = 'fetch-data-btn'; button.style.cssText = ` width: 100%; padding: 10px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; `; // 创建状态显示区域 const status = document.createElement('div'); status.id = 'fetch-status'; status.style.cssText = ` margin-top: 10px; padding: 8px; border-radius: 4px; font-size: 12px; display: none; `; // 创建最小化按钮 const minimizeBtn = document.createElement('button'); minimizeBtn.textContent = '−'; minimizeBtn.style.cssText = ` position: absolute; top: 5px; right: 5px; width: 20px; height: 20px; border: none; background: #f0f0f0; cursor: pointer; border-radius: 3px; `; // 组装UI container.appendChild(minimizeBtn); container.appendChild(title); container.appendChild(input); container.appendChild(button); container.appendChild(status); document.body.appendChild(container); return { container, input, button, status, minimizeBtn }; } // 显示状态信息 function showStatus (statusElement, message, type = 'info') { statusElement.style.display = 'block'; statusElement.textContent = message; const colors = { info: '#d1ecf1', success: '#d4edda', error: '#f8d7da', warning: '#fff3cd' }; statusElement.style.backgroundColor = colors[type] || colors.info; statusElement.style.color = type === 'error' ? '#721c24' : '#155724'; } // 构建表格HTML function buildTableHTML (attrsData) { if (!attrsData || typeof attrsData !== 'object') { return '<p>无有效数据</p>'; } `<table> ` let tableHTML = '<table><tbody>'; // 遍历attrs_txt中的所有字段 for (const [key, value] of Object.entries(attrsData)) { // 跳过pic字段,因为它是图片数组 if (key === 'pic') continue; const displayValue = Array.isArray(value) ? value.join(', ') : value; tableHTML += `<tr> <td valign="top" colspan="1" rowspan="1" width="125" style="border-color: rgb(0, 112, 192); word-break: break-all;"> ${key} </td> <td valign="top" colspan="1" rowspan="1" width="1234" style="border-color: rgb(0, 112, 192); word-break: break-all;"> <span style="color: rgb(89, 89, 89); font-family: 宋体; font-size: 14px; background-color: rgb(255, 255, 255);">${displayValue}</span> </td> </tr>` } tableHTML += '</tbody></table>'; return tableHTML; } // 通用的表单字段更新函数 function updateFormField (selector, value) { const field = document.querySelector(selector); if (field) { // 根据元素类型设置值 if (field.tagName === 'INPUT' || field.tagName === 'TEXTAREA') { field.value = value; } else if (field.tagName === 'SELECT') { // 对于select元素,尝试选择匹配的option const option = Array.from(field.options).find(opt => opt.value === value || opt.text === value); if (option) { field.selectedIndex = option.index; } } else { // 对于其他元素,设置textContent或innerHTML field.textContent = value; } // 触发各种事件,确保表单知道值已更新 field.dispatchEvent(new Event('input', { bubbles: true })); field.dispatchEvent(new Event('change', { bubbles: true })); field.dispatchEvent(new Event('blur', { bubbles: true })); return true; } return false; } // 批量更新表单字段 function updateMultipleFields (fieldMappings) { const results = {}; for (const [selector, value] of Object.entries(fieldMappings)) { results[selector] = updateFormField(selector, value); } return results; } // 更新表单字段(保持原有功能) function updateProInfoField (tableHTML) { return updateFormField('textarea[name="pro_info"], input[name="pro_info"], #pro_info, [name*="pro_info"]', tableHTML); } // 使用jQuery更新字段的函数(如果jQuery可用) function updateFieldsWithJQuery (data) { if (typeof $ !== 'undefined') { console.log('使用jQuery更新字段'); // 使用jQuery语法更新字段 $('[name="proname"], #proname').val(data.name || '').trigger('change'); $('[name="productno"], #productno').val(data.productno || '').trigger('change'); $('[name="price"], #price').val(data.price || '').trigger('change'); $('[name="platform"], #platform').val(data.platform || '').trigger('change'); // 更新标签字段(如果是数组,转换为字符串) const tags = Array.isArray(data.tag) ? data.tag.join(', ') : (data.tag || ''); $('[name="tags"], #tags, [name="tag"], #tag').val(tags).trigger('change'); // 更新其他可能的字段 if (data.attrs_txt) { // 遍历attrs_txt中的所有属性 Object.keys(data.attrs_txt).forEach(key => { if (key !== 'pic') { // 跳过图片字段 const value = data.attrs_txt[key]; const displayValue = Array.isArray(value) ? value.join(', ') : value; // 尝试多种选择器 $(`[name="${key}"], #${key}, .${key}`).val(displayValue).trigger('change'); } }); } return true; } return false; } // 使用jQuery添加隐藏input元素的函数 function addHiddenInputs (imageUrls, targetForm = 'form') { if (typeof $ !== 'undefined') { console.log('使用jQuery添加隐藏input元素'); // 清除已存在的propic[]元素(避免重复) $('input[name="propic[]"]').remove(); // 确保imageUrls是数组 const urls = Array.isArray(imageUrls) ? imageUrls : [imageUrls]; // 找到目标表单 const $form = $(targetForm).first(); if ($form.length === 0) { console.warn('未找到目标表单:', targetForm); return false; } // 为每个图片URL添加隐藏input urls.forEach((url, index) => { if (url && url.trim()) { const $hiddenInput = $('<input>', { name: 'propic[]', type: 'hidden', value: url.trim() }); // 添加到表单中 $form.append($hiddenInput); console.log(`添加隐藏input [${index}]:`, url); } }); console.log(`成功添加 ${urls.length} 个隐藏input元素`); return true; } return false; } // 原生JavaScript版本的添加隐藏input函数 function addHiddenInputsNative (imageUrls, targetFormSelector = 'form') { console.log('使用原生JavaScript添加隐藏input元素'); // 清除已存在的propic[]元素 const existingInputs = document.querySelectorAll('input[name="propic[]"]'); existingInputs.forEach(input => input.remove()); // 确保imageUrls是数组 const urls = Array.isArray(imageUrls) ? imageUrls : [imageUrls]; // 找到目标表单 const form = document.querySelector(targetFormSelector); if (!form) { console.warn('未找到目标表单:', targetFormSelector); return false; } // 为每个图片URL添加隐藏input urls.forEach((url, index) => { if (url && url.trim()) { const hiddenInput = document.createElement('input'); hiddenInput.name = 'propic[]'; hiddenInput.type = 'hidden'; hiddenInput.value = url.trim(); // 添加到表单中 form.appendChild(hiddenInput); console.log(`添加隐藏input [${index}]:`, url); } }); console.log(`成功添加 ${urls.length} 个隐藏input元素`); return true; } // 抓取数据 async function fetchData (url, statusElement) { try { showStatus(statusElement, '正在抓取数据...', 'info'); // 打印请求信息用于调试 console.log('准备发送请求到:', "https://noancell.cn/bio/fetch-data"); console.log('请求数据:', { "url": url }); // 尝试不同的数据格式,看服务器期望哪种 let requestData; // 尝试方式1: 标准JSON格式 requestData = { "url": url }; console.log('尝试发送JSON格式:', requestData); // 使用HttpUtil发送POST请求,解决跨域问题 const response = await HttpUtil.post("https://noancell.cn/bio/fetch-data", requestData, { "accept": "*/*", "accept-language": "zh-CN,zh;q=0.9,zh-TW;q=0.8,en;q=0.7", "cache-control": "no-cache", "content-type": "application/json", "origin": "https://noancell.cn", "pragma": "no-cache", "priority": "u=1, i", "referer": "https://noancell.cn/bio/create", "sec-ch-ua": "\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\"", "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": "\"Windows\"", "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-origin", "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36" } ); const data = response.data; // 添加详细的调试信息 console.log('HTTP响应状态:', response.status); console.log('完整响应数据:', data); console.log('数据类型:', typeof data); if (data && data.success && data.data && data.data.attrs_txt) { showStatus(statusElement, '数据抓取成功,正在构建表格...', 'success'); // 构建表格HTML const tableHTML = buildTableHTML(data.data.attrs_txt); // 更新表单字段 const productno = `WN-` + Math.floor(10000 + Math.random() * 90000); const proInfoUpdated = updateProInfoField(tableHTML); console.log(data.data) const price = data.data.price const name = data.data.name console.log(`name: ${name},price: ${price}`) $('#proname').val(name) $('[name="Param_24977581"]').val(price) addHiddenInputs(['https://aimg8.dlssyht.cn/u/2143541/product/9191/18380707_1726040954.jpg']) // 处理图片数组,添加隐藏input元素 // if (data.data.attrs_txt && data.data.attrs_txt.pic) { // const imageUrls = data.data.attrs_txt.pic; // console.log('找到图片数组:', imageUrls); // // 使用jQuery添加隐藏input(优先) // const jquerySuccess = addHiddenInputs(imageUrls); // // 如果jQuery不可用,使用原生JavaScript // if (!jquerySuccess) { // addHiddenInputsNative(imageUrls); // } // } else { // console.log('未找到图片数据'); // } // 更新其他字段(根据返回的数据) const fieldUpdates = updateMultipleFields({ // 通过name属性查找 // '[name="proname"]': data.data.name || '', // '[name="Param_24977581"]': data.data.price || '', '[name="prounit"]': `瓶` || '', '[name="pro_no"]': productno, '[name="Param_24977580"]': productno, '[name="Param_24977582"]': `T25`, '[name="Param_24977583"]': `已通过STR鉴定`, }); console.log('字段更新结果:', fieldUpdates); if (proInfoUpdated) { showStatus(statusElement, '数据已成功填入表单!', 'success'); } else { showStatus(statusElement, '数据抓取成功,但未找到pro_info字段', 'warning'); } GM_setClipboard(tableHTML); showStatus(statusElement, '数据已成功复制到剪切板!', 'success'); } else { // 提供更详细的错误信息 let errorMsg = '返回数据格式不正确或抓取失败'; if (!data) { errorMsg = '响应数据为空'; } else if (!data.success) { errorMsg = `API返回失败: ${data.message || '未知错误'}`; } else if (!data.data) { errorMsg = '响应中缺少data字段'; } else if (!data.data.attrs_txt) { errorMsg = '响应中缺少attrs_txt字段'; } console.error('详细错误信息:', errorMsg); console.error('实际返回的数据结构:', JSON.stringify(data, null, 2)); throw new Error(errorMsg); } } catch (error) { console.error('抓取数据时出错:', error); showStatus(statusElement, `抓取失败: ${error.message}`, 'error'); } } // 检查是否为目标页面 function isTargetPage () { const urlParams = new URLSearchParams(window.location.search); // 检查必要的URL参数 const requiredParams = { 'is_frame': '2', 'channel_id': '24932262', 'comeform': '1' }; // 验证所有必要参数是否存在且匹配 for (const [key, value] of Object.entries(requiredParams)) { if (urlParams.get(key) !== value) { return false; } } // 检查是否包含list_page参数 if (!urlParams.has('list_page')) { return false; } return true; } // 初始化插件 function init () { // 等待页面加载完成 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); return; } // 检查是否为目标页面 if (!isTargetPage()) { console.log('Bio Data Fetcher: 不是目标页面,插件未加载'); return; } const ui = createUI(); let isMinimized = false; // 绑定事件 ui.button.addEventListener('click', () => { const url = ui.input.value.trim(); if (!url) { showStatus(ui.status, '请输入有效的URL', 'warning'); return; } if (!url.startsWith('http://') && !url.startsWith('https://')) { showStatus(ui.status, 'URL必须以http://或https://开头', 'warning'); return; } fetchData(url, ui.status); }); // 最小化功能 ui.minimizeBtn.addEventListener('click', () => { isMinimized = !isMinimized; const content = ui.container.children; for (let i = 1; i < content.length; i++) { content[i].style.display = isMinimized ? 'none' : 'block'; } ui.minimizeBtn.textContent = isMinimized ? '+' : '−'; ui.container.style.height = isMinimized ? '30px' : 'auto'; }); // 回车键快捷操作 ui.input.addEventListener('keypress', (e) => { if (e.key === 'Enter') { ui.button.click(); } }); console.log('Bio Data Fetcher 插件已加载'); // 将函数暴露到全局作用域,方便调试和手动调用 window.bioFetcher = { addHiddenInputs: addHiddenInputs, addHiddenInputsNative: addHiddenInputsNative, updateFormField: updateFormField, updateMultipleFields: updateMultipleFields, updateFieldsWithJQuery: updateFieldsWithJQuery }; console.log('Bio Data Fetcher 函数已暴露到 window.bioFetcher'); } // 启动插件 init(); })();