efca70ecb2
首次推送
1161 lines
46 KiB
JavaScript
1161 lines
46 KiB
JavaScript
/**
|
|
* --------------------------------------------------------------------
|
|
* @description 项目前端核心文件,文件负责处理TVbox规则编辑器的所有前端逻辑,包括表单渲染、数据处理、
|
|
* 规则测试、弹窗管理以及与服务器的交互。
|
|
* @author https://t.me/CCfork
|
|
* @copyright Copyright (c) 2025, https://t.me/CCfork
|
|
* --------------------------------------------------------------------
|
|
*/
|
|
|
|
let currentInputEle;
|
|
window.globalVariables = {};
|
|
let tempDetailPageUrl = '';
|
|
let testResultsCache = [];
|
|
let isHtmlMode = false;
|
|
const MOBILE_UA = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1';
|
|
const PC_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36';
|
|
|
|
const jsoupToggleMap = {
|
|
'home': '首页片单是否Jsoup写法',
|
|
'category-rules-basic': '分类片单是否Jsoup写法',
|
|
'detail': '详情是否Jsoup写法',
|
|
'play': '选集标题链接是否Jsoup写法',
|
|
'search': '搜索片单是否Jsoup写法'
|
|
};
|
|
|
|
/**
|
|
* 编译并渲染 Handlebars 模板。
|
|
* @param {string} templateId - 模板的 script 标签ID。
|
|
* @param {object} [data={}] - 渲染模板所需的数据对象。
|
|
* @returns {string} 渲染后的HTML字符串,如果模板未找到则返回空字符串。
|
|
*/
|
|
function renderTemplate(templateId, data = {}) {
|
|
const source = document.getElementById(templateId)?.innerHTML;
|
|
if (!source) {
|
|
console.error(`Template with ID '${templateId}' not found.`);
|
|
return '';
|
|
}
|
|
const template = Handlebars.compile(source);
|
|
return template(data);
|
|
}
|
|
|
|
/**
|
|
* 解析JSON格式的规则内容并填充到表单中。
|
|
* @param {string} content - 包含规则的JSON格式字符串。
|
|
*/
|
|
function parseAndRenderRules(content) {
|
|
try {
|
|
let cleanedContentLines = content.split('\n').filter(line => !line.trim().startsWith('//') && !line.trim().startsWith('/*'));
|
|
let cleanedContent = cleanedContentLines.join('\n');
|
|
cleanedContent = cleanedContent.replace(/[\u0000-\u001F\u007F-\u009F]/g, '');
|
|
cleanedContent = cleanedContent.replace(/^\s*[\r\n]/gm, '');
|
|
|
|
let rules;
|
|
try {
|
|
rules = JSON.parse(cleanedContent);
|
|
} catch (err) {
|
|
let aggressiveContent = cleanedContent.replace(/,\s*([}\]])/g, '$1');
|
|
rules = JSON.parse(aggressiveContent);
|
|
}
|
|
|
|
fillForm(rules);
|
|
showToast('规则内容加载成功!', 'success');
|
|
} catch (error) {
|
|
console.error('解析JSON文件失败:', error);
|
|
showToast('规则内容加载失败,请检查文件格式。', 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 从表单中收集所有数据并组装成一个JSON对象。
|
|
* @returns {object} 包含所有表单数据的对象。
|
|
*/
|
|
function collectFormDataIntoJson() {
|
|
const form = document.getElementById('ruleForm');
|
|
const inputs = form.querySelectorAll('input, textarea');
|
|
const data = {};
|
|
|
|
// 基础字段
|
|
inputs.forEach(input => {
|
|
if (input.id && input.value) {
|
|
// 对于筛选数据,尝试解析为JSON对象
|
|
if (input.id === '筛选数据') {
|
|
try {
|
|
data[input.id] = JSON.parse(input.value);
|
|
} catch (e) {
|
|
data[input.id] = input.value; // 解析失败则存为字符串
|
|
}
|
|
} else {
|
|
data[input.id] = input.value;
|
|
}
|
|
}
|
|
});
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* 页面加载完成后的主入口函数。
|
|
*/
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
/**
|
|
* DOM加载完毕后执行的匿名函数,负责初始化页面、实例化组件和绑定事件。
|
|
*/
|
|
if (typeof formFieldsData === 'undefined') {
|
|
console.error('el.js 未能成功加载,无法渲染表单。');
|
|
alert('核心数据文件 el.js 加载失败,请检查文件路径或网络连接。');
|
|
return;
|
|
}
|
|
Handlebars.registerHelper('eq', (a, b) => a === b);
|
|
|
|
const testModal = new Modal({
|
|
id: 'testModal',
|
|
title: '测试CSS选择器',
|
|
content: renderTemplate('test-modal-template'),
|
|
footer: '<button id="manualTestBtn" class="btn primary-btn">运行测试</button>'
|
|
});
|
|
|
|
const variableModal = new Modal({
|
|
id: 'variableModal',
|
|
title: '设置变量默认值',
|
|
content: renderTemplate('variable-modal-template'),
|
|
footer: '<button class="btn primary-btn" id="saveVariablesBtn">保存</button>'
|
|
});
|
|
|
|
const helpModal = new Modal({
|
|
id: 'helpModal',
|
|
title: 'TVbox规则语法帮助',
|
|
content: renderTemplate('help-modal-template')
|
|
});
|
|
|
|
document.getElementById('manualTestBtn').addEventListener('click', manualRunTest);
|
|
document.getElementById('applySelectorBtn').addEventListener('click', applySelectorToField);
|
|
document.getElementById('toggleSourceBtn').addEventListener('click', () => {
|
|
const sourceTextarea = document.getElementById('sourceHtmlInput');
|
|
sourceTextarea.style.display = sourceTextarea.style.display === 'none' ? 'block' : 'none';
|
|
});
|
|
document.getElementById('saveVariablesBtn').addEventListener('click', saveVariables);
|
|
document.getElementById('toggleResultModeBtn').addEventListener('click', toggleResultMode);
|
|
|
|
// document.getElementById('advancedModeBtn').addEventListener('click', toggleAdvancedMode);
|
|
document.getElementById('autoTestBtn').addEventListener('click', startAutomatedTest);
|
|
|
|
document.getElementById('variableBtn').addEventListener('click', () => {
|
|
openVariableModal(variableModal);
|
|
});
|
|
// document.getElementById('helpBtn').addEventListener('click', () => helpModal.open());
|
|
document.getElementById('saveBtn').addEventListener('click', () => {
|
|
if (!filePathFromServer) {
|
|
showToast('文件路径未知,无法保存。', 'error');
|
|
return;
|
|
}
|
|
|
|
const jsonData = collectFormDataIntoJson();
|
|
const fileContent = JSON.stringify(jsonData, null, 2);
|
|
|
|
const formData = new FormData();
|
|
formData.append('filePath', filePathFromServer);
|
|
formData.append('fileContent', fileContent);
|
|
|
|
showToast('正在保存...', 'info');
|
|
fetch('/index.php/Edit/save', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(res => res.json())
|
|
.then(result => {
|
|
if (result.success) {
|
|
showToast(result.message, 'success');
|
|
} else {
|
|
throw new Error(result.message);
|
|
}
|
|
})
|
|
.catch(err => {
|
|
showToast(`保存失败: ${err.message}`, 'error');
|
|
});
|
|
});
|
|
document.getElementById('editBtn').addEventListener('click',
|
|
() => {
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const file = urlParams.get('file');
|
|
window.open('/index.php/Edit?file=' + file + '&api=editor', '_blank')
|
|
}
|
|
|
|
);
|
|
|
|
document.addEventListener('input', (event) => {
|
|
if (event.target.closest('#ruleForm') || event.target.closest('#testModal') || event.target.closest('#variableModal')) {
|
|
if (event.target.id && event.target.id.startsWith('var-')) {
|
|
return;
|
|
}
|
|
saveFormData();
|
|
}
|
|
});
|
|
|
|
renderForm();
|
|
//loadAdvancedModeState();
|
|
loadVariables();
|
|
|
|
if (typeof fileContentFromServer !== 'undefined' && fileContentFromServer && !fileContentFromServer.startsWith('错误:')) {
|
|
parseAndRenderRules(fileContentFromServer);
|
|
} else if (typeof fileContentFromServer !== 'undefined') {
|
|
alert(fileContentFromServer);
|
|
} else {
|
|
loadFormData();
|
|
}
|
|
|
|
window.modals = {
|
|
testModal,
|
|
variableModal,
|
|
helpModal
|
|
};
|
|
});
|
|
|
|
/**
|
|
* 根据 formFieldsData 渲染整个表单结构。
|
|
*/
|
|
function renderForm() {
|
|
const fieldTemplate = Handlebars.compile(document.getElementById('form-field-template').innerHTML);
|
|
|
|
const renderTabContent = (tabId, fields) => {
|
|
const container = document.getElementById(tabId);
|
|
if (!container) return;
|
|
|
|
let html = '';
|
|
fields.forEach(field => {
|
|
html += fieldTemplate(field);
|
|
});
|
|
container.innerHTML = html;
|
|
|
|
// 渲染完成后,为需要复杂交互的按钮绑定事件
|
|
fields.forEach(field => {
|
|
const formGroup = container.querySelector(`[for="${field.id}"]`)?.parentElement;
|
|
if (!formGroup) return;
|
|
|
|
const buttonContainer = formGroup.querySelector('.input-with-buttons');
|
|
|
|
if (field.test_btn) {
|
|
const testBtn = document.createElement('button');
|
|
testBtn.type = 'button';
|
|
testBtn.className = 'btn secondary-btn btn-sm';
|
|
testBtn.innerText = '测试';
|
|
testBtn.onclick = () => openTestModal(field.id);
|
|
buttonContainer.appendChild(testBtn);
|
|
}
|
|
|
|
if (field.var_btn) {
|
|
const varBtn = document.createElement('button');
|
|
varBtn.type = 'button';
|
|
varBtn.className = 'btn secondary-btn btn-sm';
|
|
varBtn.innerText = '变量';
|
|
varBtn.onclick = () => toggleAccordion(field.id, field.var_btn);
|
|
buttonContainer.appendChild(varBtn);
|
|
}
|
|
});
|
|
};
|
|
|
|
for (const tabName in formFieldsData) {
|
|
if (tabName === 'category') {
|
|
renderTabContent('category-rules-basic', formFieldsData.category.rules);
|
|
renderTabContent('category-filter-menu', formFieldsData.category.filters);
|
|
} else {
|
|
renderTabContent(tabName, formFieldsData[tabName]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 将字段渲染到指定的子标签页容器中。
|
|
* @param {string} containerId - 容器元素的ID。
|
|
* @param {Array<object>} fields - 要渲染的字段定义数组。
|
|
*/
|
|
function renderSubTabFields(containerId, fields) {
|
|
const container = document.getElementById(containerId);
|
|
if (container) {
|
|
renderFieldsToContainer(container, fields);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 将一组字段渲染到指定的容器元素中。
|
|
* @param {HTMLElement} container - 目标容器元素。
|
|
* @param {Array<object>} fields - 要渲染的字段定义数组。
|
|
*/
|
|
function renderFieldsToContainer(container, fields) {
|
|
container.innerHTML = '';
|
|
fields.forEach(field => {
|
|
const formGroup = document.createElement('div');
|
|
formGroup.className = 'form-group';
|
|
if (field.isAdvanced) {
|
|
formGroup.classList.add('advanced-field');
|
|
}
|
|
|
|
const label = document.createElement('label');
|
|
label.setAttribute('for', field.id);
|
|
label.innerText = field.key;
|
|
|
|
const input = field.type === 'textarea' ? document.createElement('textarea') : document.createElement('input');
|
|
input.type = field.type || 'text';
|
|
input.id = field.id;
|
|
input.name = field.id;
|
|
|
|
formGroup.appendChild(label);
|
|
formGroup.appendChild(input);
|
|
|
|
if (field.test_btn) {
|
|
const testBtn = document.createElement('button');
|
|
testBtn.type = 'button';
|
|
testBtn.className = 'test-btn';
|
|
testBtn.innerText = '测试';
|
|
testBtn.onclick = () => openTestModal(field.id);
|
|
formGroup.appendChild(testBtn);
|
|
}
|
|
|
|
if (field.var_btn) {
|
|
const varBtn = document.createElement('button');
|
|
varBtn.type = 'button';
|
|
varBtn.className = 'var-btn';
|
|
varBtn.innerText = '变量';
|
|
varBtn.onclick = () => toggleAccordion(field.id, field.var_btn);
|
|
formGroup.appendChild(varBtn);
|
|
}
|
|
|
|
container.appendChild(formGroup);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 切换高级/普通模式。
|
|
*/
|
|
function toggleAdvancedMode() {
|
|
const body = document.body;
|
|
const btn = document.getElementById('advancedModeBtn');
|
|
const isActive = body.classList.toggle('advanced-mode-active');
|
|
btn.classList.toggle('active', isActive);
|
|
btn.innerText = isActive ? '高级模式' : '普通模式';
|
|
localStorage.setItem('tvbox_advanced_mode', isActive);
|
|
|
|
if (isActive) {
|
|
showToast('已切换至高级模式', 'info');
|
|
} else {
|
|
showToast('已切换至普通模式', 'info');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 从localStorage加载高级模式状态。
|
|
*/
|
|
function loadAdvancedModeState() {
|
|
const isActive = localStorage.getItem('tvbox_advanced_mode') === 'true';
|
|
const body = document.body;
|
|
const btn = document.getElementById('advancedModeBtn');
|
|
if (isActive) {
|
|
body.classList.add('advanced-mode-active');
|
|
btn.classList.add('active');
|
|
btn.innerText = '高级模式';
|
|
} else {
|
|
body.classList.remove('advanced-mode-active');
|
|
btn.classList.remove('active');
|
|
btn.innerText = '普通模式';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 将当前表单数据保存到localStorage。
|
|
*/
|
|
function saveFormData() {
|
|
const formInputs = document.querySelectorAll('#ruleForm input, #ruleForm textarea');
|
|
const formData = {};
|
|
formInputs.forEach(input => {
|
|
if (input.id) {
|
|
formData[input.id] = input.value;
|
|
}
|
|
});
|
|
localStorage.setItem('tvbox_form_data', JSON.stringify(formData));
|
|
}
|
|
|
|
/**
|
|
* 从localStorage加载并填充表单数据。
|
|
*/
|
|
function loadFormData() {
|
|
const savedData = localStorage.getItem('tvbox_form_data');
|
|
if (savedData) {
|
|
try {
|
|
const formData = JSON.parse(savedData);
|
|
for (const key in formData) {
|
|
const input = document.getElementById(key);
|
|
if (input) {
|
|
input.value = formData[key];
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error('加载本地表单数据失败:', e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 使用给定的规则对象填充表单。
|
|
* @param {object} rules - 从JSON文件解析出的规则对象。
|
|
*/
|
|
function fillForm(rules) {
|
|
for (const key in rules) {
|
|
const input = document.getElementById(key);
|
|
if (input) {
|
|
if (key === '筛选数据' && typeof rules[key] === 'object') {
|
|
input.value = JSON.stringify(rules[key], null, 2);
|
|
} else {
|
|
input.value = rules[key];
|
|
}
|
|
}
|
|
}
|
|
saveFormData();
|
|
}
|
|
|
|
/**
|
|
* 切换分类页面下的子标签页。
|
|
* @param {Event} evt - 点击事件对象。
|
|
* @param {string} tabName - 要显示的目标子标签页内容ID。
|
|
*/
|
|
function openSubTab(evt, tabName) {
|
|
const tabContents = document.querySelectorAll('#category .sub-tab-content');
|
|
tabContents.forEach(tab => tab.style.display = "none");
|
|
|
|
const tabButtons = document.querySelectorAll('#category .sub-tabs .sub-tab-btn');
|
|
tabButtons.forEach(btn => btn.classList.remove("active"));
|
|
|
|
const tabElement = document.getElementById(tabName);
|
|
if (tabElement) {
|
|
tabElement.style.display = "block";
|
|
}
|
|
evt.currentTarget.classList.add("active");
|
|
}
|
|
|
|
/**
|
|
* 解析请求头参数字符串。
|
|
* @param {string} headerString - 格式化的请求头字符串。
|
|
* @returns {object} 解析后的请求头对象。
|
|
*/
|
|
function parseHeaders(headerString) {
|
|
const headers = {};
|
|
if (!headerString) return headers;
|
|
const trimmed = headerString.trim();
|
|
if (trimmed === '手机' || trimmed === 'MOBILE_UA') {
|
|
headers['User-Agent'] = MOBILE_UA;
|
|
return headers;
|
|
}
|
|
if (trimmed === '电脑' || trimmed === 'PC_UA') {
|
|
headers['User-Agent'] = PC_UA;
|
|
return headers;
|
|
}
|
|
const pairs = trimmed.split('#');
|
|
pairs.forEach(pair => {
|
|
if (pair.includes('$')) {
|
|
const parts = pair.split('$');
|
|
const key = parts.shift().trim();
|
|
const value = parts.join('$').trim();
|
|
if (key && value) {
|
|
headers[key] = value;
|
|
}
|
|
}
|
|
});
|
|
return headers;
|
|
}
|
|
|
|
/**
|
|
* 根据字段ID查找其在 formFieldsData 中的定义。
|
|
* @param {string} fieldId - 字段的ID。
|
|
* @returns {object|null} 找到的字段定义对象,或null。
|
|
*/
|
|
function findFieldById(fieldId) {
|
|
for (const tabKey in formFieldsData) {
|
|
const tabData = formFieldsData[tabKey];
|
|
let fields = [];
|
|
if (Array.isArray(tabData)) {
|
|
fields = tabData;
|
|
} else if (typeof tabData === 'object' && tabData !== null) {
|
|
if (tabData.rules) fields.push(...tabData.rules);
|
|
if (tabData.filters) fields.push(...tabData.filters);
|
|
}
|
|
const foundField = fields.find(field => field.id === fieldId);
|
|
if (foundField) {
|
|
return foundField;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 准备并打开测试弹窗。
|
|
* @param {string} key - 触发测试的输入框ID。
|
|
*/
|
|
async function openTestModal(key) {
|
|
currentInputEle = document.getElementById(key);
|
|
if (!currentInputEle) {
|
|
console.error("无法找到元素: ", key);
|
|
return;
|
|
}
|
|
|
|
const fieldDef = findFieldById(key);
|
|
window.modals.testModal.setTitle('测试:' + (fieldDef ? fieldDef.key : 'CSS选择器'));
|
|
|
|
document.getElementById('testSelectorInput').value = currentInputEle.value;
|
|
document.getElementById('testResultContent').innerHTML = '';
|
|
const resultContainer = document.querySelector('.test-result-container');
|
|
if(resultContainer) resultContainer.style.display = 'none';
|
|
|
|
testResultsCache = [];
|
|
isHtmlMode = false;
|
|
document.getElementById('toggleResultModeBtn').innerText = '切换到HTML模式';
|
|
|
|
const sourceInput = document.getElementById('sourceHtmlInput');
|
|
if (sourceInput) {
|
|
sourceInput.style.display = 'none';
|
|
sourceInput.value = '';
|
|
}
|
|
|
|
let url = '';
|
|
const parentTabContent = currentInputEle.closest('.tab-content, .sub-tab-content');
|
|
if (parentTabContent) {
|
|
const parentId = parentTabContent.id;
|
|
if (parentId.startsWith('home')) {
|
|
url = document.getElementById('首页推荐链接')?.value || '';
|
|
} else if (parentId.startsWith('category')) {
|
|
let categoryUrlTemplate = document.getElementById('分类链接')?.value || '';
|
|
if (categoryUrlTemplate) {
|
|
url = categoryUrlTemplate.replace(/\[firstPage=.*?\]/, '');
|
|
}
|
|
} else if (parentId.startsWith('detail') || parentId.startsWith('play')) {
|
|
if (!tempDetailPageUrl) {
|
|
showToast('详情链接为空,正尝试自动获取...', 'info');
|
|
document.getElementById('testUrl').value = '正在自动获取链接...';
|
|
const categoryUrl = (document.getElementById('分类链接')?.value || '').replace(/\[firstPage=.*?\]/, '');
|
|
const listRule = document.getElementById('分类列表数组规则')?.value || '';
|
|
const linkRule = document.getElementById('分类片单链接')?.value || '';
|
|
const prefix = document.getElementById('分类片单链接加前缀')?.value || '';
|
|
const suffix = document.getElementById('分类片单链接加后缀')?.value || '';
|
|
if (!categoryUrl || !listRule || !linkRule) {
|
|
url = '自动获取失败: 分类相关规则未填写';
|
|
showToast(url, 'error');
|
|
} else {
|
|
const headers = parseHeaders(document.getElementById('请求头参数').value);
|
|
const listResult = await runTest(categoryUrl, listRule, null, headers);
|
|
if (listResult.success && listResult.extractedElements.length > 0) {
|
|
const contextHtml = listResult.extractedElements[0].outerHTML;
|
|
const linkResult = await runTest(null, linkRule, contextHtml, headers);
|
|
if (linkResult.success && linkResult.finalResult.length > 0) {
|
|
const linkPart = linkResult.finalResult[0];
|
|
tempDetailPageUrl = prefix + linkPart + suffix;
|
|
url = tempDetailPageUrl;
|
|
showToast('已自动获取详情页链接!', 'success');
|
|
} else {
|
|
url = '自动获取失败: 未能从分类项中提取到链接';
|
|
showToast(url, 'error');
|
|
}
|
|
} else {
|
|
url = '自动获取失败: 未能在分类页找到列表';
|
|
showToast(url, 'error');
|
|
}
|
|
}
|
|
} else {
|
|
url = tempDetailPageUrl;
|
|
}
|
|
} else if (parentId.startsWith('search')) {
|
|
url = document.getElementById('搜索链接')?.value || '';
|
|
}
|
|
}
|
|
|
|
document.getElementById('testUrl').value = url || '请手动输入URL';
|
|
window.modals.testModal.open();
|
|
}
|
|
|
|
/**
|
|
* 在测试弹窗中更新测试结果显示。
|
|
* @param {object} result - runTest函数返回的结果对象。
|
|
* @param {string} selector - 当前测试使用的选择器。
|
|
* @param {string} url - 当前测试使用的URL或上下文描述。
|
|
* @param {boolean} isAutomated - 是否为一键自动测试流程。
|
|
*/
|
|
function updateTestModalContent(result, selector, url, isAutomated) {
|
|
const resultDiv = document.getElementById('testResultContent');
|
|
const resultContainer = document.querySelector('.test-result-container');
|
|
if(!resultDiv || !resultContainer) return;
|
|
|
|
resultContainer.style.display = 'block';
|
|
|
|
testResultsCache = [{
|
|
extractedElements: result.extractedElements,
|
|
finalResult: result.finalResult,
|
|
selector: selector,
|
|
url: url,
|
|
isFinalResultDirect: result.isFinalResultDirect
|
|
}];
|
|
|
|
const infoDiv = document.createElement('div');
|
|
if(resultDiv.innerHTML && !isAutomated){
|
|
infoDiv.innerHTML = `<hr style="margin-top:15px; margin-bottom:15px;"><b>测试URL:</b> ${url}<br><b>CSS选择器:</b> `;
|
|
} else {
|
|
resultDiv.innerHTML = '';
|
|
infoDiv.innerHTML = `<b>测试URL:</b> ${url}<br><b>CSS选择器:</b> `;
|
|
}
|
|
|
|
const selectorPre = document.createElement('pre');
|
|
selectorPre.style.cssText = 'display: inline; padding: 2px 4px; background-color: #f0f0f0; border-radius: 3px;';
|
|
selectorPre.innerText = selector;
|
|
infoDiv.appendChild(selectorPre);
|
|
resultDiv.appendChild(infoDiv);
|
|
|
|
if (result.success) {
|
|
const displayItems = result.isFinalResultDirect ? result.finalResult : result.extractedElements;
|
|
|
|
if (displayItems && displayItems.length > 0) {
|
|
const count = document.createElement('p');
|
|
count.innerHTML = `<br><b>找到 ${displayItems.length} 个结果:</b>`;
|
|
resultDiv.appendChild(count);
|
|
|
|
displayItems.forEach(item => {
|
|
const pre = document.createElement('pre');
|
|
pre.style.cssText = 'white-space: pre-wrap; word-break: break-all;';
|
|
if (result.isFinalResultDirect) {
|
|
pre.innerText = item;
|
|
} else {
|
|
pre.innerText = item.textContent ? item.textContent.trim() : '';
|
|
}
|
|
resultDiv.appendChild(pre);
|
|
});
|
|
} else {
|
|
const noResult = document.createElement('p');
|
|
noResult.innerHTML = `<br><b><span style="color:red;">未找到匹配的元素。</span></b>`;
|
|
if (result.error) {
|
|
noResult.innerHTML += `<br>错误信息: ${result.error}`;
|
|
}
|
|
resultDiv.appendChild(noResult);
|
|
}
|
|
} else {
|
|
const errorMsg = document.createElement('p');
|
|
errorMsg.innerHTML = `<br><b><span style="color:red;">测试失败:</span></b> ${result.error}`;
|
|
resultDiv.appendChild(errorMsg);
|
|
}
|
|
if (isAutomated) {
|
|
resultDiv.scrollTop = resultDiv.scrollHeight;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 执行手动测试。
|
|
*/
|
|
async function manualRunTest() {
|
|
const url = document.getElementById('testUrl').value;
|
|
const selector = document.getElementById('testSelectorInput').value;
|
|
const resultDiv = document.getElementById('testResultContent');
|
|
const resultContainer = document.querySelector('.test-result-container');
|
|
if(!resultDiv || !resultContainer) return;
|
|
|
|
const sourceHtml = document.getElementById('sourceHtmlInput').value;
|
|
if (sourceHtml && sourceHtml.trim() !== '') {
|
|
resultDiv.innerHTML = '正在使用自定义源码进行测试...';
|
|
resultContainer.style.display = 'block';
|
|
const result = await runTest(null, selector, sourceHtml);
|
|
updateTestModalContent(result, selector, '本地源码测试', false);
|
|
return;
|
|
}
|
|
|
|
if (!url || !selector || url.includes('请') || url.includes('失败')) {
|
|
resultDiv.innerHTML = 'URL和选择器都不能为空,或URL无效。';
|
|
resultContainer.style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
resultDiv.innerHTML = '正在加载并测试,请稍候...';
|
|
resultContainer.style.display = 'block';
|
|
|
|
const fieldId = currentInputEle.id;
|
|
const parentTab = currentInputEle.closest('.tab-content, .sub-tab-content');
|
|
const parentTabId = parentTab ? parentTab.id : '';
|
|
|
|
let headerString = '';
|
|
if (parentTabId === 'search') {
|
|
headerString = document.getElementById('搜索请求头参数')?.value || '';
|
|
} else {
|
|
headerString = document.getElementById('请求头参数')?.value || '';
|
|
}
|
|
const headers = parseHeaders(headerString);
|
|
|
|
const fieldDefinition = findFieldById(fieldId);
|
|
const parentRuleId = fieldDefinition ? fieldDefinition.dependsOn : null;
|
|
|
|
if (parentRuleId) {
|
|
const parentSelector = document.getElementById(parentRuleId)?.value || '';
|
|
if (!parentSelector) {
|
|
resultDiv.innerHTML = `<b><span style="color:red;">测试失败:</span></b> 依赖的父规则 "${parentRuleId}" 为空。`;
|
|
return;
|
|
}
|
|
resultDiv.innerHTML = `正在执行父规则 [<b>${parentRuleId}</b>]...`;
|
|
const parentResult = await runTest(url, parentSelector, null, headers);
|
|
if (!parentResult.success || !parentResult.extractedElements || parentResult.extractedElements.length === 0) {
|
|
resultDiv.innerHTML = `<b><span style="color:red;">父规则测试失败:</span></b> 未能从父规则 [<b>${parentRuleId}</b>] 中找到任何元素。请先确保父规则正确。`;
|
|
return;
|
|
}
|
|
const contextHtml = parentResult.extractedElements[0].outerHTML;
|
|
const firstElementText = parentResult.extractedElements[0].textContent.trim().substring(0, 100);
|
|
resultDiv.innerHTML = `父规则执行成功, 已找到 ${parentResult.extractedElements.length} 个元素。<br><b>上下文(第一个元素预览):</b> <pre style="background-color:#eee;padding:5px;">${firstElementText}...</pre>`;
|
|
const childResult = await runTest(null, selector, contextHtml, headers);
|
|
updateTestModalContent(childResult, selector, `在 [${parentRuleId}] 的第一个结果内`, false);
|
|
} else {
|
|
const result = await runTest(url, selector, null, headers);
|
|
updateTestModalContent(result, selector, url, false);
|
|
if (fieldId === '分类片单链接' && result.success && result.finalResult.length > 0) {
|
|
const prefix = document.getElementById('分类片单链接加前缀').value;
|
|
const suffix = document.getElementById('分类片单链接加后缀').value;
|
|
tempDetailPageUrl = prefix + result.finalResult[0] + suffix;
|
|
showToast('已自动获取并暂存详情页链接!', 'success');
|
|
document.getElementById('testUrl').value = tempDetailPageUrl;
|
|
}
|
|
if (fieldId === '搜索片单链接' && result.success && result.finalResult.length > 0) {
|
|
const prefix = document.getElementById('搜索片单链接加前缀').value;
|
|
const suffix = document.getElementById('搜索片单链接加后缀').value;
|
|
tempDetailPageUrl = prefix + result.finalResult[0] + suffix;
|
|
showToast('已从搜索结果自动获取并暂存详情页链接!', 'success');
|
|
document.getElementById('testUrl').value = tempDetailPageUrl;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 切换测试结果的显示模式(纯文本/HTML)。
|
|
*/
|
|
function toggleResultMode() {
|
|
isHtmlMode = !isHtmlMode;
|
|
const btn = document.getElementById('toggleResultModeBtn');
|
|
const resultDiv = document.getElementById('testResultContent');
|
|
if (!btn || !resultDiv) return;
|
|
|
|
const existingContent = Array.from(resultDiv.children).slice(0, 1);
|
|
resultDiv.innerHTML = '';
|
|
existingContent.forEach(child => resultDiv.appendChild(child));
|
|
|
|
if (testResultsCache.length > 0) {
|
|
const resultItem = testResultsCache[0];
|
|
const displayItems = resultItem.isFinalResultDirect ? resultItem.finalResult : resultItem.extractedElements;
|
|
if (displayItems && displayItems.length > 0) {
|
|
const count = document.createElement('p');
|
|
count.innerHTML = `<br><b>找到 ${displayItems.length} 个结果:</b>`;
|
|
resultDiv.appendChild(count);
|
|
displayItems.forEach(item => {
|
|
const pre = document.createElement('pre');
|
|
pre.style.cssText = 'white-space: pre-wrap; word-break: break-all;';
|
|
if(resultItem.isFinalResultDirect) {
|
|
pre.innerText = item;
|
|
} else if (isHtmlMode) {
|
|
pre.innerText = item.outerHTML || '无法显示HTML内容';
|
|
} else {
|
|
pre.innerText = item.textContent?.trim() || '';
|
|
}
|
|
resultDiv.appendChild(pre);
|
|
});
|
|
} else {
|
|
const noResult = document.createElement('p');
|
|
noResult.innerHTML = `<br><b><span style="color:red;">未找到匹配的元素。</span></b>`;
|
|
resultDiv.appendChild(noResult);
|
|
}
|
|
}
|
|
btn.innerText = isHtmlMode ? '切换到纯文本模式' : '切换到HTML模式';
|
|
}
|
|
|
|
/**
|
|
* 将测试弹窗中的选择器应用到主表单对应的输入框。
|
|
*/
|
|
function applySelectorToField() {
|
|
if (currentInputEle) {
|
|
const newSelector = document.getElementById('testSelectorInput').value;
|
|
currentInputEle.value = newSelector;
|
|
saveFormData();
|
|
showToast('新选择器已应用并保存!', 'success');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 展开或收起变量/提示的手风琴面板。
|
|
* @param {string} key - 关联的输入框ID。
|
|
* @param {object} var_btn_data - 包含变量和提示信息的对象。
|
|
*/
|
|
function toggleAccordion(key, var_btn_data) {
|
|
let accordionDiv = document.getElementById('accordion-' + key);
|
|
if (!accordionDiv) {
|
|
accordionDiv = document.createElement('div');
|
|
accordionDiv.id = 'accordion-' + key;
|
|
accordionDiv.className = 'variable-accordion';
|
|
document.getElementById(key)?.parentElement.after(accordionDiv);
|
|
}
|
|
|
|
if (accordionDiv.style.display === 'flex') {
|
|
accordionDiv.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
currentInputEle = document.getElementById(key);
|
|
accordionDiv.style.display = 'flex';
|
|
accordionDiv.innerHTML = '';
|
|
const variables = var_btn_data.vars || [];
|
|
const examples = var_btn_data.tips || [];
|
|
const varListDiv = document.createElement('div');
|
|
varListDiv.className = 'variable-list';
|
|
variables.forEach(v => {
|
|
const varItem = document.createElement('div');
|
|
varItem.className = 'variable-item';
|
|
varItem.innerText = v;
|
|
varItem.onclick = () => insertVariable(v);
|
|
varListDiv.appendChild(varItem);
|
|
});
|
|
accordionDiv.appendChild(varListDiv);
|
|
|
|
if (examples.length > 0) {
|
|
const exampleBlock = document.createElement('div');
|
|
exampleBlock.className = 'example-block';
|
|
let exampleHtml = '<h4>使用范例:</h4>';
|
|
examples.forEach(e => {
|
|
exampleHtml += `<div class="example-code">${e}</div>`;
|
|
});
|
|
exampleBlock.innerHTML = exampleHtml;
|
|
accordionDiv.appendChild(exampleBlock);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 在当前光标位置插入变量字符串。
|
|
* @param {string} variable - 要插入的变量文本。
|
|
*/
|
|
function insertVariable(variable) {
|
|
if (!currentInputEle) return;
|
|
const input = currentInputEle;
|
|
const startPos = input.selectionStart;
|
|
const endPos = input.selectionEnd;
|
|
const before = input.value.substring(0, startPos);
|
|
const after = input.value.substring(endPos, input.value.length);
|
|
input.value = before + variable + after;
|
|
input.focus();
|
|
input.selectionStart = input.selectionEnd = startPos + variable.length;
|
|
saveFormData();
|
|
}
|
|
|
|
/**
|
|
* 打开变量设置弹窗并填充内容。
|
|
* @param {Modal} modalInstance - 变量弹窗的实例。
|
|
*/
|
|
function openVariableModal(modalInstance) {
|
|
const inputsContainer = document.getElementById('variableInputs');
|
|
if(!inputsContainer) return;
|
|
inputsContainer.innerHTML = '';
|
|
|
|
const allVariables = ['wd', 'SearchPg', 'cateId', 'class', 'area', 'year', 'lang', 'by', 'catePg'];
|
|
allVariables.forEach(v => {
|
|
const div = document.createElement('div');
|
|
div.className = 'form-group';
|
|
const label = document.createElement('label');
|
|
label.innerText = `{${v}}`;
|
|
label.setAttribute('for', `var-${v}`);
|
|
const input = document.createElement('input');
|
|
input.type = 'text';
|
|
input.id = `var-${v}`;
|
|
input.setAttribute('data-variable-name', v);
|
|
input.value = window.globalVariables[v] || '';
|
|
div.appendChild(label);
|
|
div.appendChild(input);
|
|
inputsContainer.appendChild(div);
|
|
});
|
|
|
|
modalInstance.open();
|
|
}
|
|
|
|
/**
|
|
* 保存用户在弹窗中设置的变量值。
|
|
*/
|
|
function saveVariables() {
|
|
const inputs = document.querySelectorAll('#variableInputs input[data-variable-name]');
|
|
const newVariables = {};
|
|
inputs.forEach(input => {
|
|
const varName = input.getAttribute('data-variable-name');
|
|
if(varName) {
|
|
newVariables[varName] = input.value;
|
|
}
|
|
});
|
|
localStorage.setItem('tvbox_variables', JSON.stringify(newVariables));
|
|
window.globalVariables = newVariables;
|
|
window.modals.variableModal.close();
|
|
showToast('变量已保存!', 'success');
|
|
}
|
|
|
|
/**
|
|
* 从localStorage加载变量。
|
|
*/
|
|
function loadVariables() {
|
|
const savedVariables = localStorage.getItem('tvbox_variables');
|
|
if (savedVariables) {
|
|
try {
|
|
window.globalVariables = JSON.parse(savedVariables);
|
|
} catch (e) {
|
|
console.error('加载本地变量失败:', e);
|
|
window.globalVariables = {};
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 执行CSS选择器测试的核心函数。
|
|
* @param {string|null} url - 要抓取的URL,如果提供了htmlContent则可为null。
|
|
* @param {string} selector - 要执行的CSS选择器。
|
|
* @param {string|null} htmlContent - 可选的HTML内容,如果提供则不抓取URL。
|
|
* @param {object} [customHeaders={}] - 自定义请求头。
|
|
* @returns {Promise<object>} 返回包含测试结果的Promise对象。
|
|
*/
|
|
async function runTest(url, selector, htmlContent = null, customHeaders = {}) {
|
|
let doc;
|
|
let elements = [];
|
|
|
|
try {
|
|
if (htmlContent) {
|
|
const parser = new DOMParser();
|
|
doc = parser.parseFromString(htmlContent, 'text/html');
|
|
elements = Array.from(doc.body.childNodes);
|
|
} else if(url) {
|
|
let tempUrl = url;
|
|
for (const key in window.globalVariables) {
|
|
if (window.globalVariables[key]) {
|
|
tempUrl = tempUrl.replace(new RegExp(`{${key}}`, 'g'), window.globalVariables[key]);
|
|
}
|
|
}
|
|
const isPost = tempUrl.includes(';post');
|
|
if (isPost) tempUrl = tempUrl.replace(';post', '');
|
|
|
|
const proxyUrl = `/index.php/Proxy/load?target_url=${encodeURIComponent(tempUrl)}`;
|
|
const fetchOptions = { method: isPost ? 'POST' : 'GET', headers: {} };
|
|
|
|
if (Object.keys(customHeaders).length > 0) {
|
|
fetchOptions.headers['X-Custom-Headers'] = JSON.stringify(customHeaders);
|
|
}
|
|
if(isPost) {
|
|
let postData = document.getElementById('POST请求数据')?.value || '';
|
|
for (const key in window.globalVariables) {
|
|
if (window.globalVariables[key]) postData = postData.replace(new RegExp(`{${key}}`, 'g'), window.globalVariables[key]);
|
|
}
|
|
fetchOptions.body = postData;
|
|
fetchOptions.headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
}
|
|
const response = await fetch(proxyUrl, fetchOptions);
|
|
if (!response.ok) throw new Error(`HTTP 错误! 状态码: ${response.status}`);
|
|
const htmlText = await response.text();
|
|
const sourceInput = document.getElementById('sourceHtmlInput');
|
|
if (sourceInput) {
|
|
sourceInput.value = htmlText;
|
|
}
|
|
const parser = new DOMParser();
|
|
doc = parser.parseFromString(htmlText, 'text/html');
|
|
elements = [doc];
|
|
} else {
|
|
throw new Error('URL和HTML内容都为空。');
|
|
}
|
|
} catch (error) {
|
|
return { success: false, error: error.message, finalResult: [], extractedElements: [] };
|
|
}
|
|
|
|
const selectorParts = selector.split('&&');
|
|
let finalResult = [];
|
|
let isFinalResultDirect = false;
|
|
|
|
for (let i = 0; i < selectorParts.length; i++) {
|
|
let part = selectorParts[i].trim();
|
|
if (!part) continue;
|
|
|
|
const attributeKeywords = ['Text', 'Html', 'href', 'data-original', 'src', 'data-src', 'title'];
|
|
if (attributeKeywords.some(keyword => part.startsWith(keyword))) {
|
|
const extractionParts = part.split('!');
|
|
const extractor = extractionParts[0];
|
|
|
|
finalResult = elements.flatMap(el => {
|
|
let value = '';
|
|
if (extractor === 'Text') value = el.textContent || '';
|
|
else if (extractor === 'Html') value = el.innerHTML || '';
|
|
else if (el.getAttribute) value = el.getAttribute(extractor) || '';
|
|
|
|
if (extractionParts.length > 1) {
|
|
for (let j = 1; j < extractionParts.length; j++) {
|
|
value = value.replace(new RegExp(extractionParts[j], 'g'), '');
|
|
}
|
|
}
|
|
const trimmedValue = value.trim();
|
|
return trimmedValue ? [trimmedValue] : [];
|
|
});
|
|
isFinalResultDirect = true;
|
|
break;
|
|
}
|
|
|
|
let nextElements = [];
|
|
try {
|
|
for (const currentElement of elements) {
|
|
if (currentElement.nodeType !== 1 && currentElement.nodeType !== 9) continue;
|
|
nextElements.push(...Array.from(currentElement.querySelectorAll(part)));
|
|
}
|
|
} catch (e) {
|
|
nextElements = [];
|
|
}
|
|
elements = nextElements;
|
|
if (elements.length === 0) break;
|
|
}
|
|
|
|
return { success: true, finalResult: isFinalResultDirect ? finalResult : [], doc: doc, extractedElements: elements, isFinalResultDirect: isFinalResultDirect };
|
|
}
|
|
|
|
/**
|
|
* 开始一键自动测试流程。
|
|
*/
|
|
async function startAutomatedTest() {
|
|
showToast('开始一键自动测试...', 'info');
|
|
tempDetailPageUrl = '';
|
|
|
|
await testHomepage();
|
|
await testCategory();
|
|
await testDetail();
|
|
await testPlay();
|
|
|
|
showToast('一键自动测试流程完成!', 'success');
|
|
}
|
|
|
|
/**
|
|
* 自动测试首页规则。
|
|
*/
|
|
async function testHomepage() {
|
|
await openTestModal('首页推荐链接');
|
|
openTab({ currentTarget: document.querySelector('.tabs .tab-btn[onclick*="home"]') }, 'home');
|
|
const homepageUrl = document.getElementById('首页推荐链接')?.value;
|
|
const homepageRule = document.getElementById('首页片单列表数组规则')?.value;
|
|
|
|
if (!homepageUrl || !homepageRule) {
|
|
updateTestModalContent({ success: false, error: '首页规则或URL为空,跳过此测试。' }, '首页片单列表数组规则', 'N/A', true);
|
|
return;
|
|
}
|
|
|
|
currentInputEle = document.getElementById('首页片单列表数组规则');
|
|
document.getElementById('testSelectorInput').value = homepageRule;
|
|
document.getElementById('testUrl').value = homepageUrl;
|
|
|
|
const headers = parseHeaders(document.getElementById('请求头参数')?.value);
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
const result = await runTest(homepageUrl, homepageRule, null, headers);
|
|
updateTestModalContent(result, homepageRule, homepageUrl, true);
|
|
|
|
if (result.success && result.extractedElements.length > 0) {
|
|
showToast('【首页规则】测试通过!', 'success');
|
|
} else {
|
|
showToast('【首页规则】测试失败。', 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 自动测试分类规则。
|
|
*/
|
|
async function testCategory() {
|
|
openTab({ currentTarget: document.querySelector('.tabs .tab-btn[onclick*="category"]') }, 'category');
|
|
openSubTab({ currentTarget: document.querySelector('#category .sub-tab-btn') }, 'category-rules-basic');
|
|
|
|
let categoryUrlTemplate = document.getElementById('分类链接')?.value || '';
|
|
const categoryRule = document.getElementById('分类列表数组规则')?.value;
|
|
const detailUrlRule = document.getElementById('分类片单链接')?.value;
|
|
|
|
if (!categoryUrlTemplate || !categoryRule || !detailUrlRule) {
|
|
updateTestModalContent({ success: false, error: '分类规则或URL为空,跳过此测试。' }, '分类列表数组规则', 'N/A', true);
|
|
return;
|
|
}
|
|
|
|
categoryUrlTemplate = categoryUrlTemplate.replace(/\[firstPage=.*?\]/, '');
|
|
currentInputEle = document.getElementById('分类列表数组规则');
|
|
document.getElementById('testSelectorInput').value = categoryRule;
|
|
document.getElementById('testUrl').value = categoryUrlTemplate;
|
|
|
|
const headers = parseHeaders(document.getElementById('请求头参数')?.value);
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
const result = await runTest(categoryUrlTemplate, categoryRule, null, headers);
|
|
updateTestModalContent(result, categoryRule, categoryUrlTemplate, true);
|
|
|
|
if (result.success && result.extractedElements.length > 0) {
|
|
showToast('【分类列表数组规则】测试通过!', 'success');
|
|
const firstMovieEl = result.extractedElements[0].outerHTML;
|
|
const detailUrlResult = await runTest(null, detailUrlRule, firstMovieEl, headers);
|
|
|
|
if (detailUrlResult.success && detailUrlResult.finalResult.length > 0) {
|
|
const prefix = document.getElementById('分类片单链接加前缀')?.value || '';
|
|
const suffix = document.getElementById('分类片单链接加后缀')?.value || '';
|
|
tempDetailPageUrl = prefix + detailUrlResult.finalResult[0] + suffix;
|
|
showToast('已从分类结果中获取到详情页链接。', 'success');
|
|
} else {
|
|
showToast('无法从分类结果中提取详情页链接。', 'error');
|
|
}
|
|
} else {
|
|
showToast('【分类列表数组规则】测试失败。', 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 自动测试详情页规则。
|
|
*/
|
|
async function testDetail() {
|
|
openTab({ currentTarget: document.querySelector('.tabs .tab-btn[onclick*="detail"]') }, 'detail');
|
|
if (!tempDetailPageUrl) {
|
|
updateTestModalContent({ success: false, error: '详情页URL为空,跳过【详情规则】测试。'}, 'N/A', 'N/A', true);
|
|
return;
|
|
}
|
|
|
|
const detailRules = ['演员详情', '简介详情', '类型详情'];
|
|
const headers = parseHeaders(document.getElementById('请求头参数')?.value);
|
|
|
|
for (const ruleId of detailRules) {
|
|
const selector = document.getElementById(ruleId)?.value;
|
|
if (!selector) continue;
|
|
|
|
currentInputEle = document.getElementById(ruleId);
|
|
document.getElementById('testSelectorInput').value = selector;
|
|
document.getElementById('testUrl').value = tempDetailPageUrl;
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
const result = await runTest(tempDetailPageUrl, selector, null, headers);
|
|
updateTestModalContent(result, selector, tempDetailPageUrl, true);
|
|
|
|
if (result.success && (result.finalResult.length > 0 || result.extractedElements.length > 0)) {
|
|
showToast(`【${ruleId}】测试通过!`, 'success');
|
|
} else {
|
|
showToast(`【${ruleId}】测试失败。`, 'error');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 自动测试播放列表规则。
|
|
*/
|
|
async function testPlay() {
|
|
openTab({ currentTarget: document.querySelector('.tabs .tab-btn[onclick*="play"]') }, 'play');
|
|
if (!tempDetailPageUrl) {
|
|
updateTestModalContent({ success: false, error: '详情页URL为空,跳过【播放规则】测试。'}, 'N/A', 'N/A', true);
|
|
return;
|
|
}
|
|
|
|
const playRules = ['线路列表数组规则', '选集列表数组规则'];
|
|
const headers = parseHeaders(document.getElementById('请求头参数')?.value);
|
|
|
|
for (const ruleId of playRules) {
|
|
const selector = document.getElementById(ruleId)?.value;
|
|
if (!selector) continue;
|
|
|
|
currentInputEle = document.getElementById(ruleId);
|
|
document.getElementById('testSelectorInput').value = selector;
|
|
document.getElementById('testUrl').value = tempDetailPageUrl;
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
const result = await runTest(tempDetailPageUrl, selector, null, headers);
|
|
updateTestModalContent(result, selector, tempDetailPageUrl, true);
|
|
|
|
if (result.success && result.extractedElements.length > 0) {
|
|
showToast(`【${ruleId}】测试通过!`, 'success');
|
|
} else {
|
|
showToast(`【${ruleId}】测试失败。`, 'error');
|
|
}
|
|
}
|
|
} |