// ==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();
})();