ExpressionDialog.vue 6.3 KB

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