302 lines
8.1 KiB
JavaScript
302 lines
8.1 KiB
JavaScript
/*!
|
|
* invoice_hook.js · v1.0.0
|
|
* 发票申请 Hook
|
|
* - 使用 window.InvoiceApply 统一暴露接口
|
|
* - 支持重复注入,不重复自动执行
|
|
* - CEF 可通过 window.InvoiceApply.__installed 判断脚本是否已注入
|
|
* - 执行结果保存在 window.__invoiceApplyResult
|
|
*/
|
|
; (function (global) {
|
|
'use strict';
|
|
|
|
// 已注入时不重复创建对象或重复自动执行
|
|
if (global.InvoiceApply && global.InvoiceApply.__installed) {
|
|
return;
|
|
}
|
|
|
|
var VERSION = '1.0.0';
|
|
var state = {
|
|
installed: false,
|
|
running: false,
|
|
result: null,
|
|
error: null,
|
|
lastRunAt: null
|
|
};
|
|
|
|
function sleep(ms) {
|
|
return new Promise(function (resolve) {
|
|
global.setTimeout(resolve, ms);
|
|
});
|
|
}
|
|
|
|
// 支持 XPath 或 CSS 选择器
|
|
function findElement(selector, isXPath) {
|
|
if (isXPath === undefined) isXPath = true;
|
|
|
|
try {
|
|
if (isXPath) {
|
|
return global.document.evaluate(
|
|
selector,
|
|
global.document,
|
|
null,
|
|
global.XPathResult.FIRST_ORDERED_NODE_TYPE,
|
|
null
|
|
).singleNodeValue;
|
|
}
|
|
|
|
return global.document.querySelector(selector);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function changeReactInputValue(inputDom, newText) {
|
|
if (!inputDom) return false;
|
|
|
|
var lastValue = inputDom.value;
|
|
var valueSetter = null;
|
|
|
|
// 优先使用原生 input/textarea value setter,便于 React 感知值变化
|
|
try {
|
|
var proto = inputDom.tagName === 'TEXTAREA'
|
|
? global.HTMLTextAreaElement.prototype
|
|
: global.HTMLInputElement.prototype;
|
|
|
|
valueSetter = Object.getOwnPropertyDescriptor(proto, 'value');
|
|
} catch (e) {
|
|
// ignore
|
|
}
|
|
|
|
try {
|
|
if (valueSetter && valueSetter.set) {
|
|
valueSetter.set.call(inputDom, newText);
|
|
} else {
|
|
inputDom.value = newText;
|
|
}
|
|
} catch (e) {
|
|
inputDom.value = newText;
|
|
}
|
|
|
|
try {
|
|
var tracker = inputDom._valueTracker;
|
|
if (tracker) {
|
|
tracker.setValue(lastValue);
|
|
}
|
|
} catch (e) {
|
|
// ignore
|
|
}
|
|
|
|
var event;
|
|
|
|
try {
|
|
event = new global.Event('input', { bubbles: true });
|
|
} catch (e) {
|
|
event = global.document.createEvent('Event');
|
|
event.initEvent('input', true, false);
|
|
}
|
|
|
|
event.simulated = true;
|
|
inputDom.dispatchEvent(event);
|
|
|
|
return true;
|
|
}
|
|
|
|
async function fillInvoiceAndApply(data) {
|
|
data = data || {};
|
|
|
|
var title = data.title === undefined
|
|
? '某某科技有限公司'
|
|
: data.title;
|
|
|
|
var taxNumber = data.taxNumber === undefined
|
|
? '91110000123456789X'
|
|
: data.taxNumber;
|
|
|
|
console.log('[InvoiceApply] 开始填充发票信息...');
|
|
|
|
// 1. 点击“企业”按钮
|
|
var invoiceType = findElement('//*[contains(text(), "企业")]');
|
|
|
|
if (!invoiceType) {
|
|
console.warn('[InvoiceApply] 找不到“企业”按钮');
|
|
return false;
|
|
}
|
|
|
|
invoiceType.click();
|
|
|
|
console.log('[InvoiceApply] 已点击“企业”按钮');
|
|
|
|
await sleep(500);
|
|
|
|
// 2. 填充发票抬头
|
|
var invoiceTitle =
|
|
findElement('//label[contains(text(), "发票抬头")]/following-sibling::textarea[1]') ||
|
|
findElement('//*[contains(text(), "发票抬头")]/following-sibling::textarea[1]');
|
|
|
|
if (invoiceTitle) {
|
|
changeReactInputValue(invoiceTitle, title);
|
|
console.log('[InvoiceApply] 发票抬头已填充');
|
|
} else {
|
|
console.warn('[InvoiceApply] 未找到发票抬头控件');
|
|
}
|
|
|
|
// 3. 填充税号
|
|
var taxNumberInput =
|
|
findElement('//label[contains(text(), "税号")]/following-sibling::input[1]') ||
|
|
findElement('//*[contains(text(), "税号")]/following-sibling::input[1]');
|
|
|
|
if (taxNumberInput) {
|
|
changeReactInputValue(taxNumberInput, taxNumber);
|
|
console.log('[InvoiceApply] 税号已填充');
|
|
} else {
|
|
console.warn('[InvoiceApply] 未找到税号控件');
|
|
}
|
|
|
|
await sleep(300);
|
|
|
|
// 4. 点击“申请发票”
|
|
var applyBtn = findElement('//*[contains(text(), "申请发票")]');
|
|
|
|
if (!applyBtn) {
|
|
console.error('[InvoiceApply] 未找到“申请发票”按钮');
|
|
return false;
|
|
}
|
|
|
|
if (applyBtn.scrollIntoView) {
|
|
applyBtn.scrollIntoView({
|
|
behavior: 'smooth',
|
|
block: 'center'
|
|
});
|
|
}
|
|
|
|
await sleep(300);
|
|
|
|
applyBtn.click();
|
|
|
|
console.log('[InvoiceApply] 已点击“申请发票”按钮');
|
|
|
|
// 5. 等待弹窗或状态出现
|
|
await sleep(300);
|
|
|
|
// 6. 判断申请状态
|
|
var alreadyApplied = findElement(
|
|
'//*[contains(text(), "您已经提交过发票申请了")]'
|
|
);
|
|
|
|
if (alreadyApplied) {
|
|
console.log('[InvoiceApply] 已经申请过,无需重复操作');
|
|
return true;
|
|
}
|
|
|
|
var successElement = findElement(
|
|
'//*[text()="确认发票信息无误后再提交"]'
|
|
);
|
|
|
|
if (!successElement) {
|
|
console.warn('[InvoiceApply] 未检测到确认发票信息的提示');
|
|
return false;
|
|
}
|
|
|
|
var confirmBtn = findElement('//*[text()="确认"]');
|
|
|
|
if (confirmBtn) {
|
|
console.log('[InvoiceApply] 找到“确认”按钮');
|
|
|
|
await sleep(200);
|
|
confirmBtn.click();
|
|
console.log(
|
|
'[InvoiceApply] 已识别确认步骤,未自动提交最终确认'
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
console.warn(
|
|
'[InvoiceApply] 未找到“已经申请过”提示或“确认”按钮'
|
|
);
|
|
|
|
return false;
|
|
}
|
|
|
|
function run(data) {
|
|
if (state.running) {
|
|
return Promise.reject(
|
|
new Error('发票申请脚本正在执行,请勿重复调用')
|
|
);
|
|
}
|
|
|
|
state.running = true;
|
|
state.result = null;
|
|
state.error = null;
|
|
state.lastRunAt = Date.now();
|
|
|
|
global.__invoiceApplyResult = null;
|
|
|
|
return fillInvoiceAndApply(data)
|
|
.then(function (result) {
|
|
state.result = !!result;
|
|
global.__invoiceApplyResult = state.result;
|
|
|
|
return state.result;
|
|
})
|
|
.catch(function (ex) {
|
|
state.error = String((ex && ex.message) || ex);
|
|
state.result = false;
|
|
global.__invoiceApplyResult = false;
|
|
|
|
console.error('[InvoiceApply] 执行异常:', ex);
|
|
|
|
return false;
|
|
})
|
|
.then(
|
|
function (result) {
|
|
state.running = false;
|
|
return result;
|
|
},
|
|
function (ex) {
|
|
state.running = false;
|
|
throw ex;
|
|
}
|
|
);
|
|
}
|
|
|
|
var api = {
|
|
version: VERSION,
|
|
run: run,
|
|
findElement: findElement,
|
|
changeReactInputValue: changeReactInputValue,
|
|
|
|
get state() {
|
|
return state;
|
|
},
|
|
|
|
get __installed() {
|
|
return state.installed;
|
|
}
|
|
};
|
|
|
|
function install() {
|
|
if (state.installed) {
|
|
return api;
|
|
}
|
|
|
|
state.installed = true;
|
|
|
|
console.log('[InvoiceApply] v' + VERSION + ' 已注入');
|
|
|
|
// 保留原 invoice.js 的行为:脚本注入后自动执行一次
|
|
run({
|
|
title: '{公司}',
|
|
taxNumber: '{税号}'
|
|
});
|
|
|
|
return api;
|
|
}
|
|
|
|
api.install = install;
|
|
|
|
global.InvoiceApply = api;
|
|
|
|
install();
|
|
|
|
})(typeof window !== 'undefined' ? window : this); |