wwt-certificate-tool 1.2.34 → 1.2.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.html +131 -8
- package/dist/version.json +6 -6
- package/package.json +1 -1
package/dist/app.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
-
<title>WWT 金蝶凭证工具 · 纯前端版 P1.2.
|
|
6
|
+
<title>WWT 金蝶凭证工具 · 纯前端版 P1.2.35</title>
|
|
7
7
|
<style>*{box-sizing:border-box;margin:0;padding:0}
|
|
8
8
|
body{font-family:"Microsoft YaHei","PingFang SC",-apple-system,Segoe UI,sans-serif;font-size:13px;color:#1f2328;background:#f4f6f9;line-height:1.6}
|
|
9
9
|
h1{font-size:17px;font-weight:600}
|
|
@@ -52,6 +52,8 @@ main{padding:14px 18px 60px}
|
|
|
52
52
|
.btn.danger{color:#c1121f}
|
|
53
53
|
.btn.danger:hover{background:#fdf0f0;border-color:#e5a3a3}
|
|
54
54
|
.btn.ghost{background:#fafbfc}
|
|
55
|
+
.btn.warn{background:#fa8c16;color:#fff;border-color:#fa8c16}
|
|
56
|
+
.btn.warn:hover{background:#d97a14;border-color:#d97a14}
|
|
55
57
|
.btn.big{padding:9px 22px;font-size:14px}
|
|
56
58
|
.rowacts{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:10px 0}
|
|
57
59
|
.tip{font-size:11.5px;color:#8b949e}
|
|
@@ -305,7 +307,7 @@ tr.bad:hover td{background:#fbe9e9}
|
|
|
305
307
|
<div class="brand">
|
|
306
308
|
<span class="logo">WWT</span>
|
|
307
309
|
<div>
|
|
308
|
-
<h1>金蝶凭证工具 <span class="ver">P1.2.
|
|
310
|
+
<h1>金蝶凭证工具 <span class="ver">P1.2.35</span></h1>
|
|
309
311
|
<div class="sub">纯前端离线版 · 双击即用 · 数据只存在本机,不上传任何服务器</div>
|
|
310
312
|
</div>
|
|
311
313
|
</div>
|
|
@@ -372,9 +374,10 @@ tr.bad:hover td{background:#fbe9e9}
|
|
|
372
374
|
<div class="rowacts">
|
|
373
375
|
<button class="btn" onclick="UI.newRule()">✚ 新增规则</button>
|
|
374
376
|
<button class="btn" onclick="UI.saveCfg()">💾 保存所有规则</button>
|
|
377
|
+
<button class="btn warn" onclick="UI.openGlobalExcludes()">🚫 全局排除(总开关)</button>
|
|
375
378
|
<button class="btn ghost" onclick="UI.exportRules()">📤 导出规则</button>
|
|
376
379
|
<button class="btn ghost" onclick="UI.importRules()">📥 导入规则</button>
|
|
377
|
-
<span class="tip"
|
|
380
|
+
<span class="tip">规则改动要点「保存所有规则」才会生效;全局排除在弹窗里保存后即生效(也写入本地存档)</span>
|
|
378
381
|
</div>
|
|
379
382
|
<div id="ruleTabs" class="src-tabs"></div>
|
|
380
383
|
<div id="ruleList"></div>
|
|
@@ -2653,6 +2656,16 @@ window.WWT_ENGINE = (function () {
|
|
|
2653
2656
|
if (!ex || !ex.length) return false;
|
|
2654
2657
|
return evalConditions(row, ex, rule.excludeLogic || "AND", curSrc, idx);
|
|
2655
2658
|
}
|
|
2659
|
+
// 【P1.2.35】全局排除(按业务源分组的总开关):在所有针对该业务源的规则之前
|
|
2660
|
+
// 先过一道粗过滤——命中则不进任何针对该业务源的规则 rowsIn,且不计入 audit。
|
|
2661
|
+
// 与规则级 excludeWhen 行为一致;但作用范围是"该业务源下所有规则",用于
|
|
2662
|
+
// 「厂牌=资产工具库」「维修工单不挂凭证」这类横扫整张表的统一排除。
|
|
2663
|
+
function isExcludedByGlobals(row, curSrc, idx, globalExcludes) {
|
|
2664
|
+
if (!globalExcludes || typeof globalExcludes !== "object") return false;
|
|
2665
|
+
const cfg = globalExcludes[curSrc];
|
|
2666
|
+
if (!cfg || !cfg.conditions || !cfg.conditions.length) return false;
|
|
2667
|
+
return evalConditions(row, cfg.conditions, cfg.logic || "AND", curSrc, idx);
|
|
2668
|
+
}
|
|
2656
2669
|
function linesKey(lines) {
|
|
2657
2670
|
return JSON.stringify(lines, (k, v) => (k === "_uid" ? undefined : v));
|
|
2658
2671
|
}
|
|
@@ -2687,12 +2700,14 @@ window.WWT_ENGINE = (function () {
|
|
|
2687
2700
|
|
|
2688
2701
|
/* ============================================================
|
|
2689
2702
|
* 主生成:业务行 → 凭证草稿
|
|
2690
|
-
* opts: {rules, biz, monthly, maps, base, selectedIds, modeOverride, groupByOverride, voucherDate, syncBizDate}
|
|
2703
|
+
* opts: {rules, biz, monthly, maps, base, selectedIds, modeOverride, groupByOverride, voucherDate, syncBizDate, globalExcludes}
|
|
2704
|
+
* globalExcludes(【P1.2.35】)= { [srcKey]: { logic, conditions:[...] } },按业务源分组的总开关
|
|
2691
2705
|
* ============================================================ */
|
|
2692
2706
|
function generate(opts) {
|
|
2693
2707
|
const idx = buildIndex(opts.base);
|
|
2694
2708
|
const monthly = opts.monthly || {};
|
|
2695
2709
|
const sel = opts.selectedIds; // Set 或 null(null=全跑)
|
|
2710
|
+
const globalExcludes = opts.globalExcludes || {}; // 【P1.2.35】
|
|
2696
2711
|
let vno = parseInt(monthly.vstart, 10); if (!isFinite(vno)) vno = 1;
|
|
2697
2712
|
let billHead = parseInt(monthly.bstart, 10); if (!isFinite(billHead)) billHead = 1;
|
|
2698
2713
|
const acctDate = clean(opts.voucherDate) || clean(monthly.date);
|
|
@@ -2700,6 +2715,7 @@ window.WWT_ENGINE = (function () {
|
|
|
2700
2715
|
const out = [];
|
|
2701
2716
|
const missing = [];
|
|
2702
2717
|
const usedRows = {}; // src -> Set(index)
|
|
2718
|
+
const globalExcludedUsed = {}; // 【P1.2.35】src -> Set(index),全局排除行不进任何规则 rowsIn
|
|
2703
2719
|
const stats = { rules: 0, rows: 0, vouchers: 0, entries: 0, skipped: [] };
|
|
2704
2720
|
|
|
2705
2721
|
for (const rule of (opts.rules || [])) {
|
|
@@ -2709,12 +2725,18 @@ window.WWT_ENGINE = (function () {
|
|
|
2709
2725
|
const rowsAll = (opts.biz && opts.biz[curSrc]) || [];
|
|
2710
2726
|
if (!rowsAll.length) { stats.skipped.push({ rule: rule.name, why: "无业务数据" }); continue; }
|
|
2711
2727
|
if (!usedRows[curSrc]) usedRows[curSrc] = new Set();
|
|
2728
|
+
if (!globalExcludedUsed[curSrc]) globalExcludedUsed[curSrc] = new Set();
|
|
2712
2729
|
|
|
2713
2730
|
// 命中行(同一行只归属第一条命中的规则)
|
|
2714
2731
|
const rowsIn = [];
|
|
2715
2732
|
for (let i = 0; i < rowsAll.length; i++) {
|
|
2716
2733
|
if (usedRows[curSrc].has(i)) continue;
|
|
2717
2734
|
const row = rowsAll[i];
|
|
2735
|
+
// 【P1.2.35】全局排除(按业务源分组的总开关):命中行不进任何针对该业务源的规则
|
|
2736
|
+
if (isExcludedByGlobals(row, curSrc, idx, globalExcludes)) {
|
|
2737
|
+
globalExcludedUsed[curSrc].add(i);
|
|
2738
|
+
continue;
|
|
2739
|
+
}
|
|
2718
2740
|
// 【P1.2.34】规则级排除:满足 excludeWhen 的行整条跳过,不进 rowsIn、不进 audit
|
|
2719
2741
|
if (isExcludedByRule(row, rule, curSrc, idx)) continue;
|
|
2720
2742
|
if (evalConditions(row, rule.conditions || [], rule.logic || "AND", curSrc, idx)) {
|
|
@@ -2975,6 +2997,7 @@ window.WWT_ENGINE = (function () {
|
|
|
2975
2997
|
const monthly = opts.monthly || {};
|
|
2976
2998
|
const acctDate = clean(opts.voucherDate) || clean(monthly.date);
|
|
2977
2999
|
const usedRows = new Set(); // 本规则命中的业务行索引集合(Step 2 标记,Step 3 覆盖率审计复用)
|
|
3000
|
+
const globalExcludes = opts.globalExcludes || {}; // 【P1.2.35】
|
|
2978
3001
|
|
|
2979
3002
|
/* ---- Step 1:找出源表"应当处理的行"(candidate):抽 rule 里的「=」条件当候选过滤器。
|
|
2980
3003
|
只在 conditions 顶层 AND 段时按 = 条件过滤;OR 段不参与(避免把 OR 多值都当 AND 求交集)。 */
|
|
@@ -3027,6 +3050,8 @@ window.WWT_ENGINE = (function () {
|
|
|
3027
3050
|
for (let i = 0; i < rowsAll.length; i++) {
|
|
3028
3051
|
if (usedRows.has(i)) continue;
|
|
3029
3052
|
const row = rowsAll[i];
|
|
3053
|
+
// 【P1.2.35】全局排除(按业务源分组的总开关):命中行不进任何 audit 统计
|
|
3054
|
+
if (isExcludedByGlobals(row, curSrc, idx, globalExcludes)) continue;
|
|
3030
3055
|
// 【P1.2.34】规则级排除:跳过 excludeWhen 命中的行
|
|
3031
3056
|
if (isExcludedByRule(row, r, curSrc, idx)) continue;
|
|
3032
3057
|
if (evalConditions(row, r.conditions || [], r.logic || "AND", curSrc, idx)) {
|
|
@@ -3182,9 +3207,16 @@ window.WWT_ENGINE = (function () {
|
|
|
3182
3207
|
* neverMatchedRows = candidatesMissed(兼容旧字段名) */
|
|
3183
3208
|
const candidatesMissed = [], excludedRows = [];
|
|
3184
3209
|
let ruleExcludedCount = 0, ruleExcludedAmount = 0;
|
|
3210
|
+
let globalExcludedCount = 0, globalExcludedAmount = 0; // 【P1.2.35】
|
|
3185
3211
|
for (let i = 0; i < rowsAll.length; i++) {
|
|
3186
3212
|
if (matchedIdxs.has(i)) continue;
|
|
3187
3213
|
const row = rowsAll[i];
|
|
3214
|
+
// 【P1.2.35】全局排除:命中行不进任何 audit 统计
|
|
3215
|
+
if (isExcludedByGlobals(row, curSrc, idx, globalExcludes)) {
|
|
3216
|
+
globalExcludedCount++;
|
|
3217
|
+
if (amountCol) globalExcludedAmount += num(row[amountCol]);
|
|
3218
|
+
continue;
|
|
3219
|
+
}
|
|
3188
3220
|
// 【P1.2.34】规则级排除:满足 excludeWhen 的行不进任何 audit 统计
|
|
3189
3221
|
if (isExcludedByRule(row, rule, curSrc, idx)) {
|
|
3190
3222
|
ruleExcludedCount++;
|
|
@@ -3243,6 +3275,9 @@ window.WWT_ENGINE = (function () {
|
|
|
3243
3275
|
// 【P1.2.34】规则级排除:满足 excludeWhen 的行彻底从 audit 视野消失(业务上合法跳过)
|
|
3244
3276
|
rule_excluded: ruleExcludedCount,
|
|
3245
3277
|
rule_excluded_amount: round2(ruleExcludedAmount),
|
|
3278
|
+
// 【P1.2.35】全局排除(按业务源分组的总开关):扫表即跳,连候选都不进
|
|
3279
|
+
global_excluded: globalExcludedCount,
|
|
3280
|
+
global_excluded_amount: round2(globalExcludedAmount),
|
|
3246
3281
|
// 兼容旧字段(仍叫 never_matched,但其内容仅含真正应处理子集漏命中的行)
|
|
3247
3282
|
never_matched: candidatesMissed.length,
|
|
3248
3283
|
unmatched_amount: round2(unmatchedAmt),
|
|
@@ -3418,7 +3453,7 @@ window.WWT_ENGINE = (function () {
|
|
|
3418
3453
|
buildIndex, getField, compare, evalConditions, applyTransform,
|
|
3419
3454
|
parseAmtExpr, sumAmtExpr, evalAmtExpr, isAutoDiff, splitFieldsOf,
|
|
3420
3455
|
calcDimValues, leafSubject, resolveEffectiveLines, lookupTypeDim,
|
|
3421
|
-
isExcludedByRule,
|
|
3456
|
+
isExcludedByRule, isExcludedByGlobals,
|
|
3422
3457
|
generate, balanceCheck, buildKingdee, detect, auditRule,
|
|
3423
3458
|
lookupMap, lookupTypeMap,
|
|
3424
3459
|
};
|
|
@@ -3437,13 +3472,13 @@ const nz = v => (v === null || v === undefined || v === "" ? "" : v);
|
|
|
3437
3472
|
|
|
3438
3473
|
/* 全局状态 */
|
|
3439
3474
|
const S = window.__STATE__ = {
|
|
3440
|
-
monthly:{}, maps:{}, rules:[],
|
|
3475
|
+
monthly:{}, maps:{}, rules:[], globalExcludes:{}, // 【P1.2.35】按业务源分组的全局排除(总开关)
|
|
3441
3476
|
base:{}, biz:{}, draft:[], missing:[], balance:null,
|
|
3442
3477
|
selRules:new Set(), cvOut:null, checkRows:null,
|
|
3443
3478
|
};
|
|
3444
3479
|
|
|
3445
3480
|
/* ---------------- 版本 & 框架自动更新(只更新程序本身,本地数据/规则不动) ---------------- */
|
|
3446
|
-
const WWT_VER=(function(){const raw='P1.2.
|
|
3481
|
+
const WWT_VER=(function(){const raw='P1.2.35';return raw.indexOf('__WVER__')>=0?'P1.2.10':raw;})();
|
|
3447
3482
|
const UPD_URL_KEY='wwt_fe_updurl';
|
|
3448
3483
|
// 默认更新源:Gitee 上本项目的 version.json(API 接口,浏览器可直接跨域读)
|
|
3449
3484
|
const DEFAULT_UPD_URL='https://gitee.com/api/v5/repos/wwtmrhu/certificate-generation/contents/dist/version.json';
|
|
@@ -3773,7 +3808,7 @@ function uid(){return 'VR'+Date.now().toString(36)+Math.random().toString(36).sl
|
|
|
3773
3808
|
/* ---------------- 配置(localStorage) ---------------- */
|
|
3774
3809
|
const LS='wwt_fe_cfg_v1';
|
|
3775
3810
|
function saveCfg(){
|
|
3776
|
-
localStorage.setItem(LS,JSON.stringify({monthly:S.monthly,maps:S.maps,rules:S.rules}));
|
|
3811
|
+
localStorage.setItem(LS,JSON.stringify({monthly:S.monthly,maps:S.maps,rules:S.rules,globalExcludes:S.globalExcludes||{}}));
|
|
3777
3812
|
toast('配置已保存到本机');
|
|
3778
3813
|
}
|
|
3779
3814
|
function loadCfg(){
|
|
@@ -3785,12 +3820,15 @@ function loadCfg(){
|
|
|
3785
3820
|
S.monthly=Object.assign({},P.monthly,o.monthly||{});
|
|
3786
3821
|
S.maps=Object.assign({},P.maps,o.maps||{});
|
|
3787
3822
|
S.rules=(o.rules&&o.rules.length)?o.rules:deepCopy(P.rules||[]);
|
|
3823
|
+
// 【P1.2.35】全局排除(按业务源分组的总开关):老存档没这字段 → 给空对象
|
|
3824
|
+
S.globalExcludes=(o.globalExcludes&&typeof o.globalExcludes==='object')?o.globalExcludes:{};
|
|
3788
3825
|
return;
|
|
3789
3826
|
}catch(e){}
|
|
3790
3827
|
}
|
|
3791
3828
|
S.monthly=deepCopy(P.monthly||{});
|
|
3792
3829
|
S.maps=deepCopy(P.maps||{});
|
|
3793
3830
|
S.rules=deepCopy(P.rules||[]);
|
|
3831
|
+
S.globalExcludes={};
|
|
3794
3832
|
}
|
|
3795
3833
|
|
|
3796
3834
|
/* ---------------- Excel 读取(SheetJS,失败自动走兜底) ---------------- */
|
|
@@ -5141,6 +5179,7 @@ function doGenerate(){
|
|
|
5141
5179
|
selectedIds:sel, modeOverride:$('genMode').value||'',
|
|
5142
5180
|
groupByOverride:$('genGroupBy').value.trim()||'',
|
|
5143
5181
|
voucherDate:date, syncBizDate:$('genSyncBiz').checked,
|
|
5182
|
+
globalExcludes:S.globalExcludes||{}, // 【P1.2.35】
|
|
5144
5183
|
});
|
|
5145
5184
|
}catch(e){ $('genBanner').innerHTML='<div class="banner err"><b>生成出错:</b>'+esc(e.message)+'</div>'; toast('生成失败:'+e.message); return; }
|
|
5146
5185
|
S.draft=res.rows; S.missing=res.missing;
|
|
@@ -5275,6 +5314,7 @@ function previewRule(ruleId){
|
|
|
5275
5314
|
res=E.auditRule({
|
|
5276
5315
|
rule:r, biz:S.biz, base:S.base, maps:S.maps, monthly:S.monthly,
|
|
5277
5316
|
voucherDate:date, syncBizDate:$('genSyncBiz')&&$('genSyncBiz').checked,
|
|
5317
|
+
globalExcludes:S.globalExcludes||{}, // 【P1.2.35】
|
|
5278
5318
|
});
|
|
5279
5319
|
}catch(e){
|
|
5280
5320
|
$('modal').innerHTML='<div class="modalbox"><div class="modalhd"><h3>规则预览失败</h3><button class="mini" onclick="UI.closeModal()">✕ 关闭</button></div>'+
|
|
@@ -5289,6 +5329,7 @@ function previewRule(ruleId){
|
|
|
5289
5329
|
gen=E.generate({
|
|
5290
5330
|
rules:[Object.assign({},r,{enabled:true})], biz:S.biz, base:S.base, maps:S.maps,
|
|
5291
5331
|
monthly:S.monthly, voucherDate:date, syncBizDate:$('genSyncBiz')&&$('genSyncBiz').checked,
|
|
5332
|
+
globalExcludes:S.globalExcludes||{}, // 【P1.2.35】
|
|
5292
5333
|
});
|
|
5293
5334
|
}catch(e){ gen=null; }
|
|
5294
5335
|
_previewCache={rule:r, res:res, gen:gen, date:date};
|
|
@@ -6714,6 +6755,88 @@ window.UI={
|
|
|
6714
6755
|
toggleRuleBody:i=>{const b=$('rb'+i);if(b)b.classList.toggle('on');},
|
|
6715
6756
|
toggleRuleSel:cb=>{cb.checked?S.selRules.add(cb.dataset.rid):S.selRules['delete'](cb.dataset.rid);},
|
|
6716
6757
|
selAllRules:v=>{S.selRules.clear();if(v)S.rules.forEach(r=>S.selRules.add(r.id));renderRuleChecks();},
|
|
6758
|
+
// 【P1.2.35】全局排除弹窗:按业务源分组的总开关(在外面,不嵌在每条规则里)
|
|
6759
|
+
openGlobalExcludes:()=>{
|
|
6760
|
+
const ge = S.globalExcludes || (S.globalExcludes = {});
|
|
6761
|
+
// 从 WWT_CONST.BIZ_SLOTS 拿业务源 key 与显示名
|
|
6762
|
+
const slots = (window.WWT_CONST && window.WWT_CONST.BIZ_SLOTS) || [];
|
|
6763
|
+
let html = '<div class="modalbox" style="max-width:780px;max-height:88vh;display:flex;flex-direction:column">'+
|
|
6764
|
+
'<div class="modalhd"><h3>🚫 全局排除条件(按业务源分组的总开关)</h3>'+
|
|
6765
|
+
'<button class="mini" onclick="UI.closeModal()">✕ 关闭</button></div>'+
|
|
6766
|
+
'<div class="modalbd" style="overflow:auto;flex:1;min-height:0">'+
|
|
6767
|
+
'<div class="banner" style="margin-bottom:10px">'+
|
|
6768
|
+
'<b>用法:</b>按业务源分组配置「命中即跳」的排除条件。<b>满足条件的行不进任何针对该业务源的规则</b>,'+
|
|
6769
|
+
'且不计入覆盖率审计统计(不会被误标成"遗漏")。'+
|
|
6770
|
+
'<br>典型场景:「销售已确认」里厂牌 = <code>资产工具库</code> 的行由其他流程处理,整张表一刀切掉。'+
|
|
6771
|
+
'<br><span style="color:#a40e26">⚠ 全局排除是「粗过滤」,会作用于该业务源下<b>所有规则</b>;想只排除某一条规则请在「编辑规则 → 排除条件」单独配。</span>'+
|
|
6772
|
+
'</div>';
|
|
6773
|
+
// 每个业务源一个折叠 details
|
|
6774
|
+
for (const s of slots) {
|
|
6775
|
+
const cfg = ge[s.key] || (ge[s.key] = { logic: 'AND', conditions: [] });
|
|
6776
|
+
const condHTML = (cfg.conditions || []).map((c, i) => condRowHTML(i, c)).join('');
|
|
6777
|
+
const condsId = 'ge_conds_' + s.key;
|
|
6778
|
+
html += '<details open style="border:1px solid #dde2e8;border-radius:6px;margin:6px 0;background:#fff">'+
|
|
6779
|
+
'<summary style="padding:8px 12px;cursor:pointer;font-weight:600">'+
|
|
6780
|
+
'<span style="display:inline-block;width:160px">'+esc(s.label)+'</span>'+
|
|
6781
|
+
'<span style="color:#a40e26;font-size:12px">['+((cfg.conditions||[]).length)+' 个条件]</span>'+
|
|
6782
|
+
'</summary>'+
|
|
6783
|
+
'<div style="padding:4px 12px 12px">'+
|
|
6784
|
+
'<div class="row" style="margin-bottom:6px">'+
|
|
6785
|
+
'<span class="w80">逻辑</span>'+
|
|
6786
|
+
'<select class="w80" data-ge="logic" data-src="'+esc(s.key)+'">'+
|
|
6787
|
+
'<option value="AND"'+((cfg.logic||'AND')==='AND'?' selected':'')+'>AND(全部满足)</option>'+
|
|
6788
|
+
'<option value="OR"'+(cfg.logic==='OR'?' selected':'')+'>OR(任一满足)</option>'+
|
|
6789
|
+
'</select>'+
|
|
6790
|
+
'<span style="margin-left:12px;font-size:11.5px;color:#888">满足 = 该行被排除(不参与任何规则、不进 audit)</span>'+
|
|
6791
|
+
'</div>'+
|
|
6792
|
+
'<div class="condrows" id="'+condsId+'" data-src="'+esc(s.key)+'">'+condHTML+'</div>'+
|
|
6793
|
+
'<button class="mini" onclick="UI.addGlobalExcludeCond(\''+s.key+'\')">+ 加条件</button>'+
|
|
6794
|
+
'</div></details>';
|
|
6795
|
+
}
|
|
6796
|
+
html += '</div>'+
|
|
6797
|
+
'<div class="modalft">'+
|
|
6798
|
+
'<button class="btn ghost" onclick="UI.closeModal()">取消</button>'+
|
|
6799
|
+
'<button class="btn primary" onclick="UI.saveGlobalExcludes()">💾 保存全局排除</button>'+
|
|
6800
|
+
'</div></div>';
|
|
6801
|
+
const m = $('modal'); m.innerHTML = html; m.style.display = 'flex';
|
|
6802
|
+
},
|
|
6803
|
+
// 在某个业务源分组下加一行条件
|
|
6804
|
+
addGlobalExcludeCond:(srcKey)=>{
|
|
6805
|
+
const ge = S.globalExcludes || (S.globalExcludes = {});
|
|
6806
|
+
if (!ge[srcKey]) ge[srcKey] = { logic: 'AND', conditions: [] };
|
|
6807
|
+
ge[srcKey].conditions.push({ field: '', op: '=', value: '', logic: '' });
|
|
6808
|
+
UI.openGlobalExcludes(); // 直接重渲染(简单实现;数据在 S.globalExcludes 里有真实存储)
|
|
6809
|
+
},
|
|
6810
|
+
// 收集弹窗里的输入,写回 S.globalExcludes 并 saveCfg
|
|
6811
|
+
saveGlobalExcludes:()=>{
|
|
6812
|
+
const ge = S.globalExcludes || (S.globalExcludes = {});
|
|
6813
|
+
// 收集每个业务源的 logic + conditions
|
|
6814
|
+
document.querySelectorAll('[data-ge="logic"][data-src]').forEach(sel=>{
|
|
6815
|
+
const src = sel.getAttribute('data-src');
|
|
6816
|
+
if (!ge[src]) ge[src] = { logic: 'AND', conditions: [] };
|
|
6817
|
+
ge[src].logic = sel.value || 'AND';
|
|
6818
|
+
});
|
|
6819
|
+
document.querySelectorAll('.condrows[data-src]').forEach(box=>{
|
|
6820
|
+
const src = box.getAttribute('data-src');
|
|
6821
|
+
if (!ge[src]) ge[src] = { logic: 'AND', conditions: [] };
|
|
6822
|
+
const rows = [...box.querySelectorAll(':scope > .condrow')].map(row => {
|
|
6823
|
+
const f = (row.querySelector('[data-f="field"]') || {}).value || '';
|
|
6824
|
+
const op = (row.querySelector('[data-f="op"]') || {}).value || '=';
|
|
6825
|
+
const v = (row.querySelector('[data-f="value"]') || {}).value || '';
|
|
6826
|
+
const lg = (row.querySelector('[data-f="logic"]')|| {}).value || '';
|
|
6827
|
+
return { field: f, op: op, value: v, logic: lg };
|
|
6828
|
+
}).filter(c => c.field && c.op); // 空条件过滤掉
|
|
6829
|
+
ge[src].conditions = rows;
|
|
6830
|
+
// 没有任何有效条件 → 整个 srcKey 配置删掉(保持 cfg 干净)
|
|
6831
|
+
if (!rows.length) delete ge[src];
|
|
6832
|
+
});
|
|
6833
|
+
saveCfg();
|
|
6834
|
+
UI.closeModal();
|
|
6835
|
+
const totalSrc = Object.keys(ge).filter(k => (ge[k].conditions||[]).length).length;
|
|
6836
|
+
const totalCond = Object.values(ge).reduce((s, x) => s + ((x && x.conditions) ? x.conditions.length : 0), 0);
|
|
6837
|
+
toast('已保存全局排除:'+totalSrc+' 个业务源 / '+totalCond+' 条条件');
|
|
6838
|
+
},
|
|
6839
|
+
|
|
6717
6840
|
// 规则编辑器
|
|
6718
6841
|
closeModal:()=>{$('modal').style.display='none';editing=null;editingIdx=-1;renderRules();},
|
|
6719
6842
|
saveRuleForm:()=>{
|
package/dist/version.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"ver": "P1.2.
|
|
3
|
-
"npm": "1.2.
|
|
4
|
-
"url": "https://cdn.jsdelivr.net/npm/wwt-certificate-tool@1.2.
|
|
5
|
-
"url_unpkg": "https://unpkg.com/wwt-certificate-tool@1.2.
|
|
6
|
-
"url_gh": "https://api.github.com/repos/huweipei745839/certificate-generation/contents/dist/WWT%E5%87%AD%E8%AF%81%E5%B7%A5%E5%85%B7_P1.2.
|
|
7
|
-
"note": "
|
|
2
|
+
"ver": "P1.2.35",
|
|
3
|
+
"npm": "1.2.35",
|
|
4
|
+
"url": "https://cdn.jsdelivr.net/npm/wwt-certificate-tool@1.2.35/dist/app.html",
|
|
5
|
+
"url_unpkg": "https://unpkg.com/wwt-certificate-tool@1.2.35/dist/app.html",
|
|
6
|
+
"url_gh": "https://api.github.com/repos/huweipei745839/certificate-generation/contents/dist/WWT%E5%87%AD%E8%AF%81%E5%B7%A5%E5%85%B7_P1.2.35.html",
|
|
7
|
+
"note": "①「全局排除」机制升级:把排除条件从每条规则里挪出来,做成外面工具栏的「🚫 全局排除(总开关)」按钮,按业务源分组配置(折叠面板)。命中行不进任何针对该业务源的规则,且不计入审计统计。典型场景:「销售已确认」里厂牌=资产工具库 的行整张表一刀切掉。② cfg 新增 globalExcludes 字段(saveCfg/loadCfg 自动读写);引擎新增 isExcludedByGlobals(),generate/auditRule 三处接入。③ by_source 返回值新增 global_excluded/global_excluded_amount 字段(与 P1.2.34 的 rule_excluded 并列)。④ 单规则的 rule.excludeWhen 保留作为兜底细粒度配置。⑤ 工具栏新增橙底按钮 + 新增 .btn.warn 样式;弹窗复用 condRowHTML 条件行模板。回归 202/202 全通过。"
|
|
8
8
|
}
|