@robot-admin/naive-ui-components 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +257 -0
- package/dist/C_ActionBar-DWN-woTc.css.map +1 -0
- package/dist/C_ActionBar.cjs +5 -0
- package/dist/C_ActionBar.d.cts +2 -0
- package/dist/C_ActionBar.d.ts +2 -0
- package/dist/C_ActionBar.js +4 -0
- package/dist/C_ActionBar2.js +196 -0
- package/dist/C_ActionBar2.js.map +1 -0
- package/dist/C_AntV-AFKyK6hH.css.map +1 -0
- package/dist/C_AntV.cjs +8 -0
- package/dist/C_AntV.d.cts +2 -0
- package/dist/C_AntV.d.ts +2 -0
- package/dist/C_AntV.js +4 -0
- package/dist/C_AntV2.js +3150 -0
- package/dist/C_AntV2.js.map +1 -0
- package/dist/C_Barcode-P_EFj8dC.css.map +1 -0
- package/dist/C_Barcode.cjs +4 -0
- package/dist/C_Barcode.d.cts +2 -0
- package/dist/C_Barcode.d.ts +2 -0
- package/dist/C_Barcode.js +3 -0
- package/dist/C_Barcode2.js +68 -0
- package/dist/C_Barcode2.js.map +1 -0
- package/dist/C_Captcha-C-ef41xw.css.map +1 -0
- package/dist/C_Captcha.cjs +4 -0
- package/dist/C_Captcha.d.cts +2 -0
- package/dist/C_Captcha.d.ts +2 -0
- package/dist/C_Captcha.js +3 -0
- package/dist/C_Captcha2.js +155 -0
- package/dist/C_Captcha2.js.map +1 -0
- package/dist/C_Cascade-D9kNsjsV.css.map +1 -0
- package/dist/C_Cascade.cjs +4 -0
- package/dist/C_Cascade.d.cts +2 -0
- package/dist/C_Cascade.d.ts +2 -0
- package/dist/C_Cascade.js +3 -0
- package/dist/C_Cascade2.js +103 -0
- package/dist/C_Cascade2.js.map +1 -0
- package/dist/C_City-BCQ4ipiK.css.map +1 -0
- package/dist/C_City.cjs +4 -0
- package/dist/C_City.d.cts +2 -0
- package/dist/C_City.d.ts +2 -0
- package/dist/C_City.js +3 -0
- package/dist/C_City2.js +841 -0
- package/dist/C_City2.js.map +1 -0
- package/dist/C_Code-C9kvvEmO.css.map +1 -0
- package/dist/C_Code.cjs +5 -0
- package/dist/C_Code.d.cts +2 -0
- package/dist/C_Code.d.ts +2 -0
- package/dist/C_Code.js +4 -0
- package/dist/C_Code2.js +346 -0
- package/dist/C_Code2.js.map +1 -0
- package/dist/C_CollapsePanel-BUJHuYcU.css.map +1 -0
- package/dist/C_CollapsePanel.cjs +6 -0
- package/dist/C_CollapsePanel.d.cts +2 -0
- package/dist/C_CollapsePanel.d.ts +2 -0
- package/dist/C_CollapsePanel.js +4 -0
- package/dist/C_CollapsePanel2.js +319 -0
- package/dist/C_CollapsePanel2.js.map +1 -0
- package/dist/C_Cron-yx2Ob4Jl.css.map +1 -0
- package/dist/C_Cron.cjs +15 -0
- package/dist/C_Cron.d.cts +2 -0
- package/dist/C_Cron.d.ts +2 -0
- package/dist/C_Cron.js +4 -0
- package/dist/C_Cron2.js +1209 -0
- package/dist/C_Cron2.js.map +1 -0
- package/dist/C_Date.cjs +4 -0
- package/dist/C_Date.d.cts +2 -0
- package/dist/C_Date.d.ts +2 -0
- package/dist/C_Date.js +3 -0
- package/dist/C_Date2.js +219 -0
- package/dist/C_Date2.js.map +1 -0
- package/dist/C_Draggable-C483syRC.css.map +1 -0
- package/dist/C_Draggable.cjs +5 -0
- package/dist/C_Draggable.d.cts +2 -0
- package/dist/C_Draggable.d.ts +2 -0
- package/dist/C_Draggable.js +3 -0
- package/dist/C_Draggable2.js +295 -0
- package/dist/C_Draggable2.js.map +1 -0
- package/dist/C_Editor-Bp0SyIEw.css.map +1 -0
- package/dist/C_Editor.cjs +4 -0
- package/dist/C_Editor.d.cts +2 -0
- package/dist/C_Editor.d.ts +2 -0
- package/dist/C_Editor.js +3 -0
- package/dist/C_Editor2.js +160 -0
- package/dist/C_Editor2.js.map +1 -0
- package/dist/C_FilePreview-CPqvhoCy.css.map +1 -0
- package/dist/C_FilePreview.cjs +6 -0
- package/dist/C_FilePreview.d.cts +2 -0
- package/dist/C_FilePreview.d.ts +2 -0
- package/dist/C_FilePreview.js +3 -0
- package/dist/C_FilePreview2.js +1031 -0
- package/dist/C_FilePreview2.js.map +1 -0
- package/dist/C_Form-Jx7PY3sT.css.map +1 -0
- package/dist/C_Form.cjs +15 -0
- package/dist/C_Form.d.cts +2 -0
- package/dist/C_Form.d.ts +2 -0
- package/dist/C_Form.js +4 -0
- package/dist/C_Form2.js +2510 -0
- package/dist/C_Form2.js.map +1 -0
- package/dist/C_FormSearch-DvRgxlRn.css.map +1 -0
- package/dist/C_FormSearch.cjs +6 -0
- package/dist/C_FormSearch.d.cts +2 -0
- package/dist/C_FormSearch.d.ts +2 -0
- package/dist/C_FormSearch.js +3 -0
- package/dist/C_FormSearch2.js +356 -0
- package/dist/C_FormSearch2.js.map +1 -0
- package/dist/C_FormulaEditor-DtGkt4T_.css.map +1 -0
- package/dist/C_FormulaEditor.cjs +13 -0
- package/dist/C_FormulaEditor.d.cts +2 -0
- package/dist/C_FormulaEditor.d.ts +2 -0
- package/dist/C_FormulaEditor.js +4 -0
- package/dist/C_FormulaEditor2.js +1433 -0
- package/dist/C_FormulaEditor2.js.map +1 -0
- package/dist/C_FullCalendar-BF7H0YIx.css.map +1 -0
- package/dist/C_FullCalendar.cjs +9 -0
- package/dist/C_FullCalendar.d.cts +2 -0
- package/dist/C_FullCalendar.d.ts +2 -0
- package/dist/C_FullCalendar.js +3 -0
- package/dist/C_FullCalendar2.js +377 -0
- package/dist/C_FullCalendar2.js.map +1 -0
- package/dist/C_Guide.cjs +4 -0
- package/dist/C_Guide.d.cts +2 -0
- package/dist/C_Guide.d.ts +2 -0
- package/dist/C_Guide.js +3 -0
- package/dist/C_Guide2.js +58 -0
- package/dist/C_Guide2.js.map +1 -0
- package/dist/C_Icon.cjs +4 -0
- package/dist/C_Icon.d.cts +2 -0
- package/dist/C_Icon.d.ts +2 -0
- package/dist/C_Icon.js +3 -0
- package/dist/C_Icon2.js +286 -0
- package/dist/C_Icon2.js.map +1 -0
- package/dist/C_ImageCropper-BVJfUufl.css.map +1 -0
- package/dist/C_ImageCropper.cjs +6 -0
- package/dist/C_ImageCropper.d.cts +2 -0
- package/dist/C_ImageCropper.d.ts +2 -0
- package/dist/C_ImageCropper.js +4 -0
- package/dist/C_ImageCropper2.js +723 -0
- package/dist/C_ImageCropper2.js.map +1 -0
- package/dist/C_Language.cjs +4 -0
- package/dist/C_Language.d.cts +2 -0
- package/dist/C_Language.d.ts +2 -0
- package/dist/C_Language.js +3 -0
- package/dist/C_Language2.js +72 -0
- package/dist/C_Language2.js.map +1 -0
- package/dist/C_Map-DpzeuWdX.css.map +1 -0
- package/dist/C_Map.cjs +7 -0
- package/dist/C_Map.d.cts +2 -0
- package/dist/C_Map.d.ts +2 -0
- package/dist/C_Map.js +3 -0
- package/dist/C_Map2.js +199 -0
- package/dist/C_Map2.js.map +1 -0
- package/dist/C_Markdown-BEjxknqd.css.map +1 -0
- package/dist/C_Markdown.cjs +4 -0
- package/dist/C_Markdown.d.cts +2 -0
- package/dist/C_Markdown.d.ts +2 -0
- package/dist/C_Markdown.js +3 -0
- package/dist/C_Markdown2.js +186 -0
- package/dist/C_Markdown2.js.map +1 -0
- package/dist/C_NotificationCenter-0l3TY2Gn.css.map +1 -0
- package/dist/C_NotificationCenter.cjs +20 -0
- package/dist/C_NotificationCenter.d.cts +2 -0
- package/dist/C_NotificationCenter.d.ts +2 -0
- package/dist/C_NotificationCenter.js +4 -0
- package/dist/C_NotificationCenter2.js +1383 -0
- package/dist/C_NotificationCenter2.js.map +1 -0
- package/dist/C_Progress.cjs +4 -0
- package/dist/C_Progress.d.cts +2 -0
- package/dist/C_Progress.d.ts +2 -0
- package/dist/C_Progress.js +3 -0
- package/dist/C_Progress2.js +103 -0
- package/dist/C_Progress2.js.map +1 -0
- package/dist/C_QRCode-DbdiAIPg.css.map +1 -0
- package/dist/C_QRCode.cjs +5 -0
- package/dist/C_QRCode.d.cts +2 -0
- package/dist/C_QRCode.d.ts +2 -0
- package/dist/C_QRCode.js +3 -0
- package/dist/C_QRCode2.js +218 -0
- package/dist/C_QRCode2.js.map +1 -0
- package/dist/C_Signature-zhHCbra9.css.map +1 -0
- package/dist/C_Signature.cjs +8 -0
- package/dist/C_Signature.d.cts +2 -0
- package/dist/C_Signature.d.ts +2 -0
- package/dist/C_Signature.js +4 -0
- package/dist/C_Signature2.js +618 -0
- package/dist/C_Signature2.js.map +1 -0
- package/dist/C_SplitPane-C6sBsfKY.css.map +1 -0
- package/dist/C_SplitPane.cjs +6 -0
- package/dist/C_SplitPane.d.cts +2 -0
- package/dist/C_SplitPane.d.ts +2 -0
- package/dist/C_SplitPane.js +4 -0
- package/dist/C_SplitPane2.js +356 -0
- package/dist/C_SplitPane2.js.map +1 -0
- package/dist/C_Steps-CODHN5Hs.css.map +1 -0
- package/dist/C_Steps.cjs +4 -0
- package/dist/C_Steps.d.cts +2 -0
- package/dist/C_Steps.d.ts +2 -0
- package/dist/C_Steps.js +3 -0
- package/dist/C_Steps2.js +82 -0
- package/dist/C_Steps2.js.map +1 -0
- package/dist/C_Table-DSNsntmT.css.map +1 -0
- package/dist/C_Table.cjs +19 -0
- package/dist/C_Table.d.cts +2 -0
- package/dist/C_Table.d.ts +2 -0
- package/dist/C_Table.js +5 -0
- package/dist/C_Table2.js +3009 -0
- package/dist/C_Table2.js.map +1 -0
- package/dist/C_Theme.cjs +4 -0
- package/dist/C_Theme.d.cts +2 -0
- package/dist/C_Theme.d.ts +2 -0
- package/dist/C_Theme.js +3 -0
- package/dist/C_Theme2.js +60 -0
- package/dist/C_Theme2.js.map +1 -0
- package/dist/C_Time-BvZLYraL.css.map +1 -0
- package/dist/C_Time.cjs +5 -0
- package/dist/C_Time.d.cts +2 -0
- package/dist/C_Time.d.ts +2 -0
- package/dist/C_Time.js +3 -0
- package/dist/C_Time2.js +199 -0
- package/dist/C_Time2.js.map +1 -0
- package/dist/C_Tree-0GDv--jX.css.map +1 -0
- package/dist/C_Tree.cjs +7 -0
- package/dist/C_Tree.d.cts +2 -0
- package/dist/C_Tree.d.ts +2 -0
- package/dist/C_Tree.js +4 -0
- package/dist/C_Tree2.js +441 -0
- package/dist/C_Tree2.js.map +1 -0
- package/dist/C_Upload-BXd3YYLx.css.map +1 -0
- package/dist/C_Upload.cjs +12 -0
- package/dist/C_Upload.d.cts +2 -0
- package/dist/C_Upload.d.ts +2 -0
- package/dist/C_Upload.js +4 -0
- package/dist/C_Upload2.js +1388 -0
- package/dist/C_Upload2.js.map +1 -0
- package/dist/C_VideoPlayer-DYG3RL0Q.css.map +1 -0
- package/dist/C_VideoPlayer.cjs +23 -0
- package/dist/C_VideoPlayer.d.cts +2 -0
- package/dist/C_VideoPlayer.d.ts +2 -0
- package/dist/C_VideoPlayer.js +3 -0
- package/dist/C_VideoPlayer2.js +1932 -0
- package/dist/C_VideoPlayer2.js.map +1 -0
- package/dist/C_VtableGantt-fhItIiHE.css.map +1 -0
- package/dist/C_VtableGantt.cjs +6 -0
- package/dist/C_VtableGantt.d.cts +2 -0
- package/dist/C_VtableGantt.d.ts +2 -0
- package/dist/C_VtableGantt.js +4 -0
- package/dist/C_VtableGantt2.js +873 -0
- package/dist/C_VtableGantt2.js.map +1 -0
- package/dist/C_WaterFall-8sQDFXKg.css.map +1 -0
- package/dist/C_WaterFall.cjs +13 -0
- package/dist/C_WaterFall.d.cts +2 -0
- package/dist/C_WaterFall.d.ts +2 -0
- package/dist/C_WaterFall.js +3 -0
- package/dist/C_WaterFall2.js +365 -0
- package/dist/C_WaterFall2.js.map +1 -0
- package/dist/C_WorkFlow-J-dyIuh9.css.map +1 -0
- package/dist/C_WorkFlow.cjs +8 -0
- package/dist/C_WorkFlow.d.cts +2 -0
- package/dist/C_WorkFlow.d.ts +2 -0
- package/dist/C_WorkFlow.js +4 -0
- package/dist/C_WorkFlow2.js +1984 -0
- package/dist/C_WorkFlow2.js.map +1 -0
- package/dist/chunk.js +22 -0
- package/dist/city.js +4817 -0
- package/dist/city.js.map +1 -0
- package/dist/constants.d.ts +273 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants2.d.ts +178 -0
- package/dist/constants2.d.ts.map +1 -0
- package/dist/constants3.d.ts +475 -0
- package/dist/constants3.d.ts.map +1 -0
- package/dist/constants4.d.ts +430 -0
- package/dist/constants4.d.ts.map +1 -0
- package/dist/constants5.d.ts +4283 -0
- package/dist/constants5.d.ts.map +1 -0
- package/dist/data.d.ts +67 -0
- package/dist/data.d.ts.map +1 -0
- package/dist/export-helper.js +9 -0
- package/dist/index.cjs +409 -0
- package/dist/index.d.cts +96 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +230 -0
- package/dist/index.js.map +1 -0
- package/dist/index.vue.d.ts +80 -0
- package/dist/index.vue.d.ts.map +1 -0
- package/dist/index10.vue.d.ts +72 -0
- package/dist/index10.vue.d.ts.map +1 -0
- package/dist/index11.vue.d.ts +26 -0
- package/dist/index11.vue.d.ts.map +1 -0
- package/dist/index12.vue.d.ts +81 -0
- package/dist/index12.vue.d.ts.map +1 -0
- package/dist/index13.vue.d.ts +55 -0
- package/dist/index13.vue.d.ts.map +1 -0
- package/dist/index14.vue.d.ts +33 -0
- package/dist/index14.vue.d.ts.map +1 -0
- package/dist/index15.vue.d.ts +18 -0
- package/dist/index15.vue.d.ts.map +1 -0
- package/dist/index16.vue.d.ts +662 -0
- package/dist/index16.vue.d.ts.map +1 -0
- package/dist/index2.vue.d.ts +38 -0
- package/dist/index2.vue.d.ts.map +1 -0
- package/dist/index3.vue.d.ts +45 -0
- package/dist/index3.vue.d.ts.map +1 -0
- package/dist/index4.vue.d.ts +31 -0
- package/dist/index4.vue.d.ts.map +1 -0
- package/dist/index5.vue.d.ts +35 -0
- package/dist/index5.vue.d.ts.map +1 -0
- package/dist/index6.vue.d.ts +48 -0
- package/dist/index6.vue.d.ts.map +1 -0
- package/dist/index7.vue.d.ts +56 -0
- package/dist/index7.vue.d.ts.map +1 -0
- package/dist/index8.vue.d.ts +41 -0
- package/dist/index8.vue.d.ts.map +1 -0
- package/dist/index9.vue.d.ts +30 -0
- package/dist/index9.vue.d.ts.map +1 -0
- package/dist/storage.js +31 -0
- package/dist/storage.js.map +1 -0
- package/dist/style.css +7725 -0
- package/dist/useCalendarEvents.d.ts +148 -0
- package/dist/useCalendarEvents.d.ts.map +1 -0
- package/dist/useCollapsePanel.d.ts +132 -0
- package/dist/useCollapsePanel.d.ts.map +1 -0
- package/dist/useCropperCore.d.ts +102 -0
- package/dist/useCropperCore.d.ts.map +1 -0
- package/dist/useDraggableLayout.d.ts +194 -0
- package/dist/useDraggableLayout.d.ts.map +1 -0
- package/dist/useDynamicFormState.d.ts +4248 -0
- package/dist/useDynamicFormState.d.ts.map +1 -0
- package/dist/useEdgeInteraction.d.ts +7614 -0
- package/dist/useEdgeInteraction.d.ts.map +1 -0
- package/dist/useFullscreen.d.ts +166 -0
- package/dist/useFullscreen.d.ts.map +1 -0
- package/dist/useInfiniteScroll.d.ts +169 -0
- package/dist/useInfiniteScroll.d.ts.map +1 -0
- package/dist/useModalEdit.d.ts +960 -0
- package/dist/useModalEdit.d.ts.map +1 -0
- package/dist/useQRCode.d.ts +87 -0
- package/dist/useQRCode.d.ts.map +1 -0
- package/dist/useSearchState.d.ts +180 -0
- package/dist/useSearchState.d.ts.map +1 -0
- package/dist/useSignatureHistory.d.ts +189 -0
- package/dist/useSignatureHistory.d.ts.map +1 -0
- package/dist/useSplitResize.d.ts +158 -0
- package/dist/useSplitResize.d.ts.map +1 -0
- package/dist/useTimeSelection.d.ts +105 -0
- package/dist/useTimeSelection.d.ts.map +1 -0
- package/dist/useTreeOperations.d.ts +183 -0
- package/dist/useTreeOperations.d.ts.map +1 -0
- package/dist/useWorkflowValidation.d.ts +1052 -0
- package/dist/useWorkflowValidation.d.ts.map +1 -0
- package/package.json +342 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"C_FormulaEditor2.js","names":["disabled","validation","formula","placeholder","$emit","disabled","$emit","formula","hasSampleData","usedVariables","evalResult"],"sources":["../src/components/C_FormulaEditor/constants.ts","../src/components/C_FormulaEditor/composables/useFormulaParser.ts","../src/components/C_FormulaEditor/composables/useFormulaEvaluator.ts","../src/components/C_FormulaEditor/components/FormulaInput.vue","../src/components/C_FormulaEditor/components/FormulaInput.vue","../src/components/C_FormulaEditor/components/FormulaInput.vue","../src/components/C_FormulaEditor/components/VariablePanel.vue","../src/components/C_FormulaEditor/components/VariablePanel.vue","../src/components/C_FormulaEditor/components/VariablePanel.vue","../src/components/C_FormulaEditor/components/VirtualKeyboard.vue","../src/components/C_FormulaEditor/components/VirtualKeyboard.vue","../src/components/C_FormulaEditor/components/VirtualKeyboard.vue","../src/components/C_FormulaEditor/components/FormulaPreview.vue","../src/components/C_FormulaEditor/components/FormulaPreview.vue","../src/components/C_FormulaEditor/components/FormulaPreview.vue","../src/components/C_FormulaEditor/index.vue","../src/components/C_FormulaEditor/index.vue","../src/components/C_FormulaEditor/index.vue"],"sourcesContent":["/*\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-25\r\n * @Description: 公式编辑器常量定义\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n */\r\n\r\nimport type { FormulaFunction, FormulaKeyboardKey } from \"./types\";\r\n\r\n/* ─── 虚拟键盘布局 ───────────────────────────────\r\n * 与参考系统保持一致:运算符 5 列 × 3 行 | 数字 5 列 × 3 行\r\n */\r\n\r\n/** 运算符区(5 列 × 3 行) */\r\nexport const OPERATOR_KEYS: FormulaKeyboardKey[] = [\r\n /* Row 1 */\r\n { label: \"?\", value: \" ? \", type: \"logic\", color: \"warning\" },\r\n { label: \":\", value: \" : \", type: \"logic\", color: \"warning\" },\r\n { label: \"and\", value: \" AND \", type: \"logic\", color: \"warning\" },\r\n { label: \"or\", value: \" OR \", type: \"logic\", color: \"warning\" },\r\n { label: \"(\", value: \"(\", type: \"paren\" },\r\n /* Row 2 */\r\n { label: \")\", value: \")\", type: \"paren\" },\r\n { label: \",\", value: \", \", type: \"paren\" },\r\n { label: \"==\", value: \" == \", type: \"compare\" },\r\n { label: \">\", value: \" > \", type: \"compare\" },\r\n { label: \"<\", value: \" < \", type: \"compare\" },\r\n /* Row 3 */\r\n { label: \"≥\", value: \" >= \", type: \"compare\" },\r\n { label: \"≤\", value: \" <= \", type: \"compare\" },\r\n { label: \"≠\", value: \" != \", type: \"compare\" },\r\n { label: \"%\", value: \" % \", type: \"operator\" },\r\n];\r\n\r\n/** 数字区(5 列 × 3 行) */\r\nexport const NUMBER_KEYS: FormulaKeyboardKey[] = [\r\n /* Row 1 */\r\n { label: \"1\", value: \"1\", type: \"number\" },\r\n { label: \"2\", value: \"2\", type: \"number\" },\r\n { label: \"3\", value: \"3\", type: \"number\" },\r\n { label: \"+\", value: \" + \", type: \"operator\", color: \"primary\" },\r\n { label: \"−\", value: \" - \", type: \"operator\", color: \"primary\" },\r\n /* Row 2 */\r\n { label: \"4\", value: \"4\", type: \"number\" },\r\n { label: \"5\", value: \"5\", type: \"number\" },\r\n { label: \"6\", value: \"6\", type: \"number\" },\r\n { label: \"×\", value: \" * \", type: \"operator\", color: \"primary\" },\r\n { label: \"÷\", value: \" / \", type: \"operator\", color: \"primary\" },\r\n /* Row 3 */\r\n { label: \"7\", value: \"7\", type: \"number\" },\r\n { label: \"8\", value: \"8\", type: \"number\" },\r\n { label: \"9\", value: \"9\", type: \"number\" },\r\n { label: \"0\", value: \"0\", type: \"number\" },\r\n { label: \".\", value: \".\", type: \"number\" },\r\n];\r\n\r\n/** 动作键(⌫ + 清空) */\r\nexport const ACTION_KEYS: FormulaKeyboardKey[] = [\r\n { label: \"⌫\", value: \"BACKSPACE\", type: \"action\", isAction: true },\r\n { label: \"清空\", value: \"CLEAR\", type: \"action\", isAction: true },\r\n];\r\n\r\n/* ─── 内置函数 ─────────────────────────────────── */\r\n\r\n/** 默认可用函数列表 */\r\nexport const DEFAULT_FUNCTIONS: FormulaFunction[] = [\r\n {\r\n name: \"IF\",\r\n signature: \"IF(条件, 真值, 假值)\",\r\n description: \"根据条件返回不同的值\",\r\n category: \"逻辑\",\r\n },\r\n {\r\n name: \"AND\",\r\n signature: \"AND(条件1, 条件2)\",\r\n description: \"全部条件为真时返回真\",\r\n category: \"逻辑\",\r\n },\r\n {\r\n name: \"OR\",\r\n signature: \"OR(条件1, 条件2)\",\r\n description: \"任一条件为真时返回真\",\r\n category: \"逻辑\",\r\n },\r\n {\r\n name: \"NOT\",\r\n signature: \"NOT(条件)\",\r\n description: \"对条件取反\",\r\n category: \"逻辑\",\r\n },\r\n {\r\n name: \"SUM\",\r\n signature: \"SUM(值1, 值2, ...)\",\r\n description: \"计算所有值的总和\",\r\n category: \"数学\",\r\n },\r\n {\r\n name: \"AVG\",\r\n signature: \"AVG(值1, 值2, ...)\",\r\n description: \"计算所有值的平均值\",\r\n category: \"数学\",\r\n },\r\n {\r\n name: \"MAX\",\r\n signature: \"MAX(值1, 值2, ...)\",\r\n description: \"返回最大值\",\r\n category: \"数学\",\r\n },\r\n {\r\n name: \"MIN\",\r\n signature: \"MIN(值1, 值2, ...)\",\r\n description: \"返回最小值\",\r\n category: \"数学\",\r\n },\r\n {\r\n name: \"ABS\",\r\n signature: \"ABS(数值)\",\r\n description: \"返回绝对值\",\r\n category: \"数学\",\r\n },\r\n {\r\n name: \"ROUND\",\r\n signature: \"ROUND(数值, 小数位)\",\r\n description: \"四舍五入到指定小数位\",\r\n category: \"数学\",\r\n },\r\n {\r\n name: \"CEIL\",\r\n signature: \"CEIL(数值)\",\r\n description: \"向上取整\",\r\n category: \"数学\",\r\n },\r\n {\r\n name: \"FLOOR\",\r\n signature: \"FLOOR(数值)\",\r\n description: \"向下取整\",\r\n category: \"数学\",\r\n },\r\n];\r\n\r\n/* ─── 运算符集 ─────────────────────────────────── */\r\n\r\n/** 所有可识别的运算符 */\r\nexport const OPERATORS = new Set([\r\n \"+\",\r\n \"-\",\r\n \"*\",\r\n \"/\",\r\n \"%\",\r\n \">\",\r\n \"<\",\r\n \">=\",\r\n \"<=\",\r\n \"==\",\r\n \"!=\",\r\n \"?\",\r\n \":\",\r\n \"AND\",\r\n \"OR\",\r\n \"NOT\",\r\n]);\r\n\r\n/** 需要在两侧加空格的运算符 */\r\nexport const SPACED_OPERATORS = new Set([\r\n \"+\",\r\n \"-\",\r\n \"*\",\r\n \"/\",\r\n \"%\",\r\n \">\",\r\n \"<\",\r\n \">=\",\r\n \"<=\",\r\n \"==\",\r\n \"!=\",\r\n \"?\",\r\n \":\",\r\n \"AND\",\r\n \"OR\",\r\n]);\r\n","/*\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-25\r\n * @Description: 公式解析、分词、校验引擎\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n */\r\n\r\nimport { computed } from \"vue\";\r\nimport type { Ref } from \"vue\";\r\nimport type {\r\n FormulaFunction,\r\n FormulaToken,\r\n FormulaValidation,\r\n FormulaVariable,\r\n} from \"../types\";\r\nimport { OPERATORS } from \"../constants\";\r\n\r\n/* ─── 分词正则 ─────────────────────────────────── */\r\n\r\n/**\r\n * 匹配规则(有优先级):\r\n * 1. [变量名] → variable\r\n * 2. >= <= == != → operator(多字符运算符优先)\r\n * 3. AND OR NOT → operator(逻辑关键词)\r\n * 4. 数字(含小数) → number\r\n * 5. 函数名( → function(字母序列接左括号)\r\n * 6. + - * / % > < ? : → operator\r\n * 7. ( ) , → paren/comma\r\n * 8. 空白 → space\r\n * 9. 其他 → text\r\n */\r\nconst TOKEN_REGEX =\r\n /(\\[([^\\]]+)\\])|(>=|<=|==|!=)|\\b(AND|OR|NOT)\\b|(\\d+(?:\\.\\d+)?)|([A-Za-z_]\\w*)(?=\\s*\\()|([+\\-*/%><?:])|([(),])|(\\s+)/g;\r\n\r\n/**\r\n * 公式解析 & 校验引擎\r\n */\r\nexport function useFormulaParser(\r\n variables: Ref<FormulaVariable[]>,\r\n functions: Ref<FormulaFunction[]>,\r\n) {\r\n /* ─── 构建查找集 ────────────────────────────── */\r\n\r\n /** 有效变量名集合 */\r\n const variableNames = computed(\r\n () => new Set(variables.value.map((v) => v.name)),\r\n );\r\n\r\n /** 有效函数名集合(大写) */\r\n const functionNames = computed(\r\n () => new Set(functions.value.map((f) => f.name.toUpperCase())),\r\n );\r\n\r\n /* ─── 分词 ──────────────────────────────────── */\r\n\r\n /** 根据正则匹配组分类 Token */\r\n function classifyMatch(\r\n match: RegExpExecArray,\r\n start: number,\r\n end: number,\r\n ): FormulaToken {\r\n if (match[1]) return { type: \"variable\", value: match[2], start, end };\r\n if (match[3]) return { type: \"operator\", value: match[3], start, end };\r\n if (match[4]) return { type: \"operator\", value: match[4], start, end };\r\n if (match[5]) return { type: \"number\", value: match[5], start, end };\r\n if (match[6]) return { type: \"function\", value: match[6], start, end };\r\n if (match[7]) return { type: \"operator\", value: match[7], start, end };\r\n if (match[8]) {\r\n const v = match[8];\r\n return { type: v === \",\" ? \"comma\" : \"paren\", value: v, start, end };\r\n }\r\n return { type: \"space\", value: match[9] ?? \" \", start, end };\r\n }\r\n\r\n /** 将公式字符串解析为 Token 数组 */\r\n function tokenize(formula: string): FormulaToken[] {\r\n const tokens: FormulaToken[] = [];\r\n const regex = new RegExp(TOKEN_REGEX.source, \"g\");\r\n let match: RegExpExecArray | null;\r\n let lastIndex = 0;\r\n\r\n while ((match = regex.exec(formula)) !== null) {\r\n if (match.index > lastIndex) {\r\n tokens.push({\r\n type: \"text\",\r\n value: formula.slice(lastIndex, match.index),\r\n start: lastIndex,\r\n end: match.index,\r\n });\r\n }\r\n\r\n tokens.push(\r\n classifyMatch(match, match.index, match.index + match[0].length),\r\n );\r\n lastIndex = match.index + match[0].length;\r\n }\r\n\r\n if (lastIndex < formula.length) {\r\n tokens.push({\r\n type: \"text\",\r\n value: formula.slice(lastIndex),\r\n start: lastIndex,\r\n end: formula.length,\r\n });\r\n }\r\n\r\n return tokens;\r\n }\r\n\r\n /* ─── 校验 ──────────────────────────────────── */\r\n\r\n /** 校验括号平衡 */\r\n function checkParentheses(formula: string): FormulaValidation {\r\n let depth = 0;\r\n for (let i = 0; i < formula.length; i++) {\r\n if (formula[i] === \"(\") depth++;\r\n else if (formula[i] === \")\") depth--;\r\n if (depth < 0) {\r\n return {\r\n valid: false,\r\n message: `第 ${i + 1} 个字符处有多余的右括号 )`,\r\n position: i,\r\n };\r\n }\r\n }\r\n if (depth > 0) {\r\n return {\r\n valid: false,\r\n message: `缺少 ${depth} 个右括号 )`,\r\n };\r\n }\r\n return { valid: true, message: \"\" };\r\n }\r\n\r\n /** 校验变量是否都已定义 */\r\n function checkVariables(tokens: FormulaToken[]): FormulaValidation {\r\n for (const token of tokens) {\r\n if (token.type === \"variable\" && !variableNames.value.has(token.value)) {\r\n return {\r\n valid: false,\r\n message: `未知变量「${token.value}」`,\r\n position: token.start,\r\n };\r\n }\r\n }\r\n return { valid: true, message: \"\" };\r\n }\r\n\r\n /** 校验函数是否已注册 */\r\n function checkFunctions(tokens: FormulaToken[]): FormulaValidation {\r\n for (const token of tokens) {\r\n if (\r\n token.type === \"function\" &&\r\n !functionNames.value.has(token.value.toUpperCase())\r\n ) {\r\n return {\r\n valid: false,\r\n message: `未知函数「${token.value}」`,\r\n position: token.start,\r\n };\r\n }\r\n }\r\n return { valid: true, message: \"\" };\r\n }\r\n\r\n /** 完整校验公式 */\r\n function validate(formula: string): FormulaValidation {\r\n if (!formula.trim()) {\r\n return { valid: true, message: \"公式为空\" };\r\n }\r\n\r\n /* 1. 括号平衡 */\r\n const parenCheck = checkParentheses(formula);\r\n if (!parenCheck.valid) return parenCheck;\r\n\r\n /* 2. 分词 */\r\n const tokens = tokenize(formula);\r\n\r\n /* 3. 变量校验 */\r\n const varCheck = checkVariables(tokens);\r\n if (!varCheck.valid) return varCheck;\r\n\r\n /* 4. 函数校验 */\r\n const funcCheck = checkFunctions(tokens);\r\n if (!funcCheck.valid) return funcCheck;\r\n\r\n /* 5. 基本语法校验 — 不能以运算符开头(除了负号) */\r\n const meaningful = tokens.filter((t) => t.type !== \"space\");\r\n if (meaningful.length > 0) {\r\n const first = meaningful[0];\r\n if (\r\n first.type === \"operator\" &&\r\n first.value !== \"-\" &&\r\n !OPERATORS.has(first.value) === false\r\n ) {\r\n return {\r\n valid: false,\r\n message: `公式不能以运算符「${first.value}」开头`,\r\n position: first.start,\r\n };\r\n }\r\n }\r\n\r\n return { valid: true, message: \"公式合法\" };\r\n }\r\n\r\n /* ─── 公式 → 可求值表达式 ───────────────────── */\r\n\r\n /**\r\n * 将公式字符串转换为 expr-eval 可识别的表达式\r\n * [变量名] → 变量.field 标识符\r\n */\r\n function toEvalExpression(\r\n formula: string,\r\n variableMap: Map<string, string>,\r\n ): string {\r\n return formula.replace(/\\[([^\\]]+)\\]/g, (_, name: string) => {\r\n const field = variableMap.get(name);\r\n return field ?? `__unknown_${name}__`;\r\n });\r\n }\r\n\r\n return {\r\n tokenize,\r\n validate,\r\n toEvalExpression,\r\n variableNames,\r\n functionNames,\r\n };\r\n}\r\n","/*\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-25\r\n * @Description: 公式求值引擎(基于 expr-eval)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n */\r\n\r\nimport { computed } from \"vue\";\r\nimport type { Ref } from \"vue\";\r\nimport type { FormulaVariable } from \"../types\";\r\nimport { Parser } from \"expr-eval\";\r\n\r\n/**\r\n * 公式求值引擎\r\n * 将公式中的 [变量名] 替换为实际值后计算结果\r\n */\r\nexport function useFormulaEvaluator(variables: Ref<FormulaVariable[]>) {\r\n /** expr-eval 解析器实例 */\r\n const parser = new Parser({\r\n operators: {\r\n logical: true,\r\n comparison: true,\r\n conditional: true,\r\n add: true,\r\n subtract: true,\r\n multiply: true,\r\n divide: true,\r\n remainder: true,\r\n },\r\n });\r\n\r\n /* 注册自定义函数 */\r\n parser.functions.IF = (cond: unknown, t: unknown, f: unknown) =>\r\n cond ? t : f;\r\n parser.functions.AND = (...args: unknown[]) => args.every(Boolean);\r\n parser.functions.OR = (...args: unknown[]) => args.some(Boolean);\r\n parser.functions.NOT = (v: unknown) => !v;\r\n parser.functions.SUM = (...args: number[]) => args.reduce((a, b) => a + b, 0);\r\n parser.functions.AVG = (...args: number[]) =>\r\n args.length > 0 ? args.reduce((a, b) => a + b, 0) / args.length : 0;\r\n parser.functions.MAX = (...args: number[]) => Math.max(...args);\r\n parser.functions.MIN = (...args: number[]) => Math.min(...args);\r\n parser.functions.ABS = Math.abs;\r\n parser.functions.ROUND = (v: number, d = 0) => {\r\n const f = 10 ** d;\r\n return Math.round(v * f) / f;\r\n };\r\n parser.functions.CEIL = Math.ceil;\r\n parser.functions.FLOOR = Math.floor;\r\n\r\n /** 构建 变量名 → field 映射 */\r\n const variableMap = computed(() => {\r\n const map = new Map<string, string>();\r\n for (const v of variables.value) {\r\n map.set(v.name, v.field);\r\n }\r\n return map;\r\n });\r\n\r\n /**\r\n * 将公式中的 [变量名] 替换为 field 标识符\r\n * 例:[完成值] → completion_value\r\n */\r\n function replaceVariables(formula: string): string {\r\n return formula.replace(/\\[([^\\]]+)\\]/g, (_, name: string) => {\r\n const field = variableMap.value.get(name);\r\n return field ?? `__unknown__`;\r\n });\r\n }\r\n\r\n /**\r\n * 求值:用样例数据计算公式结果\r\n */\r\n function evaluate(\r\n formula: string,\r\n sampleData: Record<string, number | string | boolean>,\r\n ): { success: boolean; result: unknown; error?: string } {\r\n if (!formula.trim()) {\r\n return { success: true, result: undefined };\r\n }\r\n\r\n try {\r\n const evalExpr = replaceVariables(formula);\r\n const parsed = parser.parse(evalExpr);\r\n const result = parsed.evaluate(sampleData as Record<string, number>);\r\n return { success: true, result };\r\n } catch (e: unknown) {\r\n const message = e instanceof Error ? e.message : String(e);\r\n return { success: false, result: undefined, error: message };\r\n }\r\n }\r\n\r\n /**\r\n * 从公式中提取使用到的变量名列表\r\n */\r\n function extractVariableNames(formula: string): string[] {\r\n const names: string[] = [];\r\n const regex = /\\[([^\\]]+)\\]/g;\r\n let match: RegExpExecArray | null;\r\n while ((match = regex.exec(formula)) !== null) {\r\n if (!names.includes(match[1])) {\r\n names.push(match[1]);\r\n }\r\n }\r\n return names;\r\n }\r\n\r\n return {\r\n evaluate,\r\n extractVariableNames,\r\n variableMap,\r\n };\r\n}\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-25\r\n * @Description: 公式输入区(contenteditable + Token 渲染)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div\r\n class=\"formula-input\"\r\n :class=\"{\r\n 'formula-input--disabled': disabled,\r\n 'formula-input--error': !validation.valid && formula.trim(),\r\n }\"\r\n >\r\n <!-- 标题行 -->\r\n <div class=\"formula-input__header\">\r\n <span class=\"formula-input__label\">公式编辑</span>\r\n <a\r\n v-if=\"formula.trim() && !disabled\"\r\n class=\"formula-input__clear\"\r\n @click=\"handleClear\"\r\n >\r\n 清空\r\n </a>\r\n </div>\r\n\r\n <!-- 编辑器 -->\r\n <div\r\n ref=\"editorRef\"\r\n class=\"formula-input__editor\"\r\n contenteditable=\"true\"\r\n :data-placeholder=\"placeholder\"\r\n spellcheck=\"false\"\r\n @input=\"handleInput\"\r\n @keydown=\"handleKeyDown\"\r\n @paste=\"handlePaste\"\r\n @focus=\"isFocused = true\"\r\n @blur=\"handleBlur\"\r\n />\r\n\r\n <!-- 问题(校验信息)三态:空 / 合法 / 错误 -->\r\n <div class=\"formula-input__validation\">\r\n <span class=\"formula-input__validation-label\">问题</span>\r\n\r\n <!-- 空公式:提示占位 -->\r\n <span v-if=\"!formula.trim()\" class=\"formula-input__validation-hint\">\r\n <C_Icon name=\"mdi:information-outline\" :size=\"13\" />\r\n 请输入公式,公式合法后可计算预览\r\n </span>\r\n\r\n <!-- 合法:绿色通过 -->\r\n <span\r\n v-else-if=\"validation.valid\"\r\n class=\"formula-input__validation-success\"\r\n >\r\n <C_Icon name=\"mdi:check-circle-outline\" :size=\"13\" />\r\n 公式合法,语法正确\r\n </span>\r\n\r\n <!-- 错误:红色提示 -->\r\n <span v-else class=\"formula-input__validation-error\">\r\n <C_Icon name=\"mdi:alert-circle-outline\" :size=\"13\" />\r\n {{ validation.message }}\r\n </span>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, watch, onMounted, nextTick } from \"vue\";\r\nimport type { FormulaToken, FormulaValidation } from \"../types\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\n\r\ninterface Props {\r\n formula: string;\r\n tokens: FormulaToken[];\r\n validation: FormulaValidation;\r\n variableNames: Set<string>;\r\n disabled?: boolean;\r\n placeholder?: string;\r\n}\r\n\r\ninterface Emits {\r\n (e: \"update:formula\", value: string): void;\r\n (e: \"focus\"): void;\r\n (e: \"blur\"): void;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n disabled: false,\r\n placeholder: \"点击变量或使用键盘输入公式,变量用 [变量名] 包裹\",\r\n});\r\n\r\nconst emit = defineEmits<Emits>();\r\n\r\nconst editorRef = ref<HTMLDivElement>();\r\nconst isFocused = ref(false);\r\n\r\n/** 是否正在由外部更新内容(防止循环触发) */\r\nlet isExternalUpdate = false;\r\n\r\n/** 保存 blur 前的光标 Range,供外部调用 insertAtCursor 时恢复 */\r\nlet savedRange: Range | null = null;\r\n\r\n/* ─── 渲染公式为 HTML ───────────────────────── */\r\n\r\n/** 将公式字符串渲染为 HTML(变量显示为 chip) */\r\nfunction renderFormula(formula: string): string {\r\n if (!formula) return \"\";\r\n\r\n /* 使用分词结果构建 HTML */\r\n const { tokens } = props;\r\n if (tokens.length === 0 && formula.trim()) {\r\n return escapeHtml(formula);\r\n }\r\n\r\n let html = \"\";\r\n for (const token of tokens) {\r\n switch (token.type) {\r\n case \"variable\":\r\n html += `<span class=\"formula-chip\" contenteditable=\"false\" data-variable=\"${escapeAttr(token.value)}\">${escapeHtml(token.value)}</span>`;\r\n break;\r\n case \"function\":\r\n html += `<span class=\"formula-func\">${escapeHtml(token.value)}</span>`;\r\n break;\r\n case \"operator\":\r\n html += `<span class=\"formula-op\">${escapeHtml(token.value)}</span>`;\r\n break;\r\n case \"number\":\r\n html += `<span class=\"formula-num\">${escapeHtml(token.value)}</span>`;\r\n break;\r\n case \"paren\":\r\n html += `<span class=\"formula-paren\">${escapeHtml(token.value)}</span>`;\r\n break;\r\n default:\r\n html += escapeHtml(token.value);\r\n }\r\n }\r\n return html;\r\n}\r\n\r\n/** HTML 转义 */\r\nfunction escapeHtml(str: string): string {\r\n return str.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\r\n}\r\n\r\n/** 属性值转义 */\r\nfunction escapeAttr(str: string): string {\r\n return str.replace(/\"/g, \""\").replace(/'/g, \"'\");\r\n}\r\n\r\n/* ─── 从 DOM 提取公式 ───────────────────────── */\r\n\r\n/** 从 contenteditable DOM 提取纯公式字符串 */\r\nfunction extractFormula(): string {\r\n const el = editorRef.value;\r\n if (!el) return \"\";\r\n\r\n let result = \"\";\r\n for (const node of el.childNodes) {\r\n if (node.nodeType === Node.TEXT_NODE) {\r\n result += node.textContent ?? \"\";\r\n } else if (node.nodeType === Node.ELEMENT_NODE) {\r\n const element = node as HTMLElement;\r\n if (element.classList.contains(\"formula-chip\")) {\r\n const varName =\r\n element.getAttribute(\"data-variable\") ?? element.textContent;\r\n result += `[${varName}]`;\r\n } else {\r\n result += element.textContent ?? \"\";\r\n }\r\n }\r\n }\r\n return result;\r\n}\r\n\r\n/* ─── 光标管理 ──────────────────────────────── */\r\n\r\n/** 保存当前光标位置 */\r\nfunction saveCursorPosition(): number {\r\n const sel = window.getSelection();\r\n if (!sel || sel.rangeCount === 0 || !editorRef.value) return -1;\r\n\r\n const range = sel.getRangeAt(0);\r\n const preRange = document.createRange();\r\n preRange.selectNodeContents(editorRef.value);\r\n preRange.setEnd(range.startContainer, range.startOffset);\r\n return preRange.toString().length;\r\n}\r\n\r\n/** 在文本节点中查找目标位置 */\r\nfunction findInTextNode(\r\n node: Node,\r\n currentPos: number,\r\n charPos: number,\r\n): { found: boolean; node: Node; offset: number; pos: number } {\r\n const len = node.textContent?.length ?? 0;\r\n if (currentPos + len >= charPos) {\r\n return {\r\n found: true,\r\n node,\r\n offset: charPos - currentPos,\r\n pos: currentPos,\r\n };\r\n }\r\n return { found: false, node, offset: 0, pos: currentPos + len };\r\n}\r\n\r\n/** 在 chip 节点中查找目标位置 */\r\nfunction findInChipNode(\r\n node: Node,\r\n currentPos: number,\r\n charPos: number,\r\n walker: TreeWalker,\r\n): { found: boolean; node: Node | null; offset: number; pos: number } {\r\n const chipLen =\r\n (node as HTMLElement).getAttribute(\"data-variable\")?.length ?? 0;\r\n const fullLen = chipLen + 2;\r\n if (currentPos + fullLen >= charPos) {\r\n const parent = node.parentNode;\r\n const idx =\r\n Array.from(parent?.childNodes ?? []).indexOf(node as ChildNode) + 1;\r\n return { found: true, node: parent, offset: idx, pos: currentPos };\r\n }\r\n walker.nextNode();\r\n return { found: false, node: null, offset: 0, pos: currentPos + fullLen };\r\n}\r\n\r\n/** 恢复光标到指定字符位置 */\r\nfunction restoreCursorPosition(charPos: number) {\r\n const el = editorRef.value;\r\n if (!el || charPos < 0) return;\r\n\r\n const walker = document.createTreeWalker(\r\n el,\r\n NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,\r\n );\r\n let currentPos = 0;\r\n let targetNode: Node | null = null;\r\n let targetOffset = 0;\r\n\r\n while (walker.nextNode()) {\r\n const node = walker.currentNode;\r\n if (node.nodeType === Node.TEXT_NODE) {\r\n const result = findInTextNode(node, currentPos, charPos);\r\n if (result.found) {\r\n targetNode = result.node;\r\n targetOffset = result.offset;\r\n break;\r\n }\r\n currentPos = result.pos;\r\n } else if (\r\n node.nodeType === Node.ELEMENT_NODE &&\r\n (node as HTMLElement).classList?.contains(\"formula-chip\")\r\n ) {\r\n const result = findInChipNode(node, currentPos, charPos, walker);\r\n if (result.found) {\r\n targetNode = result.node;\r\n targetOffset = result.offset;\r\n break;\r\n }\r\n currentPos = result.pos;\r\n }\r\n }\r\n\r\n setCursorAt(el, targetNode, targetOffset);\r\n}\r\n\r\n/** 设置光标到目标节点或末尾 */\r\nfunction setCursorAt(\r\n container: HTMLElement,\r\n node: Node | null,\r\n offset: number,\r\n) {\r\n const range = document.createRange();\r\n if (!node) {\r\n range.selectNodeContents(container);\r\n range.collapse(false);\r\n } else {\r\n try {\r\n range.setStart(node, offset);\r\n range.collapse(true);\r\n } catch {\r\n range.selectNodeContents(container);\r\n range.collapse(false);\r\n }\r\n }\r\n const sel = window.getSelection();\r\n sel?.removeAllRanges();\r\n sel?.addRange(range);\r\n}\r\n\r\n/** 将光标移到末尾 */\r\nfunction moveCursorToEnd() {\r\n const el = editorRef.value;\r\n if (!el) return;\r\n const range = document.createRange();\r\n range.selectNodeContents(el);\r\n range.collapse(false);\r\n const sel = window.getSelection();\r\n sel?.removeAllRanges();\r\n sel?.addRange(range);\r\n}\r\n\r\n/* ─── 事件处理 ──────────────────────────────── */\r\n\r\n/** 输入事件 */\r\nfunction handleInput() {\r\n if (isExternalUpdate) return;\r\n const formula = extractFormula();\r\n emit(\"update:formula\", formula);\r\n}\r\n\r\n/** 键盘事件 */\r\nfunction handleKeyDown(e: KeyboardEvent) {\r\n if (props.disabled) {\r\n e.preventDefault();\r\n return;\r\n }\r\n /* Tab 键切换焦点而非插入 */\r\n if (e.key === \"Tab\") {\r\n e.preventDefault();\r\n }\r\n}\r\n\r\n/** 粘贴事件 — 只保留纯文本 */\r\nfunction handlePaste(e: ClipboardEvent) {\r\n e.preventDefault();\r\n const text = e.clipboardData?.getData(\"text/plain\") ?? \"\";\r\n document.execCommand(\"insertText\", false, text);\r\n}\r\n\r\n/** blur 时保存光标位置 Range */\r\nfunction handleBlur() {\r\n isFocused.value = false;\r\n const sel = window.getSelection();\r\n if (sel && sel.rangeCount > 0) {\r\n savedRange = sel.getRangeAt(0).cloneRange();\r\n }\r\n}\r\n\r\n/** 清空 */\r\nfunction handleClear() {\r\n emit(\"update:formula\", \"\");\r\n nextTick(() => {\r\n if (editorRef.value) {\r\n editorRef.value.innerHTML = \"\";\r\n }\r\n });\r\n}\r\n\r\n/* ─── 暴露方法 ──────────────────────────────── */\r\n\r\n/** 在光标位置插入文本 */\r\nfunction insertAtCursor(text: string) {\r\n const el = editorRef.value;\r\n if (!el) return;\r\n\r\n el.focus();\r\n\r\n /* 恢复 blur 前保存的光标位置 */\r\n if (savedRange) {\r\n const sel = window.getSelection();\r\n sel?.removeAllRanges();\r\n sel?.addRange(savedRange);\r\n }\r\n\r\n /* 如果是变量文本([xxx]),插入 chip */\r\n const varMatch = text.match(/^\\[(.+)\\]$/);\r\n if (varMatch) {\r\n const chip = document.createElement(\"span\");\r\n chip.className = \"formula-chip\";\r\n chip.contentEditable = \"false\";\r\n chip.setAttribute(\"data-variable\", varMatch[1]);\r\n chip.textContent = varMatch[1];\r\n\r\n const sel = window.getSelection();\r\n if (sel && sel.rangeCount > 0) {\r\n const range = sel.getRangeAt(0);\r\n range.deleteContents();\r\n range.insertNode(chip);\r\n\r\n /* 在 chip 后插入一个空格 */\r\n const space = document.createTextNode(\" \");\r\n range.setStartAfter(chip);\r\n range.insertNode(space);\r\n range.setStartAfter(space);\r\n range.collapse(true);\r\n sel.removeAllRanges();\r\n sel.addRange(range);\r\n savedRange = range.cloneRange();\r\n }\r\n } else {\r\n /* 普通文本直接插入 */\r\n document.execCommand(\"insertText\", false, text);\r\n /* 更新保存的光标 */\r\n const sel = window.getSelection();\r\n if (sel && sel.rangeCount > 0) {\r\n savedRange = sel.getRangeAt(0).cloneRange();\r\n }\r\n }\r\n\r\n /* 触发更新 */\r\n const formula = extractFormula();\r\n emit(\"update:formula\", formula);\r\n}\r\n\r\n/** 退格(删除光标前一个字符或 chip) */\r\nfunction backspace() {\r\n const el = editorRef.value;\r\n if (!el) return;\r\n\r\n el.focus();\r\n const sel = window.getSelection();\r\n if (!sel || sel.rangeCount === 0) return;\r\n\r\n const range = sel.getRangeAt(0);\r\n\r\n /* 检查光标前一个节点是否是 chip */\r\n if (range.startOffset > 0 && range.startContainer === el) {\r\n const prevNode = el.childNodes[range.startOffset - 1];\r\n if (\r\n prevNode &&\r\n prevNode.nodeType === Node.ELEMENT_NODE &&\r\n (prevNode as HTMLElement).classList?.contains(\"formula-chip\")\r\n ) {\r\n prevNode.remove();\r\n emit(\"update:formula\", extractFormula());\r\n return;\r\n }\r\n }\r\n\r\n /* 普通退格 */\r\n document.execCommand(\"delete\", false);\r\n emit(\"update:formula\", extractFormula());\r\n}\r\n\r\n/** 聚焦 */\r\nfunction focus() {\r\n editorRef.value?.focus();\r\n moveCursorToEnd();\r\n}\r\n\r\n/* ─── 监听外部公式变化更新显示 ─────────────── */\r\n\r\nwatch(\r\n () => props.formula,\r\n (newFormula) => {\r\n const el = editorRef.value;\r\n if (!el) return;\r\n\r\n /* 检查当前 DOM 内容是否已经一致(避免循环) */\r\n const current = extractFormula();\r\n if (current === newFormula) return;\r\n\r\n isExternalUpdate = true;\r\n const pos = saveCursorPosition();\r\n el.innerHTML = renderFormula(newFormula);\r\n nextTick(() => {\r\n restoreCursorPosition(pos);\r\n isExternalUpdate = false;\r\n });\r\n },\r\n);\r\n\r\n/* 初始化渲染 */\r\nonMounted(() => {\r\n if (editorRef.value && props.formula) {\r\n editorRef.value.innerHTML = renderFormula(props.formula);\r\n }\r\n});\r\n\r\ndefineExpose({\r\n insertAtCursor,\r\n backspace,\r\n focus,\r\n moveCursorToEnd,\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./FormulaInput.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-25\r\n * @Description: 公式输入区(contenteditable + Token 渲染)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div\r\n class=\"formula-input\"\r\n :class=\"{\r\n 'formula-input--disabled': disabled,\r\n 'formula-input--error': !validation.valid && formula.trim(),\r\n }\"\r\n >\r\n <!-- 标题行 -->\r\n <div class=\"formula-input__header\">\r\n <span class=\"formula-input__label\">公式编辑</span>\r\n <a\r\n v-if=\"formula.trim() && !disabled\"\r\n class=\"formula-input__clear\"\r\n @click=\"handleClear\"\r\n >\r\n 清空\r\n </a>\r\n </div>\r\n\r\n <!-- 编辑器 -->\r\n <div\r\n ref=\"editorRef\"\r\n class=\"formula-input__editor\"\r\n contenteditable=\"true\"\r\n :data-placeholder=\"placeholder\"\r\n spellcheck=\"false\"\r\n @input=\"handleInput\"\r\n @keydown=\"handleKeyDown\"\r\n @paste=\"handlePaste\"\r\n @focus=\"isFocused = true\"\r\n @blur=\"handleBlur\"\r\n />\r\n\r\n <!-- 问题(校验信息)三态:空 / 合法 / 错误 -->\r\n <div class=\"formula-input__validation\">\r\n <span class=\"formula-input__validation-label\">问题</span>\r\n\r\n <!-- 空公式:提示占位 -->\r\n <span v-if=\"!formula.trim()\" class=\"formula-input__validation-hint\">\r\n <C_Icon name=\"mdi:information-outline\" :size=\"13\" />\r\n 请输入公式,公式合法后可计算预览\r\n </span>\r\n\r\n <!-- 合法:绿色通过 -->\r\n <span\r\n v-else-if=\"validation.valid\"\r\n class=\"formula-input__validation-success\"\r\n >\r\n <C_Icon name=\"mdi:check-circle-outline\" :size=\"13\" />\r\n 公式合法,语法正确\r\n </span>\r\n\r\n <!-- 错误:红色提示 -->\r\n <span v-else class=\"formula-input__validation-error\">\r\n <C_Icon name=\"mdi:alert-circle-outline\" :size=\"13\" />\r\n {{ validation.message }}\r\n </span>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, watch, onMounted, nextTick } from \"vue\";\r\nimport type { FormulaToken, FormulaValidation } from \"../types\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\n\r\ninterface Props {\r\n formula: string;\r\n tokens: FormulaToken[];\r\n validation: FormulaValidation;\r\n variableNames: Set<string>;\r\n disabled?: boolean;\r\n placeholder?: string;\r\n}\r\n\r\ninterface Emits {\r\n (e: \"update:formula\", value: string): void;\r\n (e: \"focus\"): void;\r\n (e: \"blur\"): void;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n disabled: false,\r\n placeholder: \"点击变量或使用键盘输入公式,变量用 [变量名] 包裹\",\r\n});\r\n\r\nconst emit = defineEmits<Emits>();\r\n\r\nconst editorRef = ref<HTMLDivElement>();\r\nconst isFocused = ref(false);\r\n\r\n/** 是否正在由外部更新内容(防止循环触发) */\r\nlet isExternalUpdate = false;\r\n\r\n/** 保存 blur 前的光标 Range,供外部调用 insertAtCursor 时恢复 */\r\nlet savedRange: Range | null = null;\r\n\r\n/* ─── 渲染公式为 HTML ───────────────────────── */\r\n\r\n/** 将公式字符串渲染为 HTML(变量显示为 chip) */\r\nfunction renderFormula(formula: string): string {\r\n if (!formula) return \"\";\r\n\r\n /* 使用分词结果构建 HTML */\r\n const { tokens } = props;\r\n if (tokens.length === 0 && formula.trim()) {\r\n return escapeHtml(formula);\r\n }\r\n\r\n let html = \"\";\r\n for (const token of tokens) {\r\n switch (token.type) {\r\n case \"variable\":\r\n html += `<span class=\"formula-chip\" contenteditable=\"false\" data-variable=\"${escapeAttr(token.value)}\">${escapeHtml(token.value)}</span>`;\r\n break;\r\n case \"function\":\r\n html += `<span class=\"formula-func\">${escapeHtml(token.value)}</span>`;\r\n break;\r\n case \"operator\":\r\n html += `<span class=\"formula-op\">${escapeHtml(token.value)}</span>`;\r\n break;\r\n case \"number\":\r\n html += `<span class=\"formula-num\">${escapeHtml(token.value)}</span>`;\r\n break;\r\n case \"paren\":\r\n html += `<span class=\"formula-paren\">${escapeHtml(token.value)}</span>`;\r\n break;\r\n default:\r\n html += escapeHtml(token.value);\r\n }\r\n }\r\n return html;\r\n}\r\n\r\n/** HTML 转义 */\r\nfunction escapeHtml(str: string): string {\r\n return str.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\r\n}\r\n\r\n/** 属性值转义 */\r\nfunction escapeAttr(str: string): string {\r\n return str.replace(/\"/g, \""\").replace(/'/g, \"'\");\r\n}\r\n\r\n/* ─── 从 DOM 提取公式 ───────────────────────── */\r\n\r\n/** 从 contenteditable DOM 提取纯公式字符串 */\r\nfunction extractFormula(): string {\r\n const el = editorRef.value;\r\n if (!el) return \"\";\r\n\r\n let result = \"\";\r\n for (const node of el.childNodes) {\r\n if (node.nodeType === Node.TEXT_NODE) {\r\n result += node.textContent ?? \"\";\r\n } else if (node.nodeType === Node.ELEMENT_NODE) {\r\n const element = node as HTMLElement;\r\n if (element.classList.contains(\"formula-chip\")) {\r\n const varName =\r\n element.getAttribute(\"data-variable\") ?? element.textContent;\r\n result += `[${varName}]`;\r\n } else {\r\n result += element.textContent ?? \"\";\r\n }\r\n }\r\n }\r\n return result;\r\n}\r\n\r\n/* ─── 光标管理 ──────────────────────────────── */\r\n\r\n/** 保存当前光标位置 */\r\nfunction saveCursorPosition(): number {\r\n const sel = window.getSelection();\r\n if (!sel || sel.rangeCount === 0 || !editorRef.value) return -1;\r\n\r\n const range = sel.getRangeAt(0);\r\n const preRange = document.createRange();\r\n preRange.selectNodeContents(editorRef.value);\r\n preRange.setEnd(range.startContainer, range.startOffset);\r\n return preRange.toString().length;\r\n}\r\n\r\n/** 在文本节点中查找目标位置 */\r\nfunction findInTextNode(\r\n node: Node,\r\n currentPos: number,\r\n charPos: number,\r\n): { found: boolean; node: Node; offset: number; pos: number } {\r\n const len = node.textContent?.length ?? 0;\r\n if (currentPos + len >= charPos) {\r\n return {\r\n found: true,\r\n node,\r\n offset: charPos - currentPos,\r\n pos: currentPos,\r\n };\r\n }\r\n return { found: false, node, offset: 0, pos: currentPos + len };\r\n}\r\n\r\n/** 在 chip 节点中查找目标位置 */\r\nfunction findInChipNode(\r\n node: Node,\r\n currentPos: number,\r\n charPos: number,\r\n walker: TreeWalker,\r\n): { found: boolean; node: Node | null; offset: number; pos: number } {\r\n const chipLen =\r\n (node as HTMLElement).getAttribute(\"data-variable\")?.length ?? 0;\r\n const fullLen = chipLen + 2;\r\n if (currentPos + fullLen >= charPos) {\r\n const parent = node.parentNode;\r\n const idx =\r\n Array.from(parent?.childNodes ?? []).indexOf(node as ChildNode) + 1;\r\n return { found: true, node: parent, offset: idx, pos: currentPos };\r\n }\r\n walker.nextNode();\r\n return { found: false, node: null, offset: 0, pos: currentPos + fullLen };\r\n}\r\n\r\n/** 恢复光标到指定字符位置 */\r\nfunction restoreCursorPosition(charPos: number) {\r\n const el = editorRef.value;\r\n if (!el || charPos < 0) return;\r\n\r\n const walker = document.createTreeWalker(\r\n el,\r\n NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,\r\n );\r\n let currentPos = 0;\r\n let targetNode: Node | null = null;\r\n let targetOffset = 0;\r\n\r\n while (walker.nextNode()) {\r\n const node = walker.currentNode;\r\n if (node.nodeType === Node.TEXT_NODE) {\r\n const result = findInTextNode(node, currentPos, charPos);\r\n if (result.found) {\r\n targetNode = result.node;\r\n targetOffset = result.offset;\r\n break;\r\n }\r\n currentPos = result.pos;\r\n } else if (\r\n node.nodeType === Node.ELEMENT_NODE &&\r\n (node as HTMLElement).classList?.contains(\"formula-chip\")\r\n ) {\r\n const result = findInChipNode(node, currentPos, charPos, walker);\r\n if (result.found) {\r\n targetNode = result.node;\r\n targetOffset = result.offset;\r\n break;\r\n }\r\n currentPos = result.pos;\r\n }\r\n }\r\n\r\n setCursorAt(el, targetNode, targetOffset);\r\n}\r\n\r\n/** 设置光标到目标节点或末尾 */\r\nfunction setCursorAt(\r\n container: HTMLElement,\r\n node: Node | null,\r\n offset: number,\r\n) {\r\n const range = document.createRange();\r\n if (!node) {\r\n range.selectNodeContents(container);\r\n range.collapse(false);\r\n } else {\r\n try {\r\n range.setStart(node, offset);\r\n range.collapse(true);\r\n } catch {\r\n range.selectNodeContents(container);\r\n range.collapse(false);\r\n }\r\n }\r\n const sel = window.getSelection();\r\n sel?.removeAllRanges();\r\n sel?.addRange(range);\r\n}\r\n\r\n/** 将光标移到末尾 */\r\nfunction moveCursorToEnd() {\r\n const el = editorRef.value;\r\n if (!el) return;\r\n const range = document.createRange();\r\n range.selectNodeContents(el);\r\n range.collapse(false);\r\n const sel = window.getSelection();\r\n sel?.removeAllRanges();\r\n sel?.addRange(range);\r\n}\r\n\r\n/* ─── 事件处理 ──────────────────────────────── */\r\n\r\n/** 输入事件 */\r\nfunction handleInput() {\r\n if (isExternalUpdate) return;\r\n const formula = extractFormula();\r\n emit(\"update:formula\", formula);\r\n}\r\n\r\n/** 键盘事件 */\r\nfunction handleKeyDown(e: KeyboardEvent) {\r\n if (props.disabled) {\r\n e.preventDefault();\r\n return;\r\n }\r\n /* Tab 键切换焦点而非插入 */\r\n if (e.key === \"Tab\") {\r\n e.preventDefault();\r\n }\r\n}\r\n\r\n/** 粘贴事件 — 只保留纯文本 */\r\nfunction handlePaste(e: ClipboardEvent) {\r\n e.preventDefault();\r\n const text = e.clipboardData?.getData(\"text/plain\") ?? \"\";\r\n document.execCommand(\"insertText\", false, text);\r\n}\r\n\r\n/** blur 时保存光标位置 Range */\r\nfunction handleBlur() {\r\n isFocused.value = false;\r\n const sel = window.getSelection();\r\n if (sel && sel.rangeCount > 0) {\r\n savedRange = sel.getRangeAt(0).cloneRange();\r\n }\r\n}\r\n\r\n/** 清空 */\r\nfunction handleClear() {\r\n emit(\"update:formula\", \"\");\r\n nextTick(() => {\r\n if (editorRef.value) {\r\n editorRef.value.innerHTML = \"\";\r\n }\r\n });\r\n}\r\n\r\n/* ─── 暴露方法 ──────────────────────────────── */\r\n\r\n/** 在光标位置插入文本 */\r\nfunction insertAtCursor(text: string) {\r\n const el = editorRef.value;\r\n if (!el) return;\r\n\r\n el.focus();\r\n\r\n /* 恢复 blur 前保存的光标位置 */\r\n if (savedRange) {\r\n const sel = window.getSelection();\r\n sel?.removeAllRanges();\r\n sel?.addRange(savedRange);\r\n }\r\n\r\n /* 如果是变量文本([xxx]),插入 chip */\r\n const varMatch = text.match(/^\\[(.+)\\]$/);\r\n if (varMatch) {\r\n const chip = document.createElement(\"span\");\r\n chip.className = \"formula-chip\";\r\n chip.contentEditable = \"false\";\r\n chip.setAttribute(\"data-variable\", varMatch[1]);\r\n chip.textContent = varMatch[1];\r\n\r\n const sel = window.getSelection();\r\n if (sel && sel.rangeCount > 0) {\r\n const range = sel.getRangeAt(0);\r\n range.deleteContents();\r\n range.insertNode(chip);\r\n\r\n /* 在 chip 后插入一个空格 */\r\n const space = document.createTextNode(\" \");\r\n range.setStartAfter(chip);\r\n range.insertNode(space);\r\n range.setStartAfter(space);\r\n range.collapse(true);\r\n sel.removeAllRanges();\r\n sel.addRange(range);\r\n savedRange = range.cloneRange();\r\n }\r\n } else {\r\n /* 普通文本直接插入 */\r\n document.execCommand(\"insertText\", false, text);\r\n /* 更新保存的光标 */\r\n const sel = window.getSelection();\r\n if (sel && sel.rangeCount > 0) {\r\n savedRange = sel.getRangeAt(0).cloneRange();\r\n }\r\n }\r\n\r\n /* 触发更新 */\r\n const formula = extractFormula();\r\n emit(\"update:formula\", formula);\r\n}\r\n\r\n/** 退格(删除光标前一个字符或 chip) */\r\nfunction backspace() {\r\n const el = editorRef.value;\r\n if (!el) return;\r\n\r\n el.focus();\r\n const sel = window.getSelection();\r\n if (!sel || sel.rangeCount === 0) return;\r\n\r\n const range = sel.getRangeAt(0);\r\n\r\n /* 检查光标前一个节点是否是 chip */\r\n if (range.startOffset > 0 && range.startContainer === el) {\r\n const prevNode = el.childNodes[range.startOffset - 1];\r\n if (\r\n prevNode &&\r\n prevNode.nodeType === Node.ELEMENT_NODE &&\r\n (prevNode as HTMLElement).classList?.contains(\"formula-chip\")\r\n ) {\r\n prevNode.remove();\r\n emit(\"update:formula\", extractFormula());\r\n return;\r\n }\r\n }\r\n\r\n /* 普通退格 */\r\n document.execCommand(\"delete\", false);\r\n emit(\"update:formula\", extractFormula());\r\n}\r\n\r\n/** 聚焦 */\r\nfunction focus() {\r\n editorRef.value?.focus();\r\n moveCursorToEnd();\r\n}\r\n\r\n/* ─── 监听外部公式变化更新显示 ─────────────── */\r\n\r\nwatch(\r\n () => props.formula,\r\n (newFormula) => {\r\n const el = editorRef.value;\r\n if (!el) return;\r\n\r\n /* 检查当前 DOM 内容是否已经一致(避免循环) */\r\n const current = extractFormula();\r\n if (current === newFormula) return;\r\n\r\n isExternalUpdate = true;\r\n const pos = saveCursorPosition();\r\n el.innerHTML = renderFormula(newFormula);\r\n nextTick(() => {\r\n restoreCursorPosition(pos);\r\n isExternalUpdate = false;\r\n });\r\n },\r\n);\r\n\r\n/* 初始化渲染 */\r\nonMounted(() => {\r\n if (editorRef.value && props.formula) {\r\n editorRef.value.innerHTML = renderFormula(props.formula);\r\n }\r\n});\r\n\r\ndefineExpose({\r\n insertAtCursor,\r\n backspace,\r\n focus,\r\n moveCursorToEnd,\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./FormulaInput.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-25\r\n * @Description: 公式输入区(contenteditable + Token 渲染)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div\r\n class=\"formula-input\"\r\n :class=\"{\r\n 'formula-input--disabled': disabled,\r\n 'formula-input--error': !validation.valid && formula.trim(),\r\n }\"\r\n >\r\n <!-- 标题行 -->\r\n <div class=\"formula-input__header\">\r\n <span class=\"formula-input__label\">公式编辑</span>\r\n <a\r\n v-if=\"formula.trim() && !disabled\"\r\n class=\"formula-input__clear\"\r\n @click=\"handleClear\"\r\n >\r\n 清空\r\n </a>\r\n </div>\r\n\r\n <!-- 编辑器 -->\r\n <div\r\n ref=\"editorRef\"\r\n class=\"formula-input__editor\"\r\n contenteditable=\"true\"\r\n :data-placeholder=\"placeholder\"\r\n spellcheck=\"false\"\r\n @input=\"handleInput\"\r\n @keydown=\"handleKeyDown\"\r\n @paste=\"handlePaste\"\r\n @focus=\"isFocused = true\"\r\n @blur=\"handleBlur\"\r\n />\r\n\r\n <!-- 问题(校验信息)三态:空 / 合法 / 错误 -->\r\n <div class=\"formula-input__validation\">\r\n <span class=\"formula-input__validation-label\">问题</span>\r\n\r\n <!-- 空公式:提示占位 -->\r\n <span v-if=\"!formula.trim()\" class=\"formula-input__validation-hint\">\r\n <C_Icon name=\"mdi:information-outline\" :size=\"13\" />\r\n 请输入公式,公式合法后可计算预览\r\n </span>\r\n\r\n <!-- 合法:绿色通过 -->\r\n <span\r\n v-else-if=\"validation.valid\"\r\n class=\"formula-input__validation-success\"\r\n >\r\n <C_Icon name=\"mdi:check-circle-outline\" :size=\"13\" />\r\n 公式合法,语法正确\r\n </span>\r\n\r\n <!-- 错误:红色提示 -->\r\n <span v-else class=\"formula-input__validation-error\">\r\n <C_Icon name=\"mdi:alert-circle-outline\" :size=\"13\" />\r\n {{ validation.message }}\r\n </span>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, watch, onMounted, nextTick } from \"vue\";\r\nimport type { FormulaToken, FormulaValidation } from \"../types\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\n\r\ninterface Props {\r\n formula: string;\r\n tokens: FormulaToken[];\r\n validation: FormulaValidation;\r\n variableNames: Set<string>;\r\n disabled?: boolean;\r\n placeholder?: string;\r\n}\r\n\r\ninterface Emits {\r\n (e: \"update:formula\", value: string): void;\r\n (e: \"focus\"): void;\r\n (e: \"blur\"): void;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n disabled: false,\r\n placeholder: \"点击变量或使用键盘输入公式,变量用 [变量名] 包裹\",\r\n});\r\n\r\nconst emit = defineEmits<Emits>();\r\n\r\nconst editorRef = ref<HTMLDivElement>();\r\nconst isFocused = ref(false);\r\n\r\n/** 是否正在由外部更新内容(防止循环触发) */\r\nlet isExternalUpdate = false;\r\n\r\n/** 保存 blur 前的光标 Range,供外部调用 insertAtCursor 时恢复 */\r\nlet savedRange: Range | null = null;\r\n\r\n/* ─── 渲染公式为 HTML ───────────────────────── */\r\n\r\n/** 将公式字符串渲染为 HTML(变量显示为 chip) */\r\nfunction renderFormula(formula: string): string {\r\n if (!formula) return \"\";\r\n\r\n /* 使用分词结果构建 HTML */\r\n const { tokens } = props;\r\n if (tokens.length === 0 && formula.trim()) {\r\n return escapeHtml(formula);\r\n }\r\n\r\n let html = \"\";\r\n for (const token of tokens) {\r\n switch (token.type) {\r\n case \"variable\":\r\n html += `<span class=\"formula-chip\" contenteditable=\"false\" data-variable=\"${escapeAttr(token.value)}\">${escapeHtml(token.value)}</span>`;\r\n break;\r\n case \"function\":\r\n html += `<span class=\"formula-func\">${escapeHtml(token.value)}</span>`;\r\n break;\r\n case \"operator\":\r\n html += `<span class=\"formula-op\">${escapeHtml(token.value)}</span>`;\r\n break;\r\n case \"number\":\r\n html += `<span class=\"formula-num\">${escapeHtml(token.value)}</span>`;\r\n break;\r\n case \"paren\":\r\n html += `<span class=\"formula-paren\">${escapeHtml(token.value)}</span>`;\r\n break;\r\n default:\r\n html += escapeHtml(token.value);\r\n }\r\n }\r\n return html;\r\n}\r\n\r\n/** HTML 转义 */\r\nfunction escapeHtml(str: string): string {\r\n return str.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\r\n}\r\n\r\n/** 属性值转义 */\r\nfunction escapeAttr(str: string): string {\r\n return str.replace(/\"/g, \""\").replace(/'/g, \"'\");\r\n}\r\n\r\n/* ─── 从 DOM 提取公式 ───────────────────────── */\r\n\r\n/** 从 contenteditable DOM 提取纯公式字符串 */\r\nfunction extractFormula(): string {\r\n const el = editorRef.value;\r\n if (!el) return \"\";\r\n\r\n let result = \"\";\r\n for (const node of el.childNodes) {\r\n if (node.nodeType === Node.TEXT_NODE) {\r\n result += node.textContent ?? \"\";\r\n } else if (node.nodeType === Node.ELEMENT_NODE) {\r\n const element = node as HTMLElement;\r\n if (element.classList.contains(\"formula-chip\")) {\r\n const varName =\r\n element.getAttribute(\"data-variable\") ?? element.textContent;\r\n result += `[${varName}]`;\r\n } else {\r\n result += element.textContent ?? \"\";\r\n }\r\n }\r\n }\r\n return result;\r\n}\r\n\r\n/* ─── 光标管理 ──────────────────────────────── */\r\n\r\n/** 保存当前光标位置 */\r\nfunction saveCursorPosition(): number {\r\n const sel = window.getSelection();\r\n if (!sel || sel.rangeCount === 0 || !editorRef.value) return -1;\r\n\r\n const range = sel.getRangeAt(0);\r\n const preRange = document.createRange();\r\n preRange.selectNodeContents(editorRef.value);\r\n preRange.setEnd(range.startContainer, range.startOffset);\r\n return preRange.toString().length;\r\n}\r\n\r\n/** 在文本节点中查找目标位置 */\r\nfunction findInTextNode(\r\n node: Node,\r\n currentPos: number,\r\n charPos: number,\r\n): { found: boolean; node: Node; offset: number; pos: number } {\r\n const len = node.textContent?.length ?? 0;\r\n if (currentPos + len >= charPos) {\r\n return {\r\n found: true,\r\n node,\r\n offset: charPos - currentPos,\r\n pos: currentPos,\r\n };\r\n }\r\n return { found: false, node, offset: 0, pos: currentPos + len };\r\n}\r\n\r\n/** 在 chip 节点中查找目标位置 */\r\nfunction findInChipNode(\r\n node: Node,\r\n currentPos: number,\r\n charPos: number,\r\n walker: TreeWalker,\r\n): { found: boolean; node: Node | null; offset: number; pos: number } {\r\n const chipLen =\r\n (node as HTMLElement).getAttribute(\"data-variable\")?.length ?? 0;\r\n const fullLen = chipLen + 2;\r\n if (currentPos + fullLen >= charPos) {\r\n const parent = node.parentNode;\r\n const idx =\r\n Array.from(parent?.childNodes ?? []).indexOf(node as ChildNode) + 1;\r\n return { found: true, node: parent, offset: idx, pos: currentPos };\r\n }\r\n walker.nextNode();\r\n return { found: false, node: null, offset: 0, pos: currentPos + fullLen };\r\n}\r\n\r\n/** 恢复光标到指定字符位置 */\r\nfunction restoreCursorPosition(charPos: number) {\r\n const el = editorRef.value;\r\n if (!el || charPos < 0) return;\r\n\r\n const walker = document.createTreeWalker(\r\n el,\r\n NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,\r\n );\r\n let currentPos = 0;\r\n let targetNode: Node | null = null;\r\n let targetOffset = 0;\r\n\r\n while (walker.nextNode()) {\r\n const node = walker.currentNode;\r\n if (node.nodeType === Node.TEXT_NODE) {\r\n const result = findInTextNode(node, currentPos, charPos);\r\n if (result.found) {\r\n targetNode = result.node;\r\n targetOffset = result.offset;\r\n break;\r\n }\r\n currentPos = result.pos;\r\n } else if (\r\n node.nodeType === Node.ELEMENT_NODE &&\r\n (node as HTMLElement).classList?.contains(\"formula-chip\")\r\n ) {\r\n const result = findInChipNode(node, currentPos, charPos, walker);\r\n if (result.found) {\r\n targetNode = result.node;\r\n targetOffset = result.offset;\r\n break;\r\n }\r\n currentPos = result.pos;\r\n }\r\n }\r\n\r\n setCursorAt(el, targetNode, targetOffset);\r\n}\r\n\r\n/** 设置光标到目标节点或末尾 */\r\nfunction setCursorAt(\r\n container: HTMLElement,\r\n node: Node | null,\r\n offset: number,\r\n) {\r\n const range = document.createRange();\r\n if (!node) {\r\n range.selectNodeContents(container);\r\n range.collapse(false);\r\n } else {\r\n try {\r\n range.setStart(node, offset);\r\n range.collapse(true);\r\n } catch {\r\n range.selectNodeContents(container);\r\n range.collapse(false);\r\n }\r\n }\r\n const sel = window.getSelection();\r\n sel?.removeAllRanges();\r\n sel?.addRange(range);\r\n}\r\n\r\n/** 将光标移到末尾 */\r\nfunction moveCursorToEnd() {\r\n const el = editorRef.value;\r\n if (!el) return;\r\n const range = document.createRange();\r\n range.selectNodeContents(el);\r\n range.collapse(false);\r\n const sel = window.getSelection();\r\n sel?.removeAllRanges();\r\n sel?.addRange(range);\r\n}\r\n\r\n/* ─── 事件处理 ──────────────────────────────── */\r\n\r\n/** 输入事件 */\r\nfunction handleInput() {\r\n if (isExternalUpdate) return;\r\n const formula = extractFormula();\r\n emit(\"update:formula\", formula);\r\n}\r\n\r\n/** 键盘事件 */\r\nfunction handleKeyDown(e: KeyboardEvent) {\r\n if (props.disabled) {\r\n e.preventDefault();\r\n return;\r\n }\r\n /* Tab 键切换焦点而非插入 */\r\n if (e.key === \"Tab\") {\r\n e.preventDefault();\r\n }\r\n}\r\n\r\n/** 粘贴事件 — 只保留纯文本 */\r\nfunction handlePaste(e: ClipboardEvent) {\r\n e.preventDefault();\r\n const text = e.clipboardData?.getData(\"text/plain\") ?? \"\";\r\n document.execCommand(\"insertText\", false, text);\r\n}\r\n\r\n/** blur 时保存光标位置 Range */\r\nfunction handleBlur() {\r\n isFocused.value = false;\r\n const sel = window.getSelection();\r\n if (sel && sel.rangeCount > 0) {\r\n savedRange = sel.getRangeAt(0).cloneRange();\r\n }\r\n}\r\n\r\n/** 清空 */\r\nfunction handleClear() {\r\n emit(\"update:formula\", \"\");\r\n nextTick(() => {\r\n if (editorRef.value) {\r\n editorRef.value.innerHTML = \"\";\r\n }\r\n });\r\n}\r\n\r\n/* ─── 暴露方法 ──────────────────────────────── */\r\n\r\n/** 在光标位置插入文本 */\r\nfunction insertAtCursor(text: string) {\r\n const el = editorRef.value;\r\n if (!el) return;\r\n\r\n el.focus();\r\n\r\n /* 恢复 blur 前保存的光标位置 */\r\n if (savedRange) {\r\n const sel = window.getSelection();\r\n sel?.removeAllRanges();\r\n sel?.addRange(savedRange);\r\n }\r\n\r\n /* 如果是变量文本([xxx]),插入 chip */\r\n const varMatch = text.match(/^\\[(.+)\\]$/);\r\n if (varMatch) {\r\n const chip = document.createElement(\"span\");\r\n chip.className = \"formula-chip\";\r\n chip.contentEditable = \"false\";\r\n chip.setAttribute(\"data-variable\", varMatch[1]);\r\n chip.textContent = varMatch[1];\r\n\r\n const sel = window.getSelection();\r\n if (sel && sel.rangeCount > 0) {\r\n const range = sel.getRangeAt(0);\r\n range.deleteContents();\r\n range.insertNode(chip);\r\n\r\n /* 在 chip 后插入一个空格 */\r\n const space = document.createTextNode(\" \");\r\n range.setStartAfter(chip);\r\n range.insertNode(space);\r\n range.setStartAfter(space);\r\n range.collapse(true);\r\n sel.removeAllRanges();\r\n sel.addRange(range);\r\n savedRange = range.cloneRange();\r\n }\r\n } else {\r\n /* 普通文本直接插入 */\r\n document.execCommand(\"insertText\", false, text);\r\n /* 更新保存的光标 */\r\n const sel = window.getSelection();\r\n if (sel && sel.rangeCount > 0) {\r\n savedRange = sel.getRangeAt(0).cloneRange();\r\n }\r\n }\r\n\r\n /* 触发更新 */\r\n const formula = extractFormula();\r\n emit(\"update:formula\", formula);\r\n}\r\n\r\n/** 退格(删除光标前一个字符或 chip) */\r\nfunction backspace() {\r\n const el = editorRef.value;\r\n if (!el) return;\r\n\r\n el.focus();\r\n const sel = window.getSelection();\r\n if (!sel || sel.rangeCount === 0) return;\r\n\r\n const range = sel.getRangeAt(0);\r\n\r\n /* 检查光标前一个节点是否是 chip */\r\n if (range.startOffset > 0 && range.startContainer === el) {\r\n const prevNode = el.childNodes[range.startOffset - 1];\r\n if (\r\n prevNode &&\r\n prevNode.nodeType === Node.ELEMENT_NODE &&\r\n (prevNode as HTMLElement).classList?.contains(\"formula-chip\")\r\n ) {\r\n prevNode.remove();\r\n emit(\"update:formula\", extractFormula());\r\n return;\r\n }\r\n }\r\n\r\n /* 普通退格 */\r\n document.execCommand(\"delete\", false);\r\n emit(\"update:formula\", extractFormula());\r\n}\r\n\r\n/** 聚焦 */\r\nfunction focus() {\r\n editorRef.value?.focus();\r\n moveCursorToEnd();\r\n}\r\n\r\n/* ─── 监听外部公式变化更新显示 ─────────────── */\r\n\r\nwatch(\r\n () => props.formula,\r\n (newFormula) => {\r\n const el = editorRef.value;\r\n if (!el) return;\r\n\r\n /* 检查当前 DOM 内容是否已经一致(避免循环) */\r\n const current = extractFormula();\r\n if (current === newFormula) return;\r\n\r\n isExternalUpdate = true;\r\n const pos = saveCursorPosition();\r\n el.innerHTML = renderFormula(newFormula);\r\n nextTick(() => {\r\n restoreCursorPosition(pos);\r\n isExternalUpdate = false;\r\n });\r\n },\r\n);\r\n\r\n/* 初始化渲染 */\r\nonMounted(() => {\r\n if (editorRef.value && props.formula) {\r\n editorRef.value.innerHTML = renderFormula(props.formula);\r\n }\r\n});\r\n\r\ndefineExpose({\r\n insertAtCursor,\r\n backspace,\r\n focus,\r\n moveCursorToEnd,\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./FormulaInput.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-25\r\n * @Description: 变量选择面板(分组 + 搜索 + 函数)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"variable-panel\">\r\n <!-- 切换 Tab:变量 / 函数 -->\r\n <div class=\"variable-panel__tabs\">\r\n <div\r\n class=\"variable-panel__tab\"\r\n :class=\"{ 'variable-panel__tab--active': activeTab === 'variable' }\"\r\n @click=\"activeTab = 'variable'\"\r\n >\r\n 表单项目\r\n </div>\r\n <div\r\n class=\"variable-panel__tab\"\r\n :class=\"{ 'variable-panel__tab--active': activeTab === 'function' }\"\r\n @click=\"activeTab = 'function'\"\r\n >\r\n 常用函数\r\n </div>\r\n </div>\r\n\r\n <!-- 搜索框 -->\r\n <div class=\"variable-panel__search\">\r\n <NInput\r\n v-model:value=\"searchText\"\r\n size=\"small\"\r\n placeholder=\"搜索...\"\r\n clearable\r\n >\r\n <template #prefix>\r\n <C_Icon name=\"mdi:magnify\" :size=\"16\" />\r\n </template>\r\n </NInput>\r\n </div>\r\n\r\n <!-- 变量列表 -->\r\n <NScrollbar v-if=\"activeTab === 'variable'\" class=\"variable-panel__list\">\r\n <template v-if=\"groupedVariables.length > 0\">\r\n <div\r\n v-for=\"group in groupedVariables\"\r\n :key=\"group.name\"\r\n class=\"variable-panel__group\"\r\n >\r\n <div\r\n class=\"variable-panel__group-title\"\r\n @click=\"toggleGroup(group.name)\"\r\n >\r\n <C_Icon\r\n :name=\"\r\n expandedGroups.has(group.name)\r\n ? 'mdi:chevron-down'\r\n : 'mdi:chevron-right'\r\n \"\r\n :size=\"16\"\r\n />\r\n {{ group.name }}\r\n </div>\r\n <div\r\n v-show=\"expandedGroups.has(group.name)\"\r\n class=\"variable-panel__items\"\r\n >\r\n <div\r\n v-for=\"variable in group.items\"\r\n :key=\"variable.field\"\r\n class=\"variable-panel__item\"\r\n @click=\"$emit('select-variable', variable)\"\r\n >\r\n <C_Icon\r\n :name=\"getTypeIcon(variable.type)\"\r\n :size=\"15\"\r\n class=\"variable-panel__item-icon\"\r\n />\r\n <span>{{ variable.name }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n </template>\r\n <NEmpty v-else size=\"small\" description=\"无匹配变量\" />\r\n </NScrollbar>\r\n\r\n <!-- 函数列表 -->\r\n <NScrollbar v-else class=\"variable-panel__list\">\r\n <template v-if=\"filteredFunctions.length > 0\">\r\n <div\r\n v-for=\"func in filteredFunctions\"\r\n :key=\"func.name\"\r\n class=\"variable-panel__func\"\r\n @click=\"$emit('select-function', func)\"\r\n >\r\n <div class=\"variable-panel__func-name\">\r\n <span class=\"variable-panel__func-badge\">ƒ</span>\r\n {{ func.name }}\r\n </div>\r\n <div class=\"variable-panel__func-desc\">\r\n {{ func.description }}\r\n </div>\r\n <div class=\"variable-panel__func-sig\">\r\n {{ func.signature }}\r\n </div>\r\n </div>\r\n </template>\r\n <NEmpty v-else size=\"small\" description=\"无匹配函数\" />\r\n </NScrollbar>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from \"vue\";\r\nimport { NInput, NScrollbar, NEmpty } from \"naive-ui\";\r\nimport type {\r\n FormulaFunction,\r\n FormulaVariable,\r\n FormulaVariableType,\r\n} from \"../types\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\n\r\ninterface Props {\r\n variables: FormulaVariable[];\r\n functions: FormulaFunction[];\r\n}\r\n\r\ninterface Emits {\r\n (e: \"select-variable\", variable: FormulaVariable): void;\r\n (e: \"select-function\", func: FormulaFunction): void;\r\n}\r\n\r\nconst props = defineProps<Props>();\r\ndefineEmits<Emits>();\r\n\r\nconst activeTab = ref<\"variable\" | \"function\">(\"variable\");\r\nconst searchText = ref(\"\");\r\nconst expandedGroups = ref(new Set<string>());\r\n\r\n/* ─── 变量分组 ──────────────────────────────── */\r\n\r\ninterface VariableGroup {\r\n name: string;\r\n items: FormulaVariable[];\r\n}\r\n\r\n/** 过滤并按 group 分组变量 */\r\nconst groupedVariables = computed<VariableGroup[]>(() => {\r\n const keyword = searchText.value.toLowerCase();\r\n const filtered = keyword\r\n ? props.variables.filter(\r\n (v) =>\r\n v.name.toLowerCase().includes(keyword) ||\r\n v.field.toLowerCase().includes(keyword) ||\r\n (v.description ?? \"\").toLowerCase().includes(keyword),\r\n )\r\n : props.variables;\r\n\r\n const map = new Map<string, FormulaVariable[]>();\r\n for (const v of filtered) {\r\n const group = v.group ?? \"其他\";\r\n const list = map.get(group);\r\n if (list) {\r\n list.push(v);\r\n } else {\r\n map.set(group, [v]);\r\n }\r\n }\r\n\r\n return Array.from(map, ([name, items]) => ({ name, items }));\r\n});\r\n\r\n/* 默认展开所有分组 */\r\nwatch(\r\n groupedVariables,\r\n (groups) => {\r\n for (const g of groups) {\r\n expandedGroups.value.add(g.name);\r\n }\r\n },\r\n { immediate: true },\r\n);\r\n\r\n/** 切换分组折叠 */\r\nfunction toggleGroup(name: string) {\r\n if (expandedGroups.value.has(name)) {\r\n expandedGroups.value.delete(name);\r\n } else {\r\n expandedGroups.value.add(name);\r\n }\r\n}\r\n\r\n/* ─── 函数过滤 ──────────────────────────────── */\r\n\r\nconst filteredFunctions = computed(() => {\r\n const keyword = searchText.value.toLowerCase();\r\n if (!keyword) return props.functions;\r\n return props.functions.filter(\r\n (f) =>\r\n f.name.toLowerCase().includes(keyword) ||\r\n f.description.toLowerCase().includes(keyword),\r\n );\r\n});\r\n\r\n/* ─── 变量类型图标 ──────────────────────────── */\r\n\r\n/** 根据变量类型返回图标 */\r\nfunction getTypeIcon(type: FormulaVariableType): string {\r\n switch (type) {\r\n case \"number\":\r\n return \"mdi:numeric\";\r\n case \"text\":\r\n return \"mdi:format-text\";\r\n case \"boolean\":\r\n return \"mdi:toggle-switch-outline\";\r\n default:\r\n return \"mdi:variable\";\r\n }\r\n}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./VariablePanel.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-25\r\n * @Description: 变量选择面板(分组 + 搜索 + 函数)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"variable-panel\">\r\n <!-- 切换 Tab:变量 / 函数 -->\r\n <div class=\"variable-panel__tabs\">\r\n <div\r\n class=\"variable-panel__tab\"\r\n :class=\"{ 'variable-panel__tab--active': activeTab === 'variable' }\"\r\n @click=\"activeTab = 'variable'\"\r\n >\r\n 表单项目\r\n </div>\r\n <div\r\n class=\"variable-panel__tab\"\r\n :class=\"{ 'variable-panel__tab--active': activeTab === 'function' }\"\r\n @click=\"activeTab = 'function'\"\r\n >\r\n 常用函数\r\n </div>\r\n </div>\r\n\r\n <!-- 搜索框 -->\r\n <div class=\"variable-panel__search\">\r\n <NInput\r\n v-model:value=\"searchText\"\r\n size=\"small\"\r\n placeholder=\"搜索...\"\r\n clearable\r\n >\r\n <template #prefix>\r\n <C_Icon name=\"mdi:magnify\" :size=\"16\" />\r\n </template>\r\n </NInput>\r\n </div>\r\n\r\n <!-- 变量列表 -->\r\n <NScrollbar v-if=\"activeTab === 'variable'\" class=\"variable-panel__list\">\r\n <template v-if=\"groupedVariables.length > 0\">\r\n <div\r\n v-for=\"group in groupedVariables\"\r\n :key=\"group.name\"\r\n class=\"variable-panel__group\"\r\n >\r\n <div\r\n class=\"variable-panel__group-title\"\r\n @click=\"toggleGroup(group.name)\"\r\n >\r\n <C_Icon\r\n :name=\"\r\n expandedGroups.has(group.name)\r\n ? 'mdi:chevron-down'\r\n : 'mdi:chevron-right'\r\n \"\r\n :size=\"16\"\r\n />\r\n {{ group.name }}\r\n </div>\r\n <div\r\n v-show=\"expandedGroups.has(group.name)\"\r\n class=\"variable-panel__items\"\r\n >\r\n <div\r\n v-for=\"variable in group.items\"\r\n :key=\"variable.field\"\r\n class=\"variable-panel__item\"\r\n @click=\"$emit('select-variable', variable)\"\r\n >\r\n <C_Icon\r\n :name=\"getTypeIcon(variable.type)\"\r\n :size=\"15\"\r\n class=\"variable-panel__item-icon\"\r\n />\r\n <span>{{ variable.name }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n </template>\r\n <NEmpty v-else size=\"small\" description=\"无匹配变量\" />\r\n </NScrollbar>\r\n\r\n <!-- 函数列表 -->\r\n <NScrollbar v-else class=\"variable-panel__list\">\r\n <template v-if=\"filteredFunctions.length > 0\">\r\n <div\r\n v-for=\"func in filteredFunctions\"\r\n :key=\"func.name\"\r\n class=\"variable-panel__func\"\r\n @click=\"$emit('select-function', func)\"\r\n >\r\n <div class=\"variable-panel__func-name\">\r\n <span class=\"variable-panel__func-badge\">ƒ</span>\r\n {{ func.name }}\r\n </div>\r\n <div class=\"variable-panel__func-desc\">\r\n {{ func.description }}\r\n </div>\r\n <div class=\"variable-panel__func-sig\">\r\n {{ func.signature }}\r\n </div>\r\n </div>\r\n </template>\r\n <NEmpty v-else size=\"small\" description=\"无匹配函数\" />\r\n </NScrollbar>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from \"vue\";\r\nimport { NInput, NScrollbar, NEmpty } from \"naive-ui\";\r\nimport type {\r\n FormulaFunction,\r\n FormulaVariable,\r\n FormulaVariableType,\r\n} from \"../types\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\n\r\ninterface Props {\r\n variables: FormulaVariable[];\r\n functions: FormulaFunction[];\r\n}\r\n\r\ninterface Emits {\r\n (e: \"select-variable\", variable: FormulaVariable): void;\r\n (e: \"select-function\", func: FormulaFunction): void;\r\n}\r\n\r\nconst props = defineProps<Props>();\r\ndefineEmits<Emits>();\r\n\r\nconst activeTab = ref<\"variable\" | \"function\">(\"variable\");\r\nconst searchText = ref(\"\");\r\nconst expandedGroups = ref(new Set<string>());\r\n\r\n/* ─── 变量分组 ──────────────────────────────── */\r\n\r\ninterface VariableGroup {\r\n name: string;\r\n items: FormulaVariable[];\r\n}\r\n\r\n/** 过滤并按 group 分组变量 */\r\nconst groupedVariables = computed<VariableGroup[]>(() => {\r\n const keyword = searchText.value.toLowerCase();\r\n const filtered = keyword\r\n ? props.variables.filter(\r\n (v) =>\r\n v.name.toLowerCase().includes(keyword) ||\r\n v.field.toLowerCase().includes(keyword) ||\r\n (v.description ?? \"\").toLowerCase().includes(keyword),\r\n )\r\n : props.variables;\r\n\r\n const map = new Map<string, FormulaVariable[]>();\r\n for (const v of filtered) {\r\n const group = v.group ?? \"其他\";\r\n const list = map.get(group);\r\n if (list) {\r\n list.push(v);\r\n } else {\r\n map.set(group, [v]);\r\n }\r\n }\r\n\r\n return Array.from(map, ([name, items]) => ({ name, items }));\r\n});\r\n\r\n/* 默认展开所有分组 */\r\nwatch(\r\n groupedVariables,\r\n (groups) => {\r\n for (const g of groups) {\r\n expandedGroups.value.add(g.name);\r\n }\r\n },\r\n { immediate: true },\r\n);\r\n\r\n/** 切换分组折叠 */\r\nfunction toggleGroup(name: string) {\r\n if (expandedGroups.value.has(name)) {\r\n expandedGroups.value.delete(name);\r\n } else {\r\n expandedGroups.value.add(name);\r\n }\r\n}\r\n\r\n/* ─── 函数过滤 ──────────────────────────────── */\r\n\r\nconst filteredFunctions = computed(() => {\r\n const keyword = searchText.value.toLowerCase();\r\n if (!keyword) return props.functions;\r\n return props.functions.filter(\r\n (f) =>\r\n f.name.toLowerCase().includes(keyword) ||\r\n f.description.toLowerCase().includes(keyword),\r\n );\r\n});\r\n\r\n/* ─── 变量类型图标 ──────────────────────────── */\r\n\r\n/** 根据变量类型返回图标 */\r\nfunction getTypeIcon(type: FormulaVariableType): string {\r\n switch (type) {\r\n case \"number\":\r\n return \"mdi:numeric\";\r\n case \"text\":\r\n return \"mdi:format-text\";\r\n case \"boolean\":\r\n return \"mdi:toggle-switch-outline\";\r\n default:\r\n return \"mdi:variable\";\r\n }\r\n}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./VariablePanel.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-25\r\n * @Description: 变量选择面板(分组 + 搜索 + 函数)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"variable-panel\">\r\n <!-- 切换 Tab:变量 / 函数 -->\r\n <div class=\"variable-panel__tabs\">\r\n <div\r\n class=\"variable-panel__tab\"\r\n :class=\"{ 'variable-panel__tab--active': activeTab === 'variable' }\"\r\n @click=\"activeTab = 'variable'\"\r\n >\r\n 表单项目\r\n </div>\r\n <div\r\n class=\"variable-panel__tab\"\r\n :class=\"{ 'variable-panel__tab--active': activeTab === 'function' }\"\r\n @click=\"activeTab = 'function'\"\r\n >\r\n 常用函数\r\n </div>\r\n </div>\r\n\r\n <!-- 搜索框 -->\r\n <div class=\"variable-panel__search\">\r\n <NInput\r\n v-model:value=\"searchText\"\r\n size=\"small\"\r\n placeholder=\"搜索...\"\r\n clearable\r\n >\r\n <template #prefix>\r\n <C_Icon name=\"mdi:magnify\" :size=\"16\" />\r\n </template>\r\n </NInput>\r\n </div>\r\n\r\n <!-- 变量列表 -->\r\n <NScrollbar v-if=\"activeTab === 'variable'\" class=\"variable-panel__list\">\r\n <template v-if=\"groupedVariables.length > 0\">\r\n <div\r\n v-for=\"group in groupedVariables\"\r\n :key=\"group.name\"\r\n class=\"variable-panel__group\"\r\n >\r\n <div\r\n class=\"variable-panel__group-title\"\r\n @click=\"toggleGroup(group.name)\"\r\n >\r\n <C_Icon\r\n :name=\"\r\n expandedGroups.has(group.name)\r\n ? 'mdi:chevron-down'\r\n : 'mdi:chevron-right'\r\n \"\r\n :size=\"16\"\r\n />\r\n {{ group.name }}\r\n </div>\r\n <div\r\n v-show=\"expandedGroups.has(group.name)\"\r\n class=\"variable-panel__items\"\r\n >\r\n <div\r\n v-for=\"variable in group.items\"\r\n :key=\"variable.field\"\r\n class=\"variable-panel__item\"\r\n @click=\"$emit('select-variable', variable)\"\r\n >\r\n <C_Icon\r\n :name=\"getTypeIcon(variable.type)\"\r\n :size=\"15\"\r\n class=\"variable-panel__item-icon\"\r\n />\r\n <span>{{ variable.name }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n </template>\r\n <NEmpty v-else size=\"small\" description=\"无匹配变量\" />\r\n </NScrollbar>\r\n\r\n <!-- 函数列表 -->\r\n <NScrollbar v-else class=\"variable-panel__list\">\r\n <template v-if=\"filteredFunctions.length > 0\">\r\n <div\r\n v-for=\"func in filteredFunctions\"\r\n :key=\"func.name\"\r\n class=\"variable-panel__func\"\r\n @click=\"$emit('select-function', func)\"\r\n >\r\n <div class=\"variable-panel__func-name\">\r\n <span class=\"variable-panel__func-badge\">ƒ</span>\r\n {{ func.name }}\r\n </div>\r\n <div class=\"variable-panel__func-desc\">\r\n {{ func.description }}\r\n </div>\r\n <div class=\"variable-panel__func-sig\">\r\n {{ func.signature }}\r\n </div>\r\n </div>\r\n </template>\r\n <NEmpty v-else size=\"small\" description=\"无匹配函数\" />\r\n </NScrollbar>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from \"vue\";\r\nimport { NInput, NScrollbar, NEmpty } from \"naive-ui\";\r\nimport type {\r\n FormulaFunction,\r\n FormulaVariable,\r\n FormulaVariableType,\r\n} from \"../types\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\n\r\ninterface Props {\r\n variables: FormulaVariable[];\r\n functions: FormulaFunction[];\r\n}\r\n\r\ninterface Emits {\r\n (e: \"select-variable\", variable: FormulaVariable): void;\r\n (e: \"select-function\", func: FormulaFunction): void;\r\n}\r\n\r\nconst props = defineProps<Props>();\r\ndefineEmits<Emits>();\r\n\r\nconst activeTab = ref<\"variable\" | \"function\">(\"variable\");\r\nconst searchText = ref(\"\");\r\nconst expandedGroups = ref(new Set<string>());\r\n\r\n/* ─── 变量分组 ──────────────────────────────── */\r\n\r\ninterface VariableGroup {\r\n name: string;\r\n items: FormulaVariable[];\r\n}\r\n\r\n/** 过滤并按 group 分组变量 */\r\nconst groupedVariables = computed<VariableGroup[]>(() => {\r\n const keyword = searchText.value.toLowerCase();\r\n const filtered = keyword\r\n ? props.variables.filter(\r\n (v) =>\r\n v.name.toLowerCase().includes(keyword) ||\r\n v.field.toLowerCase().includes(keyword) ||\r\n (v.description ?? \"\").toLowerCase().includes(keyword),\r\n )\r\n : props.variables;\r\n\r\n const map = new Map<string, FormulaVariable[]>();\r\n for (const v of filtered) {\r\n const group = v.group ?? \"其他\";\r\n const list = map.get(group);\r\n if (list) {\r\n list.push(v);\r\n } else {\r\n map.set(group, [v]);\r\n }\r\n }\r\n\r\n return Array.from(map, ([name, items]) => ({ name, items }));\r\n});\r\n\r\n/* 默认展开所有分组 */\r\nwatch(\r\n groupedVariables,\r\n (groups) => {\r\n for (const g of groups) {\r\n expandedGroups.value.add(g.name);\r\n }\r\n },\r\n { immediate: true },\r\n);\r\n\r\n/** 切换分组折叠 */\r\nfunction toggleGroup(name: string) {\r\n if (expandedGroups.value.has(name)) {\r\n expandedGroups.value.delete(name);\r\n } else {\r\n expandedGroups.value.add(name);\r\n }\r\n}\r\n\r\n/* ─── 函数过滤 ──────────────────────────────── */\r\n\r\nconst filteredFunctions = computed(() => {\r\n const keyword = searchText.value.toLowerCase();\r\n if (!keyword) return props.functions;\r\n return props.functions.filter(\r\n (f) =>\r\n f.name.toLowerCase().includes(keyword) ||\r\n f.description.toLowerCase().includes(keyword),\r\n );\r\n});\r\n\r\n/* ─── 变量类型图标 ──────────────────────────── */\r\n\r\n/** 根据变量类型返回图标 */\r\nfunction getTypeIcon(type: FormulaVariableType): string {\r\n switch (type) {\r\n case \"number\":\r\n return \"mdi:numeric\";\r\n case \"text\":\r\n return \"mdi:format-text\";\r\n case \"boolean\":\r\n return \"mdi:toggle-switch-outline\";\r\n default:\r\n return \"mdi:variable\";\r\n }\r\n}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./VariablePanel.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-25\r\n * @Description: 虚拟键盘(运算符 + 数字 + 动作键)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"vk\">\r\n <!-- ═══════ 标题行 ═══════ -->\r\n <div class=\"vk__title\">计算公式</div>\r\n\r\n <!-- ═══════ 键盘主体:运算符 | 数字 ═══════ -->\r\n <div class=\"vk__body\">\r\n <!-- 运算符区 5 列 -->\r\n <div class=\"vk__half vk__half--ops\">\r\n <button\r\n v-for=\"key in operatorKeys\"\r\n :key=\"key.value\"\r\n class=\"vk__key\"\r\n :class=\"[key.color ? `vk__key--${key.color}` : '']\"\r\n :disabled=\"disabled\"\r\n @click=\"$emit('key-press', key)\"\r\n >\r\n {{ key.label }}\r\n </button>\r\n </div>\r\n\r\n <!-- 数字区 5 列 -->\r\n <div class=\"vk__half vk__half--nums\">\r\n <button\r\n v-for=\"key in numberKeys\"\r\n :key=\"key.label + key.value\"\r\n class=\"vk__key\"\r\n :class=\"[key.color ? `vk__key--${key.color}` : '']\"\r\n :disabled=\"disabled\"\r\n @click=\"$emit('key-press', key)\"\r\n >\r\n {{ key.label }}\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- ═══════ 动作行:⌫ + 清空 ═══════ -->\r\n <div class=\"vk__actions\">\r\n <button\r\n v-for=\"key in actionKeys\"\r\n :key=\"key.value\"\r\n class=\"vk__key vk__key--action\"\r\n :disabled=\"disabled\"\r\n @click=\"$emit('action', key.value)\"\r\n >\r\n {{ key.label }}\r\n </button>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport type { FormulaKeyboardKey } from \"../types\";\r\nimport { OPERATOR_KEYS, NUMBER_KEYS, ACTION_KEYS } from \"../constants\";\r\n\r\ninterface Props {\r\n disabled?: boolean;\r\n}\r\n\r\ninterface Emits {\r\n (e: \"key-press\", key: FormulaKeyboardKey): void;\r\n (e: \"action\", action: string): void;\r\n}\r\n\r\nwithDefaults(defineProps<Props>(), {\r\n disabled: false,\r\n});\r\n\r\ndefineEmits<Emits>();\r\n\r\nconst operatorKeys = OPERATOR_KEYS;\r\nconst numberKeys = NUMBER_KEYS;\r\nconst actionKeys = ACTION_KEYS;\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./VirtualKeyboard.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-25\r\n * @Description: 虚拟键盘(运算符 + 数字 + 动作键)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"vk\">\r\n <!-- ═══════ 标题行 ═══════ -->\r\n <div class=\"vk__title\">计算公式</div>\r\n\r\n <!-- ═══════ 键盘主体:运算符 | 数字 ═══════ -->\r\n <div class=\"vk__body\">\r\n <!-- 运算符区 5 列 -->\r\n <div class=\"vk__half vk__half--ops\">\r\n <button\r\n v-for=\"key in operatorKeys\"\r\n :key=\"key.value\"\r\n class=\"vk__key\"\r\n :class=\"[key.color ? `vk__key--${key.color}` : '']\"\r\n :disabled=\"disabled\"\r\n @click=\"$emit('key-press', key)\"\r\n >\r\n {{ key.label }}\r\n </button>\r\n </div>\r\n\r\n <!-- 数字区 5 列 -->\r\n <div class=\"vk__half vk__half--nums\">\r\n <button\r\n v-for=\"key in numberKeys\"\r\n :key=\"key.label + key.value\"\r\n class=\"vk__key\"\r\n :class=\"[key.color ? `vk__key--${key.color}` : '']\"\r\n :disabled=\"disabled\"\r\n @click=\"$emit('key-press', key)\"\r\n >\r\n {{ key.label }}\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- ═══════ 动作行:⌫ + 清空 ═══════ -->\r\n <div class=\"vk__actions\">\r\n <button\r\n v-for=\"key in actionKeys\"\r\n :key=\"key.value\"\r\n class=\"vk__key vk__key--action\"\r\n :disabled=\"disabled\"\r\n @click=\"$emit('action', key.value)\"\r\n >\r\n {{ key.label }}\r\n </button>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport type { FormulaKeyboardKey } from \"../types\";\r\nimport { OPERATOR_KEYS, NUMBER_KEYS, ACTION_KEYS } from \"../constants\";\r\n\r\ninterface Props {\r\n disabled?: boolean;\r\n}\r\n\r\ninterface Emits {\r\n (e: \"key-press\", key: FormulaKeyboardKey): void;\r\n (e: \"action\", action: string): void;\r\n}\r\n\r\nwithDefaults(defineProps<Props>(), {\r\n disabled: false,\r\n});\r\n\r\ndefineEmits<Emits>();\r\n\r\nconst operatorKeys = OPERATOR_KEYS;\r\nconst numberKeys = NUMBER_KEYS;\r\nconst actionKeys = ACTION_KEYS;\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./VirtualKeyboard.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-25\r\n * @Description: 虚拟键盘(运算符 + 数字 + 动作键)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"vk\">\r\n <!-- ═══════ 标题行 ═══════ -->\r\n <div class=\"vk__title\">计算公式</div>\r\n\r\n <!-- ═══════ 键盘主体:运算符 | 数字 ═══════ -->\r\n <div class=\"vk__body\">\r\n <!-- 运算符区 5 列 -->\r\n <div class=\"vk__half vk__half--ops\">\r\n <button\r\n v-for=\"key in operatorKeys\"\r\n :key=\"key.value\"\r\n class=\"vk__key\"\r\n :class=\"[key.color ? `vk__key--${key.color}` : '']\"\r\n :disabled=\"disabled\"\r\n @click=\"$emit('key-press', key)\"\r\n >\r\n {{ key.label }}\r\n </button>\r\n </div>\r\n\r\n <!-- 数字区 5 列 -->\r\n <div class=\"vk__half vk__half--nums\">\r\n <button\r\n v-for=\"key in numberKeys\"\r\n :key=\"key.label + key.value\"\r\n class=\"vk__key\"\r\n :class=\"[key.color ? `vk__key--${key.color}` : '']\"\r\n :disabled=\"disabled\"\r\n @click=\"$emit('key-press', key)\"\r\n >\r\n {{ key.label }}\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- ═══════ 动作行:⌫ + 清空 ═══════ -->\r\n <div class=\"vk__actions\">\r\n <button\r\n v-for=\"key in actionKeys\"\r\n :key=\"key.value\"\r\n class=\"vk__key vk__key--action\"\r\n :disabled=\"disabled\"\r\n @click=\"$emit('action', key.value)\"\r\n >\r\n {{ key.label }}\r\n </button>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport type { FormulaKeyboardKey } from \"../types\";\r\nimport { OPERATOR_KEYS, NUMBER_KEYS, ACTION_KEYS } from \"../constants\";\r\n\r\ninterface Props {\r\n disabled?: boolean;\r\n}\r\n\r\ninterface Emits {\r\n (e: \"key-press\", key: FormulaKeyboardKey): void;\r\n (e: \"action\", action: string): void;\r\n}\r\n\r\nwithDefaults(defineProps<Props>(), {\r\n disabled: false,\r\n});\r\n\r\ndefineEmits<Emits>();\r\n\r\nconst operatorKeys = OPERATOR_KEYS;\r\nconst numberKeys = NUMBER_KEYS;\r\nconst actionKeys = ACTION_KEYS;\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./VirtualKeyboard.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-25\r\n * @Description: 公式计算结果预览\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"formula-preview\">\r\n <div class=\"formula-preview__header\">\r\n <C_Icon name=\"mdi:calculator-variant-outline\" :size=\"15\" />\r\n <span>计算预览</span>\r\n </div>\r\n\r\n <!-- 当没有公式时 -->\r\n <div v-if=\"!formula.trim()\" class=\"formula-preview__empty\">\r\n 输入公式后可预览计算结果\r\n </div>\r\n\r\n <!-- 没有样例数据时 -->\r\n <div v-else-if=\"!hasSampleData\" class=\"formula-preview__empty\">\r\n 传入 sampleData 后可预览计算结果\r\n </div>\r\n\r\n <!-- 有结果 -->\r\n <template v-else>\r\n <!-- 使用到的变量值 -->\r\n <div v-if=\"usedVariables.length > 0\" class=\"formula-preview__vars\">\r\n <div class=\"formula-preview__vars-title\">变量值</div>\r\n <div\r\n v-for=\"item in usedVariables\"\r\n :key=\"item.name\"\r\n class=\"formula-preview__var-row\"\r\n >\r\n <span class=\"formula-preview__var-name\">{{ item.name }}</span>\r\n <span class=\"formula-preview__var-eq\">=</span>\r\n <span class=\"formula-preview__var-value\">{{ item.value }}</span>\r\n </div>\r\n </div>\r\n\r\n <NDivider style=\"margin: 8px 0\" />\r\n\r\n <!-- 计算结果 -->\r\n <div class=\"formula-preview__result\">\r\n <span class=\"formula-preview__result-label\">结果</span>\r\n <span\r\n v-if=\"evalResult.success && evalResult.result !== undefined\"\r\n class=\"formula-preview__result-value\"\r\n >\r\n {{ formatResult(evalResult.result) }}\r\n </span>\r\n <span\r\n v-else-if=\"evalResult.error\"\r\n class=\"formula-preview__result-error\"\r\n >\r\n <C_Icon name=\"mdi:alert-circle-outline\" :size=\"14\" />\r\n {{ evalResult.error }}\r\n </span>\r\n <span v-else class=\"formula-preview__result-empty\"> — </span>\r\n </div>\r\n </template>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { NDivider } from \"naive-ui\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\n\r\ninterface EvalResult {\r\n success: boolean;\r\n result: unknown;\r\n error?: string;\r\n}\r\n\r\ninterface UsedVariable {\r\n name: string;\r\n value: unknown;\r\n}\r\n\r\ninterface Props {\r\n formula: string;\r\n evalResult: EvalResult;\r\n usedVariables: UsedVariable[];\r\n hasSampleData: boolean;\r\n}\r\n\r\ndefineProps<Props>();\r\n\r\n/** 格式化计算结果 */\r\nfunction formatResult(value: unknown): string {\r\n if (typeof value === \"number\") {\r\n /* 数字保留合理精度 */\r\n if (Number.isInteger(value)) return String(value);\r\n return Number(value.toFixed(6)).toString();\r\n }\r\n if (typeof value === \"boolean\") {\r\n return value ? \"真 (true)\" : \"假 (false)\";\r\n }\r\n return String(value);\r\n}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./FormulaPreview.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-25\r\n * @Description: 公式计算结果预览\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"formula-preview\">\r\n <div class=\"formula-preview__header\">\r\n <C_Icon name=\"mdi:calculator-variant-outline\" :size=\"15\" />\r\n <span>计算预览</span>\r\n </div>\r\n\r\n <!-- 当没有公式时 -->\r\n <div v-if=\"!formula.trim()\" class=\"formula-preview__empty\">\r\n 输入公式后可预览计算结果\r\n </div>\r\n\r\n <!-- 没有样例数据时 -->\r\n <div v-else-if=\"!hasSampleData\" class=\"formula-preview__empty\">\r\n 传入 sampleData 后可预览计算结果\r\n </div>\r\n\r\n <!-- 有结果 -->\r\n <template v-else>\r\n <!-- 使用到的变量值 -->\r\n <div v-if=\"usedVariables.length > 0\" class=\"formula-preview__vars\">\r\n <div class=\"formula-preview__vars-title\">变量值</div>\r\n <div\r\n v-for=\"item in usedVariables\"\r\n :key=\"item.name\"\r\n class=\"formula-preview__var-row\"\r\n >\r\n <span class=\"formula-preview__var-name\">{{ item.name }}</span>\r\n <span class=\"formula-preview__var-eq\">=</span>\r\n <span class=\"formula-preview__var-value\">{{ item.value }}</span>\r\n </div>\r\n </div>\r\n\r\n <NDivider style=\"margin: 8px 0\" />\r\n\r\n <!-- 计算结果 -->\r\n <div class=\"formula-preview__result\">\r\n <span class=\"formula-preview__result-label\">结果</span>\r\n <span\r\n v-if=\"evalResult.success && evalResult.result !== undefined\"\r\n class=\"formula-preview__result-value\"\r\n >\r\n {{ formatResult(evalResult.result) }}\r\n </span>\r\n <span\r\n v-else-if=\"evalResult.error\"\r\n class=\"formula-preview__result-error\"\r\n >\r\n <C_Icon name=\"mdi:alert-circle-outline\" :size=\"14\" />\r\n {{ evalResult.error }}\r\n </span>\r\n <span v-else class=\"formula-preview__result-empty\"> — </span>\r\n </div>\r\n </template>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { NDivider } from \"naive-ui\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\n\r\ninterface EvalResult {\r\n success: boolean;\r\n result: unknown;\r\n error?: string;\r\n}\r\n\r\ninterface UsedVariable {\r\n name: string;\r\n value: unknown;\r\n}\r\n\r\ninterface Props {\r\n formula: string;\r\n evalResult: EvalResult;\r\n usedVariables: UsedVariable[];\r\n hasSampleData: boolean;\r\n}\r\n\r\ndefineProps<Props>();\r\n\r\n/** 格式化计算结果 */\r\nfunction formatResult(value: unknown): string {\r\n if (typeof value === \"number\") {\r\n /* 数字保留合理精度 */\r\n if (Number.isInteger(value)) return String(value);\r\n return Number(value.toFixed(6)).toString();\r\n }\r\n if (typeof value === \"boolean\") {\r\n return value ? \"真 (true)\" : \"假 (false)\";\r\n }\r\n return String(value);\r\n}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./FormulaPreview.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-25\r\n * @Description: 公式计算结果预览\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"formula-preview\">\r\n <div class=\"formula-preview__header\">\r\n <C_Icon name=\"mdi:calculator-variant-outline\" :size=\"15\" />\r\n <span>计算预览</span>\r\n </div>\r\n\r\n <!-- 当没有公式时 -->\r\n <div v-if=\"!formula.trim()\" class=\"formula-preview__empty\">\r\n 输入公式后可预览计算结果\r\n </div>\r\n\r\n <!-- 没有样例数据时 -->\r\n <div v-else-if=\"!hasSampleData\" class=\"formula-preview__empty\">\r\n 传入 sampleData 后可预览计算结果\r\n </div>\r\n\r\n <!-- 有结果 -->\r\n <template v-else>\r\n <!-- 使用到的变量值 -->\r\n <div v-if=\"usedVariables.length > 0\" class=\"formula-preview__vars\">\r\n <div class=\"formula-preview__vars-title\">变量值</div>\r\n <div\r\n v-for=\"item in usedVariables\"\r\n :key=\"item.name\"\r\n class=\"formula-preview__var-row\"\r\n >\r\n <span class=\"formula-preview__var-name\">{{ item.name }}</span>\r\n <span class=\"formula-preview__var-eq\">=</span>\r\n <span class=\"formula-preview__var-value\">{{ item.value }}</span>\r\n </div>\r\n </div>\r\n\r\n <NDivider style=\"margin: 8px 0\" />\r\n\r\n <!-- 计算结果 -->\r\n <div class=\"formula-preview__result\">\r\n <span class=\"formula-preview__result-label\">结果</span>\r\n <span\r\n v-if=\"evalResult.success && evalResult.result !== undefined\"\r\n class=\"formula-preview__result-value\"\r\n >\r\n {{ formatResult(evalResult.result) }}\r\n </span>\r\n <span\r\n v-else-if=\"evalResult.error\"\r\n class=\"formula-preview__result-error\"\r\n >\r\n <C_Icon name=\"mdi:alert-circle-outline\" :size=\"14\" />\r\n {{ evalResult.error }}\r\n </span>\r\n <span v-else class=\"formula-preview__result-empty\"> — </span>\r\n </div>\r\n </template>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { NDivider } from \"naive-ui\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\n\r\ninterface EvalResult {\r\n success: boolean;\r\n result: unknown;\r\n error?: string;\r\n}\r\n\r\ninterface UsedVariable {\r\n name: string;\r\n value: unknown;\r\n}\r\n\r\ninterface Props {\r\n formula: string;\r\n evalResult: EvalResult;\r\n usedVariables: UsedVariable[];\r\n hasSampleData: boolean;\r\n}\r\n\r\ndefineProps<Props>();\r\n\r\n/** 格式化计算结果 */\r\nfunction formatResult(value: unknown): string {\r\n if (typeof value === \"number\") {\r\n /* 数字保留合理精度 */\r\n if (Number.isInteger(value)) return String(value);\r\n return Number(value.toFixed(6)).toString();\r\n }\r\n if (typeof value === \"boolean\") {\r\n return value ? \"真 (true)\" : \"假 (false)\";\r\n }\r\n return String(value);\r\n}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./FormulaPreview.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-25\r\n * @Description: 公式编辑器组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-formula\" :style=\"{ height: containerHeight }\">\r\n <!-- ═══════ 顶部标题行 ═══════ -->\r\n <div class=\"c-formula__title-row\">\r\n <div class=\"c-formula__title\">\r\n <C_Icon name=\"mdi:function-variant\" :size=\"18\" />\r\n <span>公式编辑器</span>\r\n </div>\r\n <NTag :type=\"validation.valid ? 'success' : 'error'\" size=\"small\" round>\r\n <template #icon>\r\n <C_Icon\r\n :name=\"validation.valid ? 'mdi:check-circle' : 'mdi:alert-circle'\"\r\n :size=\"14\"\r\n />\r\n </template>\r\n {{ validation.valid ? \"合法\" : \"错误\" }}\r\n </NTag>\r\n </div>\r\n\r\n <!-- ═══════ 主内容区域 ═══════ -->\r\n <div class=\"c-formula__body\">\r\n <!-- 左侧:变量面板 -->\r\n <div v-if=\"props.showVariablePanel\" class=\"c-formula__sidebar\">\r\n <VariablePanel\r\n :variables=\"variableList\"\r\n :functions=\"functionList\"\r\n @select-variable=\"handleSelectVariable\"\r\n @select-function=\"handleSelectFunction\"\r\n />\r\n </div>\r\n\r\n <!-- 右侧:编辑区 + 键盘 + 预览 -->\r\n <div class=\"c-formula__main\">\r\n <!-- 公式输入区 -->\r\n <FormulaInput\r\n ref=\"formulaInputRef\"\r\n :formula=\"formula\"\r\n :tokens=\"tokens\"\r\n :validation=\"validation\"\r\n :variable-names=\"parser.variableNames.value\"\r\n :disabled=\"props.disabled\"\r\n :placeholder=\"props.placeholder\"\r\n @update:formula=\"handleFormulaUpdate\"\r\n />\r\n\r\n <!-- 虚拟键盘 -->\r\n <div v-if=\"props.showKeyboard\" class=\"c-formula__keyboard\">\r\n <VirtualKeyboard\r\n :disabled=\"props.disabled\"\r\n @key-press=\"handleKeyPress\"\r\n @action=\"handleAction\"\r\n />\r\n </div>\r\n\r\n <!-- 计算预览 -->\r\n <FormulaPreview\r\n v-if=\"props.showPreview && hasSampleData\"\r\n :formula=\"formula\"\r\n :eval-result=\"evalResult\"\r\n :used-variables=\"usedVariableValues\"\r\n :has-sample-data=\"hasSampleData\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, onMounted } from \"vue\";\r\nimport { NTag } from \"naive-ui\";\r\nimport type {\r\n FormulaEditorEmits,\r\n FormulaEditorExpose,\r\n FormulaEditorProps,\r\n FormulaFunction,\r\n FormulaKeyboardKey,\r\n FormulaVariable,\r\n} from \"./types\";\r\nimport { DEFAULT_FUNCTIONS } from \"./constants\";\r\nimport { useFormulaParser } from \"./composables/useFormulaParser\";\r\nimport { useFormulaEvaluator } from \"./composables/useFormulaEvaluator\";\r\nimport C_Icon from \"../C_Icon/index.vue\";\r\nimport FormulaInput from \"./components/FormulaInput.vue\";\r\nimport VariablePanel from \"./components/VariablePanel.vue\";\r\nimport VirtualKeyboard from \"./components/VirtualKeyboard.vue\";\r\nimport FormulaPreview from \"./components/FormulaPreview.vue\";\r\n\r\ndefineOptions({ name: \"C_FormulaEditor\" });\r\n\r\n/* ─── Props & Emits ─────────────────────────── */\r\n\r\nconst props = withDefaults(defineProps<FormulaEditorProps>(), {\r\n modelValue: \"\",\r\n variables: () => [],\r\n functions: undefined,\r\n sampleData: undefined,\r\n disabled: false,\r\n placeholder: \"点击变量或使用键盘输入公式,变量用 [变量名] 包裹\",\r\n height: \"auto\",\r\n showPreview: true,\r\n showKeyboard: true,\r\n showVariablePanel: true,\r\n});\r\n\r\nconst emit = defineEmits<FormulaEditorEmits>();\r\n\r\n/* ─── 组件引用 ──────────────────────────────── */\r\n\r\nconst formulaInputRef = ref<InstanceType<typeof FormulaInput>>();\r\n\r\n/* ─── 变量与函数列表 ────────────────────────── */\r\n\r\nconst variableList = computed(() => props.variables ?? []);\r\nconst functionList = computed(() => props.functions ?? DEFAULT_FUNCTIONS);\r\n\r\n/* ─── 初始化组合函数 ────────────────────────── */\r\n\r\nconst parser = useFormulaParser(variableList, functionList);\r\nconst evaluator = useFormulaEvaluator(variableList);\r\n\r\n/* ─── 本地状态 ──────────────────────────────── */\r\n\r\n/** 公式字符串 */\r\nconst formula = ref(props.modelValue || \"\");\r\n\r\n/** Token 列表 */\r\nconst tokens = computed(() => parser.tokenize(formula.value));\r\n\r\n/** 校验结果 */\r\nconst validation = computed(() => parser.validate(formula.value));\r\n\r\n/* ─── 容器高度 ──────────────────────────────── */\r\n\r\nconst containerHeight = computed(() => {\r\n if (typeof props.height === \"number\") return `${props.height}px`;\r\n return props.height;\r\n});\r\n\r\n/* ─── 求值相关 ──────────────────────────────── */\r\n\r\n/** 是否有样例数据 */\r\nconst hasSampleData = computed(\r\n () => !!props.sampleData && Object.keys(props.sampleData).length > 0,\r\n);\r\n\r\n/** 求值结果 */\r\nconst evalResult = computed(() => {\r\n if (\r\n !formula.value.trim() ||\r\n !hasSampleData.value ||\r\n !validation.value.valid\r\n ) {\r\n return { success: true, result: undefined };\r\n }\r\n return evaluator.evaluate(formula.value, props.sampleData!);\r\n});\r\n\r\n/** 提取公式中使用到的变量 + 对应样例数据值 */\r\nconst usedVariableValues = computed(() => {\r\n if (!hasSampleData.value) return [];\r\n const names = evaluator.extractVariableNames(formula.value);\r\n return names.map((name) => {\r\n const variable = variableList.value.find((v) => v.name === name);\r\n const field = variable?.field ?? name;\r\n const value = props.sampleData?.[field];\r\n return { name, value: value ?? \"未提供\" };\r\n });\r\n});\r\n\r\n/* ─── 初始化 ────────────────────────────────── */\r\n\r\nonMounted(() => {\r\n if (props.modelValue) {\r\n formula.value = props.modelValue;\r\n }\r\n});\r\n\r\n/* ─── 监听外部 v-model ─────────────────────── */\r\n\r\nwatch(\r\n () => props.modelValue,\r\n (newVal) => {\r\n if (newVal !== undefined && newVal !== formula.value) {\r\n formula.value = newVal;\r\n }\r\n },\r\n);\r\n\r\n/* ─── 监听内部公式变更 → 同步外部 ──────────── */\r\n\r\nwatch(formula, (newFormula) => {\r\n emit(\"update:modelValue\", newFormula);\r\n emit(\"change\", newFormula);\r\n});\r\n\r\n/* ─── 监听校验状态 ─────────────────────────── */\r\n\r\nwatch(validation, (v) => {\r\n emit(\"validation-change\", v);\r\n});\r\n\r\n/* ─── 公式更新处理 ─────────────────────────── */\r\n\r\n/** FormulaInput 输入更新 */\r\nfunction handleFormulaUpdate(value: string) {\r\n formula.value = value;\r\n}\r\n\r\n/* ─── 变量面板交互 ─────────────────────────── */\r\n\r\n/** 选择变量 → 插入到光标 */\r\nfunction handleSelectVariable(variable: FormulaVariable) {\r\n formulaInputRef.value?.insertAtCursor(`[${variable.name}]`);\r\n}\r\n\r\n/** 选择函数 → 插入到光标 */\r\nfunction handleSelectFunction(func: FormulaFunction) {\r\n formulaInputRef.value?.insertAtCursor(`${func.name}(`);\r\n}\r\n\r\n/* ─── 虚拟键盘交互 ─────────────────────────── */\r\n\r\n/** 按键 → 插入值到光标 */\r\nfunction handleKeyPress(key: FormulaKeyboardKey) {\r\n formulaInputRef.value?.insertAtCursor(key.value);\r\n}\r\n\r\n/** 动作键处理 */\r\nfunction handleAction(action: string) {\r\n if (action === \"BACKSPACE\") {\r\n formulaInputRef.value?.backspace();\r\n } else if (action === \"CLEAR\") {\r\n formula.value = \"\";\r\n }\r\n}\r\n\r\n/* ─── 暴露方法 ─────────────────────────────── */\r\n\r\ndefineExpose<FormulaEditorExpose>({\r\n getValue: () => formula.value,\r\n setValue: (expr: string) => {\r\n formula.value = expr;\r\n },\r\n reset: () => {\r\n formula.value = props.modelValue || \"\";\r\n },\r\n validate: () => parser.validate(formula.value),\r\n insertAtCursor: (text: string) => {\r\n formulaInputRef.value?.insertAtCursor(text);\r\n },\r\n focus: () => {\r\n formulaInputRef.value?.focus();\r\n },\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-25\r\n * @Description: 公式编辑器组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-formula\" :style=\"{ height: containerHeight }\">\r\n <!-- ═══════ 顶部标题行 ═══════ -->\r\n <div class=\"c-formula__title-row\">\r\n <div class=\"c-formula__title\">\r\n <C_Icon name=\"mdi:function-variant\" :size=\"18\" />\r\n <span>公式编辑器</span>\r\n </div>\r\n <NTag :type=\"validation.valid ? 'success' : 'error'\" size=\"small\" round>\r\n <template #icon>\r\n <C_Icon\r\n :name=\"validation.valid ? 'mdi:check-circle' : 'mdi:alert-circle'\"\r\n :size=\"14\"\r\n />\r\n </template>\r\n {{ validation.valid ? \"合法\" : \"错误\" }}\r\n </NTag>\r\n </div>\r\n\r\n <!-- ═══════ 主内容区域 ═══════ -->\r\n <div class=\"c-formula__body\">\r\n <!-- 左侧:变量面板 -->\r\n <div v-if=\"props.showVariablePanel\" class=\"c-formula__sidebar\">\r\n <VariablePanel\r\n :variables=\"variableList\"\r\n :functions=\"functionList\"\r\n @select-variable=\"handleSelectVariable\"\r\n @select-function=\"handleSelectFunction\"\r\n />\r\n </div>\r\n\r\n <!-- 右侧:编辑区 + 键盘 + 预览 -->\r\n <div class=\"c-formula__main\">\r\n <!-- 公式输入区 -->\r\n <FormulaInput\r\n ref=\"formulaInputRef\"\r\n :formula=\"formula\"\r\n :tokens=\"tokens\"\r\n :validation=\"validation\"\r\n :variable-names=\"parser.variableNames.value\"\r\n :disabled=\"props.disabled\"\r\n :placeholder=\"props.placeholder\"\r\n @update:formula=\"handleFormulaUpdate\"\r\n />\r\n\r\n <!-- 虚拟键盘 -->\r\n <div v-if=\"props.showKeyboard\" class=\"c-formula__keyboard\">\r\n <VirtualKeyboard\r\n :disabled=\"props.disabled\"\r\n @key-press=\"handleKeyPress\"\r\n @action=\"handleAction\"\r\n />\r\n </div>\r\n\r\n <!-- 计算预览 -->\r\n <FormulaPreview\r\n v-if=\"props.showPreview && hasSampleData\"\r\n :formula=\"formula\"\r\n :eval-result=\"evalResult\"\r\n :used-variables=\"usedVariableValues\"\r\n :has-sample-data=\"hasSampleData\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, onMounted } from \"vue\";\r\nimport { NTag } from \"naive-ui\";\r\nimport type {\r\n FormulaEditorEmits,\r\n FormulaEditorExpose,\r\n FormulaEditorProps,\r\n FormulaFunction,\r\n FormulaKeyboardKey,\r\n FormulaVariable,\r\n} from \"./types\";\r\nimport { DEFAULT_FUNCTIONS } from \"./constants\";\r\nimport { useFormulaParser } from \"./composables/useFormulaParser\";\r\nimport { useFormulaEvaluator } from \"./composables/useFormulaEvaluator\";\r\nimport C_Icon from \"../C_Icon/index.vue\";\r\nimport FormulaInput from \"./components/FormulaInput.vue\";\r\nimport VariablePanel from \"./components/VariablePanel.vue\";\r\nimport VirtualKeyboard from \"./components/VirtualKeyboard.vue\";\r\nimport FormulaPreview from \"./components/FormulaPreview.vue\";\r\n\r\ndefineOptions({ name: \"C_FormulaEditor\" });\r\n\r\n/* ─── Props & Emits ─────────────────────────── */\r\n\r\nconst props = withDefaults(defineProps<FormulaEditorProps>(), {\r\n modelValue: \"\",\r\n variables: () => [],\r\n functions: undefined,\r\n sampleData: undefined,\r\n disabled: false,\r\n placeholder: \"点击变量或使用键盘输入公式,变量用 [变量名] 包裹\",\r\n height: \"auto\",\r\n showPreview: true,\r\n showKeyboard: true,\r\n showVariablePanel: true,\r\n});\r\n\r\nconst emit = defineEmits<FormulaEditorEmits>();\r\n\r\n/* ─── 组件引用 ──────────────────────────────── */\r\n\r\nconst formulaInputRef = ref<InstanceType<typeof FormulaInput>>();\r\n\r\n/* ─── 变量与函数列表 ────────────────────────── */\r\n\r\nconst variableList = computed(() => props.variables ?? []);\r\nconst functionList = computed(() => props.functions ?? DEFAULT_FUNCTIONS);\r\n\r\n/* ─── 初始化组合函数 ────────────────────────── */\r\n\r\nconst parser = useFormulaParser(variableList, functionList);\r\nconst evaluator = useFormulaEvaluator(variableList);\r\n\r\n/* ─── 本地状态 ──────────────────────────────── */\r\n\r\n/** 公式字符串 */\r\nconst formula = ref(props.modelValue || \"\");\r\n\r\n/** Token 列表 */\r\nconst tokens = computed(() => parser.tokenize(formula.value));\r\n\r\n/** 校验结果 */\r\nconst validation = computed(() => parser.validate(formula.value));\r\n\r\n/* ─── 容器高度 ──────────────────────────────── */\r\n\r\nconst containerHeight = computed(() => {\r\n if (typeof props.height === \"number\") return `${props.height}px`;\r\n return props.height;\r\n});\r\n\r\n/* ─── 求值相关 ──────────────────────────────── */\r\n\r\n/** 是否有样例数据 */\r\nconst hasSampleData = computed(\r\n () => !!props.sampleData && Object.keys(props.sampleData).length > 0,\r\n);\r\n\r\n/** 求值结果 */\r\nconst evalResult = computed(() => {\r\n if (\r\n !formula.value.trim() ||\r\n !hasSampleData.value ||\r\n !validation.value.valid\r\n ) {\r\n return { success: true, result: undefined };\r\n }\r\n return evaluator.evaluate(formula.value, props.sampleData!);\r\n});\r\n\r\n/** 提取公式中使用到的变量 + 对应样例数据值 */\r\nconst usedVariableValues = computed(() => {\r\n if (!hasSampleData.value) return [];\r\n const names = evaluator.extractVariableNames(formula.value);\r\n return names.map((name) => {\r\n const variable = variableList.value.find((v) => v.name === name);\r\n const field = variable?.field ?? name;\r\n const value = props.sampleData?.[field];\r\n return { name, value: value ?? \"未提供\" };\r\n });\r\n});\r\n\r\n/* ─── 初始化 ────────────────────────────────── */\r\n\r\nonMounted(() => {\r\n if (props.modelValue) {\r\n formula.value = props.modelValue;\r\n }\r\n});\r\n\r\n/* ─── 监听外部 v-model ─────────────────────── */\r\n\r\nwatch(\r\n () => props.modelValue,\r\n (newVal) => {\r\n if (newVal !== undefined && newVal !== formula.value) {\r\n formula.value = newVal;\r\n }\r\n },\r\n);\r\n\r\n/* ─── 监听内部公式变更 → 同步外部 ──────────── */\r\n\r\nwatch(formula, (newFormula) => {\r\n emit(\"update:modelValue\", newFormula);\r\n emit(\"change\", newFormula);\r\n});\r\n\r\n/* ─── 监听校验状态 ─────────────────────────── */\r\n\r\nwatch(validation, (v) => {\r\n emit(\"validation-change\", v);\r\n});\r\n\r\n/* ─── 公式更新处理 ─────────────────────────── */\r\n\r\n/** FormulaInput 输入更新 */\r\nfunction handleFormulaUpdate(value: string) {\r\n formula.value = value;\r\n}\r\n\r\n/* ─── 变量面板交互 ─────────────────────────── */\r\n\r\n/** 选择变量 → 插入到光标 */\r\nfunction handleSelectVariable(variable: FormulaVariable) {\r\n formulaInputRef.value?.insertAtCursor(`[${variable.name}]`);\r\n}\r\n\r\n/** 选择函数 → 插入到光标 */\r\nfunction handleSelectFunction(func: FormulaFunction) {\r\n formulaInputRef.value?.insertAtCursor(`${func.name}(`);\r\n}\r\n\r\n/* ─── 虚拟键盘交互 ─────────────────────────── */\r\n\r\n/** 按键 → 插入值到光标 */\r\nfunction handleKeyPress(key: FormulaKeyboardKey) {\r\n formulaInputRef.value?.insertAtCursor(key.value);\r\n}\r\n\r\n/** 动作键处理 */\r\nfunction handleAction(action: string) {\r\n if (action === \"BACKSPACE\") {\r\n formulaInputRef.value?.backspace();\r\n } else if (action === \"CLEAR\") {\r\n formula.value = \"\";\r\n }\r\n}\r\n\r\n/* ─── 暴露方法 ─────────────────────────────── */\r\n\r\ndefineExpose<FormulaEditorExpose>({\r\n getValue: () => formula.value,\r\n setValue: (expr: string) => {\r\n formula.value = expr;\r\n },\r\n reset: () => {\r\n formula.value = props.modelValue || \"\";\r\n },\r\n validate: () => parser.validate(formula.value),\r\n insertAtCursor: (text: string) => {\r\n formulaInputRef.value?.insertAtCursor(text);\r\n },\r\n focus: () => {\r\n formulaInputRef.value?.focus();\r\n },\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-25\r\n * @Description: 公式编辑器组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-formula\" :style=\"{ height: containerHeight }\">\r\n <!-- ═══════ 顶部标题行 ═══════ -->\r\n <div class=\"c-formula__title-row\">\r\n <div class=\"c-formula__title\">\r\n <C_Icon name=\"mdi:function-variant\" :size=\"18\" />\r\n <span>公式编辑器</span>\r\n </div>\r\n <NTag :type=\"validation.valid ? 'success' : 'error'\" size=\"small\" round>\r\n <template #icon>\r\n <C_Icon\r\n :name=\"validation.valid ? 'mdi:check-circle' : 'mdi:alert-circle'\"\r\n :size=\"14\"\r\n />\r\n </template>\r\n {{ validation.valid ? \"合法\" : \"错误\" }}\r\n </NTag>\r\n </div>\r\n\r\n <!-- ═══════ 主内容区域 ═══════ -->\r\n <div class=\"c-formula__body\">\r\n <!-- 左侧:变量面板 -->\r\n <div v-if=\"props.showVariablePanel\" class=\"c-formula__sidebar\">\r\n <VariablePanel\r\n :variables=\"variableList\"\r\n :functions=\"functionList\"\r\n @select-variable=\"handleSelectVariable\"\r\n @select-function=\"handleSelectFunction\"\r\n />\r\n </div>\r\n\r\n <!-- 右侧:编辑区 + 键盘 + 预览 -->\r\n <div class=\"c-formula__main\">\r\n <!-- 公式输入区 -->\r\n <FormulaInput\r\n ref=\"formulaInputRef\"\r\n :formula=\"formula\"\r\n :tokens=\"tokens\"\r\n :validation=\"validation\"\r\n :variable-names=\"parser.variableNames.value\"\r\n :disabled=\"props.disabled\"\r\n :placeholder=\"props.placeholder\"\r\n @update:formula=\"handleFormulaUpdate\"\r\n />\r\n\r\n <!-- 虚拟键盘 -->\r\n <div v-if=\"props.showKeyboard\" class=\"c-formula__keyboard\">\r\n <VirtualKeyboard\r\n :disabled=\"props.disabled\"\r\n @key-press=\"handleKeyPress\"\r\n @action=\"handleAction\"\r\n />\r\n </div>\r\n\r\n <!-- 计算预览 -->\r\n <FormulaPreview\r\n v-if=\"props.showPreview && hasSampleData\"\r\n :formula=\"formula\"\r\n :eval-result=\"evalResult\"\r\n :used-variables=\"usedVariableValues\"\r\n :has-sample-data=\"hasSampleData\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, onMounted } from \"vue\";\r\nimport { NTag } from \"naive-ui\";\r\nimport type {\r\n FormulaEditorEmits,\r\n FormulaEditorExpose,\r\n FormulaEditorProps,\r\n FormulaFunction,\r\n FormulaKeyboardKey,\r\n FormulaVariable,\r\n} from \"./types\";\r\nimport { DEFAULT_FUNCTIONS } from \"./constants\";\r\nimport { useFormulaParser } from \"./composables/useFormulaParser\";\r\nimport { useFormulaEvaluator } from \"./composables/useFormulaEvaluator\";\r\nimport C_Icon from \"../C_Icon/index.vue\";\r\nimport FormulaInput from \"./components/FormulaInput.vue\";\r\nimport VariablePanel from \"./components/VariablePanel.vue\";\r\nimport VirtualKeyboard from \"./components/VirtualKeyboard.vue\";\r\nimport FormulaPreview from \"./components/FormulaPreview.vue\";\r\n\r\ndefineOptions({ name: \"C_FormulaEditor\" });\r\n\r\n/* ─── Props & Emits ─────────────────────────── */\r\n\r\nconst props = withDefaults(defineProps<FormulaEditorProps>(), {\r\n modelValue: \"\",\r\n variables: () => [],\r\n functions: undefined,\r\n sampleData: undefined,\r\n disabled: false,\r\n placeholder: \"点击变量或使用键盘输入公式,变量用 [变量名] 包裹\",\r\n height: \"auto\",\r\n showPreview: true,\r\n showKeyboard: true,\r\n showVariablePanel: true,\r\n});\r\n\r\nconst emit = defineEmits<FormulaEditorEmits>();\r\n\r\n/* ─── 组件引用 ──────────────────────────────── */\r\n\r\nconst formulaInputRef = ref<InstanceType<typeof FormulaInput>>();\r\n\r\n/* ─── 变量与函数列表 ────────────────────────── */\r\n\r\nconst variableList = computed(() => props.variables ?? []);\r\nconst functionList = computed(() => props.functions ?? DEFAULT_FUNCTIONS);\r\n\r\n/* ─── 初始化组合函数 ────────────────────────── */\r\n\r\nconst parser = useFormulaParser(variableList, functionList);\r\nconst evaluator = useFormulaEvaluator(variableList);\r\n\r\n/* ─── 本地状态 ──────────────────────────────── */\r\n\r\n/** 公式字符串 */\r\nconst formula = ref(props.modelValue || \"\");\r\n\r\n/** Token 列表 */\r\nconst tokens = computed(() => parser.tokenize(formula.value));\r\n\r\n/** 校验结果 */\r\nconst validation = computed(() => parser.validate(formula.value));\r\n\r\n/* ─── 容器高度 ──────────────────────────────── */\r\n\r\nconst containerHeight = computed(() => {\r\n if (typeof props.height === \"number\") return `${props.height}px`;\r\n return props.height;\r\n});\r\n\r\n/* ─── 求值相关 ──────────────────────────────── */\r\n\r\n/** 是否有样例数据 */\r\nconst hasSampleData = computed(\r\n () => !!props.sampleData && Object.keys(props.sampleData).length > 0,\r\n);\r\n\r\n/** 求值结果 */\r\nconst evalResult = computed(() => {\r\n if (\r\n !formula.value.trim() ||\r\n !hasSampleData.value ||\r\n !validation.value.valid\r\n ) {\r\n return { success: true, result: undefined };\r\n }\r\n return evaluator.evaluate(formula.value, props.sampleData!);\r\n});\r\n\r\n/** 提取公式中使用到的变量 + 对应样例数据值 */\r\nconst usedVariableValues = computed(() => {\r\n if (!hasSampleData.value) return [];\r\n const names = evaluator.extractVariableNames(formula.value);\r\n return names.map((name) => {\r\n const variable = variableList.value.find((v) => v.name === name);\r\n const field = variable?.field ?? name;\r\n const value = props.sampleData?.[field];\r\n return { name, value: value ?? \"未提供\" };\r\n });\r\n});\r\n\r\n/* ─── 初始化 ────────────────────────────────── */\r\n\r\nonMounted(() => {\r\n if (props.modelValue) {\r\n formula.value = props.modelValue;\r\n }\r\n});\r\n\r\n/* ─── 监听外部 v-model ─────────────────────── */\r\n\r\nwatch(\r\n () => props.modelValue,\r\n (newVal) => {\r\n if (newVal !== undefined && newVal !== formula.value) {\r\n formula.value = newVal;\r\n }\r\n },\r\n);\r\n\r\n/* ─── 监听内部公式变更 → 同步外部 ──────────── */\r\n\r\nwatch(formula, (newFormula) => {\r\n emit(\"update:modelValue\", newFormula);\r\n emit(\"change\", newFormula);\r\n});\r\n\r\n/* ─── 监听校验状态 ─────────────────────────── */\r\n\r\nwatch(validation, (v) => {\r\n emit(\"validation-change\", v);\r\n});\r\n\r\n/* ─── 公式更新处理 ─────────────────────────── */\r\n\r\n/** FormulaInput 输入更新 */\r\nfunction handleFormulaUpdate(value: string) {\r\n formula.value = value;\r\n}\r\n\r\n/* ─── 变量面板交互 ─────────────────────────── */\r\n\r\n/** 选择变量 → 插入到光标 */\r\nfunction handleSelectVariable(variable: FormulaVariable) {\r\n formulaInputRef.value?.insertAtCursor(`[${variable.name}]`);\r\n}\r\n\r\n/** 选择函数 → 插入到光标 */\r\nfunction handleSelectFunction(func: FormulaFunction) {\r\n formulaInputRef.value?.insertAtCursor(`${func.name}(`);\r\n}\r\n\r\n/* ─── 虚拟键盘交互 ─────────────────────────── */\r\n\r\n/** 按键 → 插入值到光标 */\r\nfunction handleKeyPress(key: FormulaKeyboardKey) {\r\n formulaInputRef.value?.insertAtCursor(key.value);\r\n}\r\n\r\n/** 动作键处理 */\r\nfunction handleAction(action: string) {\r\n if (action === \"BACKSPACE\") {\r\n formulaInputRef.value?.backspace();\r\n } else if (action === \"CLEAR\") {\r\n formula.value = \"\";\r\n }\r\n}\r\n\r\n/* ─── 暴露方法 ─────────────────────────────── */\r\n\r\ndefineExpose<FormulaEditorExpose>({\r\n getValue: () => formula.value,\r\n setValue: (expr: string) => {\r\n formula.value = expr;\r\n },\r\n reset: () => {\r\n formula.value = props.modelValue || \"\";\r\n },\r\n validate: () => parser.validate(formula.value),\r\n insertAtCursor: (text: string) => {\r\n formulaInputRef.value?.insertAtCursor(text);\r\n },\r\n focus: () => {\r\n formulaInputRef.value?.focus();\r\n },\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n"],"mappings":";;;;;;;;AAeA,MAAa,gBAAsC;CAEjD;EAAE,OAAO;EAAK,OAAO;EAAO,MAAM;EAAS,OAAO;EAAW;CAC7D;EAAE,OAAO;EAAK,OAAO;EAAO,MAAM;EAAS,OAAO;EAAW;CAC7D;EAAE,OAAO;EAAO,OAAO;EAAS,MAAM;EAAS,OAAO;EAAW;CACjE;EAAE,OAAO;EAAM,OAAO;EAAQ,MAAM;EAAS,OAAO;EAAW;CAC/D;EAAE,OAAO;EAAK,OAAO;EAAK,MAAM;EAAS;CAEzC;EAAE,OAAO;EAAK,OAAO;EAAK,MAAM;EAAS;CACzC;EAAE,OAAO;EAAK,OAAO;EAAM,MAAM;EAAS;CAC1C;EAAE,OAAO;EAAM,OAAO;EAAQ,MAAM;EAAW;CAC/C;EAAE,OAAO;EAAK,OAAO;EAAO,MAAM;EAAW;CAC7C;EAAE,OAAO;EAAK,OAAO;EAAO,MAAM;EAAW;CAE7C;EAAE,OAAO;EAAK,OAAO;EAAQ,MAAM;EAAW;CAC9C;EAAE,OAAO;EAAK,OAAO;EAAQ,MAAM;EAAW;CAC9C;EAAE,OAAO;EAAK,OAAO;EAAQ,MAAM;EAAW;CAC9C;EAAE,OAAO;EAAK,OAAO;EAAO,MAAM;EAAY;CAC/C;;AAGD,MAAa,cAAoC;CAE/C;EAAE,OAAO;EAAK,OAAO;EAAK,MAAM;EAAU;CAC1C;EAAE,OAAO;EAAK,OAAO;EAAK,MAAM;EAAU;CAC1C;EAAE,OAAO;EAAK,OAAO;EAAK,MAAM;EAAU;CAC1C;EAAE,OAAO;EAAK,OAAO;EAAO,MAAM;EAAY,OAAO;EAAW;CAChE;EAAE,OAAO;EAAK,OAAO;EAAO,MAAM;EAAY,OAAO;EAAW;CAEhE;EAAE,OAAO;EAAK,OAAO;EAAK,MAAM;EAAU;CAC1C;EAAE,OAAO;EAAK,OAAO;EAAK,MAAM;EAAU;CAC1C;EAAE,OAAO;EAAK,OAAO;EAAK,MAAM;EAAU;CAC1C;EAAE,OAAO;EAAK,OAAO;EAAO,MAAM;EAAY,OAAO;EAAW;CAChE;EAAE,OAAO;EAAK,OAAO;EAAO,MAAM;EAAY,OAAO;EAAW;CAEhE;EAAE,OAAO;EAAK,OAAO;EAAK,MAAM;EAAU;CAC1C;EAAE,OAAO;EAAK,OAAO;EAAK,MAAM;EAAU;CAC1C;EAAE,OAAO;EAAK,OAAO;EAAK,MAAM;EAAU;CAC1C;EAAE,OAAO;EAAK,OAAO;EAAK,MAAM;EAAU;CAC1C;EAAE,OAAO;EAAK,OAAO;EAAK,MAAM;EAAU;CAC3C;;AAGD,MAAa,cAAoC,CAC/C;CAAE,OAAO;CAAK,OAAO;CAAa,MAAM;CAAU,UAAU;CAAM,EAClE;CAAE,OAAO;CAAM,OAAO;CAAS,MAAM;CAAU,UAAU;CAAM,CAChE;;AAKD,MAAa,oBAAuC;CAClD;EACE,MAAM;EACN,WAAW;EACX,aAAa;EACb,UAAU;EACX;CACD;EACE,MAAM;EACN,WAAW;EACX,aAAa;EACb,UAAU;EACX;CACD;EACE,MAAM;EACN,WAAW;EACX,aAAa;EACb,UAAU;EACX;CACD;EACE,MAAM;EACN,WAAW;EACX,aAAa;EACb,UAAU;EACX;CACD;EACE,MAAM;EACN,WAAW;EACX,aAAa;EACb,UAAU;EACX;CACD;EACE,MAAM;EACN,WAAW;EACX,aAAa;EACb,UAAU;EACX;CACD;EACE,MAAM;EACN,WAAW;EACX,aAAa;EACb,UAAU;EACX;CACD;EACE,MAAM;EACN,WAAW;EACX,aAAa;EACb,UAAU;EACX;CACD;EACE,MAAM;EACN,WAAW;EACX,aAAa;EACb,UAAU;EACX;CACD;EACE,MAAM;EACN,WAAW;EACX,aAAa;EACb,UAAU;EACX;CACD;EACE,MAAM;EACN,WAAW;EACX,aAAa;EACb,UAAU;EACX;CACD;EACE,MAAM;EACN,WAAW;EACX,aAAa;EACb,UAAU;EACX;CACF;;AAKD,MAAa,YAAY,IAAI,IAAI;CAC/B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;AAGF,MAAa,mBAAmB,IAAI,IAAI;CACtC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;;;;;;;;;;;ACpJF,MAAM,cACJ;;;;AAKF,SAAgB,iBACd,WACA,WACA;;CAIA,MAAM,gBAAgB,eACd,IAAI,IAAI,UAA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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"C_FullCalendar-BF7H0YIx.css","names":[],"sources":["../src/components/C_FullCalendar/index.vue?vue&type=style&index=0&scoped=c70fa420&lang.css"],"sourcesContent":["\n.c-full-calendar[data-v-c70fa420] {\r\n width: 100%;\n}\r\n"],"mappings":";AACA;AACA;AACA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_C_FullCalendar = require('./C_FullCalendar.js');
|
|
3
|
+
|
|
4
|
+
exports.BUTTON_TEXT = require_C_FullCalendar.BUTTON_TEXT;
|
|
5
|
+
exports.C_FullCalendar = require_C_FullCalendar.C_FullCalendar_default;
|
|
6
|
+
exports.DEFAULT_EDIT_FORM = require_C_FullCalendar.DEFAULT_EDIT_FORM;
|
|
7
|
+
exports.EVENT_COLORS = require_C_FullCalendar.EVENT_COLORS;
|
|
8
|
+
exports.HEADER_TOOLBAR = require_C_FullCalendar.HEADER_TOOLBAR;
|
|
9
|
+
exports.useCalendarEvents = require_C_FullCalendar.useCalendarEvents;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as HEADER_TOOLBAR, c as CalendarEmits, d as CalendarProps, f as CalendarViewType, i as EVENT_COLORS, l as CalendarEvent, n as BUTTON_TEXT, o as _default, r as DEFAULT_EDIT_FORM, s as CalendarEditForm, t as useCalendarEvents, u as CalendarExpose } from "./useCalendarEvents.js";
|
|
2
|
+
export { BUTTON_TEXT, _default as C_FullCalendar, type CalendarEditForm, type CalendarEmits, type CalendarEvent, type CalendarExpose, type CalendarProps, type CalendarViewType, DEFAULT_EDIT_FORM, EVENT_COLORS, HEADER_TOOLBAR, useCalendarEvents };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as HEADER_TOOLBAR, c as CalendarEmits, d as CalendarProps, f as CalendarViewType, i as EVENT_COLORS, l as CalendarEvent, n as BUTTON_TEXT, o as _default, r as DEFAULT_EDIT_FORM, s as CalendarEditForm, t as useCalendarEvents, u as CalendarExpose } from "./useCalendarEvents.js";
|
|
2
|
+
export { BUTTON_TEXT, _default as C_FullCalendar, type CalendarEditForm, type CalendarEmits, type CalendarEvent, type CalendarExpose, type CalendarProps, type CalendarViewType, DEFAULT_EDIT_FORM, EVENT_COLORS, HEADER_TOOLBAR, useCalendarEvents };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { a as EVENT_COLORS, i as DEFAULT_EDIT_FORM, n as useCalendarEvents, o as HEADER_TOOLBAR, r as BUTTON_TEXT, t as C_FullCalendar_default } from "./C_FullCalendar2.js";
|
|
2
|
+
|
|
3
|
+
export { BUTTON_TEXT, C_FullCalendar_default as C_FullCalendar, DEFAULT_EDIT_FORM, EVENT_COLORS, HEADER_TOOLBAR, useCalendarEvents };
|