| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582 |
- <template>
- <div class="fm-formula-container">
- <el-row justify="space-between" >
- <el-col :span="24">
- <el-card :header="$t('fm.formula.header')" shadow="never" size="small" class="formula-card">
- <div :id="editorId" class="formula-editor"></div>
- </el-card>
- </el-col>
-
- </el-row>
- <el-row justify="space-between" :gutter="10">
- <el-col :span="24">
- <el-tabs class="formula-tabs">
- <el-tab-pane :label="$t('fm.formula.field')">
- <el-scrollbar ref="scrollRef" always style="height: 100%;">
- <el-tree
- :data="modelsData"
- node-key="id"
- default-expand-all
- :expand-on-click-node="false"
- style="width: 100%;"
- ref="fieldTree"
- >
- <template #default="{ node }">
- <div style="display: flex; justify-content: space-between; width: 95%; align-items: center;" @click="handleNode(node.data)">
- <span>{{ node.data.name }} {<span style="font-size: 12px;">{{node.data.id}}</span>}</span>
- <el-tag type="info" size="mini">{{$t('fm.components.fields.' + node.data.type)}}</el-tag>
- </div>
- </template>
- </el-tree>
- </el-scrollbar>
- </el-tab-pane>
- <el-tab-pane :label="$t('fm.formula.fieldId')">
- <el-scrollbar ref="scrollRef" always style="height: 100%;">
- <el-tree
- :data="modelsData"
- node-key="id"
- default-expand-all
- :expand-on-click-node="false"
- style="width: 100%;"
- >
- <template #default="{ node }">
- <div style="display: flex; justify-content: space-between; width: 95%; align-items: center;" @click="handleFieldId(node.data)">
- <span>{{ node.data.name }} {<span style="font-size: 12px;">{{node.data.id}}</span>}</span>
- <el-tag type="info" size="mini">{{$t('fm.components.fields.' + node.data.type)}}</el-tag>
- </div>
- </template>
- </el-tree>
- </el-scrollbar>
- </el-tab-pane>
- <el-tab-pane :label="$t('fm.formula.event')">
- <el-scrollbar always style="height: 100%;">
- <el-tree
- v-if="localVariables.length"
- :data="localVariables"
- node-key="id"
- default-expand-all
- :expand-on-click-node="false"
- style="width: 100%;"
- >
- <template #default="{ node }">
- <div style="display: flex; justify-content: space-between; width: 90%;" @click="handleVariableNode(node.data)">
- <span style=" padding: 0 5px; max-width: 50%;">{{ node.data.name }}</span>
- </div>
- </template>
- </el-tree>
- <el-tree
- :data="argsData"
- node-key="id"
- default-expand-all
- :expand-on-click-node="false"
- style="width: 100%;"
- >
- <template #default="{ node }">
- <div style="display: flex; justify-content: space-between; width: 95%; align-items: center;" @click="handleArgsNode(node.data)">
- <span style=" padding: 0 5px; max-width: 50%;"><span>{{ node.data.name }}</span> <span v-if="node.data.desc && $i18n.locale == 'zh-cn'">({{node.data.desc}})</span></span>
- <span style="font-size: 12px;">{{node.data.id}}</span>
- </div>
- </template>
- </el-tree>
- </el-scrollbar>
- </el-tab-pane>
- </el-tabs>
- </el-col>
- </el-row>
- </div>
- </template>
- <script>
- import CodeMirror from 'codemirror/lib/codemirror.js'
- import 'codemirror/lib/codemirror.css'
- import 'codemirror/mode/javascript/javascript.js'
- import 'codemirror/theme/ayu-dark.css'
- import 'codemirror/addon/edit/closebrackets.js'
- import 'codemirror/addon/selection/active-line.js'
- import 'codemirror/addon/scroll/simplescrollbars.css'
- import 'codemirror/addon/scroll/simplescrollbars.js'
- export default {
- props: ['value', 'showArguments', 'showRow'],
- inject: {
- getFormModels: {
- default: () => {return new Function()}
- },
- getResponseVariables: {
- default: () => {return new Function()}
- },
- getLocalVariables: {
- default: () => {return new Function()}
- }
- },
- data () {
- let curArgsData = []
- if (this.showArguments) {
- curArgsData = [{
- id: 'arguments[0]',
- name: this.$t('fm.formula.argsData.name'),
- children: [
- {
- id: ' arguments[0].value',
- name: 'value',
- desc: '当前字段值',
- }, {
- id: ' arguments[0].fieldNode',
- name: 'fieldNode',
- desc: '当前字段标识',
- }, {
- id: ' arguments[0].currentRef',
- name: 'currentRef',
- desc: '当前组件实例'
- }
- ]
- }]
- if (this.showRow) {
- curArgsData[0].children = curArgsData[0].children.concat([
- {
- id: ' arguments[0].rowIndex',
- name: 'rowIndex',
- desc: '当前操作行下标',
- }, {
- id: ' arguments[0].row',
- name: 'row',
- desc: '当前行数据'
- }
- ])
- }
- }
- return {
- modelValue: this.value,
- editorId: 'formula-' + Math.random().toString(36).slice(-8),
- modelsData: [],
- argsData: curArgsData,
- responseVariables: [],
- localVariables: []
- }
- },
- mounted () {
- this.modelsData = this.getFormModels() || []
- let responseVariables = this.getResponseVariables() || []
- let localVars = this.getLocalVariables() || []
- if (responseVariables.length || localVars.length) {
- this.localVariables = [{
- id: '',
- name: this.$t('fm.formula.argsData.variable'),
- children: [...responseVariables, ...localVars].map(item => ({
- id: ' ' + item,
- name: item
- }))
- }]
- }
- setTimeout(() => {
- this.initEditor()
- })
- },
- methods: {
- initEditor () {
- let theme = 'default'
- if (document.querySelector('html').className.indexOf('dark')>-1) {
- theme = 'ayu-dark'
- }
- this.editor = CodeMirror(document.getElementById(this.editorId), {
- value: this.modelValue,
- lineNumbers: false,
- mode: 'javascript',
- lineWrapping: false,
- autofocus: true,
- theme: theme,
- autoCloseBrackets: true,
- styleActiveLine: true,
- scrollbarStyle: "simple"
- })
- this.editor.on('change', cm => {
- this.modelValue = cm.getValue()
- })
- this.replaceFieldContent()
- this.replaceArgsContent()
- this.replaceVarContent()
- this.editor.execCommand("goDocEnd")
- },
- _escapeRegExp (string) {
- return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
- },
- replaceVarContent () {
- if (this.localVariables && this.localVariables.length) {
- this.localVariables[0].children.forEach(item => {
- const regex = new RegExp(item.id, 'g')
- const matches = []
-
- let match
- while ((match = regex.exec(this.value)) !== null) {
- // 匹配到的内容
- const matchedValue = match[0]
- // 匹配到的内容在文本中的起始位置
- const startIndex = match.index - this.calcLastIndex(this.value, match.index) - 1
- const lineIndex = this.calcLineCount(this.value, match.index)
- // 将匹配结果存储到数组中
- matches.push({
- value: matchedValue,
- startIndex: startIndex,
- lineIndex: lineIndex,
- id: item.id,
- name: item.name
- })
- }
- matches.forEach(mitem => {
- let widgetNode = document.createElement('span')
- widgetNode.className = 'cm-eventvar'
- widgetNode.textContent = mitem.name
- widgetNode.title = mitem.id
- this.editor.markText({line: mitem.lineIndex, ch: mitem.startIndex}, { line: mitem.lineIndex, ch: mitem.startIndex + mitem.value.length }, {
- atomic: true,
- selectLeft: true,
- selectRight: true,
- inclusiveLeft: false,
- inclusiveRight: false,
- replacedWith: widgetNode,
- handleMouseEvents: true
- })
- })
- })
- }
- },
- replaceArgsContent () {
- this.argsData.length && this.argsData[0].children.forEach(item => {
- const regex = new RegExp(this._escapeRegExp(item.id), 'g')
- const matches = []
-
- let match
- while ((match = regex.exec(this.value)) !== null) {
- // 匹配到的内容
- const matchedValue = match[0]
- // 匹配到的内容在文本中的起始位置
- const startIndex = match.index - this.calcLastIndex(this.value, match.index) - 1
- const lineIndex = this.calcLineCount(this.value, match.index)
- // 将匹配结果存储到数组中
- matches.push({
- value: matchedValue,
- startIndex: startIndex,
- lineIndex: lineIndex,
- id: item.id,
- name: item.name
- })
- }
- matches.forEach(mitem => {
- let widgetNode = document.createElement('span')
- widgetNode.className = 'cm-eventargs'
- widgetNode.textContent = mitem.name
- widgetNode.title = mitem.id
- this.editor.markText({line: mitem.lineIndex, ch: mitem.startIndex}, { line: mitem.lineIndex, ch: mitem.startIndex + mitem.value.length }, {
- atomic: true,
- selectLeft: true,
- selectRight: true,
- inclusiveLeft: false,
- inclusiveRight: false,
- replacedWith: widgetNode,
- handleMouseEvents: true
- })
- })
- })
- },
- replaceFieldContent () {
- // 定义正则表达式模式,匹配 this.getValue("xxx") 形式的内容
- const regex = / this\.getValue\("([^"]+)"\)/g
- const matches = []
- let match
- while ((match = regex.exec(this.value)) !== null) {
- // 匹配到的内容
- const matchedValue = match[0]
- const matchedId = match[1]
- // 匹配到的内容在文本中的起始位置
- const startIndex = match.index - this.calcLastIndex(this.value, match.index) - 1
- const lineIndex = this.calcLineCount(this.value, match.index)
- const matchNode = this.$refs.fieldTree.getNode(matchedId)
- if (matchNode) {
- // 将匹配结果存储到数组中
- matches.push({
- value: matchedValue,
- startIndex: startIndex,
- lineIndex: lineIndex,
- id: matchedId,
- data: matchNode.data
- })
- }
- }
- matches.forEach(item => {
- let widgetNode = document.createElement('span')
- widgetNode.className = 'cm-value'
- widgetNode.textContent = item.data.name || item.data.id
- widgetNode.title = item.value
- this.editor.markText({line: item.lineIndex, ch: item.startIndex}, { line: item.lineIndex, ch: item.startIndex + item.value.length }, {
- title: item.value,
- atomic: true,
- selectLeft: true,
- selectRight: true,
- inclusiveLeft: false,
- inclusiveRight: false,
- replacedWith: widgetNode,
- handleMouseEvents: true
- })
- })
- },
- calcLineCount (str, n) {
- let count = 0
- for (let i = 0; i < n; i++) {
- if (str[i] === '\n') {
- count++
- }
- }
- return count
- },
- calcLastIndex (str, n) {
- let lastIndex = -1
- for (let i = 0; i < n; i++) {
- if (str[i] === '\n') {
- lastIndex = i
- }
- }
- return lastIndex
- },
- handleFieldId (data) {
- let cursor = this.editor.getCursor()
- let text = `"${data.id}"`
- this.editor.replaceRange(text, cursor)
- this.editor.focus()
- },
- handleNode(data) {
- let cursor = this.editor.getCursor()
- let widgetNode = document.createElement('span')
- widgetNode.className = 'cm-value'
- widgetNode.textContent = data.name || data.id
- let text = ` this.getValue("${data.id}")`
- widgetNode.title = text
- this.editor.replaceRange(text, cursor)
- this.editor.markText({line: cursor.line, ch: cursor.ch}, { line: cursor.line, ch: cursor.ch + text.length }, {
- title: text,
- atomic: true,
- selectLeft: true,
- selectRight: true,
- inclusiveLeft: false,
- inclusiveRight: false,
- replacedWith: widgetNode,
- handleMouseEvents: true
- })
- this.editor.focus()
- },
- handleArgsNode (data) {
- let cursor = this.editor.getCursor()
- let widgetNode = document.createElement('span')
- widgetNode.className = 'cm-eventargs'
- widgetNode.textContent = data.name
- widgetNode.title = data.id
- let text = data.id
- this.editor.replaceRange(text, cursor)
- this.editor.markText({line: cursor.line, ch: cursor.ch}, { line: cursor.line, ch: cursor.ch + text.length }, {
- atomic: true,
- selectLeft: true,
- selectRight: true,
- inclusiveLeft: false,
- inclusiveRight: false,
- replacedWith: widgetNode,
- handleMouseEvents: true
- })
- this.editor.focus()
- },
- handleVariableNode (data) {
- let cursor = this.editor.getCursor()
- let text = data.id
- let widgetNode = document.createElement('span')
- widgetNode.className = 'cm-eventvar'
- widgetNode.textContent = data.name
- widgetNode.title = data.id
- this.editor.replaceRange(text, cursor)
- this.editor.markText({line: cursor.line, ch: cursor.ch}, { line: cursor.line, ch: cursor.ch + text.length }, {
- atomic: true,
- selectLeft: true,
- selectRight: true,
- inclusiveLeft: false,
- inclusiveRight: false,
- replacedWith: widgetNode,
- handleMouseEvents: true
- })
- this.editor.focus()
- },
- },
- watch: {
- value (val) {
- this.modelValue = val
- },
- modelValue (val) {
- this.$emit('input', val)
- }
- }
- }
- </script>
- <style lang="scss">
- .fm-formula-container{
- .formula-tabs {
- border: 1px solid #e0e0e0;
- // margin-bottom: 5px;
- --el-tabs-header-height: 35px;
- .el-tabs__header{
- padding: 0;
- background: #f5f7fa;
- margin: 0;
- margin-bottom: 5px;
- .el-tabs__item{
- font-size: 14px;
- font-weight: normal;
- padding: 0 10px;
- }
- }
- .el-tabs__content{
- height: 240px;
- .el-tab-pane{
- height: 100%;
- }
- }
- }
- .formula-card{
- margin-bottom: 10px;
- .el-card__header{
- padding: 8px;
- background: #f5f7fa;
- }
- .el-card__body{
- padding: 0;
- }
- }
- .formula-editor{
- height: 160px;
- .CodeMirror{
- height: 100%;
- font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
- pre.CodeMirror-line {
- line-height: 20px;
- font-size: 13px;
- }
- }
- .cm-field{
- background-color: #409EFF;
- color: white;
- margin: 2px 0;
- padding: 0 2px;
- border-radius: 4px;
- display: inline-block;
- }
- .cm-value{
- background-color: #409EFF;
- color: white;
- margin: 2px 1px 2px 2px;
- padding: 0 4px;
- border-radius: 4px;
- display: inline-block;
- font-size: 13px;
- }
- .cm-eventargs{
- background-color: #E6A23C;
- color: white;
- margin: 2px 1px 2px 2px;
- padding: 0 4px;
- border-radius: 4px;
- font-size: 13px;
- display: inline-block;
- }
- .cm-eventvar {
- background-color: rgb(250, 236, 216);
- color: var(--el-color-warning);
- padding: 0 2px;
- border-radius: 4px;
- display: inline-block;
- }
- }
- .el-tree-node{
- padding: 2px 0;
- }
- .formula-field,.formula-method{
- .el-card__body{
- height: 240px;
- padding: 0;
- }
- }
- }
- </style>
|