Files
29WxOrder/29WxOrderWpf/Ctrls/PDDInvoice/Resources/invoice.js
T
2026-10-15 13:33:08 +08:00

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);