优化发票助手
This commit is contained in:
1 parent
6b36de2494
commit
a81ba26de8
9 files changed
+584
-262
No files matched your search
@@ -66,10 +66,12 @@
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\**\*" />
|
||||
<None Remove="Ctrls\PDDInvoice\Resources\invoice.js" />
|
||||
<None Remove="Ctrls\PDDInvoice\Resources\order.js" />
|
||||
<Resource Include="29wx.ico" />
|
||||
<Resource Include="29_common_icon.ico" />
|
||||
<Resource Include="Ctrls\OrderCtrls\Resources\tbAutoAddress.js" />
|
||||
<Resource Include="Ctrls\PDDInvoice\Resources\invoice.js" />
|
||||
<Resource Include="Ctrls\PDDInvoice\Resources\order.js" />
|
||||
<Resource Include="gear.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -7,6 +7,7 @@ using _29WxOrderWpf.Services;
|
||||
using AllBase.Helper;
|
||||
using AllBase.Model.DBModel;
|
||||
using NaiBa.Util.Extension;
|
||||
using Prism.Services.Dialogs;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -308,11 +309,8 @@ public partial class OrderListCtrl
|
||||
|
||||
private void ShowPDDInvoiceDialog()
|
||||
{
|
||||
new DialogWindow
|
||||
{
|
||||
Title = "发票助手",
|
||||
Content = new PDDInvoiceView()
|
||||
}.Show();
|
||||
var view = new PDDInvoiceView();
|
||||
DialogService.Show(view);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
xmlns:vm="clr-namespace:_29WxOrderWpf.Ctrls.PDDInvoice.ViewModels"
|
||||
xmlns:wpf="clr-namespace:WPFCommon.Controls;assembly=WPFCommon"
|
||||
comm:DialogWindowProperty.ResizeMode="CanResizeWithGrip"
|
||||
comm:DialogWindowProperty.ShowMinButton="False"
|
||||
comm:DialogWindowProperty.ShowMinButton="True"
|
||||
common:DialogWindowProperty.Height="720"
|
||||
common:DialogWindowProperty.Width="1260"
|
||||
d:DataContext="{d:DesignInstance Type=vm:PDDInvoiceViewModel,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using _29WxOrderWpf.Ctrls.PDDInvoice.ViewModels;
|
||||
using CefSharp.DevTools.Overlay;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -65,9 +66,6 @@ public partial class PDDInvoiceView : UserControl
|
||||
{
|
||||
_scrollStopTimer.Stop();
|
||||
|
||||
System.Diagnostics.Debug.WriteLine("滚动停止了");
|
||||
|
||||
|
||||
// 用 Background 优先级设置,避免和当前这一帧的滚动惯性/渲染争抢,
|
||||
// 让 UI 线程先把滚动动画收尾,再去处理这一波图片加载触发
|
||||
Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
|
||||
@@ -100,4 +98,5 @@ public partial class PDDInvoiceView : UserControl
|
||||
.ContainerFromItem(vm.SelectedOrder) as FrameworkElement;
|
||||
container?.BringIntoView();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,134 +1,302 @@
|
||||
// 填写信息
|
||||
function changeReactInputValue(inputDom, newText) {
|
||||
let lastValue = inputDom.value;
|
||||
inputDom.value = newText;
|
||||
let event = new Event('input', { bubbles: true });
|
||||
event.simulated = true;
|
||||
let tracker = inputDom._valueTracker;
|
||||
if (tracker) {
|
||||
tracker.setValue(lastValue);
|
||||
}
|
||||
inputDom.dispatchEvent(event);
|
||||
}
|
||||
/*!
|
||||
* invoice_hook.js · v1.0.0
|
||||
* 发票申请 Hook
|
||||
* - 使用 window.InvoiceApply 统一暴露接口
|
||||
* - 支持重复注入,不重复自动执行
|
||||
* - CEF 可通过 window.InvoiceApply.__installed 判断脚本是否已注入
|
||||
* - 执行结果保存在 window.__invoiceApplyResult
|
||||
*/
|
||||
; (function (global) {
|
||||
'use strict';
|
||||
|
||||
// 异步 sleep
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
// 查找元素(支持选择器或XPath)
|
||||
function findElement(selector, isXPath = true) {
|
||||
if (isXPath) {
|
||||
return document.evaluate(
|
||||
selector,
|
||||
document,
|
||||
null,
|
||||
XPathResult.FIRST_ORDERED_NODE_TYPE,
|
||||
null
|
||||
).singleNodeValue;
|
||||
} else {
|
||||
return document.querySelector(selector);
|
||||
}
|
||||
}
|
||||
|
||||
// 完整流程
|
||||
async function fillInvoiceAndApply(data = {}) {
|
||||
const {
|
||||
title = '某某科技有限公司',
|
||||
taxNumber = '91110000123456789X'
|
||||
} = data;
|
||||
|
||||
console.log('📝 开始填充发票信息...');
|
||||
|
||||
// 1. 点击"企业"按钮
|
||||
const invoiceType = findElement('//*[contains(text(), "企业")]');
|
||||
if (!invoiceType) {
|
||||
console.warn('⚠️ 找不到"企业"按钮');
|
||||
return false;
|
||||
// 已注入时不重复创建对象或重复自动执行
|
||||
if (global.InvoiceApply && global.InvoiceApply.__installed) {
|
||||
return;
|
||||
}
|
||||
|
||||
invoiceType.click();
|
||||
console.log('✅ 已点击"企业"按钮');
|
||||
await sleep(500);
|
||||
var VERSION = '1.0.0';
|
||||
var state = {
|
||||
installed: false,
|
||||
running: false,
|
||||
result: null,
|
||||
error: null,
|
||||
lastRunAt: null
|
||||
};
|
||||
|
||||
// 2. 查找并填充发票抬头
|
||||
const invoiceTitle = findElement('//label[contains(text(), "发票抬头")]/following-sibling::textarea[1]') ||
|
||||
findElement('//*[contains(text(), "发票抬头")]/following-sibling::textarea[1]');
|
||||
if (invoiceTitle) {
|
||||
changeReactInputValue(invoiceTitle, title);
|
||||
console.log('✅ 发票抬头已填充');
|
||||
} else {
|
||||
console.warn('⚠️ 未找到发票抬头控件');
|
||||
function sleep(ms) {
|
||||
return new Promise(function (resolve) {
|
||||
global.setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
// 3. 查找并填充税号
|
||||
const taxNumberInput = findElement('//label[contains(text(), "税号")]/following-sibling::input[1]') ||
|
||||
findElement('//*[contains(text(), "税号")]/following-sibling::input[1]');
|
||||
if (taxNumberInput) {
|
||||
changeReactInputValue(taxNumberInput, taxNumber);
|
||||
console.log('✅ 税号已填充');
|
||||
} else {
|
||||
console.warn('⚠️ 未找到税号控件');
|
||||
// 支持 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;
|
||||
}
|
||||
}
|
||||
|
||||
await sleep(300);
|
||||
function changeReactInputValue(inputDom, newText) {
|
||||
if (!inputDom) return false;
|
||||
|
||||
// 4. 查找并点击"申请发票"按钮
|
||||
const applyBtn = findElement('//*[contains(text(), "申请发票")]');
|
||||
if (!applyBtn) {
|
||||
console.error('❌ 未找到"申请发票"按钮');
|
||||
return false;
|
||||
}
|
||||
var lastValue = inputDom.value;
|
||||
var valueSetter = null;
|
||||
|
||||
applyBtn.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
await sleep(300);
|
||||
applyBtn.click();
|
||||
console.log('✅ 已点击"申请发票"按钮');
|
||||
// 优先使用原生 input/textarea value setter,便于 React 感知值变化
|
||||
try {
|
||||
var proto = inputDom.tagName === 'TEXTAREA'
|
||||
? global.HTMLTextAreaElement.prototype
|
||||
: global.HTMLInputElement.prototype;
|
||||
|
||||
// 5. 等待弹窗/状态出现
|
||||
await sleep(300);
|
||||
valueSetter = Object.getOwnPropertyDescriptor(proto, 'value');
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// 6. 判断状态
|
||||
// 检查是否有"已经申请过"的提示
|
||||
const alreadyApplied = findElement('//*[contains(text(), "您已经提交过发票申请了")]')
|
||||
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
|
||||
}
|
||||
|
||||
if (alreadyApplied) {
|
||||
console.log('✅ 已经申请过,无需重复操作');
|
||||
return true;
|
||||
}
|
||||
const successElement = findElement('//*[text()="确认发票信息无误后再提交"]');
|
||||
if (successElement == null) {
|
||||
return false;
|
||||
}
|
||||
// 检查是否有"确认开具"按钮或提示
|
||||
const confirmBtn = findElement('//*[text()= "确认"]');
|
||||
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);
|
||||
|
||||
if (confirmBtn) {
|
||||
console.log('✅ 找到"确认"按钮,准备点击...');
|
||||
await sleep(200);
|
||||
confirmBtn.click();
|
||||
console.log('✅ 已点击"确认"按钮');
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果都没有找到
|
||||
console.warn('⚠️ 未找到"已经申请过"或"确认"按钮,请手动检查');
|
||||
return false;
|
||||
}
|
||||
async function fillInvoiceAndApply(data) {
|
||||
data = data || {};
|
||||
|
||||
// ========== 调用 ==========
|
||||
(async function () {
|
||||
try {
|
||||
const result = await fillInvoiceAndApply({
|
||||
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: '{税号}'
|
||||
});
|
||||
console.log('执行结果:', result);
|
||||
// 批量开票时,C# 端轮询该变量获取执行结果
|
||||
window.__invoiceApplyResult = result;
|
||||
} catch (ex) {
|
||||
console.error('执行异常:', ex);
|
||||
window.__invoiceApplyResult = false;
|
||||
|
||||
return api;
|
||||
}
|
||||
})();
|
||||
|
||||
api.install = install;
|
||||
|
||||
global.InvoiceApply = api;
|
||||
|
||||
install();
|
||||
|
||||
})(typeof window !== 'undefined' ? window : this);
|
||||
@@ -0,0 +1 @@
|
||||
var _0xodc = 'jsjiami.com.v7'; var _0x20193a = _0x210f; function _0x1a46() { var _0x56c665 = (function () { return [_0xodc, 'PqljsTeUjiCarWmi.UwcyfobCmr.yuvW7tCtuXBl==', 'WRBdTGpdT0u', 'WO0CW47dHa', 'W43cUXVdLSon', 'W4BcS03cQ8ks', 'ygFdL8kNdCojrq', 'zSkCD8kItW', 'WPjwW6pcNqJcQa', 'zoINMUwmGU++Gq', 'W69xWPxcMJ0', 'qhFdQ0T1', 'WR/cKLPWWQC', 'WOJcOmo4W4zBxa', 'fCkxzSkWsa', 'WRaMW6xdUJ4', 'i8klgWhdQq', 'tCkAcwZcLa', 'BSk/WPKUFSooWORcU8kw', 'W7pcSmkQFKi', '5QMh5OMR5RMw5yUA5Roa5P2V6kwl5y2P5lMB5l2w6k2O5RoF77Yi6AcC6zYG5y6y6ioh5BEy57Ut5Rov5P6/5PU+5Asm77Yp5OUB5yQd6kgy5lUa5zY75RMi5yQL5AY75zUc5yAd7765', 'WPtcR8oKW5a', 'W64hW5xdSCot', 'qSkfWPS8C8oLWOdcL8krW5ufyG', 'eSoDW6exdHpdMq', 'W58MWQCya1y', 'CSk7C8kDxa', 'AZvVWQu', 'mrfYtf8', 'fmo8W7O0cq', 'hCkyWQ3dQCo9', 'W7FcIXFdKmoF', 'W6XTrmkHgcFcVWyZW53cLSoGWR4eW6y2kSkLgr5pyqhcV0m2WPtdS8kNkr8LW4edwmkWrL/dR3X3W5ZdINnzybPwbmoueW', 'vt5rWRye', 'f8kBcaddUG', 'k8kEWPJdU8o4WQrToSow', 'WQFcIfhdUCk1', 'WQFcGhrd', 'WRldJSonp8k+', 'tgZcO8oIW59G', 'W44CpCoDq8obvG', 'W5ZcMmkVAgG', 'W6ZcTCocWP0pW5u/W7qx', 'W43cKbaj', 'W4T6WPNcGYu', 'vCoPjCoFW7JcK1lMIP3PLje', 'vmoroSouW6y', 'W4tcI0BcPSkjW78', 'W5TqWP/cHG', 'W5xcMSovWRO0', 'WRJcM3XcWPK', 'W5z5WPdcGqe', 'nSk7DmkFDW', 'W5ZcIsJdVmoC', 'W6H+WRBcJHu', 'W7upW5VdVSoD', 'fCoeW6aBaGm', 'WPtdVSoiiCkbW4rk', 'cSkTgWZdPmk3WQ4YWP8', 'W5hcVmoAWOi1', 'h8knxCkhBq', 'hmogmCoHWOpcNSkT5OI+6zEO', 'WQJcOeJdTCkk', 'WPKPAb0k', 'W47cGbCZzu5eWQPXeCoQWOVdQSo7nCkL', 'amole8oVWOJcL8ofW4dcMctcL8om', 'W5mSpCoSzq', 'WQ5sa8k8W6m', 'FCoqn8oY', 'W5TEfNBdKq', 'cdLgvNa', 'kYGxoCkf', 'eSonW7Wx', 'WO3cGCoAW7bB', 'WRajW4FdMIu', 'nhqnW4BdHW', 'pw/dJ8kBW5/cQG', 'WQjtbmkcxmoiW6tdRSo9', 'BSkdC0/cHa5c', 'vSoOmmoVW4K', 'nmkChWhdN8kBWRm1WOZdQCkLWOC', 'WPT+W7ZcTby', 'gxGwW6FdHG', 'W7ddVt/cUfRdK3S5W6q', 'W4ZcGdxdUCoa', 'FhFdHmkwpG', 'W6VcTCobWPKHW5SVW6G', 'W6GAWQ4Tjq', 'WRGLCCkl', 'ww/dQK5dW7FcGmoChColyq', 'W4C5W4NdOmosqWrRWRq', 'rgtdQmkWjq', 'W7GbuSkSsSoM', 'w8kwWQ8suW', 'l8ktWPddOmoD', 'CSkKwfJcPa', 'f0RcPCo7lG', 'WRX8W4xcKtO', 'W7C7WO/dM8oU', 'W4ipWOxdQ8oh', 'W6JcMYFdLSoS', 'nSkgtCktFW', 'W4FcHcddVSonW7mR', 'WPZdUSkMA8kMW6GHWOBcOW', 'W59uWOFcQZJdOSofWRldGHaqutOMlUwNJoI2Na', 'W6CRWQGqpG', 'pJDXC0rXW4RdLmoti3q', 'W5tcGmovWQWu', 'C8ksoLxcUa', 'W5/cGflcPG', 'rCkRsCkIz8oR', 'pfmMW6ldHWa', 'FCkeumkPzG', 'kmk0WRFdHmoH', 'WQ18W4RcSGO', 'W6SsW5BdTa', 'WQmMW7ddPHS', 'WPTvW6VcNX/cTmkdW6FcVCkIWOP0umkzW50', 'W5VcJfhcQW', 'thJcOCo0W4HN', 'gCoBdSoU', 'WR95r8keWRhcMCk6W41+W5SDAay', 'WR0buI8O', 'nLNcOSo8lLRdHH3dQa', 'f8kCWP3dN8od', 'WQtcIxvdWPNdSG', 'idnZCNnNW4/dRSoengvQW6O', 'W7mmWOZdU8og', 'W4ZcM8ohWR8V', 'DSkAgKZcUcuDWQi', 'BSoJn8oBW6G', 'smklWQmmDW', 'W5dcIGeiBfOFWRzJc8ofWOxdRSo7jCk4WQePDMu', 'W7lcH8oeWQmx', 'qxJdV1Tf', 'WPpdVaBdMLK', 'imomeSoMWP0', 'WQdcGCouW41Z', 'WOxcIg/dJmk1', 'cCoAW6aDaai', 'WRZdJSkHsSk4', 'WR85E8kxoq', 'b0xdSCkVW4q', 'WRONvcik', 'WQpcJMb/WOVdQctcHSoWpCoCW5uhhW', 'WRldUCkUB8kB', 'WRyKuSkpoq', 'W4qGEmketmoQW4/dSSoc', 'W4FdTSo7W5tdQa', 'W6pdGSobxWhdTSoP', 'WRfYv8kUWRVcSSk5', 'WQBdTcJdMMe', 'C8kvduG', 'W5FcUSkQW4Knvmk3W6/dJW', 'WR3dHrxdKfC', 'rNlcSCoJW5TDWPRdKJi', 'W4JcKuVcR8kF', 'A8kdyeRcGb5DmmkFW5C+emki', 'DCkFi1JcMG', 'BmowkCoU', 'vWvKbSo1WO01WORdMwarwmkSWPNdLL/cICoEja', 'W77dQdNcR1JdJNO4', 'WOGpxGGOW5FdQq', 'smoIbmolW7RcHqzjESkVWOTqW7m', 'sCoKb8orW7pcMJPJyCkSWOzw', 'xSo+sCkaWPpcI2RcNSogct5LDCkIfG', 'WRuBESkjeG', 'WPRdHd7dP3O', 'DMZdKmk8aCoyqCokW55T', 'F0pdN3ns', 'W5ZcHdpdRmobW6i9', 'v8oqdCokW4K', 'WQ3cIMDtWPq', 'WR3cRwbaWR3cGCkU', 'W5VcQYeyBW', 'W5efDmk4Dq', 'W6xdU8ohW7VdK8kJW5lcTCk8vw8KmMC', 'WPqfrSkWjq', 'bSktWQldUmoF', 'W6eMv8kXCW', 'vmkqA3VcJa', 'me7cUSoUleZdGbhdVSk6W6JcQa', 'W41Ti0BdJa', 'ESoqimoYWObGWQu', 'wCkMv8kIASoJ', 'vKNcRmo9W7i', 'WOnYnCkPW5PG', 'nmoDcCkyW4hcVZBcJCkCegG', 'p8oUbCoSWRW', 'W60oW7xdLSos', 'W4PPWP/cHG4', 'W5uoWOhdJmoS', 'y0ZdVLja', 'BmkAoKZcTIq', 'aqKxemkr', 'WOaqxJ8o', 'WP7cSxbYWRi', 'WQP1W57cSX4', 'WOTJiCkeW6C', 'W6tdNSodta', 'e8krqCkhC8kmEG', 'WPz+rSk+WRC', 'fuRcNCoyjG', 'FCoCi8oJWQ16WRxdIa', 'WQOszY4K', 'W57dNs/cUM8', 'tgZdIK5dW5/cH8oZcmorvSkUcey6', 'WOBdTCoopSk3W4raB8oWpSkvW7u', 'WOGlvWK', 'aHLZFM8', '77YA77Yo5yo55Q6Z576F6AoF', 'oCkiDeFcKva', 'W7BdJSo/tai', 'WQVdGCkpuCkGW7u1WRhcTCoFWPGMWQH8W4FLTi7LIQJMJzq', 'WOPPW5FcQt4', 'W4DqihBdPqq', 'B8oxoCo+WRb2WR7dN8kDvmoKWOK', 'WQNcLmoBW7nC', 'gCoNcSkjW4NcGWRcLSkyb2K', 'mL3cV8o8lG', 'W78AWOVdKmoSW58', 'W6KYa8oLW7m', 'W7CqW5ZdTSoeyG9HWQW', 'W5RcUCohWRGu', 'WQxcJN5v', 'W67dGrhcH3O', 'W7mqWOtdHSoNW58NW4mwpstdGW', 'W47cHCkrWO7cMq', 'vCogaCo8W5e', 'W4rAo2ddRW/cGq', 'BCkreLNcNICTW6pcR0a', 'gSkwhIldPq', 'BmkUwKxcTW', 'W6hdPCoWBsK', 'W4VdT8o3BsddUSoJyCoXW7i', 'W5/cRG42BW', 'W6JcNaOksG', 'W7CrtSk5w8oGW57dLmoinSkO', 'W5byWOdcNJNdPmov', 'lN/cISohdW', 'lmkxWPZdQmoX', 'rCkNtSk/AmoI', 'WRRcM2n1WQ0', 'WPxdMYZdTMq', 'W6hdSCogW5tdIq', 'yhBdHmkLeCoz', 'z3VdLCk0', 'psrMyMrN', 'WRlcM0tdSW', 'DdTVWQmK', 'gmkXxSk+rq', 'WQTOxmk5WQy', 'mMVcOCodlq', 'W4NcQCoGWRuU', 'WQydW6ZdNW0', 'xSoAoSofWP0', 'W4OZWO8toq', 'CCkjyKVcIWXegG', 'peCKW7tdKaCeAK/cVcO', 'W6Wpgmooua', 'xSksWO0VAmo1', 'z3ddJmk2a8opuG', 'EsXLWQWy', 'imoOi8kPW60', 'tmkLine truncated
|
||||
@@ -16,6 +16,7 @@ using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using Work._29Tools.CefTools;
|
||||
using Work.BaseForms;
|
||||
using WxOrder.Framework.Helpers;
|
||||
|
||||
namespace _29WxOrderWpf.Ctrls.PDDInvoice.ViewModels;
|
||||
@@ -26,19 +27,11 @@ public partial class PDDInvoiceViewModel
|
||||
/// 订单列表浏览器
|
||||
/// </summary>
|
||||
public ChromiumWebBrowser Browser { get; private set; }
|
||||
/// <summary>
|
||||
/// 内容浏览器
|
||||
/// 订单详情
|
||||
/// 发票详情
|
||||
/// 申请发票
|
||||
/// </summary>
|
||||
public ChromiumWebBrowser BrowserDetail { get; private set; }
|
||||
private IFrame frame;
|
||||
|
||||
partial void InitBrowser()
|
||||
{
|
||||
BrowserZIndex = -1;
|
||||
BrowserDetailZIndex = -1;
|
||||
|
||||
Browser = WPFCEFBrowser.CreateChromiumWebBrowser("about:blank", false);
|
||||
|
||||
LocalResourceRequestHandler resourceRequestHandler = new LocalResourceRequestHandler();
|
||||
@@ -49,13 +42,7 @@ public partial class PDDInvoiceViewModel
|
||||
Browser.KeyboardHandler = new KeyboardHandler();
|
||||
Browser.LoadingStateChanged += Browser_LoadingStateChanged;
|
||||
|
||||
BrowserDetail = WPFCEFBrowser.CreateChromiumWebBrowser("about:blank", false);
|
||||
LocalResourceRequestHandler detailResourceRequestHandler = new LocalResourceRequestHandler();
|
||||
detailResourceRequestHandler.ResourceLoadCompleteEvent += DetailRequestHandler_ResourceLoadComplete;
|
||||
BrowserDetail.RequestHandler = new LocalRequestHandler(resourceRequestHandler);
|
||||
BrowserDetail.LifeSpanHandler = new LocalLifeSpanHandler(PopWindowType.SelfWindow);
|
||||
BrowserDetail.KeyboardHandler = new KeyboardHandler();
|
||||
BrowserDetail.LoadingStateChanged += BrowserDetail_LoadingStateChanged;
|
||||
|
||||
}
|
||||
|
||||
private void Browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e)
|
||||
@@ -65,75 +52,31 @@ public partial class PDDInvoiceViewModel
|
||||
var b = (ChromiumWebBrowser)sender;
|
||||
b.Dispatcher.InvokeAsync(async () =>
|
||||
{
|
||||
if (b.Address != null && b.Address.StartsWith("https://mobile.yangkeduo.com/orders.html"))
|
||||
{
|
||||
StartAutoScroll();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void BrowserDetail_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e)
|
||||
{
|
||||
if (e.IsLoading == false)
|
||||
{
|
||||
var b = (ChromiumWebBrowser)sender;
|
||||
b.Dispatcher.InvokeAsync(async () =>
|
||||
{
|
||||
if (b.Address != null && b.Address.StartsWith("https://mobile.yangkeduo.com/transac_return_compensation.html"))
|
||||
if (b.Address != null && b.Address.StartsWith("https://mobile.yangkeduo.com/orders.html"))//加载完成 加载 js
|
||||
{
|
||||
string html = await b.GetBrowser().GetSourceAsync();
|
||||
if (html.Contains("window.rawData"))
|
||||
frame = b.GetMainFrame();
|
||||
|
||||
string content = ResourceHelper.GetResourceText("/Ctrls/PDDInvoice/Resources/order.js", "29WxFindOrder");
|
||||
if (string.IsNullOrEmpty(content)) return;
|
||||
Install(frame, content);
|
||||
if (await IsInstalledAsync(frame))
|
||||
{
|
||||
await ExecuteInvoiceApplyScriptAsync(b);
|
||||
StartAutoScroll();
|
||||
//await b.EvaluateScriptAsync(frame, content);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 页面异常(未渲染出发票表单)时直接判定失败,避免批量开票长时间等待
|
||||
_currentInvoiceTcs?.TrySetResult(false);
|
||||
BaseMsgForm.ShowError("订单读取失败");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注入发票填写脚本并执行。
|
||||
/// 单张申请模式保持原有行为;批量开票模式下轮询脚本写入的
|
||||
/// <c>window.__invoiceApplyResult</c>,完成后通过 <see cref="_currentInvoiceTcs"/> 回传结果。
|
||||
/// </summary>
|
||||
private async Task ExecuteInvoiceApplyScriptAsync(ChromiumWebBrowser b)
|
||||
{
|
||||
string content = ResourceHelper.GetResourceText("/Ctrls/PDDInvoice/Resources/invoice.js", "29WxFindOrder");
|
||||
content = content.Replace("{公司}", Config.Title);
|
||||
content = content.Replace("{税号}", Config.TaxNo);
|
||||
if (string.IsNullOrEmpty(content)) return;
|
||||
|
||||
await b.EvaluateScriptAsync(content);
|
||||
|
||||
if (_currentInvoiceTcs == null) return; // 单张申请模式:保持原有行为
|
||||
|
||||
// 批量模式:等待异步脚本执行完成
|
||||
// 捕获当前 TCS,避免批量循环超时进入下一个订单后,迟到的轮询误触发新订单的结果
|
||||
var tcs = _currentInvoiceTcs;
|
||||
for (int i = 0; i < 120; i++)
|
||||
{
|
||||
await Task.Delay(500);
|
||||
try
|
||||
{
|
||||
var res = await b.EvaluateScriptAsync("window.__invoiceApplyResult");
|
||||
if (res.Success && res.Result != null)
|
||||
{
|
||||
tcs.TrySetResult(Convert.ToBoolean(res.Result));
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
tcs.TrySetResult(false);
|
||||
}
|
||||
|
||||
public ICommand CloseInvoiceDetailCommand => new DelegateCommand(HidenOrderBrowserDetail);
|
||||
|
||||
@@ -142,11 +85,6 @@ public partial class PDDInvoiceViewModel
|
||||
get => GetValue<int>();
|
||||
set => SetValue(value);
|
||||
}
|
||||
public int BrowserDetailZIndex
|
||||
{
|
||||
get => GetValue<int>();
|
||||
set => SetValue(value);
|
||||
}
|
||||
/// <summary>
|
||||
/// 显示主浏览器
|
||||
/// </summary>
|
||||
@@ -155,22 +93,12 @@ public partial class PDDInvoiceViewModel
|
||||
BrowserZIndex = 10;
|
||||
BrowserDetailZIndex = -1;
|
||||
}
|
||||
/// <summary>
|
||||
/// 显示子浏览器
|
||||
/// </summary>
|
||||
public void ShowOrderBrowserDetail()
|
||||
{
|
||||
BrowserZIndex = -1;
|
||||
BrowserDetailZIndex = 10;
|
||||
}
|
||||
|
||||
public void HidenOrderBrowser()
|
||||
{
|
||||
BrowserZIndex = -1;
|
||||
}
|
||||
public void HidenOrderBrowserDetail()
|
||||
{
|
||||
BrowserDetailZIndex = -1;
|
||||
}
|
||||
|
||||
public bool IsReadOrder
|
||||
{
|
||||
get => GetValue<bool>();
|
||||
@@ -178,31 +106,28 @@ public partial class PDDInvoiceViewModel
|
||||
}
|
||||
private int top = 0;
|
||||
private DateTime lastGetOrderDataTime = DateTime.Now;
|
||||
private string lastOrderSN = "0";
|
||||
private void RequestHandler_ResourceLoadComplete(object sender, string url, string postData, string responseData, IRequest request)
|
||||
{
|
||||
if (url.StartsWith("https://mobile.yangkeduo.com/orders.html"))
|
||||
{
|
||||
//if (isAutoScroll == false)
|
||||
//{
|
||||
// StartAutoScroll();
|
||||
//}
|
||||
|
||||
StartAutoScroll();
|
||||
}
|
||||
if (url.StartsWith("https://mobile.yangkeduo.com/proxy/api/api/aristotle/order_list_v"))
|
||||
{
|
||||
lastGetOrderDataTime = DateTime.Now;
|
||||
parserData(responseData);
|
||||
|
||||
parserData(responseData, out lastOrderSN);
|
||||
Debug.WriteLine($"LastOrder:{lastOrderSN}");
|
||||
//File.WriteAllText(@$"E:\{Guid.NewGuid().ToString()}.txt", responseData);
|
||||
}
|
||||
}
|
||||
public void StartAutoScroll()
|
||||
{
|
||||
if (frame == null)
|
||||
{
|
||||
StopAutoScroll(); return;
|
||||
}
|
||||
if (IsReadOrder) return;
|
||||
|
||||
IsReadOrder = true;
|
||||
_ = autoScroll(_cancellationTokenSource.Token);
|
||||
//_ = autoScroll(_cancellationTokenSource.Token);
|
||||
_ = autoLoadOrderData(_cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
// 停止滚动
|
||||
@@ -210,39 +135,82 @@ public partial class PDDInvoiceViewModel
|
||||
{
|
||||
IsReadOrder = false;
|
||||
_cancellationTokenSource?.Cancel();
|
||||
}
|
||||
|
||||
private async Task autoScroll(CancellationToken token)
|
||||
|
||||
Application.Current?.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
BottomMsg = $"订单读取结束";
|
||||
}));
|
||||
CompleteProgress();
|
||||
|
||||
}
|
||||
private async Task autoLoadOrderData(CancellationToken token)
|
||||
{
|
||||
string templastOrderSN = "";
|
||||
while (IsReadOrder && !token.IsCancellationRequested)
|
||||
{
|
||||
if ((DateTime.Now - lastGetOrderDataTime).Seconds > 30) StopAutoScroll();
|
||||
|
||||
top += Randomx.GetId(450, 600);
|
||||
var jsStr = @"window.scroll({
|
||||
top: " + top + @",
|
||||
left: 0,
|
||||
behavior: 'smooth' // 可选,使滚动平滑
|
||||
});";
|
||||
|
||||
var client = Browser.GetDevToolsClient();
|
||||
if (client != null)
|
||||
if (lastOrderSN == templastOrderSN)
|
||||
{
|
||||
var result = client.Runtime.EvaluateAsync(jsStr, null, true, null, null, true, null, true);
|
||||
result.Wait(5000);
|
||||
await Task.Delay(500);
|
||||
continue;
|
||||
}
|
||||
await Task.Delay(Randomx.GetId(100, 300));
|
||||
|
||||
string jsStr = $"PDDOrders.trigger({{offset:'MO-01-{lastOrderSN}',type:0,size:10,mode:'store'}})";
|
||||
templastOrderSN = lastOrderSN;
|
||||
|
||||
await frame.EvaluateScriptAsync(jsStr);
|
||||
|
||||
await Task.Delay(Randomx.GetId(1000, 3000));
|
||||
//Thread.Sleep();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void DetailRequestHandler_ResourceLoadComplete(object sender, string url, string postData, string responseData, IRequest request)
|
||||
/// <summary>文档创建时注入(每个 document 注入一次即可,脚本本身幂等)</summary>
|
||||
public void Install(IFrame frame, string hookJs)
|
||||
{
|
||||
if (url.StartsWith("https://mobile.yangkeduo.com/transac_return_compensation.html"))
|
||||
{
|
||||
|
||||
}
|
||||
if (frame == null || string.IsNullOrEmpty(hookJs)) return;
|
||||
frame.ExecuteJavaScriptAsync(hookJs);
|
||||
}
|
||||
|
||||
/// <summary>脚本是否已就绪(注入后自检用;异步,不会卡 UI 线程)</summary>
|
||||
public async Task<bool> IsInstalledAsync(IFrame frame, int timeoutMs = 3000)
|
||||
{
|
||||
var deadline = DateTime.Now.AddMilliseconds(timeoutMs);
|
||||
while (DateTime.Now < deadline)
|
||||
{
|
||||
var r = await frame.EvaluateScriptAsync("!!(window.PDDOrders && window.PDDOrders.__installed)")
|
||||
.ConfigureAwait(false);
|
||||
if (r != null && r.Success && r.Result is bool && (bool)r.Result) return true;
|
||||
await Task.Delay(100).ConfigureAwait(false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
//private async Task autoScroll(CancellationToken token)
|
||||
//{
|
||||
// while (IsReadOrder && !token.IsCancellationRequested)
|
||||
// {
|
||||
// if ((DateTime.Now - lastGetOrderDataTime).Seconds > 30) StopAutoScroll();
|
||||
|
||||
// top += Randomx.GetId(450, 600);
|
||||
// var jsStr = @"window.scroll({
|
||||
// top: " + top + @",
|
||||
// left: 0,
|
||||
// behavior: 'smooth' // 可选,使滚动平滑
|
||||
// });";
|
||||
|
||||
// var client = Browser.GetDevToolsClient();
|
||||
// if (client != null)
|
||||
// {
|
||||
// var result = client.Runtime.EvaluateAsync(jsStr, null, true, null, null, true, null, true);
|
||||
// result.Wait(5000);
|
||||
// }
|
||||
// await Task.Delay(Randomx.GetId(100, 300));
|
||||
// //Thread.Sleep();
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
using _29WxOrderWpf.CefHelper;
|
||||
using CefSharp;
|
||||
using CefSharp.Handler;
|
||||
using CefSharp.Wpf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
using Work._29Tools.CefTools;
|
||||
using Work.BaseForms;
|
||||
using WxOrder.Framework.Helpers;
|
||||
|
||||
namespace _29WxOrderWpf.Ctrls.PDDInvoice.ViewModels;
|
||||
|
||||
public partial class PDDInvoiceViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// 内容浏览器
|
||||
/// 订单详情
|
||||
/// 发票详情
|
||||
/// 申请发票
|
||||
/// </summary>
|
||||
public ChromiumWebBrowser BrowserDetail { get; private set; }
|
||||
partial void InitBrowserDetail()
|
||||
{
|
||||
BrowserDetailZIndex = -1;
|
||||
|
||||
BrowserDetail = WPFCEFBrowser.CreateChromiumWebBrowser("about:blank", false);
|
||||
LocalResourceRequestHandler resourceRequestHandler = new LocalResourceRequestHandler();
|
||||
|
||||
LocalResourceRequestHandler detailResourceRequestHandler = new LocalResourceRequestHandler();
|
||||
detailResourceRequestHandler.ResourceLoadCompleteEvent += DetailRequestHandler_ResourceLoadComplete;
|
||||
BrowserDetail.RequestHandler = new LocalRequestHandler(resourceRequestHandler);
|
||||
BrowserDetail.LifeSpanHandler = new LocalLifeSpanHandler(PopWindowType.SelfWindow);
|
||||
BrowserDetail.KeyboardHandler = new KeyboardHandler();
|
||||
BrowserDetail.LoadingStateChanged += BrowserDetail_LoadingStateChanged;
|
||||
}
|
||||
public int BrowserDetailZIndex
|
||||
{
|
||||
get => GetValue<int>();
|
||||
set => SetValue(value);
|
||||
}
|
||||
/// <summary>
|
||||
/// 显示子浏览器
|
||||
/// </summary>
|
||||
public void ShowOrderBrowserDetail()
|
||||
{
|
||||
BrowserZIndex = -1;
|
||||
BrowserDetailZIndex = 10;
|
||||
}
|
||||
public void HidenOrderBrowserDetail()
|
||||
{
|
||||
BrowserDetailZIndex = -1;
|
||||
}
|
||||
private void BrowserDetail_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e)
|
||||
{
|
||||
if (e.IsLoading == false)
|
||||
{
|
||||
var b = (ChromiumWebBrowser)sender;
|
||||
b.Dispatcher.InvokeAsync(async () =>
|
||||
{
|
||||
if (b.Address != null && b.Address.StartsWith("https://mobile.yangkeduo.com/transac_return_compensation.html"))
|
||||
{
|
||||
string orderSN = Regex.Match(b.Address, "order_sn=(?<sn>\\d+-\\d+)").Groups["sn"].Value;
|
||||
string html = await b.GetBrowser().GetSourceAsync();
|
||||
if (html.Contains("window.rawData"))
|
||||
{
|
||||
await ExecuteInvoiceApplyScriptAsync(b, orderSN);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 页面异常(未渲染出发票表单)时直接判定失败,避免批量开票长时间等待
|
||||
_currentInvoiceTcs?.TrySetResult(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注入发票填写脚本并执行。
|
||||
/// 单张申请模式保持原有行为;批量开票模式下轮询脚本写入的
|
||||
/// <c>window.__invoiceApplyResult</c>,完成后通过 <see cref="_currentInvoiceTcs"/> 回传结果。
|
||||
/// </summary>
|
||||
private async Task ExecuteInvoiceApplyScriptAsync(ChromiumWebBrowser b, string orderSn)
|
||||
{
|
||||
string content = ResourceHelper.GetResourceText("/Ctrls/PDDInvoice/Resources/invoice.js", "29WxFindOrder");
|
||||
content = content.Replace("{公司}", Config.Title);
|
||||
content = content.Replace("{税号}", Config.TaxNo);
|
||||
if (string.IsNullOrEmpty(content)) return;
|
||||
|
||||
await b.EvaluateScriptAsync(content);
|
||||
|
||||
if (_currentInvoiceTcs == null)
|
||||
{
|
||||
var temp = Orders?.FirstOrDefault(a => a.order_sn == orderSn);
|
||||
if (temp != null) temp.IsInvoiced = true;
|
||||
return; // 单张申请模式:保持原有行为
|
||||
}
|
||||
|
||||
// 捕获当前 TCS,避免批量循环超时进入下一个订单后,迟到的轮询误触发新订单的结果
|
||||
var tcs = _currentInvoiceTcs;
|
||||
|
||||
// 检查是否注入成功
|
||||
if (await IsInstalledDetailAsync(b) == false)
|
||||
{
|
||||
IsBatchInvoicing = false;
|
||||
BaseMsgForm.ShowError("JS错误,开票失败");
|
||||
tcs.TrySetResult(false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
for (int i = 0; i < 120; i++)
|
||||
{
|
||||
await Task.Delay(500);
|
||||
try
|
||||
{
|
||||
var res = await b.EvaluateScriptAsync("window.__invoiceApplyResult");
|
||||
if (res.Success && res.Result != null)
|
||||
{
|
||||
tcs.TrySetResult(Convert.ToBoolean(res.Result));
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
tcs.TrySetResult(false);
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> IsInstalledDetailAsync(ChromiumWebBrowser frame, int timeoutMs = 3000)
|
||||
{
|
||||
var deadline = DateTime.Now.AddMilliseconds(timeoutMs);
|
||||
while (DateTime.Now < deadline)
|
||||
{
|
||||
var r = await frame.EvaluateScriptAsync("!!(window.InvoiceApply && window.InvoiceApply.__installed)")
|
||||
.ConfigureAwait(false);
|
||||
if (r != null && r.Success && r.Result is bool && (bool)r.Result) return true;
|
||||
await Task.Delay(100).ConfigureAwait(false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void DetailRequestHandler_ResourceLoadComplete(object sender, string url, string postData, string responseData, IRequest request)
|
||||
{
|
||||
if (url.StartsWith("https://mobile.yangkeduo.com/transac_return_compensation.html"))
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ namespace _29WxOrderWpf.Ctrls.PDDInvoice.ViewModels;
|
||||
/// <summary>
|
||||
/// 拼多多发票助手 视图模型
|
||||
/// </summary>
|
||||
public partial class PDDInvoiceViewModel : BindableBaseEx
|
||||
public partial class PDDInvoiceViewModel : BindableBaseEx, IDialogAware, IDisposable
|
||||
{
|
||||
private const string ConfigPath = "./invoice.config";
|
||||
private List<OrderInvoiceInfo> OrderInvoiceInfoList = new();
|
||||
@@ -76,7 +76,7 @@ public partial class PDDInvoiceViewModel : BindableBaseEx
|
||||
//Orders = new ObservableCollection<PDDInvoiceOrderModel>(_allOrders);
|
||||
|
||||
|
||||
InitBrowser();
|
||||
InitBrowser(); InitBrowserDetail();
|
||||
if (Config == null) Config = new InvoiceConfig();
|
||||
|
||||
EndTime = DateTime.Now;
|
||||
@@ -106,7 +106,7 @@ public partial class PDDInvoiceViewModel : BindableBaseEx
|
||||
return true;
|
||||
}
|
||||
partial void InitBrowser();
|
||||
|
||||
partial void InitBrowserDetail();
|
||||
/// <summary> 列表增删时订阅/退订订单勾选状态,用于刷新表头全选状态 </summary>
|
||||
private void Orders_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
@@ -490,6 +490,8 @@ public partial class PDDInvoiceViewModel : BindableBaseEx
|
||||
}
|
||||
|
||||
public event EventHandler<int> BatchInvoiceChanged;
|
||||
public event Action<IDialogResult> RequestClose;
|
||||
|
||||
/// <summary>
|
||||
/// 后台批量开票:逐单加载开票页并自动填写提交,底部进度条实时更新,成功后更新列表状态
|
||||
/// </summary>
|
||||
@@ -556,7 +558,7 @@ public partial class PDDInvoiceViewModel : BindableBaseEx
|
||||
BottomMsg = $"正在批量开票:{i}/{targets.Count},当前订单:{order.order_sn}。总金额:{AmountTotal.ToString("N2")}";
|
||||
BottomRMsg = $"批量开票状态:成功 {success} 个,失败 {fail} 个。总金额:{AmountTotal.ToString("N2")}";
|
||||
StatisticsInvoiceAmount();
|
||||
await Task.Delay(1000);
|
||||
await Task.Delay(200);
|
||||
|
||||
if (Config.IsSaveData)
|
||||
{
|
||||
@@ -668,36 +670,36 @@ public partial class PDDInvoiceViewModel : BindableBaseEx
|
||||
|
||||
|
||||
#region 数据解析
|
||||
|
||||
private void parserData(string data)
|
||||
{
|
||||
parserData(data, out string _);
|
||||
}
|
||||
private void parserData(string data, out string lastOrderSN)
|
||||
{
|
||||
lastOrderSN = "0";
|
||||
List<PddBrowserOrderInvoiceViewModel> list = JsonHelper.DeserializeJsonToList<PddBrowserOrderInvoiceViewModel>(JsonHelper.JObjectParse(data)["orders"].ParseToString());
|
||||
|
||||
if (list.Count == 0)
|
||||
{
|
||||
StopAutoScroll();
|
||||
return;
|
||||
}
|
||||
|
||||
lastOrderSN = list.Last().order_sn;
|
||||
var currentLastItem = list.Last();
|
||||
var matched = list.Where(item => item.status == 4 && item.pay_status == 2).ToList();
|
||||
if (matched.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// CEF 回调运行在后台线程,ObservableCollection 的更新需要切回 UI 线程
|
||||
var dispatcher = Application.Current?.Dispatcher;
|
||||
if (dispatcher == null || dispatcher.CheckAccess())
|
||||
Application.Current?.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
AddOrders(matched);
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatcher.BeginInvoke(new Action(() =>
|
||||
if (matched.Count > 0)
|
||||
{
|
||||
AddOrders(matched);
|
||||
BottomMsg = $"正在读取订单,读取日期{matched.Last().OrderTime.ToString("yyyy-MM-dd")}";
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
BottomMsg = $"正在读取订单,读取日期{currentLastItem.OrderTime.ToString("yyyy-MM-dd")}";
|
||||
|
||||
}));
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -831,6 +833,9 @@ public partial class PDDInvoiceViewModel : BindableBaseEx
|
||||
get => _progressMaximum;
|
||||
set => SetValue(ref _progressMaximum, value);
|
||||
}
|
||||
|
||||
public string Title => "发票助手";
|
||||
|
||||
public void SetProgress(int value, int maxValue)
|
||||
{
|
||||
StopUnknownTotalProgressTimer();
|
||||
@@ -904,6 +909,28 @@ public partial class PDDInvoiceViewModel : BindableBaseEx
|
||||
}
|
||||
_unknownTotalProgressTimer = null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StopAutoScroll();
|
||||
Browser.Dispose();
|
||||
BrowserDetail.Dispose();
|
||||
}
|
||||
public void OnDialogOpened(IDialogParameters parameters)
|
||||
{
|
||||
|
||||
}
|
||||
public bool CanCloseDialog()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void OnDialogClosed()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
||||
@@ -913,7 +940,7 @@ public class InvoiceConfig
|
||||
public string Title { get; set; }
|
||||
public string TaxNo { get; set; }
|
||||
public decimal MaxAmount { get; set; }
|
||||
public bool IsSaveData { get; set; }
|
||||
public bool IsSaveData { get; set; } = true;
|
||||
|
||||
public void Assign(InvoiceConfig config)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user