@xw-components/formula-editor 18.1.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/esm2022/formula-editor-icon.mjs +9 -0
- package/esm2022/formula-editor-request.service.mjs +18 -0
- package/esm2022/formula-editor.component.mjs +299 -0
- package/esm2022/formula-editor.mjs +5 -0
- package/esm2022/formula-editor.model.mjs +57 -0
- package/esm2022/formula-editor.service.mjs +318 -0
- package/esm2022/index.mjs +6 -0
- package/fesm2022/formula-editor.mjs +699 -0
- package/fesm2022/formula-editor.mjs.map +1 -0
- package/formula-editor-icon.d.ts +8 -0
- package/formula-editor-request.service.d.ts +33 -0
- package/formula-editor.component.d.ts +134 -0
- package/formula-editor.model.d.ts +40 -0
- package/formula-editor.service.d.ts +41 -0
- package/index.d.ts +5 -0
- package/package.json +29 -0
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
import * as i5 from '@angular/common';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import * as i0 from '@angular/core';
|
|
4
|
+
import { InjectionToken, inject, Injectable, input, signal, effect, ViewChild, Component } from '@angular/core';
|
|
5
|
+
import * as i3 from '@angular/forms';
|
|
6
|
+
import { FormsModule } from '@angular/forms';
|
|
7
|
+
import { Node, mergeAttributes, Editor } from '@tiptap/core';
|
|
8
|
+
import StarterKit from '@tiptap/starter-kit';
|
|
9
|
+
import * as i7 from 'ng-zorro-antd/button';
|
|
10
|
+
import { NzButtonModule } from 'ng-zorro-antd/button';
|
|
11
|
+
import { NzHighlightPipe } from 'ng-zorro-antd/core/highlight';
|
|
12
|
+
import * as i1 from 'ng-zorro-antd/grid';
|
|
13
|
+
import { NzColDirective, NzGridModule } from 'ng-zorro-antd/grid';
|
|
14
|
+
import * as i4 from 'ng-zorro-antd/icon';
|
|
15
|
+
import { NzIconModule } from 'ng-zorro-antd/icon';
|
|
16
|
+
import * as i2 from 'ng-zorro-antd/input';
|
|
17
|
+
import { NzInputModule } from 'ng-zorro-antd/input';
|
|
18
|
+
import { NzMessageService } from 'ng-zorro-antd/message';
|
|
19
|
+
import * as i9 from 'ng-zorro-antd/tooltip';
|
|
20
|
+
import { NzToolTipModule } from 'ng-zorro-antd/tooltip';
|
|
21
|
+
import * as i6 from 'ng-zorro-antd/tree';
|
|
22
|
+
import { NzTreeModule } from 'ng-zorro-antd/tree';
|
|
23
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
24
|
+
import * as i8 from 'ng-zorro-antd/core/transition-patch';
|
|
25
|
+
|
|
26
|
+
const FormulaEditorRequestServiceToken = new InjectionToken('FormulaEditorRequestServiceToken');
|
|
27
|
+
/**
|
|
28
|
+
* 公式编辑器请求服务,用于获取函数列表、字段列表、指标列表和校验公式格式
|
|
29
|
+
* 实现类需要实现四个方法。如果有部分方法不需要,可以实现空方法。
|
|
30
|
+
*/
|
|
31
|
+
class FormulaEditorRequestService {
|
|
32
|
+
constructor() { }
|
|
33
|
+
/**
|
|
34
|
+
* 获取字段的描述
|
|
35
|
+
* @param item 字段对象
|
|
36
|
+
* @returns 描述字符串
|
|
37
|
+
*/
|
|
38
|
+
getFieldDescription() {
|
|
39
|
+
return Promise.resolve('');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 输入项类型,可选择指标模式或字段模式
|
|
45
|
+
*/
|
|
46
|
+
var ItemType;
|
|
47
|
+
(function (ItemType) {
|
|
48
|
+
ItemType["Field"] = "field";
|
|
49
|
+
ItemType["Template"] = "template"; // 模板模式
|
|
50
|
+
})(ItemType || (ItemType = {}));
|
|
51
|
+
/**
|
|
52
|
+
* 输入校验格式方法
|
|
53
|
+
*/
|
|
54
|
+
var CheckFormatMethod;
|
|
55
|
+
(function (CheckFormatMethod) {
|
|
56
|
+
CheckFormatMethod["self"] = "self";
|
|
57
|
+
CheckFormatMethod["server"] = "server"; // 自定义异步校验
|
|
58
|
+
})(CheckFormatMethod || (CheckFormatMethod = {}));
|
|
59
|
+
/**
|
|
60
|
+
* 函数列表模式,可选择列表或树模式
|
|
61
|
+
*/
|
|
62
|
+
var FuncListType;
|
|
63
|
+
(function (FuncListType) {
|
|
64
|
+
FuncListType["List"] = "list";
|
|
65
|
+
FuncListType["Tree"] = "tree"; // 树模式
|
|
66
|
+
})(FuncListType || (FuncListType = {}));
|
|
67
|
+
const OPERATOR_LIST = [
|
|
68
|
+
{
|
|
69
|
+
type: 'add',
|
|
70
|
+
name: '+',
|
|
71
|
+
description: '加法'
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
type: 'minus',
|
|
75
|
+
name: '-',
|
|
76
|
+
description: '减法'
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
type: 'multiply',
|
|
80
|
+
name: '*',
|
|
81
|
+
description: '乘法'
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
type: 'divide',
|
|
85
|
+
name: '/',
|
|
86
|
+
description: '除法'
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
type: 'left-parenthesis',
|
|
90
|
+
name: '(',
|
|
91
|
+
description: '左括号'
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
type: 'right-parenthesis',
|
|
95
|
+
name: ')',
|
|
96
|
+
description: '右括号'
|
|
97
|
+
}
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
class FormulaEditorService {
|
|
101
|
+
constructor() {
|
|
102
|
+
this.sanitizer = inject(DomSanitizer);
|
|
103
|
+
}
|
|
104
|
+
initNodes() {
|
|
105
|
+
this.createFuncNode();
|
|
106
|
+
this.createFieldNode();
|
|
107
|
+
}
|
|
108
|
+
createFieldNode() {
|
|
109
|
+
this.fieldNode = Node.create({
|
|
110
|
+
name: 'field',
|
|
111
|
+
inline: true,
|
|
112
|
+
group: 'inline',
|
|
113
|
+
selectable: false,
|
|
114
|
+
draggable: true,
|
|
115
|
+
atom: true,
|
|
116
|
+
addAttributes() {
|
|
117
|
+
return {
|
|
118
|
+
id: {
|
|
119
|
+
default: null
|
|
120
|
+
},
|
|
121
|
+
name: {
|
|
122
|
+
default: ''
|
|
123
|
+
},
|
|
124
|
+
className: {
|
|
125
|
+
default: ''
|
|
126
|
+
},
|
|
127
|
+
dataType: {
|
|
128
|
+
default: 'field'
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
renderText({ node }) {
|
|
133
|
+
return `${node.attrs['name']}`;
|
|
134
|
+
},
|
|
135
|
+
parseHTML() {
|
|
136
|
+
return [{ tag: 'div[data-type="field"]' }];
|
|
137
|
+
},
|
|
138
|
+
renderHTML({ HTMLAttributes, node }) {
|
|
139
|
+
return [
|
|
140
|
+
'div',
|
|
141
|
+
mergeAttributes({
|
|
142
|
+
'data-type': 'field',
|
|
143
|
+
class: `field-node ${node.attrs['className']}` || '',
|
|
144
|
+
contenteditable: 'false',
|
|
145
|
+
style: `display: inline-block;margin: 0 4px;font-size: 13px;height: 18px;line-height: 18px;`
|
|
146
|
+
}, HTMLAttributes),
|
|
147
|
+
node.attrs['name']
|
|
148
|
+
];
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
createFuncNode() {
|
|
153
|
+
this.funcNode = Node.create({
|
|
154
|
+
name: 'func',
|
|
155
|
+
inline: true,
|
|
156
|
+
group: 'inline',
|
|
157
|
+
selectable: false,
|
|
158
|
+
draggable: false,
|
|
159
|
+
content: 'inline*',
|
|
160
|
+
atom: true,
|
|
161
|
+
addAttributes() {
|
|
162
|
+
return {
|
|
163
|
+
id: {
|
|
164
|
+
default: null
|
|
165
|
+
},
|
|
166
|
+
name: {
|
|
167
|
+
default: ''
|
|
168
|
+
},
|
|
169
|
+
className: {
|
|
170
|
+
default: ''
|
|
171
|
+
},
|
|
172
|
+
dataType: {
|
|
173
|
+
default: 'func'
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
},
|
|
177
|
+
renderText({ node }) {
|
|
178
|
+
const funcName = node.attrs['name'] || '';
|
|
179
|
+
const childrenText = node.content.content
|
|
180
|
+
.map(child => {
|
|
181
|
+
if (child.text) {
|
|
182
|
+
return child.text;
|
|
183
|
+
}
|
|
184
|
+
if (child.type && child.type.spec && child.type.spec['toText']) {
|
|
185
|
+
return child.type.spec['toText']({ node: child });
|
|
186
|
+
}
|
|
187
|
+
return '';
|
|
188
|
+
})
|
|
189
|
+
.join('');
|
|
190
|
+
return `${funcName}(${childrenText})`;
|
|
191
|
+
},
|
|
192
|
+
parseHTML() {
|
|
193
|
+
return [{ tag: 'div[data-type="func"]' }];
|
|
194
|
+
},
|
|
195
|
+
addNodeView() {
|
|
196
|
+
return ({ node }) => {
|
|
197
|
+
const createElement = (tag, props = {}) => {
|
|
198
|
+
const element = document.createElement(tag);
|
|
199
|
+
Object.entries(props).forEach(([key, value]) => {
|
|
200
|
+
if (key === 'style') {
|
|
201
|
+
Object.entries(value).forEach(([styleKey, styleValue]) => {
|
|
202
|
+
element.style[styleKey] = styleValue;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
else if (key === 'innerText') {
|
|
206
|
+
element.innerText = value;
|
|
207
|
+
}
|
|
208
|
+
else if (key === 'className') {
|
|
209
|
+
element.className = value;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
element.setAttribute(key, value);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
return element;
|
|
216
|
+
};
|
|
217
|
+
const className = node.attrs['className'] || '';
|
|
218
|
+
const funcName = node.attrs['name'];
|
|
219
|
+
const container = createElement('span', {
|
|
220
|
+
style: {
|
|
221
|
+
display: 'inline-block',
|
|
222
|
+
margin: '0 4px',
|
|
223
|
+
fontSize: '13px'
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
const content = createElement('span', {
|
|
227
|
+
style: {
|
|
228
|
+
whiteSpace: 'pre-wrap',
|
|
229
|
+
display: 'inline-block'
|
|
230
|
+
},
|
|
231
|
+
contenteditable: 'true'
|
|
232
|
+
});
|
|
233
|
+
const before = createElement('span', {
|
|
234
|
+
innerText: `${funcName} `,
|
|
235
|
+
contenteditable: 'false',
|
|
236
|
+
className
|
|
237
|
+
});
|
|
238
|
+
const bracket = (value, style = {}) => {
|
|
239
|
+
return createElement('span', {
|
|
240
|
+
innerText: value,
|
|
241
|
+
contenteditable: 'false',
|
|
242
|
+
style: {
|
|
243
|
+
...style
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
};
|
|
247
|
+
container.append(before, bracket('(', { marginRight: '2px' }), content, bracket(')', { marginLeft: '2px' }));
|
|
248
|
+
return {
|
|
249
|
+
dom: container,
|
|
250
|
+
contentDOM: content
|
|
251
|
+
};
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* 校验公式的JSON表示是否有效
|
|
258
|
+
* @param formula 公式的JSON表示
|
|
259
|
+
* @returns 校验结果,包含是否有效和错误信息数组
|
|
260
|
+
*/
|
|
261
|
+
validateFormula(formula) {
|
|
262
|
+
const errors = [];
|
|
263
|
+
if (!formula) {
|
|
264
|
+
errors.push('公式不能为空');
|
|
265
|
+
return { isValid: false, errors };
|
|
266
|
+
}
|
|
267
|
+
if (formula.type !== 'doc') {
|
|
268
|
+
errors.push('公式根节点类型必须是 doc');
|
|
269
|
+
}
|
|
270
|
+
if (!formula.content || !Array.isArray(formula.content)) {
|
|
271
|
+
errors.push('公式必须包含 content 数组');
|
|
272
|
+
return { isValid: false, errors };
|
|
273
|
+
}
|
|
274
|
+
for (const paragraph of formula.content) {
|
|
275
|
+
if (paragraph.type !== 'paragraph') {
|
|
276
|
+
errors.push('段落类型必须是 paragraph');
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (!paragraph.content || !Array.isArray(paragraph.content)) {
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
for (const node of paragraph.content) {
|
|
283
|
+
this.validateNode(node, errors);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
isValid: errors.length === 0,
|
|
288
|
+
errors
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* 从JSON数据中提取内容文本或HTML
|
|
293
|
+
* @param jsonData 公式的JSON表示
|
|
294
|
+
* @param outputFormat 输出格式,'text'或'html',默认'text'
|
|
295
|
+
* @returns 提取的内容文本或HTML字符串
|
|
296
|
+
*/
|
|
297
|
+
getContentFromJson(jsonData, outputFormat = 'text') {
|
|
298
|
+
if (!jsonData || !jsonData.content)
|
|
299
|
+
return '';
|
|
300
|
+
return outputFormat === 'html'
|
|
301
|
+
? this.sanitizer.bypassSecurityTrustHtml(this.processContentToHtml(jsonData.content))
|
|
302
|
+
: this.processContent(jsonData.content);
|
|
303
|
+
}
|
|
304
|
+
processContent(content) {
|
|
305
|
+
if (!content || !Array.isArray(content))
|
|
306
|
+
return '';
|
|
307
|
+
return content.map(item => this.processNode(item)).join('');
|
|
308
|
+
}
|
|
309
|
+
processNode(node) {
|
|
310
|
+
if (!node)
|
|
311
|
+
return '';
|
|
312
|
+
switch (node.type) {
|
|
313
|
+
case 'func':
|
|
314
|
+
return this.processFuncNode(node);
|
|
315
|
+
case 'field':
|
|
316
|
+
return `{${node.attrs?.name || ''}}`;
|
|
317
|
+
case 'metric':
|
|
318
|
+
return `[${node.attrs?.name || ''}]`;
|
|
319
|
+
case 'text':
|
|
320
|
+
return node.text || '';
|
|
321
|
+
case 'paragraph':
|
|
322
|
+
return this.processContent(node.content);
|
|
323
|
+
case 'doc':
|
|
324
|
+
return this.processContent(node.content);
|
|
325
|
+
default:
|
|
326
|
+
return '';
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
processFuncNode(node) {
|
|
330
|
+
const funcName = node.attrs?.name || '';
|
|
331
|
+
const content = this.processContent(node.content);
|
|
332
|
+
return `${funcName}(${content})`;
|
|
333
|
+
}
|
|
334
|
+
processContentToHtml(content) {
|
|
335
|
+
if (!content || !Array.isArray(content))
|
|
336
|
+
return '';
|
|
337
|
+
return content.map(item => this.processNodeToHtml(item)).join('');
|
|
338
|
+
}
|
|
339
|
+
processNodeToHtml(node) {
|
|
340
|
+
if (!node)
|
|
341
|
+
return '';
|
|
342
|
+
switch (node.type) {
|
|
343
|
+
case 'func':
|
|
344
|
+
return this.processFuncNodeToHtml(node);
|
|
345
|
+
case 'field':
|
|
346
|
+
return `<span class="${node.attrs?.className || ''}" style="display: inline-block; margin: 0 4px;height: 18px;line-height: 18px;">${node.attrs?.name || ''}</span>`;
|
|
347
|
+
case 'text':
|
|
348
|
+
return this.escapeHtml(node.text || '');
|
|
349
|
+
case 'paragraph':
|
|
350
|
+
return this.processContentToHtml(node.content);
|
|
351
|
+
case 'doc':
|
|
352
|
+
return this.processContentToHtml(node.content);
|
|
353
|
+
default:
|
|
354
|
+
return '';
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
processFuncNodeToHtml(node) {
|
|
358
|
+
const funcName = node.attrs?.name || '';
|
|
359
|
+
const className = node.attrs?.className || '';
|
|
360
|
+
const content = this.processContentToHtml(node.content);
|
|
361
|
+
return `<span style="display: inline-block; margin: 0 4px;"><span contenteditable="false" class="${className}">${funcName}(</span><span style="white-space: pre-wrap; display: inline-block; margin: 0 4px;" contenteditable="true">${content}</span><span contenteditable="false" class="${className}">)</span></span>`;
|
|
362
|
+
}
|
|
363
|
+
escapeHtml(text) {
|
|
364
|
+
const div = document.createElement('div');
|
|
365
|
+
div.textContent = text;
|
|
366
|
+
return div.innerHTML;
|
|
367
|
+
}
|
|
368
|
+
validateNode(node, errors, path = '') {
|
|
369
|
+
if (!node || !node.type) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const currentPath = path ? `${path} > ${node.type}` : node.type;
|
|
373
|
+
if (node.type === 'func') {
|
|
374
|
+
this.validateFuncNode(node, errors, currentPath);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
validateFuncNode(node, errors, path) {
|
|
378
|
+
if (!node.content || !Array.isArray(node.content)) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const content = node.content;
|
|
382
|
+
const length = content.length;
|
|
383
|
+
for (let i = 0; i < length; i++) {
|
|
384
|
+
const currentItem = content[i];
|
|
385
|
+
const nextItem = content[i + 1];
|
|
386
|
+
if (currentItem.type === 'field' || currentItem.type === 'func') {
|
|
387
|
+
if (i < length - 1) {
|
|
388
|
+
if (nextItem && nextItem.type === 'text') {
|
|
389
|
+
const text = nextItem.text || '';
|
|
390
|
+
if (!text.startsWith(',')) {
|
|
391
|
+
errors.push(`${path}: 参数之间必须用逗号分隔,位置 ${i + 1}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
errors.push(`${path}: 参数之间必须用逗号分隔,位置 ${i + 1}`);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (currentItem.type === 'func') {
|
|
400
|
+
this.validateFuncNode(currentItem, errors, `${path} > ${currentItem.attrs?.name || 'func'}`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FormulaEditorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
405
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FormulaEditorService, providedIn: 'root' }); }
|
|
406
|
+
}
|
|
407
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FormulaEditorService, decorators: [{
|
|
408
|
+
type: Injectable,
|
|
409
|
+
args: [{
|
|
410
|
+
providedIn: 'root'
|
|
411
|
+
}]
|
|
412
|
+
}] });
|
|
413
|
+
|
|
414
|
+
const ICONS = {
|
|
415
|
+
cheng: `<svg class="icon" width="16px" height="16px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M460.8 896c-7.1 0-12.8-5.7-12.8-12.8V614.8L268.8 794c-5 5-13.1 5.1-18.1 0.1l-0.1-0.1-49.5-49.8c-5-5-5.1-13.1-0.1-18.1l0.1-0.1L383 544.1H140.8c-7.1 0-12.8-5.7-12.8-12.8v-70.4c0-7.1 5.7-12.8 12.8-12.8h239.4L200.9 268.9c-5-5-5.1-13.1-0.1-18.1l0.1-0.1 49.5-49.8c5-5 13.1-5.1 18.1-0.1l0.1 0.1 179.2 179.2V140.8c0-7.1 5.7-12.8 12.8-12.8H531c7.1 0 12.8 5.7 12.8 12.8v242.3L725.9 201c5-4.9 12.9-4.9 17.9 0l49.8 49.8c5 5 5.1 13.1 0.1 18.1l-0.1 0.1-179.2 179.1h268.4c7.1 0 12.8 5.7 12.8 12.8v70.4c0 7.1-5.7 12.8-12.8 12.8H611.6L793.5 726c5 5 5.1 13.1 0.1 18.1l-0.1 0.1-49.8 49.8c-5 4.9-12.9 4.9-17.9 0L543.7 611.8v271.6c0 7.1-5.7 12.8-12.8 12.8l-70.1-0.2z" /></svg>`,
|
|
416
|
+
chu: `<svg class="icon" width="16px" height="16px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M808.3 283.6L283.6 808.3c-5 5-13.1 5-18.1 0l-49.8-49.8c-5-5-5-13.1 0-18.1l524.7-524.7c5-5 13.1-5 18.1 0l49.8 49.8c4.9 5 4.9 13.1 0 18.1z" /></svg>`,
|
|
417
|
+
jia: `<svg class="icon" width="16px" height="16px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M476.8 896c-7.1 0-12.8-5.7-12.8-12.8V560H140.8c-7.1 0-12.8-5.7-12.8-12.8v-70.4c0-7.1 5.7-12.8 12.8-12.8H464V140.8c0-7.1 5.7-12.8 12.8-12.8h70.4c7.1 0 12.8 5.7 12.8 12.8V464h323.2c7.1 0 12.8 5.7 12.8 12.8v70.4c0 7.1-5.7 12.8-12.8 12.8H560v323.2c-0.2 7-5.8 12.7-12.8 12.8h-70.4z" /></svg>`,
|
|
418
|
+
jian: `<svg class="icon" width="16px" height="16px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M140.8 464h742.4c7.1 0 12.8 5.7 12.8 12.8v70.4c0 7.1-5.7 12.8-12.8 12.8H140.8c-7.1 0-12.8-5.7-12.8-12.8v-70.4c0-7.1 5.7-12.8 12.8-12.8z" /></svg>`,
|
|
419
|
+
youkuohao: `<svg class="icon" width="16px" height="16px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M416 163c38.7 49.3 68.8 104.8 88.9 164.1 19.8 59.6 29.7 122.1 29.3 184.9 1.1 62.3-8.5 124.2-28.4 183.2-23.2 59.2-53.3 115.4-89.8 167.4l56.9 33.3c42.9-56 76.8-118.4 100.4-184.9 23.2-64.1 34.9-131.8 34.7-199.9 0.3-69.3-11.4-138-34.7-203.3-23.2-65-57.2-125.8-100.4-179.8L416 163z" /></svg>`,
|
|
420
|
+
zuokuohao: `<svg class="icon" width="16px" height="16px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M608 163c-38.7 49.3-68.8 104.8-88.9 164.1-19.8 59.6-29.7 122.1-29.3 184.9-1.1 62.3 8.5 124.2 28.4 183.2 23.1 59.2 53.2 115.4 89.6 167.4L550.9 896c-42.8-56-76.6-118.4-100.2-184.9-23.2-64.1-34.9-131.8-34.7-199.9-0.3-69.3 11.4-138 34.7-203.3C473.9 242.8 507.9 182 551.1 128l56.9 35z" /></svg>`
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* 公式编辑器组件
|
|
425
|
+
* 用于创建和编辑计算公式,支持函数、指标和字段的插入与验证
|
|
426
|
+
* 依赖className与在使用组件处使用穿透样式实现样式
|
|
427
|
+
*/
|
|
428
|
+
class FormulaEditorComponent {
|
|
429
|
+
getOperatorIcon(type) {
|
|
430
|
+
const iconMap = {
|
|
431
|
+
add: ICONS.jia,
|
|
432
|
+
minus: ICONS.jian,
|
|
433
|
+
multiply: ICONS.cheng,
|
|
434
|
+
divide: ICONS.chu,
|
|
435
|
+
'left-parenthesis': ICONS.zuokuohao,
|
|
436
|
+
'right-parenthesis': ICONS.youkuohao
|
|
437
|
+
};
|
|
438
|
+
return this.sanitizer.bypassSecurityTrustHtml(iconMap[type] || '');
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* 构造函数
|
|
442
|
+
* 初始化信号监听,处理搜索和项目类型变化
|
|
443
|
+
*/
|
|
444
|
+
constructor() {
|
|
445
|
+
this.formulaEditorService = inject(FormulaEditorService);
|
|
446
|
+
this.sanitizer = inject(DomSanitizer);
|
|
447
|
+
this.msg = inject(NzMessageService);
|
|
448
|
+
this.formulaEditorRequestService = inject(FormulaEditorRequestServiceToken);
|
|
449
|
+
/**
|
|
450
|
+
* 项目类型,决定显示字段或自定义
|
|
451
|
+
* 默认值:ItemType.Field
|
|
452
|
+
*/
|
|
453
|
+
this.itemType = input(ItemType.Field);
|
|
454
|
+
/**
|
|
455
|
+
* 字段标题,当项目类型为字段时显示
|
|
456
|
+
* 默认值:'字段'
|
|
457
|
+
*/
|
|
458
|
+
this.fieldTitle = input('字段');
|
|
459
|
+
/**
|
|
460
|
+
* 格式检查方法
|
|
461
|
+
* 默认值:CheckFormatMethod.self
|
|
462
|
+
*/
|
|
463
|
+
this.checkFormatMethod = input(CheckFormatMethod.self);
|
|
464
|
+
/**
|
|
465
|
+
* 函数列表显示类型
|
|
466
|
+
* 默认值:FuncListType.Tree
|
|
467
|
+
*/
|
|
468
|
+
this.functionListType = input(FuncListType.Tree);
|
|
469
|
+
this.itemTemplate = input(null);
|
|
470
|
+
this._itemType = ItemType;
|
|
471
|
+
this._funcListType = FuncListType;
|
|
472
|
+
this.searchWord = '';
|
|
473
|
+
this.fieldListOrigin = [];
|
|
474
|
+
this.fieldList = [];
|
|
475
|
+
this.checkLoading = false;
|
|
476
|
+
this.showDescription = true;
|
|
477
|
+
this.funcNode = [];
|
|
478
|
+
this.fieldDescription = '';
|
|
479
|
+
this.editDescription = '请选择函数';
|
|
480
|
+
this.editDescShow = 'none';
|
|
481
|
+
this.searchField = signal('');
|
|
482
|
+
this.searchFunction = signal('');
|
|
483
|
+
this._operatorList = OPERATOR_LIST;
|
|
484
|
+
effect(() => {
|
|
485
|
+
const searchWord = this.searchField();
|
|
486
|
+
this.fieldList = this.fieldListOrigin.filter(item => item.name.includes(searchWord));
|
|
487
|
+
});
|
|
488
|
+
effect(() => {
|
|
489
|
+
const itemType = this.itemType();
|
|
490
|
+
if (itemType === ItemType.Field) {
|
|
491
|
+
this.formulaEditorRequestService.getFields().then(res => {
|
|
492
|
+
this.fieldListOrigin = res;
|
|
493
|
+
this.fieldList = [...this.fieldListOrigin];
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
ngOnInit() {
|
|
499
|
+
this.formulaEditorRequestService.getFunctions().then(res => {
|
|
500
|
+
this.funcNode = res;
|
|
501
|
+
});
|
|
502
|
+
this.formulaEditorRequestService.getFieldDescription().then(res => {
|
|
503
|
+
this.fieldDescription = res;
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
ngAfterViewInit() {
|
|
507
|
+
this.formulaEditorService.initNodes();
|
|
508
|
+
this.editorObj = new Editor({
|
|
509
|
+
element: this.editor.nativeElement,
|
|
510
|
+
extensions: [
|
|
511
|
+
StarterKit.configure({
|
|
512
|
+
blockquote: false,
|
|
513
|
+
codeBlock: false,
|
|
514
|
+
code: false
|
|
515
|
+
}),
|
|
516
|
+
this.formulaEditorService.funcNode,
|
|
517
|
+
this.formulaEditorService.fieldNode
|
|
518
|
+
]
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
insertFunc(func) {
|
|
522
|
+
this.editorObj
|
|
523
|
+
.chain()
|
|
524
|
+
.insertContent({
|
|
525
|
+
type: 'func',
|
|
526
|
+
attrs: {
|
|
527
|
+
name: func.name,
|
|
528
|
+
id: func.id,
|
|
529
|
+
className: `text-func-${func.className}`
|
|
530
|
+
},
|
|
531
|
+
content: []
|
|
532
|
+
})
|
|
533
|
+
.focus()
|
|
534
|
+
.run();
|
|
535
|
+
}
|
|
536
|
+
insertField(field) {
|
|
537
|
+
this.editorObj
|
|
538
|
+
.chain()
|
|
539
|
+
.insertContent({
|
|
540
|
+
type: 'field',
|
|
541
|
+
attrs: {
|
|
542
|
+
name: field.name,
|
|
543
|
+
id: field.id,
|
|
544
|
+
className: `text-field-${field.className}`
|
|
545
|
+
}
|
|
546
|
+
})
|
|
547
|
+
.focus()
|
|
548
|
+
.run();
|
|
549
|
+
}
|
|
550
|
+
insertOperator(operator) {
|
|
551
|
+
this.editorObj
|
|
552
|
+
.chain()
|
|
553
|
+
.insertContent({
|
|
554
|
+
type: 'text',
|
|
555
|
+
text: operator
|
|
556
|
+
})
|
|
557
|
+
.focus()
|
|
558
|
+
.run();
|
|
559
|
+
}
|
|
560
|
+
functionClick(node) {
|
|
561
|
+
const func = node.node.origin;
|
|
562
|
+
this.editDescription = func.description || '';
|
|
563
|
+
this.editDescShow = 'func';
|
|
564
|
+
this.insertFunc(func);
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* 获取公式JSON
|
|
568
|
+
* @returns 公式的JSON表示
|
|
569
|
+
* @example
|
|
570
|
+
* // 返回示例
|
|
571
|
+
* {
|
|
572
|
+
* "type": "doc",
|
|
573
|
+
* "content": [
|
|
574
|
+
* {
|
|
575
|
+
* "type": "paragraph",
|
|
576
|
+
* "content": [
|
|
577
|
+
* {
|
|
578
|
+
* "type": "func",
|
|
579
|
+
* "attrs": {
|
|
580
|
+
* "name": "SUM",
|
|
581
|
+
* "id": "sum",
|
|
582
|
+
* "className": "text-func-agg"
|
|
583
|
+
* }
|
|
584
|
+
* }
|
|
585
|
+
* ]
|
|
586
|
+
* }
|
|
587
|
+
* ]
|
|
588
|
+
* }
|
|
589
|
+
*/
|
|
590
|
+
getFormula() {
|
|
591
|
+
const formula = this.editorObj.getJSON();
|
|
592
|
+
return formula;
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* 获取公式文本
|
|
596
|
+
* @returns 公式的纯文本表示
|
|
597
|
+
* @example
|
|
598
|
+
* // 返回示例
|
|
599
|
+
* "SUM(field1, field2)"
|
|
600
|
+
*/
|
|
601
|
+
getFormulaText() {
|
|
602
|
+
const formulaText = this.editorObj.getText();
|
|
603
|
+
return formulaText;
|
|
604
|
+
}
|
|
605
|
+
checkFormat() {
|
|
606
|
+
if (this.checkFormatMethod() === CheckFormatMethod.self) {
|
|
607
|
+
const result = this.validateFormula();
|
|
608
|
+
if (result.isValid) {
|
|
609
|
+
this.msg.success('格式校验通过');
|
|
610
|
+
}
|
|
611
|
+
else {
|
|
612
|
+
this.msg.error(`格式校验不通过: ${result.errors.join(', ')}`);
|
|
613
|
+
this.editDescription = result.errors.join(', ');
|
|
614
|
+
this.editDescShow = 'error';
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
else {
|
|
618
|
+
this.checkLoading = true;
|
|
619
|
+
this.formulaEditorRequestService.validateFormula(this.getFormula()).then(res => {
|
|
620
|
+
this.checkLoading = false;
|
|
621
|
+
if (res.isValid) {
|
|
622
|
+
this.msg.success('格式校验通过');
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
this.msg.error(`格式校验不通过: ${res.errors.join(', ')}`);
|
|
626
|
+
this.editDescription = res.errors.join(', ');
|
|
627
|
+
this.editDescShow = 'error';
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* 从JSON加载公式
|
|
634
|
+
* @param formula 公式的JSON表示
|
|
635
|
+
* @example
|
|
636
|
+
* // 使用示例
|
|
637
|
+
* const formula = {
|
|
638
|
+
* "type": "doc",
|
|
639
|
+
* "content": [
|
|
640
|
+
* {
|
|
641
|
+
* "type": "paragraph",
|
|
642
|
+
* "content": [
|
|
643
|
+
* {
|
|
644
|
+
* "type": "func",
|
|
645
|
+
* "attrs": {
|
|
646
|
+
* "name": "SUM",
|
|
647
|
+
* "id": "sum",
|
|
648
|
+
* "className": "text-func-agg"
|
|
649
|
+
* }
|
|
650
|
+
* }
|
|
651
|
+
* ]
|
|
652
|
+
* }
|
|
653
|
+
* ]
|
|
654
|
+
* };
|
|
655
|
+
* formulaEditor.loadFormula(formula);
|
|
656
|
+
*/
|
|
657
|
+
loadFormula(formula) {
|
|
658
|
+
if (!formula || !this.editorObj) {
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
this.editorObj.commands.setContent(formula);
|
|
662
|
+
}
|
|
663
|
+
// 校验公式
|
|
664
|
+
validateFormula() {
|
|
665
|
+
const formula = this.editorObj.getJSON();
|
|
666
|
+
const result = this.formulaEditorService.validateFormula(formula);
|
|
667
|
+
return result;
|
|
668
|
+
}
|
|
669
|
+
toggleDescription() {
|
|
670
|
+
this.showDescription = !this.showDescription;
|
|
671
|
+
}
|
|
672
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FormulaEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
673
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: FormulaEditorComponent, isStandalone: true, selector: "app-formula-editor", inputs: { itemType: { classPropertyName: "itemType", publicName: "itemType", isSignal: true, isRequired: false, transformFunction: null }, fieldTitle: { classPropertyName: "fieldTitle", publicName: "fieldTitle", isSignal: true, isRequired: false, transformFunction: null }, checkFormatMethod: { classPropertyName: "checkFormatMethod", publicName: "checkFormatMethod", isSignal: true, isRequired: false, transformFunction: null }, functionListType: { classPropertyName: "functionListType", publicName: "functionListType", isSignal: true, isRequired: false, transformFunction: null }, itemTemplate: { classPropertyName: "itemTemplate", publicName: "itemTemplate", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "editor", first: true, predicate: ["editor"], descendants: true }], ngImport: i0, template: "<div nz-row class=\"formula-editor\">\n <div nz-col nzSpan=\"10\" class=\"editor-box\">\n <div class=\"editor-title\">\n <div class=\"label-title label-require\">\u5B57\u6BB5\u8868\u8FBE\u5F0F</div>\n <button nz-button nzType=\"link\" nzSize=\"small\" [nzLoading]=\"checkLoading\" (click)=\"checkFormat()\">\u683C\u5F0F\u6821\u9A8C</button>\n </div>\n <div class=\"edit-box\">\n <div class=\"edit-operation-box\">\n @for (item of _operatorList; track item) {\n <div class=\"edit-operation-item\" (click)=\"insertOperator(item.name)\">\n <span class=\"operator-icon\" [innerHTML]=\"getOperatorIcon(item.type)\"></span>\n </div>\n }\n </div>\n <div #editor class=\"editor\"></div>\n @if (editDescShow !== 'none') {\n <div class=\"edit-description-box\">\n <div class=\"edit-description-head\" [style]=\"{ marginBottom: !showDescription ? '10px' : '0' }\">\n <span class=\"edit-description-title\">{{ editDescShow === 'func' ? '\u51FD\u6570\u91CA\u4E49' : '\u6821\u9A8C\u9519\u8BEF\u4FE1\u606F' }}</span>\n <span\n class=\"edit-description-icon\"\n nz-icon\n [nzType]=\"showDescription ? 'down' : 'up'\"\n nzTheme=\"outline\"\n (click)=\"toggleDescription()\"\n ></span>\n </div>\n @if (showDescription) {\n <div class=\"edit-description-text\"> {{ editDescription }} </div>\n }\n </div>\n }\n </div>\n </div>\n @switch (itemType()) {\n @case (_itemType.Field) {\n <div nz-col nzSpan=\"7\" class=\"field-box\">\n <div class=\"label-title\"\n >{{ fieldTitle() }}\n @if (fieldDescription) {\n <span nz-icon nzType=\"question-circle\" nzTheme=\"outline\" nz-tooltip [nzTooltipTitle]=\"fieldDescription\"></span>\n }\n </div>\n <nz-input-group [nzSuffix]=\"suffixIconSearch\">\n <input type=\"text\" nz-input placeholder=\"\u641C\u7D22\" [(ngModel)]=\"searchField\" />\n </nz-input-group>\n <ng-template #suffixIconSearch>\n <i nz-icon nzType=\"search\"></i>\n </ng-template>\n <div class=\"field-list\">\n @for (item of fieldList; track item) {\n <div (click)=\"insertField(item)\" [class]=\"item.className ? 'item-field-' + item.className : ''\" class=\"field-list-item\">\n @if (item.image) {\n <img class=\"field-img\" [src]=\"item.image\" />\n }\n @if (item.icon) {\n <span class=\"field-icon\" nz-icon [nzIconfont]=\"item.icon\"></span>\n }\n <span class=\"field-name\" [innerHTML]=\"item.name | nzHighlight: searchField() : 'i' : 'node-highlight'\"></span>\n @if (item.description) {\n <span\n class=\"desc-icon\"\n nz-icon\n nzType=\"question-circle\"\n nzTheme=\"outline\"\n nz-tooltip\n [nzTooltipTitle]=\"item.description\"\n ></span>\n }\n </div>\n }\n </div>\n </div>\n }\n @case (_itemType.Template) {\n <div nz-col nzSpan=\"7\" class=\"field-box\">\n <ng-container *ngTemplateOutlet=\"itemTemplate()\"></ng-container>\n </div>\n }\n }\n <div nz-col nzSpan=\"7\" class=\"function-box\">\n <div class=\"label-title\">\u51FD\u6570</div>\n <nz-input-group [nzSuffix]=\"suffixIconSearch\">\n <input type=\"text\" nz-input placeholder=\"\u641C\u7D22\" [(ngModel)]=\"searchFunction\" />\n </nz-input-group>\n <ng-template #suffixIconSearch>\n <i nz-icon nzType=\"search\"></i>\n </ng-template>\n <nz-tree\n class=\"function-tree\"\n [nzHideUnMatched]=\"true\"\n [nzSearchValue]=\"searchFunction()\"\n [nzTreeTemplate]=\"nzTreeTemplate\"\n [nzData]=\"funcNode\"\n [nzShowExpand]=\"functionListType() === _funcListType.Tree\"\n (nzClick)=\"functionClick($event)\"\n ></nz-tree>\n <ng-template #nzTreeTemplate let-node let-origin=\"origin\">\n <span class=\"custom-node\" [innerHTML]=\"origin.name | nzHighlight: searchFunction() : 'i' : 'node-highlight'\">\n @if (origin.description) {\n <span\n class=\"desc-icon\"\n nz-icon\n nzType=\"question-circle\"\n nzTheme=\"outline\"\n nz-tooltip\n [nzTooltipTitle]=\"origin.description\"\n ></span>\n }\n </span>\n </ng-template>\n </div>\n</div>\n", styles: [":host ::ng-deep .ant-tree-node-content-wrapper{width:100%}:host ::ng-deep .ant-input-affix-wrapper{padding:0 11px;height:32px}:host ::ng-deep .ProseMirror-focused{outline:none}:host ::ng-deep .node-highlight{color:#ff4d4f!important}:host .total-box{width:1000px;padding:8px;background-color:#fff}:host .ant-input-affix-wrapper{width:100%}:host .desc-icon{margin-left:auto}:host .formula-editor .editor-box{padding:12px;border-right:1px solid #e8e8e8}:host .formula-editor .editor-box .editor-title{display:flex;justify-content:space-between}:host .formula-editor .editor-box .edit-box{display:flex;flex-direction:column;height:400px;border:1px solid #ccc;border-radius:2px}:host .formula-editor .editor-box .edit-box .edit-operation-box{display:flex;gap:10px;align-items:center;height:32px;padding:0 10px;border-bottom:1px solid #ccc}:host .formula-editor .editor-box .edit-box .edit-operation-box .edit-operation-item{width:16px;height:16px;color:#687488;font-size:16px;line-height:16px;cursor:pointer;-webkit-user-select:none;user-select:none}:host .formula-editor .editor-box .edit-box .edit-operation-box .edit-operation-item .operator-icon{pointer-events:none}:host .formula-editor .editor-box .edit-box .edit-operation-box .edit-operation-item:hover .operator-icon{fill:#2e87ff}:host .formula-editor .editor-box .edit-box .edit-description-box{display:flex;flex-direction:column;padding:10px 10px 0;border-top:1px solid #ccc}:host .formula-editor .editor-box .edit-box .edit-description-box .edit-description-head{display:flex;justify-content:space-between}:host .formula-editor .editor-box .edit-box .edit-description-box .edit-description-text{max-height:128px;overflow:hidden auto;color:#333;font-size:13px;white-space:pre-wrap}:host .formula-editor .editor-box .edit-box .edit-description-box .edit-description-title{color:#333;font-weight:700;font-size:14px}:host .formula-editor .editor-box .edit-box .edit-description-box .edit-description-icon{font-size:14px;cursor:pointer}:host .formula-editor .editor-box .edit-box .edit-description-box .edit-description-icon:hover{color:#2e87ff}:host .formula-editor .editor-box .edit-box .editor{flex:1;min-height:0;padding:12px 10px;overflow:auto;font-size:13px}:host .formula-editor .metric-box{padding:12px}:host .formula-editor .metric-box .metric-list{display:flex;flex-direction:column;gap:4px;height:360px;margin-top:12px;overflow-y:auto}:host .formula-editor .metric-box .metric-list-item{display:flex;align-items:center;height:32px;min-height:32px;padding:0 12px;border:1px solid #ccc;border-radius:2px;cursor:pointer}:host .formula-editor .metric-box .metric-list-item:hover{background:#2e87ff1a;border:1px solid #2e87ff;border-radius:2px}:host .formula-editor .metric-box .metric-list-item .metric-img{width:24px;height:24px;margin-right:8px}:host .formula-editor .metric-box .metric-list-item .metric-icon{margin-right:4px;font-size:16px}:host .formula-editor .metric-box .metric-list-item .metric-name{flex:1;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}:host .formula-editor .field-box{padding:12px}:host .formula-editor .field-box .field-list{display:flex;flex-direction:column;gap:4px;height:360px;margin-top:12px;overflow-y:auto}:host .formula-editor .field-box .field-list-item{display:flex;flex-direction:row;align-items:center;height:32px;min-height:32px;padding:0 10px;border:1px solid #ccc;border-radius:2px;cursor:pointer}:host .formula-editor .field-box .field-list-item:hover{background:#2e87ff1a;border:1px solid #2e87ff;border-radius:2px}:host .formula-editor .field-box .field-list-item .field-img{width:24px;height:24px;margin-right:4px}:host .formula-editor .field-box .field-list-item .field-icon{margin-right:4px;font-size:16px}:host .formula-editor .field-box .field-list-item .field-name{flex:1;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}:host .formula-editor .function-box{padding:12px;border-left:1px solid #e8e8e8}:host .formula-editor .function-box .function-tree{height:360px;margin-top:8px;overflow-y:auto}:host .formula-editor .function-box .function-tree .custom-node{display:flex;align-items:center}:host .formula-editor .label-title{margin-bottom:12px;color:#333;font-weight:400;font-size:14px}:host .formula-editor .label-require:after{margin-left:4px;color:#f5222d;content:\"*\"}\n"], dependencies: [{ kind: "directive", type: NzColDirective, selector: "[nz-col],nz-col,nz-form-control,nz-form-label", inputs: ["nzFlex", "nzSpan", "nzOrder", "nzOffset", "nzPush", "nzPull", "nzXs", "nzSm", "nzMd", "nzLg", "nzXl", "nzXXl"], exportAs: ["nzCol"] }, { kind: "ngmodule", type: NzGridModule }, { kind: "directive", type: i1.NzRowDirective, selector: "[nz-row],nz-row,nz-form-item", inputs: ["nzAlign", "nzJustify", "nzGutter"], exportAs: ["nzRow"] }, { kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i2.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }, { kind: "component", type: i2.NzInputGroupComponent, selector: "nz-input-group", inputs: ["nzAddOnBeforeIcon", "nzAddOnAfterIcon", "nzPrefixIcon", "nzSuffixIcon", "nzAddOnBefore", "nzAddOnAfter", "nzPrefix", "nzStatus", "nzSuffix", "nzSize", "nzSearch", "nzCompact"], exportAs: ["nzInputGroup"] }, { kind: "directive", type: i2.NzInputGroupWhitSuffixOrPrefixDirective, selector: "nz-input-group[nzSuffix], nz-input-group[nzPrefix]" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: NzIconModule }, { kind: "directive", type: i4.NzIconDirective, selector: "[nz-icon]", inputs: ["nzSpin", "nzRotate", "nzType", "nzTheme", "nzTwotoneColor", "nzIconfont"], exportAs: ["nzIcon"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i5.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: NzTreeModule }, { kind: "component", type: i6.NzTreeComponent, selector: "nz-tree", inputs: ["nzShowIcon", "nzHideUnMatched", "nzBlockNode", "nzExpandAll", "nzSelectMode", "nzCheckStrictly", "nzShowExpand", "nzShowLine", "nzCheckable", "nzAsyncData", "nzDraggable", "nzMultiple", "nzExpandedIcon", "nzVirtualItemSize", "nzVirtualMaxBufferPx", "nzVirtualMinBufferPx", "nzVirtualHeight", "nzTreeTemplate", "nzBeforeDrop", "nzData", "nzExpandedKeys", "nzSelectedKeys", "nzCheckedKeys", "nzSearchValue", "nzSearchFunc"], outputs: ["nzExpandedKeysChange", "nzSelectedKeysChange", "nzCheckedKeysChange", "nzSearchValueChange", "nzClick", "nzDblClick", "nzContextMenu", "nzCheckBoxChange", "nzExpandChange", "nzOnDragStart", "nzOnDragEnter", "nzOnDragOver", "nzOnDragLeave", "nzOnDrop", "nzOnDragEnd"], exportAs: ["nzTree"] }, { kind: "ngmodule", type: NzButtonModule }, { kind: "component", type: i7.NzButtonComponent, selector: "button[nz-button], a[nz-button]", inputs: ["nzBlock", "nzGhost", "nzSearch", "nzLoading", "nzDanger", "disabled", "tabIndex", "nzType", "nzShape", "nzSize"], exportAs: ["nzButton"] }, { kind: "directive", type: i8.ɵNzTransitionPatchDirective, selector: "[nz-button], nz-button-group, [nz-icon], [nz-menu-item], [nz-submenu], nz-select-top-control, nz-select-placeholder, nz-input-group", inputs: ["hidden"] }, { kind: "ngmodule", type: NzToolTipModule }, { kind: "directive", type: i9.NzTooltipDirective, selector: "[nz-tooltip]", inputs: ["nzTooltipTitle", "nzTooltipTitleContext", "nz-tooltip", "nzTooltipTrigger", "nzTooltipPlacement", "nzTooltipOrigin", "nzTooltipVisible", "nzTooltipMouseEnterDelay", "nzTooltipMouseLeaveDelay", "nzTooltipOverlayClassName", "nzTooltipOverlayStyle", "nzTooltipArrowPointAtCenter", "cdkConnectedOverlayPush", "nzTooltipColor"], outputs: ["nzTooltipVisibleChange"], exportAs: ["nzTooltip"] }, { kind: "pipe", type: NzHighlightPipe, name: "nzHighlight" }] }); }
|
|
674
|
+
}
|
|
675
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FormulaEditorComponent, decorators: [{
|
|
676
|
+
type: Component,
|
|
677
|
+
args: [{ selector: 'app-formula-editor', standalone: true, imports: [
|
|
678
|
+
NzColDirective,
|
|
679
|
+
NzGridModule,
|
|
680
|
+
NzInputModule,
|
|
681
|
+
FormsModule,
|
|
682
|
+
NzIconModule,
|
|
683
|
+
CommonModule,
|
|
684
|
+
NzTreeModule,
|
|
685
|
+
NzButtonModule,
|
|
686
|
+
NzToolTipModule,
|
|
687
|
+
NzHighlightPipe
|
|
688
|
+
], template: "<div nz-row class=\"formula-editor\">\n <div nz-col nzSpan=\"10\" class=\"editor-box\">\n <div class=\"editor-title\">\n <div class=\"label-title label-require\">\u5B57\u6BB5\u8868\u8FBE\u5F0F</div>\n <button nz-button nzType=\"link\" nzSize=\"small\" [nzLoading]=\"checkLoading\" (click)=\"checkFormat()\">\u683C\u5F0F\u6821\u9A8C</button>\n </div>\n <div class=\"edit-box\">\n <div class=\"edit-operation-box\">\n @for (item of _operatorList; track item) {\n <div class=\"edit-operation-item\" (click)=\"insertOperator(item.name)\">\n <span class=\"operator-icon\" [innerHTML]=\"getOperatorIcon(item.type)\"></span>\n </div>\n }\n </div>\n <div #editor class=\"editor\"></div>\n @if (editDescShow !== 'none') {\n <div class=\"edit-description-box\">\n <div class=\"edit-description-head\" [style]=\"{ marginBottom: !showDescription ? '10px' : '0' }\">\n <span class=\"edit-description-title\">{{ editDescShow === 'func' ? '\u51FD\u6570\u91CA\u4E49' : '\u6821\u9A8C\u9519\u8BEF\u4FE1\u606F' }}</span>\n <span\n class=\"edit-description-icon\"\n nz-icon\n [nzType]=\"showDescription ? 'down' : 'up'\"\n nzTheme=\"outline\"\n (click)=\"toggleDescription()\"\n ></span>\n </div>\n @if (showDescription) {\n <div class=\"edit-description-text\"> {{ editDescription }} </div>\n }\n </div>\n }\n </div>\n </div>\n @switch (itemType()) {\n @case (_itemType.Field) {\n <div nz-col nzSpan=\"7\" class=\"field-box\">\n <div class=\"label-title\"\n >{{ fieldTitle() }}\n @if (fieldDescription) {\n <span nz-icon nzType=\"question-circle\" nzTheme=\"outline\" nz-tooltip [nzTooltipTitle]=\"fieldDescription\"></span>\n }\n </div>\n <nz-input-group [nzSuffix]=\"suffixIconSearch\">\n <input type=\"text\" nz-input placeholder=\"\u641C\u7D22\" [(ngModel)]=\"searchField\" />\n </nz-input-group>\n <ng-template #suffixIconSearch>\n <i nz-icon nzType=\"search\"></i>\n </ng-template>\n <div class=\"field-list\">\n @for (item of fieldList; track item) {\n <div (click)=\"insertField(item)\" [class]=\"item.className ? 'item-field-' + item.className : ''\" class=\"field-list-item\">\n @if (item.image) {\n <img class=\"field-img\" [src]=\"item.image\" />\n }\n @if (item.icon) {\n <span class=\"field-icon\" nz-icon [nzIconfont]=\"item.icon\"></span>\n }\n <span class=\"field-name\" [innerHTML]=\"item.name | nzHighlight: searchField() : 'i' : 'node-highlight'\"></span>\n @if (item.description) {\n <span\n class=\"desc-icon\"\n nz-icon\n nzType=\"question-circle\"\n nzTheme=\"outline\"\n nz-tooltip\n [nzTooltipTitle]=\"item.description\"\n ></span>\n }\n </div>\n }\n </div>\n </div>\n }\n @case (_itemType.Template) {\n <div nz-col nzSpan=\"7\" class=\"field-box\">\n <ng-container *ngTemplateOutlet=\"itemTemplate()\"></ng-container>\n </div>\n }\n }\n <div nz-col nzSpan=\"7\" class=\"function-box\">\n <div class=\"label-title\">\u51FD\u6570</div>\n <nz-input-group [nzSuffix]=\"suffixIconSearch\">\n <input type=\"text\" nz-input placeholder=\"\u641C\u7D22\" [(ngModel)]=\"searchFunction\" />\n </nz-input-group>\n <ng-template #suffixIconSearch>\n <i nz-icon nzType=\"search\"></i>\n </ng-template>\n <nz-tree\n class=\"function-tree\"\n [nzHideUnMatched]=\"true\"\n [nzSearchValue]=\"searchFunction()\"\n [nzTreeTemplate]=\"nzTreeTemplate\"\n [nzData]=\"funcNode\"\n [nzShowExpand]=\"functionListType() === _funcListType.Tree\"\n (nzClick)=\"functionClick($event)\"\n ></nz-tree>\n <ng-template #nzTreeTemplate let-node let-origin=\"origin\">\n <span class=\"custom-node\" [innerHTML]=\"origin.name | nzHighlight: searchFunction() : 'i' : 'node-highlight'\">\n @if (origin.description) {\n <span\n class=\"desc-icon\"\n nz-icon\n nzType=\"question-circle\"\n nzTheme=\"outline\"\n nz-tooltip\n [nzTooltipTitle]=\"origin.description\"\n ></span>\n }\n </span>\n </ng-template>\n </div>\n</div>\n", styles: [":host ::ng-deep .ant-tree-node-content-wrapper{width:100%}:host ::ng-deep .ant-input-affix-wrapper{padding:0 11px;height:32px}:host ::ng-deep .ProseMirror-focused{outline:none}:host ::ng-deep .node-highlight{color:#ff4d4f!important}:host .total-box{width:1000px;padding:8px;background-color:#fff}:host .ant-input-affix-wrapper{width:100%}:host .desc-icon{margin-left:auto}:host .formula-editor .editor-box{padding:12px;border-right:1px solid #e8e8e8}:host .formula-editor .editor-box .editor-title{display:flex;justify-content:space-between}:host .formula-editor .editor-box .edit-box{display:flex;flex-direction:column;height:400px;border:1px solid #ccc;border-radius:2px}:host .formula-editor .editor-box .edit-box .edit-operation-box{display:flex;gap:10px;align-items:center;height:32px;padding:0 10px;border-bottom:1px solid #ccc}:host .formula-editor .editor-box .edit-box .edit-operation-box .edit-operation-item{width:16px;height:16px;color:#687488;font-size:16px;line-height:16px;cursor:pointer;-webkit-user-select:none;user-select:none}:host .formula-editor .editor-box .edit-box .edit-operation-box .edit-operation-item .operator-icon{pointer-events:none}:host .formula-editor .editor-box .edit-box .edit-operation-box .edit-operation-item:hover .operator-icon{fill:#2e87ff}:host .formula-editor .editor-box .edit-box .edit-description-box{display:flex;flex-direction:column;padding:10px 10px 0;border-top:1px solid #ccc}:host .formula-editor .editor-box .edit-box .edit-description-box .edit-description-head{display:flex;justify-content:space-between}:host .formula-editor .editor-box .edit-box .edit-description-box .edit-description-text{max-height:128px;overflow:hidden auto;color:#333;font-size:13px;white-space:pre-wrap}:host .formula-editor .editor-box .edit-box .edit-description-box .edit-description-title{color:#333;font-weight:700;font-size:14px}:host .formula-editor .editor-box .edit-box .edit-description-box .edit-description-icon{font-size:14px;cursor:pointer}:host .formula-editor .editor-box .edit-box .edit-description-box .edit-description-icon:hover{color:#2e87ff}:host .formula-editor .editor-box .edit-box .editor{flex:1;min-height:0;padding:12px 10px;overflow:auto;font-size:13px}:host .formula-editor .metric-box{padding:12px}:host .formula-editor .metric-box .metric-list{display:flex;flex-direction:column;gap:4px;height:360px;margin-top:12px;overflow-y:auto}:host .formula-editor .metric-box .metric-list-item{display:flex;align-items:center;height:32px;min-height:32px;padding:0 12px;border:1px solid #ccc;border-radius:2px;cursor:pointer}:host .formula-editor .metric-box .metric-list-item:hover{background:#2e87ff1a;border:1px solid #2e87ff;border-radius:2px}:host .formula-editor .metric-box .metric-list-item .metric-img{width:24px;height:24px;margin-right:8px}:host .formula-editor .metric-box .metric-list-item .metric-icon{margin-right:4px;font-size:16px}:host .formula-editor .metric-box .metric-list-item .metric-name{flex:1;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}:host .formula-editor .field-box{padding:12px}:host .formula-editor .field-box .field-list{display:flex;flex-direction:column;gap:4px;height:360px;margin-top:12px;overflow-y:auto}:host .formula-editor .field-box .field-list-item{display:flex;flex-direction:row;align-items:center;height:32px;min-height:32px;padding:0 10px;border:1px solid #ccc;border-radius:2px;cursor:pointer}:host .formula-editor .field-box .field-list-item:hover{background:#2e87ff1a;border:1px solid #2e87ff;border-radius:2px}:host .formula-editor .field-box .field-list-item .field-img{width:24px;height:24px;margin-right:4px}:host .formula-editor .field-box .field-list-item .field-icon{margin-right:4px;font-size:16px}:host .formula-editor .field-box .field-list-item .field-name{flex:1;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}:host .formula-editor .function-box{padding:12px;border-left:1px solid #e8e8e8}:host .formula-editor .function-box .function-tree{height:360px;margin-top:8px;overflow-y:auto}:host .formula-editor .function-box .function-tree .custom-node{display:flex;align-items:center}:host .formula-editor .label-title{margin-bottom:12px;color:#333;font-weight:400;font-size:14px}:host .formula-editor .label-require:after{margin-left:4px;color:#f5222d;content:\"*\"}\n"] }]
|
|
689
|
+
}], ctorParameters: () => [], propDecorators: { editor: [{
|
|
690
|
+
type: ViewChild,
|
|
691
|
+
args: ['editor']
|
|
692
|
+
}] } });
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Generated bundle index. Do not edit.
|
|
696
|
+
*/
|
|
697
|
+
|
|
698
|
+
export { CheckFormatMethod, FormulaEditorComponent, FormulaEditorRequestService, FormulaEditorRequestServiceToken, FormulaEditorService, FuncListType, ICONS, ItemType, OPERATOR_LIST };
|
|
699
|
+
//# sourceMappingURL=formula-editor.mjs.map
|