基于Ant的Mentions自定义公式功能

前言: 最近系统中需要增加一个自定义公式功能,大致的功能就是让用户能够根据自己的需求,去定义表格中的某个列根据自定义的公式得出的结果,效果图如下在这里插入图片描述
上面的git图可以分析出需求
  1. 支持直接键盘手动输入,#号呼出变量选择,并且变量支持输入匹配
  2. 支持点击虚拟键盘上数字与运算符,变量等进行输入
  3. 数字,运算符,变量等不同类型的字符在下面的框中要展示不同的样式
开始根据需求分析实现方案
  1. 项目使用的ant的UI框架,文档中查到Mentions组件符合第一条需求,故而选择
  2. 虚拟键盘点击事件与渲染都没什么问题,关键点如何在用户移动光标不在字符最后的时候,如何进行光标插值,这里通过document.selection.createRange实现
  3. 不同类型字符展示不同样式,实现逻辑就是不断正则匹配,然后字符串切割
代码实现

1, Mentions基本都满足了第一需求,这里我配置的呼出的关键词是#,选择变量后的尾缀增加一个空格字符,这个很重要,后续跟匹配变量的正则需要对应上

                <a-mentions class="input-box" @change="changeHtml" ref="varBox" v-model="currentVar"  :prefix="'#'"   autoFocus >
                    <a-mentions-option v-for="value in varArr"  :key="value.value" :value="value.value">
                        {{ value.label }}
                    </a-mentions-option>
                </a-mentions>
2, 在光标处插值,需要利用range
     cursorPostion(){
            let _this = this
            this.cursorPositionObj = {
                get: function (textarea) {
                    // 获取光标位置,传入的参数需要是一个input或者textarea的dom元素
                    var rangeData = { text: "", start: 0, end: 0 };
                    if (textarea.setSelectionRange) { // W3C
                        textarea.focus();
                        rangeData.start = textarea.selectionStart;
                        rangeData.end = textarea.selectionEnd;
                        rangeData.text = (rangeData.start != rangeData.end) ? textarea.value.substring(rangeData.start, rangeData.end) : "";
                    } else if (document.selection) { // IE
                        textarea.focus();
                        var i,
                            oS = document.selection.createRange(),
                            // Don't: oR = textarea.createTextRange()
                            oR = document.body.createTextRange();
                        oR.moveToElementText(textarea);

                        rangeData.text = oS.text;
                        rangeData.bookmark = oS.getBookmark();

                        // object.moveStart(sUnit [, iCount])
                        // Return Value: Integer that returns the number of units moved.
                        for (i = 0; oR.compareEndPoints('StartToStart', oS) < 0 && oS.moveStart("character", -1) !== 0; i++) {
                            // Why? You can alert(textarea.value.length)
                            if (textarea.value.charAt(i) == '\r') {
                                i++;
                            }
                        }
                        rangeData.start = i;
                        rangeData.end = rangeData.text.length + rangeData.start;
                    }

                    return rangeData;
                },

                set: function (textarea, rangeData) {
                    var oR, start, end;
                    if (!rangeData) {
                        alert("You must get cursor position first.")
                    }
                    textarea.focus();
                    if (textarea.setSelectionRange) { // W3C
                        textarea.setSelectionRange(rangeData.start, rangeData.end);
                    } else if (textarea.createTextRange) { // IE
                        oR = textarea.createTextRange();

                        // Fixbug : ues moveToBookmark()
                        // In IE, if cursor position at the end of textarea, the set function don't work
                        if (textarea.value.length === rangeData.start) {
                            //alert('hello')
                            oR.collapse(false);
                            oR.select();
                        } else {
                            oR.moveToBookmark(rangeData.bookmark);
                            oR.select();
                        }
                    }
                },

                add: function (textarea, rangeData, text) {
                    /*
                    * 1, textarea -- textarea原生dom
                    * 2, rangeData -- 当前光标处,可以通过上面的get方法获取返回值之后,当参数传入即可
                    * 3, text -- 需要追加的文本内容
                    * */
                    var oValue, nValue, oR, sR, nStart, nEnd, st;
                    this.set(textarea, rangeData);
                    if (textarea.setSelectionRange) { // W3C
                        oValue = textarea.value;
                        nValue = oValue.substring(0, rangeData.start) + text + oValue.substring(rangeData.end);
                        nStart = nEnd = rangeData.start + text.length;
                        st = textarea.scrollTop;
                        textarea.value = nValue;
                        _this.currentVar = nValue;
                        // Fixbug:
                        // After textarea.values = nValue, scrollTop value to 0
                        if (textarea.scrollTop != st) {
                            textarea.scrollTop = st;
                        }
                        textarea.setSelectionRange(nStart, nEnd);
                    } else if (textarea.createTextRange) { // IE
                        console.log("IE");
                        sR = document.selection.createRange();
                        sR.text = text;
                        sR.setEndPoint('StartToEnd', sR);
                        sR.select();
                    }
                }
            }
        },
内容追加在用户点击的时候,将对应的内容追加进去即可
        setInputValue(value){
            // 从输入框的光标处,插入字符
            let cur = this.cursorPositionObj.get(this.textareaDom)
            this.cursorPositionObj.add(this.textareaDom,cur,value)
        },
