@rockn/vue-formula-editor 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/LICENSE +21 -0
- package/README.md +199 -0
- package/dist/FormulaEditor.d.ts +70 -0
- package/dist/engine/evaluator.d.ts +14 -0
- package/dist/engine/format.d.ts +19 -0
- package/dist/engine/index.cjs +7 -0
- package/dist/engine/index.d.ts +26 -0
- package/dist/engine/index.js +1383 -0
- package/dist/engine/lexer.d.ts +2 -0
- package/dist/engine/parser.d.ts +21 -0
- package/dist/engine/types.d.ts +182 -0
- package/dist/index.cjs +39 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +1537 -0
- package/dist/style.css +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ExprNode } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* 递归下降解析器(优先级爬升)。
|
|
4
|
+
* 语法:
|
|
5
|
+
* expr := ternary
|
|
6
|
+
* ternary := or ('?' expr ':' expr)?
|
|
7
|
+
* or := and ('||' and)*
|
|
8
|
+
* and := equality ('&&' equality)*
|
|
9
|
+
* equality := relational (('=='|'!='|'==='|'!==') relational)*
|
|
10
|
+
* relational := additive (('<'|'<='|'>'|'>=') additive)*
|
|
11
|
+
* additive := multiplicative (('+'|'-') multiplicative)*
|
|
12
|
+
* multiplicative := unary (('*'|'/'|'%'|'**') unary)*
|
|
13
|
+
* unary := ('-'|'+'|'!') unary | primary
|
|
14
|
+
* primary := number | string | boolean | null | variable | call | '(' expr ')'
|
|
15
|
+
*/
|
|
16
|
+
export declare class ParseError extends Error {
|
|
17
|
+
start: number;
|
|
18
|
+
end: number;
|
|
19
|
+
constructor(message: string, start: number, end: number);
|
|
20
|
+
}
|
|
21
|
+
export declare function parse(src: string): ExprNode;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 公式引擎共享类型定义
|
|
3
|
+
* 纯 TypeScript、零依赖,可脱离 Vue 独立复用。
|
|
4
|
+
*/
|
|
5
|
+
/** 变量类型 */
|
|
6
|
+
export type ValueType = 'number' | 'string' | 'boolean' | 'object' | 'enum' | 'any';
|
|
7
|
+
/** 枚举选项:label 为公式中书写/显示的标签,value 为求值/比较使用的代码值 */
|
|
8
|
+
export interface EnumOption {
|
|
9
|
+
label: string;
|
|
10
|
+
value: unknown;
|
|
11
|
+
}
|
|
12
|
+
/** 键值对变量的字段定义(可选;未定义时以测试值对象的键兜底) */
|
|
13
|
+
export interface FieldDef {
|
|
14
|
+
/** 字段名(公式中作为成员访问的标签) */
|
|
15
|
+
key: string;
|
|
16
|
+
/** 显示名称(默认取 key) */
|
|
17
|
+
label?: string;
|
|
18
|
+
/** 字段值类型 */
|
|
19
|
+
type?: ValueType;
|
|
20
|
+
/** 字段描述 */
|
|
21
|
+
description?: string;
|
|
22
|
+
}
|
|
23
|
+
/** 变量定义(传给编辑器的可选变量列表) */
|
|
24
|
+
export interface VariableDef {
|
|
25
|
+
/** 变量名,公式中以 {名称} 引用 */
|
|
26
|
+
name: string;
|
|
27
|
+
/** 显示名称(默认取 name) */
|
|
28
|
+
label?: string;
|
|
29
|
+
/** 变量描述,展示在变量面板中 */
|
|
30
|
+
description?: string;
|
|
31
|
+
/** 值类型,用于测试值输入与类型徽标 */
|
|
32
|
+
type?: ValueType;
|
|
33
|
+
/** 是否必填 */
|
|
34
|
+
required?: boolean;
|
|
35
|
+
/** 默认值(用于测试值面板初始化) */
|
|
36
|
+
defaultValue?: unknown;
|
|
37
|
+
/** 可选值(下拉选择),string 或 {label, value};type='enum' 时作为枚举标签-值映射 */
|
|
38
|
+
options?: Array<string | {
|
|
39
|
+
label: string;
|
|
40
|
+
value: unknown;
|
|
41
|
+
}>;
|
|
42
|
+
/** 键值对变量的字段定义(type='object' 时生效) */
|
|
43
|
+
fields?: FieldDef[];
|
|
44
|
+
}
|
|
45
|
+
/** 词法单元类型 */
|
|
46
|
+
export type TokenType = 'number' | 'string' | 'identifier' | 'variable' | 'operator' | 'lparen' | 'rparen' | 'lbracket' | 'rbracket' | 'dot' | 'comma' | 'question' | 'colon' | 'eof' | 'error';
|
|
47
|
+
/** 词法单元 */
|
|
48
|
+
export interface Token {
|
|
49
|
+
type: TokenType;
|
|
50
|
+
/** 语义值:number 为数值文本、string 为解转义后的内容、variable 为变量名、identifier 为标识符文本等 */
|
|
51
|
+
value: string;
|
|
52
|
+
/** 起始偏移(含) */
|
|
53
|
+
start: number;
|
|
54
|
+
/** 结束偏移(不含) */
|
|
55
|
+
end: number;
|
|
56
|
+
/** 原始文本 */
|
|
57
|
+
raw: string;
|
|
58
|
+
/** 仅 string/variable:是否闭合(引号/右花括号存在) */
|
|
59
|
+
closed?: boolean;
|
|
60
|
+
}
|
|
61
|
+
export interface NumberNode {
|
|
62
|
+
type: 'number';
|
|
63
|
+
value: number;
|
|
64
|
+
start: number;
|
|
65
|
+
end: number;
|
|
66
|
+
}
|
|
67
|
+
export interface StringNode {
|
|
68
|
+
type: 'string';
|
|
69
|
+
value: string;
|
|
70
|
+
start: number;
|
|
71
|
+
end: number;
|
|
72
|
+
}
|
|
73
|
+
export interface BooleanNode {
|
|
74
|
+
type: 'boolean';
|
|
75
|
+
value: boolean;
|
|
76
|
+
start: number;
|
|
77
|
+
end: number;
|
|
78
|
+
}
|
|
79
|
+
export interface NullNode {
|
|
80
|
+
type: 'null';
|
|
81
|
+
start: number;
|
|
82
|
+
end: number;
|
|
83
|
+
}
|
|
84
|
+
export interface VariableNode {
|
|
85
|
+
type: 'variable';
|
|
86
|
+
name: string;
|
|
87
|
+
start: number;
|
|
88
|
+
end: number;
|
|
89
|
+
}
|
|
90
|
+
/** 成员访问:{订单}.金额 / {订单}['金额'] / {订单}.商品[0] */
|
|
91
|
+
export interface MemberNode {
|
|
92
|
+
type: 'member';
|
|
93
|
+
/** 被访问的表达式(变量、函数调用、括号表达式或另一成员访问) */
|
|
94
|
+
object: ExprNode;
|
|
95
|
+
/** 点访问的成员名(.金额)或字符串下标(['金额']);下标表达式访问时为空字符串 */
|
|
96
|
+
prop: string;
|
|
97
|
+
/** 下标表达式([expr]);点访问/字符串下标时为 null */
|
|
98
|
+
index: ExprNode | null;
|
|
99
|
+
start: number;
|
|
100
|
+
end: number;
|
|
101
|
+
}
|
|
102
|
+
export interface CallNode {
|
|
103
|
+
type: 'call';
|
|
104
|
+
name: string;
|
|
105
|
+
args: ExprNode[];
|
|
106
|
+
start: number;
|
|
107
|
+
end: number;
|
|
108
|
+
}
|
|
109
|
+
export interface BinaryNode {
|
|
110
|
+
type: 'binary';
|
|
111
|
+
op: string;
|
|
112
|
+
left: ExprNode;
|
|
113
|
+
right: ExprNode;
|
|
114
|
+
start: number;
|
|
115
|
+
end: number;
|
|
116
|
+
}
|
|
117
|
+
export interface UnaryNode {
|
|
118
|
+
type: 'unary';
|
|
119
|
+
op: string;
|
|
120
|
+
operand: ExprNode;
|
|
121
|
+
start: number;
|
|
122
|
+
end: number;
|
|
123
|
+
}
|
|
124
|
+
export interface ConditionalNode {
|
|
125
|
+
type: 'conditional';
|
|
126
|
+
test: ExprNode;
|
|
127
|
+
consequent: ExprNode;
|
|
128
|
+
alternate: ExprNode;
|
|
129
|
+
start: number;
|
|
130
|
+
end: number;
|
|
131
|
+
}
|
|
132
|
+
/** IF 分支:condition 满足则求值 body */
|
|
133
|
+
export interface IfBranch {
|
|
134
|
+
condition: ExprNode;
|
|
135
|
+
body: ExprNode;
|
|
136
|
+
}
|
|
137
|
+
/** 块状 IF / ELSE IF / ELSE 节点(IF 条件 THEN 真值 ELSE 假值) */
|
|
138
|
+
export interface IfNode {
|
|
139
|
+
type: 'if';
|
|
140
|
+
/** 至少一个分支,按书写顺序依次判断 */
|
|
141
|
+
branches: IfBranch[];
|
|
142
|
+
/** 兜底分支,可省略(省略时条件全不满足返回 null) */
|
|
143
|
+
elseBody: ExprNode | null;
|
|
144
|
+
start: number;
|
|
145
|
+
end: number;
|
|
146
|
+
}
|
|
147
|
+
export type ExprNode = NumberNode | StringNode | BooleanNode | NullNode | VariableNode | MemberNode | CallNode | BinaryNode | UnaryNode | ConditionalNode | IfNode;
|
|
148
|
+
export interface FormulaError {
|
|
149
|
+
message: string;
|
|
150
|
+
/** 出错区间(对应原公式字符串偏移) */
|
|
151
|
+
start: number;
|
|
152
|
+
end: number;
|
|
153
|
+
severity: 'error' | 'warning';
|
|
154
|
+
kind: 'lex' | 'parse' | 'semantic' | 'runtime';
|
|
155
|
+
}
|
|
156
|
+
/** 内置函数规格 */
|
|
157
|
+
export interface FunctionSpec {
|
|
158
|
+
name: string;
|
|
159
|
+
signature: string;
|
|
160
|
+
description: string;
|
|
161
|
+
minArgs: number;
|
|
162
|
+
maxArgs: number;
|
|
163
|
+
/** 参数说明(按序),用于悬停提示与函数面板 */
|
|
164
|
+
params?: string[];
|
|
165
|
+
/** 示例(完整调用文本),用于悬停提示与函数面板 */
|
|
166
|
+
example?: string;
|
|
167
|
+
fn: (args: unknown[]) => unknown;
|
|
168
|
+
}
|
|
169
|
+
/** 校验结果 */
|
|
170
|
+
export interface ValidationResult {
|
|
171
|
+
ok: boolean;
|
|
172
|
+
errors: FormulaError[];
|
|
173
|
+
ast: ExprNode | null;
|
|
174
|
+
}
|
|
175
|
+
/** 求值结果 */
|
|
176
|
+
export type EvalResult = {
|
|
177
|
+
ok: true;
|
|
178
|
+
value: unknown;
|
|
179
|
+
} | {
|
|
180
|
+
ok: false;
|
|
181
|
+
error: FormulaError;
|
|
182
|
+
};
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("vue"),m=require("./engine/index.cjs"),Ge={class:"fe-header"},Xe={class:"fe-title-wrap"},Qe={class:"fe-title-box"},Ze={class:"fe-title"},Je={class:"fe-subtitle"},et={class:"fe-header-actions"},tt=["disabled","title"],lt=["disabled","title"],nt=["disabled"],ot=["disabled"],at=["disabled"],st=["disabled"],rt=["disabled","title"],it={class:"fe-btn-ico"},ct={key:0,class:"fe-help"},ut={class:"fe-help-col"},dt={class:"fe-help-fns"},pt={key:0,class:"fe-toolbar"},mt={class:"fe-toolbar-group"},ft=["disabled","title","onClick"],Et={class:"fe-body"},kt={key:0,class:"fe-vars"},Nt={class:"fe-vars-head"},vt={class:"fe-vars-count"},yt={class:"fe-vars-search"},bt=["disabled"],gt={class:"fe-vars-list"},Vt=["title","onClick"],Tt={class:"fe-var-line1"},ht=["title","onClick"],St={class:"fe-var-name"},xt={key:1,class:"fe-var-req",title:"必填"},Ct={class:"fe-var-desc"},Bt={key:0,class:"fe-var-sub"},It=["title","onClick"],Lt={key:1,class:"fe-var-sub"},Ot=["title","onClick"],Ft={key:0,class:"fe-var-sub-empty"},Dt={key:0,class:"fe-vars-empty"},Rt={key:0},wt={key:1},_t={key:1,class:"fe-fns"},At={class:"fe-vars-head"},$t={class:"fe-vars-count"},Ut={class:"fe-vars-search"},Mt=["disabled"],Pt={class:"fe-vars-list"},Ht=["title","onClick"],zt={class:"fe-fn-line1"},Wt={class:"fe-fn-name"},Yt={class:"fe-fn-group"},jt={class:"fe-fn-sig"},qt={class:"fe-fn-desc"},Kt={key:0,class:"fe-vars-empty"},Gt={class:"fe-editor"},Xt={class:"fe-code-shell"},Qt={class:"fe-gutter"},Zt=["innerHTML"],Jt=["placeholder","disabled","readonly"],el={key:0,class:"fe-errors"},tl=["onClick"],ll={class:"fe-error-msg"},nl={class:"fe-error-kind"},ol={class:"fe-footer"},al={class:"fe-foot-left"},sl={key:0,class:"fe-foot-err-count"},rl=["title"],il={key:0,class:"fe-result-type"},cl={key:1,class:"fe-result-value empty"},ul={class:"fe-foot-caret"},dl=e.defineComponent({__name:"FormulaEditor",props:{modelValue:{default:""},variables:{default:()=>[]},context:{default:()=>({})},functions:{default:()=>({})},functionSpecs:{default:()=>({})},title:{default:"公式编辑器"},subtitle:{default:"FORMULA EDITOR"},placeholder:{default:'例如:IF {订单金额} >= 199 THEN "免运费" ELSE "收运费"'},height:{default:"340px"},disabled:{type:Boolean,default:!1},readonly:{type:Boolean,default:!1},showVariablesPanel:{type:Boolean,default:!0},showToolbar:{type:Boolean,default:!0},showPreview:{type:Boolean,default:!0},theme:{default:"dark"}},emits:["update:modelValue","change","error","insert","update:theme"],setup(p,{expose:U,emit:R}){const u=p,y=R,d=e.ref(u.modelValue),h=e.ref(u.theme),L=e.ref(u.showVariablesPanel),O=e.ref(!1),w=e.ref(!1),M=e.ref(!1),P=e.ref(""),H=e.ref(""),_=e.ref(0),X=e.ref(0),A=e.ref({line:1,col:1});e.watch(()=>u.modelValue,t=>{t!==d.value&&(d.value=t)}),e.watch(()=>u.theme,t=>{t&&(h.value=t)});const E=e.ref(null),Ne=e.ref(null),Q=e.ref(null),b=e.ref(null),Z=new Set(["IF","ELSE","THEN","ELSEIF","如果","那么","否则","否则如果"]),J=new Set(["AND","OR"]);function z(t){return Z.has(t)?!0:/^[A-Za-z]+$/.test(t)?Z.has(t.toUpperCase()):!1}function ee(t){return J.has(t)?!0:/^[A-Za-z]+$/.test(t)?J.has(t.toUpperCase()):!1}const W=e.computed(()=>m.tokenize(d.value)),F=e.ref({ok:!0,errors:[],ast:null}),S=e.ref(null);let te;function ve(){window.clearTimeout(te),te=window.setTimeout(()=>{F.value=m.validateFormula(d.value,u.variables,u.functions,Object.keys(u.context)),S.value=d.value.trim()?m.evaluateFormula(d.value,u.context,u.functions,u.variables):null},120)}e.watch([d,()=>u.variables,()=>u.context,()=>u.functions],ve,{immediate:!0});const ye=e.computed(()=>d.value.split(`
|
|
2
|
+
`).length),be=e.computed(()=>12+(A.value.line-1)*21.25-_.value),le=e.computed(()=>{const t=P.value.trim().toLowerCase();return t?u.variables.filter(l=>(l.name+" "+(l.label??"")+" "+(l.description??"")).toLowerCase().includes(t)):u.variables}),I=e.computed(()=>F.value.errors),ge=e.computed(()=>{const t=new Set;for(const l of I.value){const n=d.value.slice(0,l.start).split(`
|
|
3
|
+
`).length;t.add(n)}return t}),Ve=e.computed(()=>{const t=I.value;return t.length===0?{label:"公式有效",cls:"ok"}:{label:`${t.length} 个问题`,cls:"bad"}}),Te=e.computed(()=>{const t=S.value;return t?t.ok?m.formatValue(t.value):t.error.message:null}),ne=e.computed(()=>{const t=S.value;return t&&t.ok?m.valueTypeName(t.value):""}),he=e.computed(()=>{const t=S.value;return t?t.ok?"ok":"bad":"empty"});function Y(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}const oe=e.ref(0);let x=null;function Se(t){const l=oe.value;let n=-1;for(let r=0;r<t.length;r++){const i=t[r];if(i.type==="eof")break;if(l>=i.start&&l<=i.end){n=r;break}}if(n<0)return null;const a={lparen:"rparen",lbracket:"rbracket"},o={rparen:"lparen",rbracket:"lbracket"},s=t[n];if(s.type in a){let r=0;for(let i=n;i<t.length;i++){const c=t[i];if(c.type==="eof")break;if(c.type===s.type)r++;else if(c.type===a[s.type]&&(r--,r===0))return{a:n,b:i}}}else if(s.type in o){let r=0;for(let i=n;i>=0;i--){const c=t[i];if(c.type==="eof")break;if(c.type===s.type)r++;else if(c.type===o[s.type]&&(r--,r===0))return{a:i,b:n}}}return null}function xe(t,l,n){if(n!==void 0&&(n===(x==null?void 0:x.a)||n===(x==null?void 0:x.b)))return"tok-pair";switch(t.type){case"number":return"tok-num";case"string":return t.closed===!1?"tok-err":"tok-str";case"variable":return t.closed===!1?"tok-err":"tok-var";case"identifier":return z(t.value)&&(l==null?void 0:l.type)!=="lparen"?"tok-kw":ee(t.value)&&(l==null?void 0:l.type)!=="lparen"?"tok-op":"tok-fn";case"operator":return"tok-op";case"lparen":case"rparen":case"lbracket":case"rbracket":case"comma":case"question":case"colon":return"tok-paren";case"error":return"tok-err";default:return""}}const Ce=e.computed(()=>{let t="",l=0;const n=W.value;x=Se(n);for(let a=0;a<n.length;a++){const o=n[a];if(o.type==="eof")break;t+=Y(d.value.slice(l,o.start)),t+=`<span class="${xe(o,n[a+1],a)}">${Y(o.raw)}</span>`,l=o.end}return t+=Y(d.value.slice(l)),t+`
|
|
4
|
+
`});function Be(){var l;const t=((l=E.value)==null?void 0:l.value)??d.value;t!==d.value&&(d.value=t),y("update:modelValue",t),y("change",t)}e.watch(F,t=>y("error",t));function v(){const t=E.value;if(!t)return;const l=t.selectionStart;oe.value=l;const n=d.value.slice(0,l),a=n.split(`
|
|
5
|
+
`).length,o=l-n.lastIndexOf(`
|
|
6
|
+
`);A.value={line:a,col:o}}function Ie(){const t=E.value;t&&(_.value=t.scrollTop,X.value=t.scrollLeft)}function Le(t,l){var n,a;if(t.type==="variable"&&t.closed!==!1){const o=u.variables.find(V=>V.name===t.value),s=o!=null&&o.type?`[${me(o.type)}]`:"",r=o!=null&&o.required?" 必填":"",i=o!=null&&o.description?`
|
|
7
|
+
${o.description}`:"",c=o!=null&&o.label&&o.label!==o.name?`(${o.label})`:"",k=u.context[t.value];let f="";k!==void 0&&(f=`
|
|
8
|
+
当前值: ${m.formatValue(k,60)}`);let g="";(o==null?void 0:o.type)==="enum"&&o.options&&(g=`
|
|
9
|
+
标签: ${o.options.map(B=>typeof B=="string"?B:B.label).join(" / ")}`);let C="";return(o==null?void 0:o.type)==="object"&&o.fields&&o.fields.length>0&&(C=`
|
|
10
|
+
字段: ${o.fields.map(V=>V.key).join(" / ")}`),`变量 {${t.value}}${c} ${s}${r}${i}${f}${g}${C}`}if(t.type==="identifier"){if((l==null?void 0:l.type)==="lparen"){const r=t.value.toUpperCase(),i=m.BUILTIN_FUNCTIONS[r];if(i){const c=(n=i.params)!=null&&n.length?`
|
|
11
|
+
`+i.params.map((f,g)=>`${g+1}. ${f}`).join(`
|
|
12
|
+
`):"",k=i.example?`
|
|
13
|
+
示例: ${i.example}`:"";return`${i.signature}
|
|
14
|
+
${i.description}${c}${k}`}if(t.value in u.functions){const c=u.functionSpecs[t.value];if(c){const k=(a=c.params)!=null&&a.length?`
|
|
15
|
+
`+c.params.map((g,C)=>`${C+1}. ${g}`).join(`
|
|
16
|
+
`):"",f=c.example?`
|
|
17
|
+
示例: ${c.example}`:"";return`${c.signature}
|
|
18
|
+
${c.description}${k}${f}`}return`自定义函数 ${t.value}(...)`}return null}const o=t.value.toUpperCase(),s={IF:`IF 条件 THEN 真值 ELSE IF 条件 THEN 真值 … ELSE 假值
|
|
19
|
+
块状条件判断,条件满足即返回对应分支,支持多级 ELSE IF`,ELSEIF:`ELSE IF 条件 THEN 真值
|
|
20
|
+
在前一个条件不满足时继续判断`,THEN:`IF 条件 THEN 真值
|
|
21
|
+
条件与结果之间的分隔关键字`,ELSE:`ELSE 假值
|
|
22
|
+
所有条件都不满足时的兜底分支`,如果:`如果 条件 那么 真值 否则 假值
|
|
23
|
+
块状条件判断(中文关键字写法)`,那么:`如果 条件 那么 真值
|
|
24
|
+
条件与结果之间的分隔关键字(中文)`,否则:`否则 假值
|
|
25
|
+
所有条件都不满足时的兜底分支(中文)`,否则如果:`否则如果 条件 那么 真值
|
|
26
|
+
在前一个条件不满足时继续判断(中文)`};if(z(t.value))return s[o]??s[t.value]??null;if(ee(t.value))return t.value.toUpperCase()==="AND"?"AND — 逻辑与(两边同时为真)":"OR — 逻辑或(任一边为真)"}return t.type==="operator"?{"&&":"逻辑与(同时满足)","||":"逻辑或(任一满足)","==":'宽松等于(数字 5 与字符串 "5" 视为相等)',"===":"严格等于(类型也必须相同)","!=":"宽松不等于","!==":"严格不等于","<":"小于","<=":"小于等于",">":"大于",">=":"大于等于","+":"加(数字相加;含字符串时拼接)","-":"减","*":"乘","/":"除(除数不能为 0)","%":"取余","**":"幂运算","!":"逻辑非"}[t.value]??null:null}function Oe(t){const l=E.value,n=Q.value;if(!l||!n){b.value=null;return}const a=document;let o=null;if(a.caretRangeFromPoint){const f=a.caretRangeFromPoint(t.clientX,t.clientY);f&&(f.startContainer===l||l.contains(f.startContainer))&&(o=f.startOffset)}else if(a.caretPositionFromPoint){const f=a.caretPositionFromPoint(t.clientX,t.clientY);f&&(o=f.offset)}if(o==null){b.value=null;return}const s=W.value,r=s.find(f=>f.type!=="eof"&&o>=f.start&&o<f.end);if(!r){b.value=null;return}const i=s.indexOf(r),c=Le(r,s[i+1]);if(!c){b.value=null;return}const k=n.getBoundingClientRect();b.value={text:c,x:t.clientX-k.left,y:t.clientY-k.top}}function Fe(){b.value=null}function De(){const t=E.value;if(!t||u.disabled||u.readonly)return;const l=t.selectionStart,n=se(l);n&&(t.setSelectionRange(n.start,n.end),v())}function ae(t){let l=null,n=!1;const a=d.value,o=Math.min(t,a.length);for(let s=0;s<o;s++){const r=a[s];l?n?n=!1:r==="\\"?n=!0:r===l&&(l=null):(r==="'"||r==='"')&&(l=r)}return l!==null}function se(t){const l=d.value,n=l.length;if(t<n&&l[t]==="{"){const s=l.indexOf("}",t+1);return s>=0?{start:t,end:s+1}:null}if(t>0&&l[t-1]==="}"){const s=t-1;for(let r=s-1;r>=0;r--)if(l[r]==="{")return{start:r,end:s+1};return null}let a=-1;for(let s=t-1;s>=0;s--){const r=l[s];if(r==="}")break;if(r==="{"){a=s;break}}if(a<0)return null;const o=l.indexOf("}",a+1);return o>=0?{start:a,end:o+1}:null}function D(t,l,n,a="insertText"){const o=E.value;if(!o||t>l)return;o.focus(),o.setSelectionRange(t,l);const s=o.value;let r=!1;try{r=n===""?document.execCommand("delete"):document.execCommand("insertText",!1,n)}catch{r=!1}if(!r||o.value===s){if(o.value===s&&o.value.slice(t,l)===n)return;o.setRangeText(n,t,l),o.dispatchEvent(new InputEvent("input",{inputType:a,data:n===""?null:n,bubbles:!0}))}}function Re(t,l,n){D(t,l,n,"deleteContent")}function re(t){var r;const l=d.value,n=l.lastIndexOf(`
|
|
27
|
+
`,t-1)+1,a=l.slice(n,t),o=((r=/^[ \t]*/.exec(a))==null?void 0:r[0])??"",s=a.trim();return/THEN$/i.test(s)||/那么$/.test(s)||/^ELSE$/i.test(s)||s==="否则"?o+" ":o}function we(t,l){const n=W.value;let a=null;for(const o of n){if(o.type==="eof")break;o.start<t||o.end>l||o.type==="identifier"&&z(o.value)&&/^(ELSE|否则)$/i.test(o.value)&&(a=o)}return a}function _e(t){var Ee;const l=d.value,n=l.lastIndexOf(`
|
|
28
|
+
`,t-1)+1,a=l.indexOf(`
|
|
29
|
+
`,t),o=a===-1?l.length:a,s=we(n,o);if(!s||t<s.start-1||t>s.end)return!1;const i=l.slice(n,s.start).trimEnd();if(i.trim()==="")return!1;const c=((Ee=/^[ \t]*/.exec(i))==null?void 0:Ee[0])??"",k=/^(IF|如果)(?=\s)/i.test(i.trim())?"":c.length>=2?c.slice(0,-2):"",f=l.slice(s.end,o),g=k+" ",C=i+`
|
|
30
|
+
`+k+s.raw+`
|
|
31
|
+
`+g;let V=o,B=f.trim();if(B===""&&l[o]===`
|
|
32
|
+
`){let T=o+1;for(;T<l.length&&(l[T]===" "||l[T]===" ");)T++;V=T,B=""}D(n,V,C+B,"insertText");const fe=n+C.length;return e.nextTick(()=>{var T,ke;(T=E.value)==null||T.focus(),(ke=E.value)==null||ke.setSelectionRange(fe,fe),v()}),!0}function Ae(t){const l=E.value;if(!l||u.disabled||u.readonly)return;if(t.key==="Tab"){t.preventDefault(),N(" ");return}if(l.selectionStart!==l.selectionEnd){if(t.key==="Enter"&&!t.isComposing){const a=l.selectionStart,o=l.selectionEnd,s=d.value.slice(a,o);if(s.length>2&&s.startsWith("{")&&s.endsWith("}")&&!s.slice(1,-1).includes("{")&&!s.slice(1,-1).includes("}")){t.preventDefault(),l.setSelectionRange(o,o),N(`
|
|
33
|
+
`+re(o));return}}return}const n=l.selectionStart;if(t.key==="Backspace"||t.key==="Delete"){if(!ae(n)){const a=se(n);a&&n>a.start&&n<a.end&&(t.preventDefault(),Re(a.start,a.end,""),e.nextTick(()=>{l.focus(),l.setSelectionRange(a.start,a.start),v()}))}return}if(t.key==="Enter"&&!t.isComposing){if(t.preventDefault(),_e(n))return;N(`
|
|
34
|
+
`+re(n));return}if((t.key==="{"||t.key==='"'||t.key==="'")&&!t.ctrlKey&&!t.metaKey&&!t.altKey&&!t.isComposing){if(t.preventDefault(),t.key==="{"){N("{}"),e.nextTick(()=>{l.focus(),l.setSelectionRange(n+1,n+1),v()});return}const a=t.key,o=d.value[n];ae(n)||o===a?N(a):(N(a+a),e.nextTick(()=>{l.focus(),l.setSelectionRange(n+1,n+1),v()}));return}}function j(){var t;(t=E.value)==null||t.focus()}function $e(){const t=h.value==="dark"?"light":"dark";h.value=t,y("update:theme",t)}function N(t,l){const n=E.value;if(!n)return;const a=n.selectionStart??d.value.length,o=n.selectionEnd??a;D(a,o,t,"insertText"),e.nextTick(()=>{if(n.focus(),l){const r=l.exec(t);if(r&&r.index>=0){n.setSelectionRange(a+r.index,a+r.index+r[0].length);return}}const s=a+t.length;n.setSelectionRange(s,s),v()})}function Ue(t){N(`{${t}}`),y("insert",{type:"variable",value:t})}function ie(t){N(t.snippet,t.select)}function Me(t,l){const n=E.value;n&&(n.focus(),n.setSelectionRange(t,l),n.scrollTop=n.scrollTop,v())}function Pe(){D(0,d.value.length,"","deleteContent"),j()}function He(){var r;const t=d.value;if(!t.trim())return;const l=m.formatFormula(t);if(l===t){j();return}const n=((r=E.value)==null?void 0:r.selectionStart)??t.length,a=m.tokenize(t);let o=null;for(let i=0;i<a.length;i++){const c=a[i];if(c.type==="eof")break;if(n>=c.start&&n<=c.end){o=c;break}}D(0,t.length,l,"insertText");let s=l.length;if(o){const c=m.tokenize(l).find(k=>k.type!=="eof"&&k.type===o.type&&k.raw===o.raw);c&&(s=c.start)}e.nextTick(()=>{var i,c;(i=E.value)==null||i.focus(),(c=E.value)==null||c.setSelectionRange(s,s),v()})}async function ze(){try{await navigator.clipboard.writeText(d.value)}catch{const t=E.value;t&&(t.select(),document.execCommand("copy"),t.setSelectionRange(t.value.length,t.value.length))}}const q=[{key:"if",label:"IF…ELSE",title:"块状 IF:IF 条件 THEN 真值 ELSE 假值(支持多级 ELSE IF)",group:"logic",snippet:`IF 条件 THEN
|
|
35
|
+
真值
|
|
36
|
+
ELSE
|
|
37
|
+
假值`,select:/条件/},{key:"elseif",label:"ELSE IF",title:"追加条件分支:ELSE IF 条件 THEN 真值",group:"logic",snippet:`ELSE IF 条件 THEN
|
|
38
|
+
真值`,select:/条件/},{key:"ifs",label:"IFS",title:"IFS(条件1, 值1, 条件2, 值2, 默认值) — 多条件判断",group:"logic",snippet:"IFS(条件1, 值1, 条件2, 值2, 默认值)",select:/条件1/},{key:"ternary",label:"a?b:c",title:"三元表达式:条件 ? 真值 : 假值",group:"logic",snippet:"条件 ? 真值 : 假值",select:/条件/},{key:"and",label:"AND",title:"AND(条件1, 条件2) — 全部满足",group:"logic",snippet:"AND(条件1, 条件2)",select:/条件1/},{key:"or",label:"OR",title:"OR(条件1, 条件2) — 任一满足",group:"logic",snippet:"OR(条件1, 条件2)",select:/条件1/},{key:"not",label:"NOT",title:"NOT(条件) — 取反",group:"logic",snippet:"NOT(条件)",select:/条件/},{key:"add",label:"+",title:"加法",group:"math",snippet:" + "},{key:"sub",label:"−",title:"减法",group:"math",snippet:" - "},{key:"mul",label:"×",title:"乘法",group:"math",snippet:" * "},{key:"div",label:"÷",title:"除法",group:"math",snippet:" / "},{key:"round",label:"ROUND",title:"ROUND(数字, 小数位)",group:"math",snippet:"ROUND(数字, 2)",select:/数字/},{key:"sum",label:"SUM",title:"SUM(数字1, 数字2, …) — 数值总和(忽略非数字参数)",group:"math",snippet:"SUM(数字1, 数字2)",select:/数字1/},{key:"average",label:"AVERAGE",title:"AVERAGE(数字1, 数字2, …) — 平均值(忽略非数字参数)",group:"math",snippet:"AVERAGE(数字1, 数字2)",select:/数字1/},{key:"abs",label:"ABS",title:"ABS(数字) — 取绝对值",group:"math",snippet:"ABS(数字)",select:/数字/},{key:"floor",label:"FLOOR",title:"FLOOR(数字) — 向下取整",group:"math",snippet:"FLOOR(数字)",select:/数字/},{key:"ceil",label:"CEIL",title:"CEIL(数字) — 向上取整",group:"math",snippet:"CEIL(数字)",select:/数字/},{key:"min",label:"MIN",title:"MIN(数字1, 数字2, …) — 取最小值",group:"math",snippet:"MIN(数字1, 数字2)",select:/数字1/},{key:"max",label:"MAX",title:"MAX(数字1, 数字2, …) — 取最大值",group:"math",snippet:"MAX(数字1, 数字2)",select:/数字1/},{key:"eq",label:"=",title:"等于 ==",group:"compare",snippet:" == "},{key:"neq",label:"≠",title:"不等于 !=",group:"compare",snippet:" != "},{key:"lt",label:"<",title:"小于",group:"compare",snippet:" < "},{key:"gt",label:">",title:"大于",group:"compare",snippet:" > "},{key:"andop",label:"&&",title:"逻辑与",group:"compare",snippet:" && "},{key:"orop",label:"||",title:"逻辑或",group:"compare",snippet:" || "},{key:"between",label:"BETWEEN",title:"BETWEEN(值, 下限, 上限) — 是否在 [下限, 上限] 区间内(含边界)",group:"compare",snippet:"BETWEEN(值, 下限, 上限)",select:/值/},{key:"isnull",label:"ISNULL",title:"ISNULL(值) — 是否为空值(NULL / 未定义)",group:"compare",snippet:"ISNULL(值)",select:/值/},{key:"isempty",label:"ISEMPTY",title:"ISEMPTY(值) — 是否为空(NULL、空字符串、空数组/对象)",group:"compare",snippet:"ISEMPTY(值)",select:/值/},{key:"isnumeric",label:"ISNUMERIC",title:"ISNUMERIC(值) — 是否为数字(含可转换的数字字符串)",group:"compare",snippet:"ISNUMERIC(值)",select:/值/},{key:"concat",label:"拼接",title:"CONCAT(文本1, 文本2) — 字符串拼接",group:"text",snippet:"CONCAT(文本1, 文本2)",select:/文本1/},{key:"len",label:"LEN",title:"LEN(文本) — 返回文本长度(按字符数)",group:"text",snippet:"LEN(文本)",select:/文本/},{key:"upper",label:"UPPER",title:"UPPER(文本) — 转大写",group:"text",snippet:"UPPER(文本)",select:/文本/},{key:"lower",label:"LOWER",title:"LOWER(文本) — 转小写",group:"text",snippet:"LOWER(文本)",select:/文本/},{key:"trim",label:"TRIM",title:"TRIM(文本) — 去除首尾空白",group:"text",snippet:"TRIM(文本)",select:/文本/},{key:"substr",label:"SUBSTR",title:"SUBSTR(文本, 起始下标[, 长度]) — 截取子串(0 起始)",group:"text",snippet:"SUBSTR(文本, 起始下标)",select:/文本/},{key:"mid",label:"MID",title:"MID(文本, 起始位, 长度) — Excel 风格截取(1 起始)",group:"text",snippet:"MID(文本, 起始位, 长度)",select:/文本/},{key:"left",label:"LEFT",title:"LEFT(文本, 长度) — 取左侧 N 个字符",group:"text",snippet:"LEFT(文本, 长度)",select:/文本/},{key:"right",label:"RIGHT",title:"RIGHT(文本, 长度) — 取右侧 N 个字符",group:"text",snippet:"RIGHT(文本, 长度)",select:/文本/},{key:"contains",label:"CONTAINS",title:"CONTAINS(文本, 子串) — 是否包含子串(大小写敏感)",group:"text",snippet:"CONTAINS(文本, 子串)",select:/文本/},{key:"startswith",label:"STARTSWITH",title:"STARTSWITH(文本, 前缀) — 是否以指定前缀开头",group:"text",snippet:"STARTSWITH(文本, 前缀)",select:/文本/},{key:"endswith",label:"ENDSWITH",title:"ENDSWITH(文本, 后缀) — 是否以指定后缀结尾",group:"text",snippet:"ENDSWITH(文本, 后缀)",select:/文本/},{key:"indexof",label:"INDEXOF",title:"INDEXOF(文本, 子串) — 子串位置(0 起始),未找到 -1",group:"text",snippet:"INDEXOF(文本, 子串)",select:/文本/},{key:"replace",label:"REPLACE",title:"REPLACE(文本, 被替换, 替换为) — 替换所有匹配",group:"text",snippet:"REPLACE(文本, 被替换, 替换为)",select:/文本/},{key:"split",label:"SPLIT",title:"SPLIT(文本, 分隔符) — 拆分为数组",group:"text",snippet:"SPLIT(文本, 分隔符)",select:/文本/},{key:"ifnull",label:"IFNULL",title:"IFNULL(值, 兜底值) — 为空时返回兜底值",group:"logic",snippet:"IFNULL(值, 兜底值)",select:/值/},{key:"coalesce",label:"COALESCE",title:"COALESCE(值1, 值2, …) — 取第一个非空值",group:"logic",snippet:"COALESCE(值1, 值2, 值3)",select:/值1/},{key:"switch",label:"SWITCH",title:"SWITCH(值, 情况1, 结果1, …, 默认值) — 按值匹配",group:"logic",snippet:"SWITCH(值, 情况1, 结果1, 默认值)",select:/值/},{key:"tostring",label:"TOSTRING",title:"TOSTRING(值) — 转字符串",group:"convert",snippet:"TOSTRING(值)",select:/值/},{key:"tonumber",label:"TONUMBER",title:"TONUMBER(值) — 转数字",group:"convert",snippet:"TONUMBER(值)",select:/值/},{key:"toboolean",label:"TOBOOLEAN",title:"TOBOOLEAN(值) — 转布尔",group:"convert",snippet:"TOBOOLEAN(值)",select:/值/},{key:"now",label:"NOW",title:"NOW() — 当前时间戳(毫秒)",group:"date",snippet:"NOW()"},{key:"today",label:"TODAY",title:"TODAY() — 今天零点时间戳",group:"date",snippet:"TODAY()"},{key:"dateadd",label:"DATEADD",title:"DATEADD(时间戳, 天数) — 日期加 N 天",group:"date",snippet:"DATEADD(TODAY(), 7)",select:/TODAY\(\)/},{key:"datedif",label:"DATEDIF",title:"DATEDIF(起始, 结束) — 相隔整天数",group:"date",snippet:"DATEDIF(TODAY(), DATEADD(TODAY(), 7))",select:/TODAY\(\)/},{key:"count",label:"COUNT",title:"COUNT(数组) — 数组长度",group:"agg",snippet:"COUNT(数组)",select:/数组/},{key:"countif",label:"COUNTIF",title:"COUNTIF(数组, 目标值) — 统计相等元素个数",group:"agg",snippet:"COUNTIF(数组, 目标值)",select:/数组/},{key:"sumif",label:"SUMIF",title:"SUMIF(数组, 目标值) — 相等元素求和",group:"agg",snippet:"SUMIF(数组, 目标值)",select:/数组/},{key:"keys",label:"KEYS",title:"KEYS(对象) — 对象的键名数组",group:"agg",snippet:"KEYS(对象)",select:/对象/},{key:"values",label:"VALUES",title:"VALUES(对象) — 对象的键值数组",group:"agg",snippet:"VALUES(对象)",select:/对象/}],ce=[{key:"logic",label:"逻辑"},{key:"math",label:"运算"},{key:"compare",label:"比较"},{key:"text",label:"文本"},{key:"convert",label:"转换"},{key:"date",label:"日期"},{key:"agg",label:"聚合"}];function We(t){var l;return((l=ce.find(n=>n.key===t))==null?void 0:l.label)??t}const $=Object.values(m.BUILTIN_FUNCTIONS).map(t=>{var l;return{name:t.name,signature:t.signature,description:t.description,params:t.params??[],example:t.example,group:((l=q.find(n=>n.label.toUpperCase()===t.name))==null?void 0:l.group)??"logic"}}),ue=e.computed(()=>{const t=H.value.trim().toLowerCase();return t?$.filter(l=>(l.name+" "+l.signature+" "+l.description).toLowerCase().includes(t)):$}),K=e.ref(new Set);function Ye(t){const l=new Set(K.value);l.has(t)?l.delete(t):l.add(t),K.value=l}function G(t){return K.value.has(t)}function de(t){if(t.fields&&t.fields.length>0)return t.fields.map(n=>n.key);const l=u.context[t.name];return l&&typeof l=="object"&&!Array.isArray(l)?Object.keys(l):[]}const pe={number:"数字",string:"文本",boolean:"布尔",enum:"枚举",object:"键值对",any:"任意"};function me(t){return t&&pe[t]?pe[t]:"任意"}function je(t,l){N(`{${t.name}} == '${l}'`),y("insert",{type:"text",value:`{${t.name}} == '${l}'`})}function qe(t,l){N(`{${t.name}}.${l}`),y("insert",{type:"text",value:`{${t.name}}.${l}`})}function Ke(t){const l=q.find(n=>n.label.toUpperCase()===t.name);l?ie(l):N(t.signature),y("insert",{type:"function",value:t.name})}return U({validate:()=>F.value,evaluate:t=>m.evaluateFormula(d.value,t??u.context,u.functions,u.variables),insertText:t=>N(t),focus:j}),(t,l)=>(e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(["fe",{"theme-light":h.value==="light"}]),style:e.normalizeStyle({"--fe-height":p.height})},[e.createElementVNode("header",Ge,[e.createElementVNode("div",Xe,[l[7]||(l[7]=e.createElementVNode("span",{class:"fe-mark"},"Σ",-1)),e.createElementVNode("div",Qe,[e.createElementVNode("div",Ze,e.toDisplayString(p.title),1),e.createElementVNode("div",Je,e.toDisplayString(p.subtitle),1)])]),e.createElementVNode("div",et,[p.showVariablesPanel?(e.openBlock(),e.createElementBlock("button",{key:0,class:e.normalizeClass(["fe-btn",{active:L.value}]),type:"button",disabled:p.disabled,title:L.value?"收起变量面板":"展开变量面板",onClick:l[0]||(l[0]=n=>L.value=!L.value)},[...l[8]||(l[8]=[e.createElementVNode("span",{class:"fe-btn-ico"},"☰",-1),e.createTextVNode("变量 ",-1)])],10,tt)):e.createCommentVNode("",!0),e.createElementVNode("button",{class:e.normalizeClass(["fe-btn",{active:O.value}]),type:"button",disabled:p.disabled,title:O.value?"收起函数面板":"展开函数面板(点选插入函数)",onClick:l[1]||(l[1]=n=>O.value=!O.value)},[...l[9]||(l[9]=[e.createElementVNode("span",{class:"fe-btn-ico"},"ƒ",-1),e.createTextVNode("函数 ",-1)])],10,lt),e.createElementVNode("button",{class:"fe-btn",type:"button",disabled:p.disabled,title:"一键格式化(按块状 IF 层级对齐缩进)",onClick:He},[...l[10]||(l[10]=[e.createElementVNode("span",{class:"fe-btn-ico"},"⇄",-1),e.createTextVNode("格式化 ",-1)])],8,nt),e.createElementVNode("button",{class:"fe-btn",type:"button",disabled:p.disabled,title:"复制公式",onClick:ze},[...l[11]||(l[11]=[e.createElementVNode("span",{class:"fe-btn-ico"},"⧉",-1),e.createTextVNode("复制 ",-1)])],8,ot),e.createElementVNode("button",{class:"fe-btn",type:"button",disabled:p.disabled,title:"清空公式",onClick:Pe},[...l[12]||(l[12]=[e.createElementVNode("span",{class:"fe-btn-ico"},"✕",-1),e.createTextVNode("清空 ",-1)])],8,at),e.createElementVNode("button",{class:e.normalizeClass(["fe-btn",{active:w.value}]),type:"button",disabled:p.disabled,title:"语法帮助",onClick:l[2]||(l[2]=n=>w.value=!w.value)},[...l[13]||(l[13]=[e.createElementVNode("span",{class:"fe-btn-ico"},"?",-1),e.createTextVNode("帮助 ",-1)])],10,st),e.createElementVNode("button",{class:"fe-btn",type:"button",disabled:p.disabled,title:h.value==="dark"?"切换到浅色主题":"切换到深色主题",onClick:$e},[e.createElementVNode("span",it,e.toDisplayString(h.value==="dark"?"☀":"🌙"),1),e.createTextVNode(" "+e.toDisplayString(h.value==="dark"?"浅色":"深色"),1)],8,rt)])]),e.createVNode(e.Transition,{name:"fe-drop"},{default:e.withCtx(()=>[w.value?(e.openBlock(),e.createElementBlock("div",ct,[l[15]||(l[15]=e.createElementVNode("div",{class:"fe-help-col"},[e.createElementVNode("div",{class:"fe-help-title"},"语法速查"),e.createElementVNode("table",{class:"fe-help-table"},[e.createElementVNode("tbody",null,[e.createElementVNode("tr",null,[e.createElementVNode("td",{class:"fe-help-k"},"变量引用"),e.createElementVNode("td",null,[e.createElementVNode("code",null,"{变量名}")])]),e.createElementVNode("tr",null,[e.createElementVNode("td",{class:"fe-help-k"},"成员访问"),e.createElementVNode("td",null,[e.createElementVNode("code",null,"{订单}.金额"),e.createTextVNode(" / "),e.createElementVNode("code",null,"{订单}['金额']"),e.createTextVNode(" / "),e.createElementVNode("code",null,"{订单}.商品[0]")])]),e.createElementVNode("tr",null,[e.createElementVNode("td",{class:"fe-help-k"},"字符串"),e.createElementVNode("td",null,[e.createElementVNode("code",null,"'文本'"),e.createTextVNode(" 或 "),e.createElementVNode("code",null,'"文本"')])]),e.createElementVNode("tr",null,[e.createElementVNode("td",{class:"fe-help-k"},"布尔/空"),e.createElementVNode("td",null,[e.createElementVNode("code",null,"true"),e.createTextVNode(),e.createElementVNode("code",null,"false"),e.createTextVNode(),e.createElementVNode("code",null,"null"),e.createTextVNode(),e.createElementVNode("code",null,"undefined")])]),e.createElementVNode("tr",null,[e.createElementVNode("td",{class:"fe-help-k"},"块状条件"),e.createElementVNode("td",null,[e.createElementVNode("code",null,"IF 条件 THEN 真值 ELSE 假值"),e.createTextVNode("(可多级 "),e.createElementVNode("code",null,"ELSE IF"),e.createTextVNode(")")])]),e.createElementVNode("tr",null,[e.createElementVNode("td",{class:"fe-help-k"},"中文关键字"),e.createElementVNode("td",null,[e.createElementVNode("code",null,"如果 条件 那么 真值 否则 假值"),e.createTextVNode(" / "),e.createElementVNode("code",null,"否则如果")])]),e.createElementVNode("tr",null,[e.createElementVNode("td",{class:"fe-help-k"},"函数条件"),e.createElementVNode("td",null,[e.createElementVNode("code",null,"IF(条件, 真值, 假值)"),e.createTextVNode("、"),e.createElementVNode("code",null,"IFS(…)"),e.createTextVNode("(兼容写法)")])]),e.createElementVNode("tr",null,[e.createElementVNode("td",{class:"fe-help-k"},"枚举比较"),e.createElementVNode("td",null,[e.createTextVNode("公式写标签 "),e.createElementVNode("code",null,"{会员等级} == '金卡'"),e.createTextVNode(",求值用代码值,标签/值双向可匹配")])]),e.createElementVNode("tr",null,[e.createElementVNode("td",{class:"fe-help-k"},"比较"),e.createElementVNode("td",null,[e.createElementVNode("code",null,"=="),e.createTextVNode(),e.createElementVNode("code",null,"!="),e.createTextVNode(),e.createElementVNode("code",null,"<"),e.createTextVNode(),e.createElementVNode("code",null,">"),e.createTextVNode(),e.createElementVNode("code",null,"<="),e.createTextVNode(),e.createElementVNode("code",null,">=")])]),e.createElementVNode("tr",null,[e.createElementVNode("td",{class:"fe-help-k"},"逻辑"),e.createElementVNode("td",null,[e.createElementVNode("code",null,"&&"),e.createTextVNode(),e.createElementVNode("code",null,"||"),e.createTextVNode(),e.createElementVNode("code",null,"!"),e.createTextVNode(),e.createElementVNode("code",null,"AND"),e.createTextVNode(),e.createElementVNode("code",null,"OR"),e.createTextVNode(),e.createElementVNode("code",null,"NOT()")])]),e.createElementVNode("tr",null,[e.createElementVNode("td",{class:"fe-help-k"},"三元"),e.createElementVNode("td",null,[e.createElementVNode("code",null,"条件 ? 真值 : 假值")])])])]),e.createElementVNode("p",{class:"fe-help-tip"},[e.createTextVNode(" 提示:插入的模板会选中占位文字(如“条件”),直接输入即可替换;光标在变量 "),e.createElementVNode("code",null,"{…}"),e.createTextVNode(" 内按退格/删除键可整体删除该变量。 ")])],-1)),e.createElementVNode("div",ut,[l[14]||(l[14]=e.createElementVNode("div",{class:"fe-help-title"},"内置函数",-1)),e.createElementVNode("ul",dt,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(e.unref($),n=>(e.openBlock(),e.createElementBlock("li",{key:n.name},[e.createElementVNode("code",null,e.toDisplayString(n.signature),1),e.createElementVNode("span",null,e.toDisplayString(n.description),1)]))),128))])])])):e.createCommentVNode("",!0)]),_:1}),p.showToolbar?(e.openBlock(),e.createElementBlock("div",pt,[(e.openBlock(),e.createElementBlock(e.Fragment,null,e.renderList(ce,n=>(e.openBlock(),e.createElementBlock(e.Fragment,{key:n.key},[e.createElementVNode("span",mt,e.toDisplayString(n.label),1),(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(q.filter(a=>a.group===n.key),a=>(e.openBlock(),e.createElementBlock("button",{key:a.key,class:"fe-chip",type:"button",disabled:p.disabled,title:a.title,onClick:o=>ie(a)},e.toDisplayString(a.label),9,ft))),128))],64))),64)),l[16]||(l[16]=e.createElementVNode("span",{class:"fe-toolbar-hint"},"插入的占位文字可直接输入替换",-1))])):e.createCommentVNode("",!0),e.createElementVNode("div",Et,[L.value?(e.openBlock(),e.createElementBlock("aside",kt,[e.createElementVNode("div",Nt,[l[17]||(l[17]=e.createElementVNode("span",null,"可选变量",-1)),e.createElementVNode("span",vt,e.toDisplayString(p.variables.length),1)]),e.createElementVNode("div",yt,[e.withDirectives(e.createElementVNode("input",{"onUpdate:modelValue":l[3]||(l[3]=n=>P.value=n),type:"text",placeholder:"搜索变量…",disabled:p.disabled},null,8,bt),[[e.vModelText,P.value]])]),e.createElementVNode("div",gt,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(le.value,n=>(e.openBlock(),e.createElementBlock(e.Fragment,{key:n.name},[e.createElementVNode("div",{class:e.normalizeClass(["fe-var-row",{disabled:p.disabled}]),title:`点击插入 {${n.name}}`,onClick:a=>Ue(n.name)},[e.createElementVNode("div",Tt,[n.type==="enum"||n.type==="object"?(e.openBlock(),e.createElementBlock("span",{key:0,class:e.normalizeClass(["fe-var-expand",{open:G(n.name)}]),title:n.type==="enum"?"展开标签":"展开字段",onClick:e.withModifiers(a=>Ye(n.name),["stop"])},"▸",10,ht)):e.createCommentVNode("",!0),e.createElementVNode("span",St,"{"+e.toDisplayString(n.name)+"}",1),e.createElementVNode("span",{class:e.normalizeClass(["fe-var-badge",`vt-${n.type??"any"}`])},e.toDisplayString(me(n.type)),3),n.required?(e.openBlock(),e.createElementBlock("span",xt,"*")):e.createCommentVNode("",!0)]),e.createElementVNode("div",Ct,[e.createTextVNode(e.toDisplayString(n.label??n.name),1),n.description?(e.openBlock(),e.createElementBlock(e.Fragment,{key:0},[e.createTextVNode(" — "+e.toDisplayString(n.description),1)],64)):e.createCommentVNode("",!0)])],10,Vt),G(n.name)&&n.type==="enum"?(e.openBlock(),e.createElementBlock("div",Bt,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(n.options??[],a=>(e.openBlock(),e.createElementBlock("div",{key:typeof a=="string"?a:a.label,class:"fe-var-chip",title:`插入 {${n.name}} == '${typeof a=="string"?a:a.label}'`,onClick:e.withModifiers(o=>je(n,typeof a=="string"?a:a.label),["stop"])},e.toDisplayString(typeof a=="string"?a:a.label),9,It))),128))])):e.createCommentVNode("",!0),G(n.name)&&n.type==="object"?(e.openBlock(),e.createElementBlock("div",Lt,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(de(n),a=>(e.openBlock(),e.createElementBlock("div",{key:a,class:"fe-var-chip",title:`插入 {${n.name}}.${a}`,onClick:e.withModifiers(o=>qe(n,a),["stop"])},e.toDisplayString(a),9,Ot))),128)),de(n).length===0?(e.openBlock(),e.createElementBlock("div",Ft," 无字段(context 中对象为空) ")):e.createCommentVNode("",!0)])):e.createCommentVNode("",!0)],64))),128)),le.value.length===0?(e.openBlock(),e.createElementBlock("div",Dt,[l[18]||(l[18]=e.createElementVNode("div",{class:"fe-vars-empty-ico"},"∅",-1)),p.variables.length===0?(e.openBlock(),e.createElementBlock("div",Rt,"暂无可用变量")):(e.openBlock(),e.createElementBlock("div",wt,"没有匹配的变量"))])):e.createCommentVNode("",!0)]),l[19]||(l[19]=e.createElementVNode("div",{class:"fe-vars-foot"},"点击变量插入;枚举/键值对可点 ▸ 展开选标签/字段",-1))])):e.createCommentVNode("",!0),O.value?(e.openBlock(),e.createElementBlock("aside",_t,[e.createElementVNode("div",At,[l[20]||(l[20]=e.createElementVNode("span",null,"内置函数",-1)),e.createElementVNode("span",$t,e.toDisplayString(e.unref($).length),1)]),e.createElementVNode("div",Ut,[e.withDirectives(e.createElementVNode("input",{"onUpdate:modelValue":l[4]||(l[4]=n=>H.value=n),type:"text",placeholder:"搜索函数…",disabled:p.disabled},null,8,Mt),[[e.vModelText,H.value]])]),e.createElementVNode("div",Pt,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(ue.value,n=>(e.openBlock(),e.createElementBlock("div",{key:n.name,class:e.normalizeClass(["fe-fn-row",{disabled:p.disabled}]),title:n.example?`${n.signature}
|
|
39
|
+
示例: ${n.example}`:n.signature,onClick:a=>Ke(n)},[e.createElementVNode("div",zt,[e.createElementVNode("span",Wt,e.toDisplayString(n.name),1),e.createElementVNode("span",Yt,e.toDisplayString(We(n.group)),1)]),e.createElementVNode("div",jt,e.toDisplayString(n.signature),1),e.createElementVNode("div",qt,e.toDisplayString(n.description),1)],10,Ht))),128)),ue.value.length===0?(e.openBlock(),e.createElementBlock("div",Kt,[...l[21]||(l[21]=[e.createElementVNode("div",{class:"fe-vars-empty-ico"},"∅",-1),e.createElementVNode("div",null,"没有匹配的函数",-1)])])):e.createCommentVNode("",!0)]),l[22]||(l[22]=e.createElementVNode("div",{class:"fe-vars-foot"},"点击函数插入到光标处",-1))])):e.createCommentVNode("",!0),e.createElementVNode("div",Gt,[e.createElementVNode("div",Xt,[e.createElementVNode("div",Qt,[e.createElementVNode("div",{class:"fe-gutter-inner",style:e.normalizeStyle({transform:`translateY(${-_.value}px)`})},[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(ye.value,n=>(e.openBlock(),e.createElementBlock("span",{key:n,class:e.normalizeClass(["fe-gutter-line",{err:ge.value.has(n)}])},e.toDisplayString(n),3))),128))],4)]),e.createElementVNode("div",{ref_key:"wrapRef",ref:Q,class:"fe-code-wrap"},[e.createElementVNode("div",{class:"fe-line-marker",style:e.normalizeStyle({top:be.value+"px"})},null,4),e.createElementVNode("pre",{ref_key:"preRef",ref:Ne,class:"fe-highlight","aria-hidden":"true",style:e.normalizeStyle({transform:`translate(${-X.value}px, ${-_.value}px)`})},[e.createElementVNode("code",{innerHTML:Ce.value},null,8,Zt)],4),e.withDirectives(e.createElementVNode("textarea",{ref_key:"taRef",ref:E,"onUpdate:modelValue":l[5]||(l[5]=n=>d.value=n),class:"fe-textarea",placeholder:p.placeholder,disabled:p.disabled,readonly:p.readonly,spellcheck:!1,autocapitalize:"off",autocomplete:"off",wrap:"off",onInput:Be,onScroll:Ie,onKeydown:Ae,onClick:v,onDblclick:De,onKeyup:v,onSelect:v,onMousemove:Oe,onMouseleave:Fe},null,40,Jt),[[e.vModelText,d.value]]),e.createVNode(e.Transition,{name:"fe-fade"},{default:e.withCtx(()=>[b.value?(e.openBlock(),e.createElementBlock("div",{key:0,class:"fe-tooltip",style:e.normalizeStyle({left:b.value.x+"px",top:b.value.y+"px"})},e.toDisplayString(b.value.text),5)):e.createCommentVNode("",!0)]),_:1})],512)]),e.createVNode(e.Transition,{name:"fe-drop"},{default:e.withCtx(()=>[M.value&&I.value.length>0?(e.openBlock(),e.createElementBlock("div",el,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(I.value,(n,a)=>(e.openBlock(),e.createElementBlock("div",{key:a,class:"fe-error-row",onClick:o=>Me(n.start,n.end)},[e.createElementVNode("span",{class:e.normalizeClass(["fe-error-dot",n.severity])},null,2),e.createElementVNode("span",ll,e.toDisplayString(n.message),1),e.createElementVNode("span",nl,e.toDisplayString(n.kind),1)],8,tl))),128))])):e.createCommentVNode("",!0)]),_:1})])]),e.createElementVNode("footer",ol,[e.createElementVNode("div",al,[e.createElementVNode("span",{class:e.normalizeClass(["fe-status",F.value.ok&&d.value.trim()?"ok":"bad"]),onClick:l[6]||(l[6]=n=>M.value=!M.value)},[l[23]||(l[23]=e.createElementVNode("span",{class:"fe-status-dot"},null,-1)),e.createTextVNode(" "+e.toDisplayString(d.value.trim()?Ve.value.label:"等待输入"),1)],2),I.value.length>0?(e.openBlock(),e.createElementBlock("span",sl,e.toDisplayString(I.value.length)+" 处 ",1)):e.createCommentVNode("",!0)]),p.showPreview?(e.openBlock(),e.createElementBlock("div",{key:0,class:e.normalizeClass(["fe-foot-result",he.value])},[l[24]||(l[24]=e.createElementVNode("span",{class:"fe-result-label"},"实时求值",-1)),S.value?(e.openBlock(),e.createElementBlock(e.Fragment,{key:0},[e.createElementVNode("span",{class:"fe-result-value",title:S.value.ok?String(S.value.value):""},e.toDisplayString(Te.value),9,rl),ne.value?(e.openBlock(),e.createElementBlock("span",il,e.toDisplayString(ne.value),1)):e.createCommentVNode("",!0)],64)):(e.openBlock(),e.createElementBlock("span",cl,"—"))],2)):e.createCommentVNode("",!0),e.createElementVNode("div",ul,"Ln "+e.toDisplayString(A.value.line)+", Col "+e.toDisplayString(A.value.col),1)])],6))}}),pl=(p,U)=>{const R=p.__vccOpts||p;for(const[u,y]of U)R[u]=y;return R},ml=pl(dl,[["__scopeId","data-v-10da7669"]]);exports.BUILTIN_FUNCTIONS=m.BUILTIN_FUNCTIONS;exports.ParseError=m.ParseError;exports.RuntimeError=m.RuntimeError;exports.evaluate=m.evaluate;exports.evaluateFormula=m.evaluateFormula;exports.formatFormula=m.formatFormula;exports.formatValue=m.formatValue;exports.lexErrors=m.lexErrors;exports.parse=m.parse;exports.parseFormula=m.parseFormula;exports.tokenize=m.tokenize;exports.validateFormula=m.validateFormula;exports.valueTypeName=m.valueTypeName;exports.FormulaEditor=ml;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export { FormulaEditor } from './FormulaEditor'
|
|
2
|
+
export type { FormulaEditorProps, FormulaEditorExpose, FormulaEditorEmits, CustomFunctionSpec } from './FormulaEditor'
|
|
3
|
+
|
|
4
|
+
/* 引擎(./engine 子路径同源,主入口一并导出便于直接使用) */
|
|
5
|
+
export { tokenize } from './engine/lexer'
|
|
6
|
+
export { parse, ParseError } from './engine/parser'
|
|
7
|
+
export { evaluate, RuntimeError, BUILTIN_FUNCTIONS } from './engine/evaluator'
|
|
8
|
+
export { formatFormula } from './engine/format'
|
|
9
|
+
export type { FnMap } from './engine/evaluator'
|
|
10
|
+
export type {
|
|
11
|
+
Token,
|
|
12
|
+
TokenType,
|
|
13
|
+
ExprNode,
|
|
14
|
+
FunctionSpec,
|
|
15
|
+
FormulaError,
|
|
16
|
+
ValidationResult,
|
|
17
|
+
EvalResult,
|
|
18
|
+
VariableDef,
|
|
19
|
+
ValueType,
|
|
20
|
+
EnumOption,
|
|
21
|
+
FieldDef,
|
|
22
|
+
NumberNode,
|
|
23
|
+
StringNode,
|
|
24
|
+
BooleanNode,
|
|
25
|
+
NullNode,
|
|
26
|
+
VariableNode,
|
|
27
|
+
MemberNode,
|
|
28
|
+
CallNode,
|
|
29
|
+
BinaryNode,
|
|
30
|
+
UnaryNode,
|
|
31
|
+
ConditionalNode,
|
|
32
|
+
IfNode,
|
|
33
|
+
} from './engine/types'
|
|
34
|
+
|
|
35
|
+
export { parseFormula, evaluateFormula, validateFormula, formatFormula, formatValue, valueTypeName, lexErrors } from './engine'
|