UNPKG

formula-editor-vue3

Version:
412 lines (411 loc) 14.9 kB
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 };