formula-editor-vue3
Version:
codemirror formula editor for vue3
412 lines (411 loc) • 14.9 kB
JavaScript
var B = Object.defineProperty;
var S = (e, a, t) => a in e ? B(e, a, { enumerable: !0, configurable: !0, writable: !0, value: t }) : e[a] = t;
var v = (e, a, t) => S(e, typeof a != "symbol" ? a + "" : a, t);
import { defineComponent, mergeModels, ref, computed, useModel, shallowRef, watch, createElementBlock, openBlock, createCommentVNode, createElementVNode, toDisplayString, unref, normalizeClass, createVNode, normalizeStyle, renderSlot, Fragment, renderList } from "vue";
import { javascript } from "@codemirror/lang-javascript";
import { oneDark } from "@codemirror/theme-one-dark";
import { pickedCompletion, closeBrackets, autocompletion } from "@codemirror/autocomplete";
import { Codemirror } from "vue-codemirror";
import { MatchDecorator, Decoration, ViewPlugin, EditorView, WidgetType } from "@codemirror/view";
import { mean, max, min, sum } from "lodash-es";
/**
* formula-editor-vue3 - 0.0.4
* (c) gkf442573575@163.com©2024
* @license MIT
*/
class PlaceholderWidget extends WidgetType {
constructor(t, u) {
super();
v(this, "name");
v(this, "category");
this.name = t, this.category = u;
}
eq(t) {
return this.name == t.name && this.category == t.category;
}
toDOM() {
const t = document.createElement("span");
return t.className = `cm-widget-placeholder cm-widget-placeholder__${this.category}`, t.textContent = this.name, t;
}
ignoreEvent() {
return !1;
}
}
const createMathPlaceholder = (e, a, t) => {
const u = new MatchDecorator({
regexp: e,
//支持中文
decoration: (c) => {
const i = a(c);
return Decoration.replace({
widget: new PlaceholderWidget(i, t || "widget")
});
}
});
return ViewPlugin.fromClass(
class {
constructor(c) {
v(this, "placeholders");
this.placeholders = u.createDeco(c);
}
update(c) {
this.placeholders = u.updateDeco(c, this.placeholders);
}
},
{
decorations: (c) => c.placeholders,
provide: (c) => EditorView.atomicRanges.of((i) => {
var o;
return ((o = i.plugin(c)) == null ? void 0 : o.placeholders) || Decoration.none;
})
}
);
}, FORMULA_MATHS = [
{
name: "ABS",
handler: (e) => isNaN(Number(e)) ? "" : Math.abs(e),
desc: "ABS函数可以获取一个数的绝对值",
usage: "ABS(数字)",
example: "ABS(-8) 可以返回8,也就是-8的绝对值"
},
{
name: "AVERAGE",
handler: (...e) => mean(e.map((t) => isNaN(Number(t)) ? 0 : Number(t))) || "",
desc: "AVERAGE函数可以获取一组数值的算术平均值",
usage: "AVERAGE(数字1, 数字2, ...)",
example: "AVERAGE(语文成绩, 数学成绩, 英语成绩) 返回三门课程的平均分"
},
{
name: "FIXED",
handler: (e, a) => isNaN(Number(e)) ? "" : parseFloat(e).toFixed(a || 0),
desc: "FIXED函数可将数字舍入到指定的小数位数并输出为文本",
usage: "FIXED(数字, 小数位数)",
example: 'FIXED(3.1415, 2) 返回"3.14"'
},
{
name: "MAX",
handler: (...e) => max(e.map((t) => isNaN(Number(t)) ? 0 : Number(t))) || "",
desc: "MAX函数可以获取一组数值的最大值",
usage: "MAX(数字1, 数字2, ...)",
example: "MAX(语文成绩, 数学成绩, 英语成绩) 返回三门课程中的最高分"
},
{
name: "MIN",
handler: (...e) => min(e.map((t) => isNaN(Number(t)) ? 0 : Number(t))) || "",
desc: "MIN函数可以获取一组数值的最小值",
usage: "MIN(数字1, 数字2, ...)",
example: "MIN(语文成绩, 数学成绩, 英语成绩)返回三门课程中的最低分"
},
{
name: "RAND",
handler: () => Math.random(),
desc: "RAND函数可返回大于等于0且小于1的均匀分布随机实数",
usage: "RAND()",
example: "RAND() 返回0.424656"
},
{
name: "SQRT",
handler: (e) => isNaN(Number(e)) ? "" : Math.sqrt(e),
desc: "SQRT函数可以获取一个数字的正平方根",
usage: "SQRT(数字)",
example: "SQRT(9) 返回3,也就是9的正平方根"
},
{
name: "SUM",
handler: (...e) => sum(e.map((t) => isNaN(Number(t)) ? 0 : Number(t))) || "",
desc: "SUM函数可以获取一组数值的总和",
usage: "SUM(数字1, 数字2, ...)",
example: "SUM(语文成绩, 数学成绩, 英语成绩) 返回三门课程的总分"
},
{
name: "LOWER",
handler: (e) => e && typeof e == "string" ? e.toLowerCase() : "",
desc: "LOWER函数可以将一个文本中的所有大写字母转换为小写字母",
usage: "LOWER(文本)",
example: 'LOWER("QWER") 返回"qwer"'
},
{
name: "UPPER",
handler: (e) => e && typeof e == "string" ? e.toUpperCase() : "",
desc: "UPPER函数可以将一个文本中的所有小写字母转换为大写字母",
usage: "UPPER(文本)",
example: 'UPPER("qwer") 返回"QWER"'
},
{
name: "REPEAT",
handler: (e, a) => (a = isNaN(parseInt(a)) ? 0 : Math.abs(parseInt(a)), e && typeof e == "string" ? e.repeat(a) : ""),
desc: "REPT函数可以将文本重复一定次数",
usage: "REPT(文本, 重复次数)",
example: 'REPT("TEST", 3) 返回"TESTTESTTEST"'
},
{
name: "AND",
handler: (...e) => e.every((a) => !!a),
desc: "如果所有参数都为真,AND 函数返回布尔值 true,否则返回布尔值 false",
usage: "AND(逻辑表达式1, 逻辑表达式2, ...)",
example: "AND(语文成绩>90, 数学成绩>90, 英语成绩>90),如果三门课成绩都> 90,返回true,否则返回false"
},
{
name: "OR",
handler: (...e) => e.some((a) => !!a),
desc: "如果任意参数为真,OR 函数返回布尔值 true;如果所有参数为假,返回布尔值 false。",
usage: "OR(逻辑表达式1, 逻辑表达式2, ...)",
example: "OR(语文成绩>90, 数学成绩>90, 英语成绩>90),任何一门课成绩>90,返回true,否则返回false"
},
{
name: "NOT",
handler: (e) => !e,
desc: "NOT函数返回与指定表达式相反的布尔值。",
usage: "NOT(逻辑表达式)",
example: "NOT(语文成绩>60),如果语文成绩大于60返回false,否则返回true"
},
{
name: "IF",
handler: (e, a, t) => e ? a ?? "" : t ?? "",
desc: "IF函数判断一个条件能否满足;如果满足返回一个值,如果不满足则返回另外一个值",
usage: "IF(逻辑表达式, 为true时返回的值, 为false时返回的值)",
example: 'IF(语文成绩>60, "及格", "不及格"),当语文成绩>60时返回及格,否则返回不及格。'
}
], uuid = () => "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(e) {
var a = Math.random() * 16 | 0, t = e == "x" ? a : a & 3 | 8;
return t.toString(16);
}), isStrBrackets = (e) => /[\'\"]{1}/.test(e), VARIABLE_REG = /\$\{(.*?)\}/g, isCloseBrackets = (e) => {
const a = [
["{", "}"],
["[", "]"],
["(", ")"]
], t = e.split("");
if (!t.length)
return !0;
const u = {}, h = [], c = [];
for (let o = 0; o < a.length; o++) {
const [n, m] = a[o];
u[n] = o + 1, u[m] = -(o + 1), h.push(n), c.push(m);
}
const i = [];
for (let o = 0; o < t.length; o++) {
const n = t[o], m = u[n];
if (m && !(o > 0 && isStrBrackets(t[o - 1]) && isStrBrackets(t[o + 1]))) {
if (h.includes(n))
i.push(m);
else if (c.includes(n)) {
const f = i.length;
f > 0 && i[f - 1] + m === 0 ? i.pop() : i.push(m);
}
}
}
return i.length === 0;
}, evalFormula = (formula, variableHandler, variableData, mathList = FORMULA_MATHS) => {
const isPass = isCloseBrackets(formula);
if (!isPass)
throw new Error("括号不匹配");
const formulaMaths = {}, nameList = mathList.map((e) => {
const { name: a, handler: t } = e;
return formulaMaths[a] = t, a;
}), MATH_REG = new RegExp(`${nameList.join("|")}`, "g");
try {
const mock_variable = variableData && variableData[uuid()], mock_math = formulaMaths && formulaMaths[uuid()];
let evalFormula = formula.replace(VARIABLE_REG, (e, a) => "variableData" + variableHandler(a));
evalFormula = evalFormula.replace(MATH_REG, (e) => `formulaMaths['${e}']`);
const result = eval(evalFormula);
return result;
} catch (e) {
throw e;
}
}, _hoisted_1 = { class: "formula-editor" }, _hoisted_2 = {
key: 0,
class: "formula-editor-title"
}, _hoisted_3 = { class: "formula-params" }, _hoisted_4 = { class: "formula-params-variable formula-params-list" }, _hoisted_5 = ["onClick"], _hoisted_6 = { class: "variable-item-label" }, _hoisted_7 = {
key: 0,
class: "variable-item-desc"
}, _hoisted_8 = { class: "formula-params-maths formula-params-list" }, _hoisted_9 = ["onClick", "onMouseenter"], _hoisted_10 = {
key: 0,
class: "formula-maths-desc formula-params-list"
}, _hoisted_11 = {
key: 0,
class: "math-desc"
}, _hoisted_12 = {
key: 1,
class: "math-usage"
}, _hoisted_13 = {
key: 2,
class: "math-example"
}, _sfc_main = /* @__PURE__ */ defineComponent({
name: "FormulaEditor",
__name: "index",
props: /* @__PURE__ */ mergeModels({
title: {
type: String,
default: ""
},
isDark: {
type: Boolean,
default: !1
},
disabled: {
type: Boolean,
default: !1
},
height: {
type: Number,
default: 200
},
placeholder: {
type: String,
default: ""
},
// 变量列表
variables: {
type: Array,
default: () => []
},
// 支持的math方法
mathList: {
type: Array,
default: () => FORMULA_MATHS
}
}, {
modelValue: { default: "" },
modelModifiers: {}
}),
emits: ["update:modelValue"],
setup(e, { expose: a }) {
const t = e, u = uuid(), h = ref(0), c = computed(() => t.variables.map((r) => r.value)), i = useModel(e, "modelValue"), o = shallowRef(), n = ref(t.mathList[0]), m = computed(() => t.mathList.map((r) => r.name)), f = computed(() => new RegExp(`${m.value.join("|")}`, "g")), _ = createMathPlaceholder(
VARIABLE_REG,
(r) => {
const s = r[1], l = t.variables.find((d) => d.value === s);
return l && l.label || "不可用字段";
},
"variable"
), b = createMathPlaceholder(
f.value,
(r) => r[0],
"mathfunc"
), R = t.mathList.map((r) => ({
label: r.name,
type: "keyword",
apply: (s, l, d, k) => {
var p;
s.dispatch({
changes: {
from: d,
to: k,
insert: `${l.label}()`
}
});
let g = ((p = o.value) == null ? void 0 : p.state.selection.main.head) || 0;
s.dispatch({
selection: { anchor: g - 1 },
annotations: pickedCompletion.of(l)
});
}
})), y = (r) => {
let s = r.matchBefore(/\w+/);
return !r.explicit && !s ? null : {
from: s ? s.from : r.pos,
options: [...R],
validFor: /^\w*$/
};
}, M = computed(() => {
const r = [
javascript(),
closeBrackets(),
autocompletion({ override: [y] }),
_,
b
];
return t.isDark && r.push(oneDark), r;
}), N = (r, s, l = !1) => {
if (!o.value)
return;
const { from: d, to: k } = o.value.state.selection.main, g = s(r), p = g.length;
o.value.dispatch({
changes: {
from: d,
to: k,
insert: g
},
selection: {
anchor: d + (l ? p - 1 : p)
}
}), o.value.focus();
}, E = (r) => {
N(r, (s) => "${" + s + "}");
}, x = (r) => {
N(r, (s) => `${s}()`, !0);
}, A = (r) => {
o.value = r.view;
};
return watch(
() => c.value,
() => {
h.value++;
},
{
deep: !0,
immediate: !0
}
), a({
insertMaths: x,
insertVariables: E
}), (r, s) => (openBlock(), createElementBlock("div", _hoisted_1, [
e.title ? (openBlock(), createElementBlock("div", _hoisted_2, toDisplayString(e.title) + " =", 1)) : createCommentVNode("", !0),
(openBlock(), createElementBlock("div", {
class: normalizeClass(["formula-editor-container", { hasTitle: e.title }]),
key: `${unref(u)}_${h.value}`
}, [
createVNode(unref(Codemirror), {
modelValue: i.value,
"onUpdate:modelValue": s[0] || (s[0] = (l) => i.value = l),
class: "formula-editor-codemirror",
style: normalizeStyle({ height: `${e.height}px`, minHeight: "200px" }),
autofocus: !0,
"indent-with-tab": !0,
"tab-size": 2,
extensions: M.value,
disabled: e.disabled,
placeholder: e.placeholder || "请输入...",
onReady: A
}, null, 8, ["modelValue", "style", "extensions", "disabled", "placeholder"])
], 2)),
createElementVNode("div", _hoisted_3, [
renderSlot(r.$slots, "variable", { insert: E }, () => [
createElementVNode("ul", _hoisted_4, [
(openBlock(!0), createElementBlock(Fragment, null, renderList(e.variables, (l) => (openBlock(), createElementBlock("li", {
class: "variable-item formula-params-item",
key: l.value,
onClick: (d) => E(l.value)
}, [
createElementVNode("span", _hoisted_6, toDisplayString(l.label), 1),
l.desc ? (openBlock(), createElementBlock("span", _hoisted_7, toDisplayString(l.desc), 1)) : createCommentVNode("", !0)
], 8, _hoisted_5))), 128))
])
]),
renderSlot(r.$slots, "math", { insert: x }, () => [
createElementVNode("ul", _hoisted_8, [
(openBlock(!0), createElementBlock(Fragment, null, renderList(e.mathList, (l) => (openBlock(), createElementBlock("li", {
class: "maths-item formula-params-item",
key: l.name,
onClick: (d) => x(l.name),
onMouseenter: (d) => n.value = l
}, toDisplayString(l.name), 41, _hoisted_9))), 128))
]),
n.value ? (openBlock(), createElementBlock("div", _hoisted_10, [
n.value.desc ? (openBlock(), createElementBlock("p", _hoisted_11, toDisplayString(n.value.desc), 1)) : createCommentVNode("", !0),
n.value.usage ? (openBlock(), createElementBlock("p", _hoisted_12, "用法:" + toDisplayString(n.value.usage), 1)) : createCommentVNode("", !0),
n.value.example ? (openBlock(), createElementBlock("p", _hoisted_13, "示例:" + toDisplayString(n.value.example), 1)) : createCommentVNode("", !0)
])) : createCommentVNode("", !0)
])
])
]));
}
}), withInstall = (e) => (e.install = (a) => {
a.component(e.name, e);
}, e), FormulaEditor = withInstall(_sfc_main);
export {
FORMULA_MATHS,
FormulaEditor,
FormulaEditor as default,
evalFormula,
isCloseBrackets
};