ExpressionDialog.vue 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. <template>
  2. <el-dialog
  3. :close-on-click-modal="false"
  4. title="表达式"
  5. width="60%"
  6. :visible.sync="formVisible"
  7. :append-to-body="false"
  8. class="bs-dialog-wrap bs-el-dialog"
  9. >
  10. <div class="main-box">
  11. <div class="left-box">
  12. <el-tree
  13. ref="tree"
  14. :data="treeData"
  15. :indent="0"
  16. :props="defaultProps"
  17. :default-expand-all="true"
  18. :highlight-current="true"
  19. :expand-on-click-node="false"
  20. class="bs-el-tree tree-box"
  21. @node-click="handleNodeClick"
  22. >
  23. <template #default="{ node, data }">
  24. <!-- Check if the node is a top-level node -->
  25. <span
  26. v-if="node.level === 1"
  27. :class="{ 'disabled': node.disabled}"
  28. >
  29. <i
  30. class="el-icon-folder"
  31. />
  32. {{ data.label }}
  33. </span>
  34. <span
  35. v-else
  36. :class="{ 'disabled': node.disabled}"
  37. style="padding-left: 20px"
  38. >
  39. {{ data.label }}
  40. </span>
  41. </template>
  42. </el-tree>
  43. </div>
  44. <div class="right-box">
  45. <div class="codemirror-wrap">
  46. <codemirror
  47. ref="codemirrorRef"
  48. v-model="currentConfig.expression"
  49. class="codemirror-box"
  50. :options="codemirrorOption"
  51. @dragover.prevent
  52. />
  53. <el-button
  54. class="btn-box"
  55. type="primary"
  56. @click="executeScript"
  57. >
  58. 运行脚本
  59. </el-button>
  60. </div>
  61. <div class="script-content-box">
  62. {{ scriptContent }}
  63. </div>
  64. </div>
  65. </div>
  66. <div
  67. slot="footer"
  68. class="dialog-footer"
  69. >
  70. <el-button
  71. class="bs-el-button-default cancel"
  72. @click="cancel"
  73. >
  74. 取消
  75. </el-button>
  76. <el-button
  77. type="primary"
  78. @click="sure"
  79. >
  80. 确定
  81. </el-button>
  82. </div>
  83. </el-dialog>
  84. </template>
  85. <script>
  86. import { codemirror } from 'vue-codemirror'
  87. import 'codemirror/mode/javascript/javascript'
  88. import 'codemirror/lib/codemirror.css'
  89. import 'codemirror/theme/nord.css'
  90. import { mapMutations, mapState } from 'vuex'
  91. import cloneDeep from 'lodash/cloneDeep'
  92. export default {
  93. name: 'ExpressionDialog',
  94. components: {
  95. codemirror
  96. },
  97. props: {
  98. config: {
  99. type: Object,
  100. default: () => {
  101. }
  102. }
  103. },
  104. data () {
  105. return {
  106. scriptContent: '', // 脚本执行的内容
  107. expression: '123',
  108. formVisible: false,
  109. codemirrorOption: {
  110. mode: 'text/javascript',
  111. lineNumbers: true,
  112. lineWrapping: true,
  113. theme: 'nord',
  114. extraKey: { Ctrl: 'autocomplete' },
  115. hintOptions: {
  116. completeSingle: true
  117. }
  118. },
  119. defaultProps: { label: 'label', children: 'children' }
  120. }
  121. },
  122. computed: {
  123. ...mapState({
  124. dataset: state => state.bigScreen.dataset,
  125. computedDatas: state => state.bigScreen.computedDatas
  126. }),
  127. // 获取树节点数据
  128. treeData () {
  129. const list = []
  130. for (const item in this.dataset) {
  131. let children = []
  132. if (this.dataset[item][0]) {
  133. const fields = Object.keys(this.dataset[item][0])
  134. children = fields.map((field) => {
  135. return {
  136. label: field,
  137. code: item,
  138. value: `dataset.${item}[0].${field}`,
  139. disabled: item.includes(this.config.code)
  140. }
  141. })
  142. }
  143. list.push({
  144. label: item.split('_')[0],
  145. code: item,
  146. value: `dataset.${item}`,
  147. disabled: item.includes(this.config.code),
  148. children
  149. })
  150. }
  151. for (const item in this.computedDatas) {
  152. list.push({
  153. label: item.split('_')[0],
  154. code: item,
  155. value: `computedDatas.${item}`,
  156. disabled: item.includes(this.config.code)
  157. })
  158. }
  159. return list
  160. },
  161. currentConfig () {
  162. return cloneDeep(this.config)
  163. }
  164. },
  165. watch: {},
  166. created () {},
  167. mounted () {},
  168. methods: {
  169. ...mapMutations({
  170. changeChartConfig: 'bigScreen/changeChartConfig'
  171. }),
  172. init () {
  173. this.formVisible = true
  174. },
  175. // 运行脚本
  176. executeScript () {
  177. // eslint-disable-next-line no-new-func
  178. const result = new Function('dataset', 'computedDatas', this.currentConfig.expression)
  179. this.scriptContent = result(this.dataset, this.computedDatas)
  180. },
  181. // 点击树节点将数据添加到脚本编辑器中
  182. handleNodeClick (node, data, nodeObj) {
  183. const str = node.value
  184. const code = node.code
  185. if (node.disabled) return
  186. // 判断编辑器里面是return + 0或者多个空格 还是包含其他表达式,每次添加第一个表达式时不要加‘+’,防止计算错误
  187. const isInit = /^[\w\s]+$/.test(this.currentConfig.expression)
  188. const newStr = isInit ? this.currentConfig.expression + str : this.currentConfig.expression + ' + ' + str
  189. this.$refs.codemirrorRef.codemirror.setValue(newStr)
  190. // 同时将点击的数据存在expressionCodes中
  191. if (this.currentConfig.expressionCodes && Array.isArray(this.currentConfig.expressionCodes)) {
  192. this.currentConfig.expressionCodes.push(code)
  193. }
  194. },
  195. cancel () {
  196. this.formVisible = false
  197. },
  198. sure () {
  199. this.formVisible = false
  200. this.changeChartConfig(this.currentConfig)
  201. }
  202. }
  203. }
  204. </script>
  205. <style scoped lang="scss">
  206. @import '../../assets/style/bsTheme.scss';
  207. .bs-dialog-wrap{
  208. ::v-deep.el-dialog__body{
  209. min-height: 500px;
  210. }
  211. }
  212. .main-box{
  213. width: 100%;
  214. height: 500px;
  215. display: flex;
  216. .left-box{
  217. flex: 1;
  218. height: 100%;
  219. .tree-box{
  220. height: 100%;
  221. overflow-y: auto;
  222. }
  223. }
  224. .right-box{;
  225. flex: 3 ;
  226. height: 100%;
  227. .codemirror-wrap{
  228. height: 50%;
  229. position: relative;
  230. .btn-box{
  231. position: absolute;
  232. right: 10px;
  233. bottom: 10px;
  234. }
  235. }
  236. .codemirror-box {
  237. height: 100% !important;
  238. ::v-deep .CodeMirror {
  239. height: 100% !important;
  240. font-family: Helvetica, Tahoma;
  241. }
  242. }
  243. .script-content-box{
  244. padding:10px
  245. }
  246. }
  247. }
  248. .disabled{
  249. cursor: not-allowed;
  250. color: #666666;
  251. }
  252. </style>