当内容改变时,使用正则匹配,转成不同的html内容,以表示不同的样式
1. 定义3种类型的正则,分别是运算符,数字,变量
            operationReg:/[\+\-\*//()]/, // 运算符正则
            numberReg:/\d+(\.\d+)?/, // 数字正则,不限制小数点
            varReg:/^#\S*\s/, // 变量的正则,这里对应上面的变量唤起的关键字的#号开头,空格结尾,如果修改,对应修改即可
2,这里还有一个点,如果当+ -这样出现时,那这时候的-号,是作为负号,也就是当做跟数字当做同类型,所以需要记录变量的顺序
typeOrder:[], // 记录字符类型的顺序
3,定义方法,在每一次输入框内容改变的时候调用,正则方式使用match,可以拿到匹配到的字符,与字符出现的位置
filterStr(str){
            /*
        * 1, 根据正则匹配字符,根据index的大小进行判断当前的字符的类型是什么
        * 2, 然后根据index加上匹配到的字符长度,进行原字符的切割,然后继续匹配,一直匹配到没有字符为止
        * 3, 记录字符类型的出现的顺序,因为如果-号前面是运算符的话,那它将不是运算符,而是负数的数字
        * */
            if(!str){
                return;
            }
            // 将字符头部的空格字符去掉,否则影响变量的匹配
            str = str.replace(/^\s+/,'');
            let arr = [
                {match:'varReg',index:null},
                {match:'operationReg',index:null},
                {match:'numberReg',index:null},
            ]

            arr.map((item)=>{
                item.match = str.match(this[item.match])
                if(item.match !== null){
                    item.index = item.match.index
                }
            })

            // 比较匹配到的正则谁在最前面
            let res = arr.reduce(function(prev,cur){
                if(cur.index !== null && prev.index !== null){
                    return cur.index < prev.index ? cur : prev
                }else{
                    return  cur.index !== null ? cur : prev
                }
            })

            if(res.index !== null){
                // 将匹配到的字符用数组保存起来
                this.strArr.push(res.match[0])
                // 这里将已经匹配的字符去掉,剩下的字符继续匹配,一直到结束
                this.filterStr(str.slice(res.index + res.match[0].length))
            }
        },
输入框的内容改变时,需要将字符串数组与类型数组置空,然后将文本变成数组,再将数组进行匹配转成HTML
changeHtml(){
            this.strArr = [];
            this.typeOrder = [];
            let strHtml = ''
            let txt = this.currentVar; // 这里是输入框内容
            // 虚拟键盘中的乘号与除号需要替换成为真正的运算符
            txt  = txt.replace(/x/g,'*')
            txt  = txt.replace(/÷/g,'/')
            this.filterStr(txt) // 将文本内容转成到数组
            // 将字符转换成html进行格式化
            this.strArr.map((item,index)=>{
                if(this.varReg.test(item)){
                    this.typeOrder.push('var')
                    strHtml += `<span class="c-2979FF bg-D8E6FF radius-4">${item}</span>`
                }else if(this.operationReg.test(item)){
                    if(item == '-' && this.typeOrder.pop() == 'operation'){
                        //  如果当前字符是-,并且上一个字符类型是运算符,则当前的-当做数字处理
                        strHtml += `<span class="c-65B168">${item}</span>`
                    }else{
                        strHtml += `<span class="c-F65F54">${item}</span>`
                    }
                    this.typeOrder.push('operation')
                }else if(this.numberReg.test(item)){
                    this.typeOrder.push('number')
                    strHtml += `<span class="c-65B168">${item}</span>`
                }
            })
            this.statisticsHtml = strHtml;
        },

至此该功能已完成,核心在于正则的字符串匹配与光标处插值,下面是完整代码链接

demo

至于校验公式是否合理,其实把变量替换成数字,然后看是否能正常运算出结果就可以了
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要使用GPT Mentions调用GPT模型,您可以按照以下步骤进行操作: 1. 安装OpenAI Python库:首先,您需要安装OpenAI Python库。您可以使用pip命令在终端中运行以下命令进行安装: ``` pip install openai ``` 2. 导入必要的库和设置API密钥:在Python脚本中,您需要导入openai库并设置您的API密钥。您可以使用以下代码完成这一步骤: ```python import openai # 设置API密钥 openai.api_key = 'YOUR_API_KEY' ``` 3. 调用GPT模型:使用openai.Completion.create()方法来调用GPT模型。您需要提供一个prompt(提示)来指导模型生成响应。以下是一个示例代码: ```python response = openai.Completion.create( engine="text-davinci-003", prompt="Once upon a time", max_tokens=50, n=1, stop=None, temperature=0.7 ) ``` 在上面的示例中,我们使用了"text-davinci-003"引擎,设置了一个简单的提示"Once upon a time",并指定了生成的最大标记数为50。您可以根据需要调整其他参数,如stop(停止标记)、temperature(温度)等。 4. 提取生成的响应:从API响应中提取生成的文本。以下是一个示例代码: ```python generated_text = response.choices[0].text.strip() print(generated_text) ``` 在上面的示例中,我们提取了生成的文本,并使用print语句将其打印出来。 这样,您就可以使用GPT Mentions调用GPT模型了。请确保您已经获得了OpenAI的API密钥,并将其替换为代码中的"YOUR_API_KEY"。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值