ExpressionDialog.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  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. const fields = Object.keys(this.dataset[item][0])
  132. const children = fields.map((field) => {
  133. return {
  134. label: field,
  135. code: item,
  136. value: `dataset.${item}[0].${field}`,
  137. disabled: item.includes(this.config.code)
  138. }
  139. })
  140. list.push({
  141. label: item,
  142. code: item,
  143. value: `dataset.${item}`,
  144. disabled: item.includes(this.config.code),
  145. children
  146. })
  147. }
  148. for (const item in this.computedDatas) {
  149. list.push({
  150. label: item,
  151. code: item,
  152. value: `computedDatas.${item}`,
  153. disabled: item.includes(this.config.code)
  154. })
  155. }
  156. return list
  157. },
  158. currentConfig () {
  159. return cloneDeep(this.config)
  160. }
  161. },
  162. watch: {},
  163. created () {},
  164. mounted () {},
  165. methods: {
  166. ...mapMutations({
  167. changeChartConfig: 'bigScreen/changeChartConfig'
  168. }),
  169. init () {
  170. this.formVisible = true
  171. },
  172. // 运行脚本
  173. executeScript () {
  174. // eslint-disable-next-line no-new-func
  175. const result = new Function('dataset', 'computedDatas', this.currentConfig.expression)
  176. this.scriptContent = result(this.dataset, this.computedDatas)
  177. },
  178. // 点击树节点将数据添加到脚本编辑器中
  179. handleNodeClick (node, data, nodeObj) {
  180. const str = node.value
  181. const code = node.code
  182. if (node.disabled) return
  183. this.$refs.codemirrorRef.codemirror.setValue(this.currentConfig.expression + ' + ' + str)
  184. // 同时将点击的数据存在expressionCodes中
  185. if (this.currentConfig.expressionCodes && Array.isArray(this.currentConfig.expressionCodes)) {
  186. this.currentConfig.expressionCodes.push(code)
  187. }
  188. },
  189. cancel () {
  190. this.formVisible = false
  191. },
  192. sure () {
  193. this.formVisible = false
  194. this.changeChartConfig(this.currentConfig)
  195. }
  196. }
  197. }
  198. </script>
  199. <style scoped lang="scss">
  200. @import '../../assets/style/bsTheme.scss';
  201. .bs-dialog-wrap{
  202. ::v-deep.el-dialog__body{
  203. min-height: 500px;
  204. }
  205. }
  206. .main-box{
  207. width: 100%;
  208. height: 500px;
  209. display: flex;
  210. .left-box{
  211. flex: 1;
  212. height: 100%;
  213. .tree-box{
  214. height: 100%;
  215. overflow-y: auto;
  216. }
  217. }
  218. .right-box{;
  219. flex: 3 ;
  220. height: 100%;
  221. .codemirror-wrap{
  222. height: 50%;
  223. position: relative;
  224. .btn-box{
  225. position: absolute;
  226. right: 10px;
  227. bottom: 10px;
  228. }
  229. }
  230. .codemirror-box {
  231. height: 100% !important;
  232. ::v-deep .CodeMirror {
  233. height: 100% !important;
  234. font-family: Helvetica, Tahoma;
  235. }
  236. }
  237. .script-content-box{
  238. padding:10px
  239. }
  240. }
  241. }
  242. .disabled{
  243. cursor: not-allowed;
  244. color: #666666;
  245. }
  246. </style>