; (async () => { /** * @typedef {Object} Gateway * @property {string} devName - device name * @property {Element} btn - button element * @property {string} sn - serial number */ /** * Centralised timeout/interval constants (milliseconds). */ const WAIT = { POLL_INTERVAL: 100, PANEL_FADE: 100, PANEL_VISIBLE_TIMEOUT: 600, BUTTON_FIND: 5000, CONFIG_DIALOG_VISIBLE: 8000, TEMPLATE_LIST_RENDER: 200, DROPDOWN_CLOSE: 100, MSGBOX_APPEAR: 2000, DIALOG_CLOSE: 8000, DETAIL_PAGE_RENDER: 15000, TABLE_ROWS_RENDER: 15000, } /** * Clicks through: 参数信息 → 参数配置 → 参数模板配置 */ async function openTemplateConfig() { // 1. Click the "参数信息" tab const paramTab = document.getElementById('tab-parameterInfo') || [...document.querySelectorAll('.el-tabs__item')] .find(el => el.textContent.trim() === '参数信息') if (!paramTab) throw new Error('无法找到"参数信息"标签') if (!paramTab.classList.contains('is-active')) { // Some implementations bind click on the inner
, // so try it first, then fall back to the wrapper. const inner = paramTab.querySelector('#step1, .el-tabs-padding8') ; (inner ?? paramTab).click() // wait for its panel to become visible await new Promise(res => { const panel = document.getElementById('pane-parameterInfo') const mo = new MutationObserver(() => { if (panel.style.display !== 'none') { mo.disconnect() // give any fade-in a moment setTimeout(res, WAIT.PANEL_FADE) } }) mo.observe(panel, { attributes: true, attributeFilter: ['style'] }) // fallback setTimeout(() => { mo.disconnect(); res() }, WAIT.PANEL_VISIBLE_TIMEOUT) }) } // 2. Wait for并点击 "参数配置" 按钮 const cfgBtn = await waitFor(() => [...document.querySelectorAll('#pane-parameterInfo button')] .find(b => b.textContent.trim() === '参数配置'), WAIT.BUTTON_FIND, ) cfgBtn.click() // 3. Wait for the configuration dialog to appear await waitFor(() => { const dlgWrapper = document.querySelector('.configurationDialog') return dlgWrapper && getComputedStyle(dlgWrapper).display !== 'none' }, WAIT.CONFIG_DIALOG_VISIBLE) // 4. Click the "参数模板配置" radio const radio = [...document.querySelectorAll('.configurationDialog .el-radio-button')] .find(r => r.textContent.trim() === '参数模板配置') if (!radio) throw new Error('无法找到"参数模板配置"项') radio.click() } /** * After 参数模板配置 is active, select a named template, apply it, and confirm the message box. * @param {string} templateName — e.g. "gw_template_01" */ async function applyParameterTemplate(templateName = 'gw_template_01') { // 1. Open the template-config UI await openTemplateConfig() // assumes you've already defined openTemplateConfig() // 2. Click the 参数配置 dropdown const selectInput = document.querySelector('.configurationDialog input[placeholder*="参数模板"]') if (!selectInput) throw new Error('找不到参数模板下拉框') selectInput.click() // 3. Wait a moment for the list to render await new Promise(r => setTimeout(r, WAIT.TEMPLATE_LIST_RENDER)) // 4. Pick the desired template const option = Array.from(document.querySelectorAll('.el-select-dropdown__item')) .find(li => li.textContent.trim() === templateName) if (!option) throw new Error(`模板 "${templateName}" 不存在`) option.click() // 5. Wait for the dropdown to close await new Promise(r => setTimeout(r, WAIT.DROPDOWN_CLOSE)) // 6. Click the "配置" button in the dialog footer const applyBtn = document.querySelector('.configurationDialog .el-dialog__footer .el-button.el-button--primary') if (!applyBtn) throw new Error('找不到"配置"按钮') applyBtn.click() // 7. Wait for the confirmation message box to appear await new Promise(resolve => { const selector = '.el-message-box__wrapper[aria-label="提示"]' const check = () => { const mb = document.querySelector(selector) if (mb && getComputedStyle(mb).display !== 'none') { return setTimeout(resolve, WAIT.MSGBOX_APPEAR) } return false } if (check()) return const mo = new MutationObserver(() => { if (check()) { mo.disconnect() } }) mo.observe(document.body, { childList: true, subtree: true }) // fallback timeout setTimeout(() => { mo.disconnect(); resolve() }, WAIT.MSGBOX_APPEAR) }) // 8. Click the "好的" button in the message-box footer const okBtn = Array.from(document.querySelectorAll('.el-message-box__btns .el-button')) .find(b => b.textContent.trim().startsWith('好的')) if (!okBtn) throw new Error('找不到消息框中的"好的"按钮') okBtn.click() // 9. Wait for both message box and configuration dialog to disappear await waitFor(() => { const msgWrap = document.querySelector('.el-message-box__wrapper[aria-label="提示"]') const cfgDlg = document.querySelector('.configurationDialog') const msgHidden = !msgWrap || getComputedStyle(msgWrap).display === 'none' const dlgHidden = cfgDlg && getComputedStyle(cfgDlg).display === 'none' return msgHidden && dlgHidden }, WAIT.DIALOG_CLOSE) } /** * @returns {boolean} */ function isConsoleLogNative() { try { return console.log.toString().includes('native code') } catch (e) { return false } } /** * @description create a clean log function from iframe * @returns {Function} */ function createCleanLog() { const f = document.createElement('iframe') f.style.display = 'none' document.body.appendChild(f) return f.contentWindow.console.log.bind(f.contentWindow.console) } /** * @returns {Gateway[]} */ function getGateways() { /** * @type {Gateway[]} */ const gateways = [] const rows = document.querySelectorAll('.el-table__body .el-table__row') rows.forEach(row => { const devNameEl = row.querySelector('.DevNameCls') const snEl = row.querySelectorAll('td')[3] // 4th column is SN if (devNameEl && snEl) { const btn = devNameEl const devName = devNameEl.textContent.trim() const sn = snEl.textContent.trim() gateways.push({ devName, btn, sn }) } }) return gateways } /** * Wait until a predicate returns a truthy value. * @template T * @param {() => T} predicate – polling function * @param {number} [timeout=5000] – max wait in ms * @param {number} [interval=100] – polling interval in ms * @returns {Promise} */ function waitFor(predicate, timeout = 5000, interval = WAIT.POLL_INTERVAL) { const start = Date.now() return new Promise((resolve, reject) => { const check = () => { let res try { res = predicate() } catch { /* ignore */ } if (res) return resolve(res) if (Date.now() - start >= timeout) return reject(new Error('waitFor timeout')) setTimeout(check, interval) } check() }) } /** * Waits for a DOM element matching the selector. * @param {string} selector * @param {number} [timeout=5000] * @returns {Promise} */ function waitForElement(selector, timeout = 5000) { return waitFor(() => document.querySelector(selector), timeout) } /** * Opens gateway details, applies the template, and navigates back. * @param {string} devName – gateway display name * @param {string} [templateName='gw_template_01'] */ async function processGateway(devName, templateName = 'gw_template_01') { console.group(`Processing gateway: ${devName}`) // Wait for the 参数信息 tab (or any detail-specific element) to appear await waitFor(() => document.querySelector('#tab-parameterInfo'), WAIT.DETAIL_PAGE_RENDER) // 2. Run the existing template-application workflow await applyParameterTemplate(templateName) // 3. Navigate back to the gateway list via breadcrumb let listLink = [...document.querySelectorAll('.CompBreadcrumb a')] .find(a => a.textContent.trim() === '网关列表') if (!listLink) { // 有时文字在 中 listLink = [...document.querySelectorAll('.CompBreadcrumb [role="link"]')] .find(el => el.textContent.trim() === '网关列表') } if (!listLink) throw new Error('无法找到"网关列表"面包屑链接') listLink.click() // 4. Wait until the table rows reappear await waitFor(() => document.querySelectorAll('.el-table__body .el-table__row').length > 0, WAIT.TABLE_ROWS_RENDER) console.groupEnd() } /** * Entrypoint – iterates over every gateway and applies the template. */ async function init() { // Expose helpers for manual debugging Object.assign(window, { openTemplateConfig, applyParameterTemplate, processGateway, waitFor, waitForElement, getGateways, }) if (!isConsoleLogNative()) { const cleanLog = createCleanLog() console.log = cleanLog console.info("re-override console.log") } const initialGateways = getGateways() const names = initialGateways.map(g => g.devName) window.userState = { names } console.info('Gateways detected:', names) for (const devName of names) { try { // Re-query each round in case DOM has been re-rendered const row = [...document.querySelectorAll('.el-table__body .el-table__row')] .find(r => r.querySelector('.DevNameCls')?.textContent.trim() === devName) if (!row) { console.warn(`Row for "${devName}" not found; skipping.`) continue } // Prefer anchor/button inside the DevName cell, else the cell div itself const btn = row.querySelector('.DevNameCls a, .DevNameCls button') || row.querySelector('.DevNameCls') if (!btn) { console.warn(`Gateway button for "${devName}" not found; skipping.`) continue } btn.click() await processGateway(devName, 'gw_template_01') } catch (e) { console.error(`Failed to process gateway "${devName}"`, e) } } console.info('Automation complete.') } await init() })()