bdip-office-assistant-cli 0.0.0-stage → 1.0.0
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/README.md +37 -2
- package/dist/accounts.js +30 -0
- package/dist/args.js +36 -0
- package/dist/auth-token.js +778 -0
- package/dist/auth.js +290 -0
- package/dist/blog.js +562 -0
- package/dist/browser-links.js +223 -0
- package/dist/captcha-display.js +30 -0
- package/dist/cli.js +5479 -0
- package/dist/client.js +254 -0
- package/dist/config.js +170 -0
- package/dist/createcontext.js +41 -0
- package/dist/crypto.js +139 -0
- package/dist/cube.js +1771 -0
- package/dist/email.js +841 -0
- package/dist/field-preparation.js +185 -0
- package/dist/fileutil.js +20 -0
- package/dist/hrm.js +478 -0
- package/dist/keepalive.js +99 -0
- package/dist/kq.js +280 -0
- package/dist/links.js +34 -0
- package/dist/nodemap.js +242 -0
- package/dist/odoc.js +199 -0
- package/dist/office-tools.js +138 -0
- package/dist/pageopen.js +29 -0
- package/dist/parse.js +177 -0
- package/dist/password-input.js +84 -0
- package/dist/password-login.js +208 -0
- package/dist/querystate.js +38 -0
- package/dist/render.js +21 -0
- package/dist/report.js +50 -0
- package/dist/session-worker.js +54 -0
- package/dist/session.js +280 -0
- package/dist/submissionplan.js +45 -0
- package/dist/submissionstate.js +81 -0
- package/dist/subordinate.js +458 -0
- package/dist/supervise.js +152 -0
- package/dist/todoaskidea.js +54 -0
- package/dist/todobatch.js +172 -0
- package/dist/todocc.js +128 -0
- package/dist/todoforward.js +66 -0
- package/dist/todoreject.js +215 -0
- package/dist/todosubmit.js +325 -0
- package/dist/todotransfer.js +54 -0
- package/dist/util.js +25 -0
- package/dist/wfagent.js +538 -0
- package/dist/wfcreate.js +102 -0
- package/dist/wfform.js +169 -0
- package/dist/wfops.js +40 -0
- package/dist/wfreceiver.js +43 -0
- package/dist/wfstart.js +558 -0
- package/dist/wfsummary.js +89 -0
- package/dist/wfupload.js +78 -0
- package/dist/wfuser.js +96 -0
- package/dist/workflow-runtime.js +151 -0
- package/dist/workflow.js +570 -0
- package/dist/workplan.js +431 -0
- package/package.json +28 -6
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import {definitions,detailTables,fingerprint,loadAdapter,buildDraft,verifyDraft} from './workflow-runtime.js';
|
|
2
|
+
import {createApiFromProfile} from './client.js';
|
|
3
|
+
|
|
4
|
+
const clone=x=>structuredClone(x);
|
|
5
|
+
const cell=x=>x&&typeof x==='object'&&'value'in x?clone(x):{value:x??''};
|
|
6
|
+
const fieldKey=id=>String(id).replace(/^field/,'');
|
|
7
|
+
const equal=(a,b)=>JSON.stringify(a)===JSON.stringify(b);
|
|
8
|
+
export function paragraphText(value,field) {
|
|
9
|
+
if(Number(field?.htmltype)!==2 || Number(field?.detailtype)!==2 || !/[\r\n]/.test(String(value)) || /<[^>]+>/.test(String(value))) return value;
|
|
10
|
+
const escape=s=>s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
11
|
+
return String(value).replace(/\r\n/g,'\n').split('\n').map(line=>'<p>'+escape(line)+'</p>').join('');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const fail=(state,message)=>{const error=Error(message);error.preparationState=state;throw error;};
|
|
15
|
+
export function stableConfig(value) {
|
|
16
|
+
if(Array.isArray(value))return value.map(stableConfig);
|
|
17
|
+
if(value&&typeof value==='object')return Object.fromEntries(Object.keys(value).sort().filter(k=>!['systemdatas','files','linkageUUID','currenttime','signatureSecretKey','signatureAttributesStr','formulakey'].includes(k)).map(k=>[k,stableConfig(value[k])]));
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
// 小语法解释器:字段引用、字符串、IF和相等判断;从不执行服务器JavaScript。
|
|
21
|
+
export function formulaValue(text,read) {
|
|
22
|
+
const tokens=text.match(/\[(?:main|detail_\d+)\.-?\d+\]|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|==|!=|IF|[(),]|\s+|-?\d+(?:\.\d+)?/g)??[];
|
|
23
|
+
if(tokens.join('')!==text)fail('unsupported','不支持的公式语法');
|
|
24
|
+
const ts=tokens.filter(t=>!/^\s+$/.test(t));let i=0;
|
|
25
|
+
const primary=()=>{
|
|
26
|
+
const t=ts[i++];
|
|
27
|
+
if(t==='IF'){if(ts[i++]!=='(')throw Error('IF语法');const cond=expr();if(ts[i++]!==',')throw Error('IF语法');const yes=expr();if(ts[i++]!==',')throw Error('IF语法');const no=expr();if(ts[i++]!==')')throw Error('IF语法');return cond?yes:no;}
|
|
28
|
+
if(/^\[/.test(t??''))return read(t.slice(1,-1));
|
|
29
|
+
if(/^['"]/.test(t??''))return t.slice(1,-1);
|
|
30
|
+
if(/^-?\d/.test(t??''))return t;
|
|
31
|
+
fail('unsupported','无法解释公式');
|
|
32
|
+
};
|
|
33
|
+
const expr=()=>{let a=primary();if(ts[i]==='=='||ts[i]==='!='){const op=ts[i++],b=primary();a=op==='=='?String(a)===String(b):String(a)!==String(b);}return a;};
|
|
34
|
+
const result=expr();if(i!==ts.length)fail('unsupported','公式存在未处理内容');return result;
|
|
35
|
+
}
|
|
36
|
+
export async function executeLinkages(api,form,tables,input,{maxSteps=100,covered=[]}={}) {
|
|
37
|
+
const f=clone(form),t=clone(tables),defs=definitions(f),cfg=f.linkageCfg??{};
|
|
38
|
+
const evidence={state:'preparing',coverage:[],rowChanges:[]};
|
|
39
|
+
// 未支持的配置必须明确由已验证适配承担,不能吞错继续。
|
|
40
|
+
for(const [kind,data]of Object.entries(cfg))if(!['formulaCfg','dataInputCfg','fieldSqlCfg'].includes(kind)&&JSON.stringify(data)!=='{}'&&JSON.stringify(data)!=='[]'&&!covered.includes(kind))fail('unsupported','联动配置尚未支持:'+kind);
|
|
41
|
+
const getCells=(table,row)=>table==='main'?f.maindata:t[table]?.rows[row];
|
|
42
|
+
const protectedValues=new Map();
|
|
43
|
+
const write=(table,row,id,value,source)=>{
|
|
44
|
+
const field=defs.find(x=>x.table===table&&x.id===String(id));if(!field)fail('unsupported','联动目标字段不存在:'+id);
|
|
45
|
+
const cells=getCells(table,row);if(!cells)fail('unsupported','联动行不存在');
|
|
46
|
+
const next=cell(value),key=`${table}.${id}:${row}`;
|
|
47
|
+
if(protectedValues.has(key)&&String(next.value)!==String(protectedValues.get(key)))fail('conflict','用户值与联动冲突:'+id);
|
|
48
|
+
if(String(cells['field'+id]?.value??'')===String(next.value)&&(!next.specialobj||equal(cells['field'+id]?.specialobj,next.specialobj)))return false;
|
|
49
|
+
cells['field'+id]=next;return true;
|
|
50
|
+
};
|
|
51
|
+
for(let [raw,v]of Object.entries(input.values??{})){
|
|
52
|
+
const id=fieldKey(raw);if(Number(id)<0)continue;
|
|
53
|
+
const d=defs.find(x=>x.id===id&&x.table==='main');if(!d||![2,3].includes(Number(d.viewattr)))fail('conflict','禁止用户覆盖只读/隐藏字段:'+id);
|
|
54
|
+
v=paragraphText(v,d);
|
|
55
|
+
write('main',-1,id,{value:v,specialobj:input.browserNames?.[id]?[{name:input.browserNames[id]}]:undefined});protectedValues.set(`main.${id}:-1`,v);
|
|
56
|
+
}
|
|
57
|
+
for(const dt of input.details??[]){const mark='detail_'+(Number(dt.index)+1);if(!t[mark])fail('unsupported','明细不存在');
|
|
58
|
+
if(input.requestId&&t[mark].rows.length!==dt.rows.length)fail('unsupported','用户增删明细需专门适配');
|
|
59
|
+
if(!input.requestId)t[mark].rows=dt.rows.map(()=>clone(t[mark].addRowDefValue??{}));
|
|
60
|
+
dt.rows.forEach((row,i)=>Object.entries(row).forEach(([raw,v])=>{const id=fieldKey(raw),d=defs.find(x=>x.id===id&&x.table===mark);if(!d||![2,3].includes(Number(d.viewattr)))fail('conflict','禁止用户覆盖只读明细:'+id);v=paragraphText(v,d);write(mark,i,id,{value:v,specialobj:input.detailBrowserNames?.[dt.index]?.[i]?.[id]?[{name:input.detailBrowserNames[dt.index][i][id]}]:undefined});protectedValues.set(`${mark}.${id}:${i}`,v);}));
|
|
61
|
+
}
|
|
62
|
+
const seen=new Map();let steps=0;
|
|
63
|
+
const formulas=cfg.formulaCfg?.formulas??[];
|
|
64
|
+
for(let pass=0;pass<maxSteps;pass++){
|
|
65
|
+
let changed=false;
|
|
66
|
+
for(const rule of formulas){
|
|
67
|
+
const m=String(rule.formula??'').match(/^\[(main|detail_\d+)\.(-?\d+)\]\s*=\s*(.*)$/);if(!m)fail('unsupported','公式目标语法不支持');
|
|
68
|
+
const [,table,id,expression]=m;
|
|
69
|
+
const rows=table==='main'?[-1]:(t[table]?.rows??[]).map((_,i)=>i);
|
|
70
|
+
for(const row of rows){const read=ref=>{const [tab,fid]=ref.split('.');return getCells(tab,tab==='main'?-1:row)?.['field'+fid]?.value??'';};
|
|
71
|
+
const value=formulaValue(expression,read);
|
|
72
|
+
const sourceRef=expression.trim().match(/^\[(main|detail_\d+)\.(\d+)\]$/);
|
|
73
|
+
const next=sourceRef?getCells(sourceRef[1],sourceRef[1]==='main'?-1:row)?.['field'+sourceRef[2]]:{value};
|
|
74
|
+
// 双向互拷贝以非空输入为起点;未设置一端不能清掉另一端。
|
|
75
|
+
if(sourceRef&&String(next?.value??'')===''&&String(getCells(table,row)?.['field'+id]?.value??'')!=='')continue;
|
|
76
|
+
changed=write(table,row,id,next??{value},'formula')||changed;
|
|
77
|
+
evidence.coverage.push({kind:'formula',target:table+'.'+id,row,state:'executed'});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
for(const [trigger,rules]of Object.entries(cfg.fieldSqlCfg??{}))for(const rule of rules){
|
|
81
|
+
const targetIds=String(rule.assignField??'').split(',').filter(Boolean);
|
|
82
|
+
const targetDefs=targetIds.map(id=>defs.find(d=>d.id===id));
|
|
83
|
+
if(!targetDefs.length||targetDefs.some(d=>!d))fail('unsupported','SQL字段查询目标未知');
|
|
84
|
+
const table=targetDefs[0].table;
|
|
85
|
+
if(targetDefs.some(d=>d.table!==table))fail('unsupported','SQL字段查询跨表目标需适配');
|
|
86
|
+
for(const row of table==='main'?[-1]:(t[table]?.rows??[]).map((_,i)=>i)){
|
|
87
|
+
const params={};
|
|
88
|
+
for(const id of rule.relateFields??[]){const def=defs.find(d=>d.id===String(id));if(!def)fail('unsupported','SQL依赖字段不存在');params['field'+id+(def.table==='main'?'':'_'+row)]=getCells(def.table,def.table==='main'?-1:row)?.['field'+id]?.value??'';}
|
|
89
|
+
const key='sql:'+rule.config+':'+row,signature=JSON.stringify(params);if(seen.get(key)===signature)continue;
|
|
90
|
+
if(++steps>maxSteps)fail('nonconvergent','字段查询超限');seen.set(key,signature);
|
|
91
|
+
const p=f.params,body={...params,requestid:String(input.requestId??'-1'),workflowid:String(input.workflowId),nodeid:String(p.nodeid),formid:String(p.formid),isbill:String(p.isbill??'1'),triSource:'1',showAI:'1',linkageid:String(rule.config),linkageUUID:f.submitParams.linkageUUID??'',
|
|
92
|
+
['triFieldid_'+rule.config]:trigger==='emptyTriField'?'':trigger,['rowIndexStr_'+rule.config]:String(row),['triTableMark_'+rule.config]:table,f_weaver_belongto_userid:input.accountId,f_weaver_belongto_usertype:String(p.f_weaver_belongto_usertype??'0')};
|
|
93
|
+
const response=await api.post('/api/workflow/linkage/reqFieldSqlResult',body,{forceCookie:true});const info=response.json?.['assignInfo_'+rule.config];
|
|
94
|
+
if(!info||info.resultOutOfLimit||response.json?.status===false)fail('failed','字段查询失败:'+rule.config);
|
|
95
|
+
for(const key of Object.keys(info))if(!['changeValue','linkageSql','resultOutOfLimit'].includes(key))fail('unsupported','字段查询响应未支持:'+key);
|
|
96
|
+
for(const [raw,value]of Object.entries(info.changeValue??{})){const m=raw.match(/^field(\d+)(?:_(\d+))?$/);if(!m)fail('unsupported','SQL目标格式未知');const d=defs.find(d=>d.id===m[1]);changed=write(d?.table,m[2]?Number(m[2]):d?.table==='main'?-1:row,m[1],value,'server')||changed;}
|
|
97
|
+
evidence.coverage.push({kind:'fieldSql',config:String(rule.config),row,state:'executed'});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
for(const [raw,configs]of Object.entries(cfg.dataInputCfg??{})){
|
|
101
|
+
const id=fieldKey(raw),d=defs.find(x=>x.id===id);if(!d||!Array.isArray(configs))fail('unsupported','数据联动配置无效');
|
|
102
|
+
for(const row of d.table==='main'?[-1]:(t[d.table]?.rows??[]).map((_,i)=>i))for(const rule of configs){
|
|
103
|
+
const key=`${rule.config}:${d.table}:${row}`;
|
|
104
|
+
const values={};for(const field of defs){const cells=getCells(field.table,field.table==='main'?-1:row);if(cells?.['field'+field.id])values['field'+field.id]=cells['field'+field.id].value;}
|
|
105
|
+
const signature=JSON.stringify(values);if(seen.get(key)===signature)continue;
|
|
106
|
+
if(++steps>maxSteps)fail('nonconvergent','联动超限或循环不收敛');seen.set(key,signature);
|
|
107
|
+
const p=f.params,sp=f.submitParams;
|
|
108
|
+
const body={...values,requestid:String(input.requestId??'-1'),workflowid:String(input.workflowId),nodeid:String(p.nodeid),formid:String(p.formid),isbill:String(p.isbill??'1'),triSource:'1',showAI:'1',linkageid:String(rule.config),linkageUUID:sp.linkageUUID??'',
|
|
109
|
+
[`triFieldid_${rule.config}`]:id,[`rowIndexStr_${rule.config}`]:String(row),[`triTableMark_${rule.config}`]:d.table,f_weaver_belongto_userid:input.accountId,f_weaver_belongto_usertype:String(p.f_weaver_belongto_usertype??'0')};
|
|
110
|
+
if(row>=0)for(const [k,v]of Object.entries(values))body[k+'_'+row]=v;
|
|
111
|
+
for(const assigned of rule.otherParams?.assignfields??[]) {
|
|
112
|
+
const assignedDef=defs.find(x=>x.id===String(assigned));
|
|
113
|
+
const dependencies=assignedDef?.browserattr?.cusDependShip??[];
|
|
114
|
+
if(dependencies.length){const depend={};for(const dep of dependencies){const d=defs.find(x=>x.id===String(dep.fieldid));if(!d)fail('unsupported','自定义浏览框依赖不存在');const c=getCells(d.table,d.table==='main'?-1:row);if(!c)fail('unsupported','缺少自定义浏览框同行依赖');depend[dep.key]=c['field'+dep.fieldid]?.value??'';}body['customDepend_field'+assigned+(assignedDef.table==='main'?'':'_'+row)]=JSON.stringify(depend);}
|
|
115
|
+
}
|
|
116
|
+
const r=await api.post('/api/workflow/linkage/reqDataInputResult',body,{forceCookie:true});
|
|
117
|
+
const info=r.json?.['assignInfo_'+rule.config];if(!info||info.resultOutOfLimit||info.error||r.json?.status===false)fail('failed','数据联动失败/超限:'+rule.config);
|
|
118
|
+
for(const k of Object.keys(info))if(!['linkageSql','changeValue','addDetailRow','resultOutOfLimit'].includes(k))fail('unsupported','联动响应操作未支持:'+k);
|
|
119
|
+
for(const [target,v]of Object.entries(info.changeValue??{})){
|
|
120
|
+
const match=target.match(/^field(\d+)(?:_(\d+))?$/);if(!match)fail('unsupported','联动目标格式未知');const dest=defs.find(x=>x.id===match[1]);
|
|
121
|
+
changed=write(dest?.table,match[2]?Number(match[2]):dest?.table==='main'?-1:row,match[1],v,'server')||changed;
|
|
122
|
+
}
|
|
123
|
+
for(const [mark,rows]of Object.entries(info.addDetailRow??{})){
|
|
124
|
+
if(!Array.isArray(rows)||!t[mark])fail('unsupported','联动新增明细格式未知');
|
|
125
|
+
const existing=t[mark].rows,used=new Set();
|
|
126
|
+
const result=rows.map(r=>{const fields=Object.keys(r);const index=existing.findIndex((e,i)=>!used.has(i)&&fields.every(k=>String(e[k]?.value??'')===String(r[k]?.value??'')));if(index>=0)used.add(index);return {...clone(t[mark].addRowDefValue??{}),...(index>=0?existing[index]:{}),...r};});
|
|
127
|
+
if(String(rule.otherParams?.isdel)==='1'){
|
|
128
|
+
if(existing.some((e,i)=>e.keyid&&!used.has(i)))fail('unsupported','联动删除/替换已有明细需经验证的行删除适配');
|
|
129
|
+
if(!equal(existing,result)){t[mark].rows=result;changed=true;}
|
|
130
|
+
}else{
|
|
131
|
+
const additions=result.filter(r=>!existing.some(e=>Object.keys(r).filter(k=>k!=='keyid').every(k=>equal(e[k],r[k]))));
|
|
132
|
+
if(additions.length){existing.push(...additions);changed=true;}
|
|
133
|
+
}
|
|
134
|
+
evidence.rowChanges.push({config:String(rule.config),table:mark,rows:t[mark].rows.length,mode:String(rule.otherParams?.isdel)==='1'?'replace':'append'});
|
|
135
|
+
}
|
|
136
|
+
evidence.coverage.push({kind:'dataInput',config:String(rule.config),row,state:'executed'});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if(!changed){for(const [key,value]of protectedValues){const [ref,row]=key.split(':');const [table,id]=ref.split('.');if(String(getCells(table,Number(row))?.['field'+id]?.value??'')!==String(value))fail('conflict','联动明细覆盖用户值:'+id);}evidence.state='complete';return {form:f,tables:t,evidence};}
|
|
140
|
+
}
|
|
141
|
+
fail('nonconvergent','联动循环未收敛');
|
|
142
|
+
}
|
|
143
|
+
export async function prepareFields(cfg,plan,{api,form,tables}={}) {
|
|
144
|
+
api??=await createApiFromProfile(cfg);
|
|
145
|
+
if(!form){const r=await api.post('/api/workflow/reqform/loadForm',{workflowid:String(plan.workflowId),requestid:String(plan.requestId??'-1'),iscreate:plan.requestId?'0':'1'},{forceCookie:true});form=r.json;}
|
|
146
|
+
if(form?.params?.verifyRight!==true||String(form.params.currentUserid)!==String(plan.accountId))throw Error('账号或表单权限不符');
|
|
147
|
+
tables??=await detailTables(api,form,plan.workflowId,plan.requestId);
|
|
148
|
+
const adapter=await loadAdapter(cfg,plan.workflowId,form);
|
|
149
|
+
if(adapter?.prepare)await adapter.prepare({plan,form,tables,mapping:adapter.mapping});
|
|
150
|
+
const linked=await executeLinkages(api,form,tables,plan,{covered:adapter?.supportedLinkageKinds??[]});
|
|
151
|
+
const prepared=buildDraft({...plan,serverResolved:true,values:{},details:[],browserNames:{},detailBrowserNames:{}},linked.form,linked.tables);
|
|
152
|
+
// buildDraft receives only server/proven state; user permissions were checked before execution.
|
|
153
|
+
prepared.expected.coverage=linked.evidence;
|
|
154
|
+
prepared.expected.adapterDigest=adapter?.scriptDigest??null;
|
|
155
|
+
prepared.expected.originalFingerprint=fingerprint(form);
|
|
156
|
+
return {...prepared,evidence:linked.evidence,baseline:baseline(form,tables)};
|
|
157
|
+
}
|
|
158
|
+
export async function checkAdapter(cfg,plan,form) {
|
|
159
|
+
const adapter=await loadAdapter(cfg,plan.workflowId,form);
|
|
160
|
+
if((adapter?.scriptDigest??null)!==plan.prepared.expected.adapterDigest)throw Error('适配逻辑变化,请重新预览');
|
|
161
|
+
}
|
|
162
|
+
export function baseline(form,tables) {
|
|
163
|
+
const values=cells=>Object.fromEntries(Object.entries(cells??{}).filter(([k])=>/^field\d+$/.test(k)).map(([k,c])=>[k,{value:c?.value??'',specialobj:Array.isArray(c?.specialobj)?c.specialobj.map(x=>({id:x.id,name:x.name})):undefined}]));
|
|
164
|
+
return {main:values(form.maindata),details:Object.fromEntries(Object.entries(tables).map(([mark,t])=>[mark,t.rows.map(row=>({keyid:row.keyid,fields:values(row)}))]))};
|
|
165
|
+
}
|
|
166
|
+
export function checkBaseline(prepared,form,tables) {
|
|
167
|
+
if(!equal(prepared.baseline,baseline(form,tables)))throw Error('服务器默认值或原稿内容变化,请重新预览;不会静默重算');
|
|
168
|
+
}
|
|
169
|
+
export function classifyWrite(response,requestId) {
|
|
170
|
+
const data=response.json?.data??response.json??{},type=data.type;
|
|
171
|
+
return {responseType:type,requestId:data.resultInfo?.requestid??data.requestid??requestId,
|
|
172
|
+
state:type==='FAILD'||type==='FAILED'||data.status===false?'failed':type==='SUCCESS'?'written-unverified':'uncertain',messageInfo:data.messageInfo??response.json?.msg};
|
|
173
|
+
}
|
|
174
|
+
export function actionBody(prepared,form,action='save') {
|
|
175
|
+
if(!['save','submit'].includes(action)||prepared.expected?.coverage?.state!=='complete')throw Error('字段准备/联动覆盖未完成');
|
|
176
|
+
if(fingerprint(form)!==prepared.expected.originalFingerprint)throw Error('表单配置变化,请重新预览');
|
|
177
|
+
const business=Object.fromEntries(Object.entries(prepared.body).filter(([k])=>/^field|^nodesnum|^indexnum|^submitdtlid|^dtl_id_|^deldtlid|FieldUnEmptyCount$/.test(k)||['existChangeRange','requestname','requestid','iscreate'].includes(k)));
|
|
178
|
+
const body={...prepared.body,...form.submitParams,...business,signatureAttributesStr:String(form.params.signatureAttributesStr??''),signatureSecretKey:String(form.params.signatureSecretKey??''),src:action,isaffirmance:'0',isaffirmanceSrc:''};
|
|
179
|
+
if(String(form.params.currentUserid)!==prepared.expected.accountId)throw Error('执行账号已变化');
|
|
180
|
+
return body;
|
|
181
|
+
}
|
|
182
|
+
export function verifyAction(expected,form,tables,action='save') {
|
|
183
|
+
if(expected.coverage?.state!=='complete')return ['联动覆盖未完成'];
|
|
184
|
+
return verifyDraft(expected,form,tables,{action});
|
|
185
|
+
}
|
package/dist/fileutil.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
/** 下载文件不覆盖用户已有文件;重名时追加 (1)、(2)… */
|
|
5
|
+
export function nextAvailablePath(outDir, filename) {
|
|
6
|
+
const parsed = path.parse(filename);
|
|
7
|
+
let candidate = path.join(outDir, filename);
|
|
8
|
+
for (let index = 1; fs.existsSync(candidate); index++)
|
|
9
|
+
candidate = path.join(outDir, `${parsed.name} (${index})${parsed.ext}`);
|
|
10
|
+
return candidate;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** 接受 OOXML zip 或传统 OLE Excel;拒绝 HTML 登录页、JSON 错误和空响应。 */
|
|
14
|
+
export function assertExcelLike(buf, label = 'Excel 文件') {
|
|
15
|
+
const isZip = Buffer.isBuffer(buf) && buf.length >= 64 && buf.readUInt32LE(0) === 0x04034b50;
|
|
16
|
+
const isOle = Buffer.isBuffer(buf) && buf.length >= 512 && buf.subarray(0, 8).equals(Buffer.from('D0CF11E0A1B11AE1', 'hex'));
|
|
17
|
+
if (!isZip && !isOle)
|
|
18
|
+
throw new Error(`${label}响应不是有效的 xls/xlsx,可能是登录页或错误页。`);
|
|
19
|
+
return { format: isZip ? 'xlsx' : 'xls', size: buf.length };
|
|
20
|
+
}
|