index.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. <template>
  2. <div class="bs-custom-components">
  3. <div class="bs-custom-component-header">
  4. <div class="left-title">
  5. <div class="logo-wrap item-wrap">
  6. <img
  7. class="menu-img"
  8. src="../BigScreenDesign/images/app.png"
  9. alt="返回"
  10. @click="backManagement"
  11. >
  12. <span class="logo-text name-span">{{ form.name }}</span>
  13. </div>
  14. </div>
  15. <div class="right-btn-wrap">
  16. <CusBtn
  17. :loading="loading"
  18. @click="save"
  19. >
  20. 保存
  21. </CusBtn>
  22. </div>
  23. </div>
  24. <div class="bs-custom-component-content">
  25. <div class="bs-custom-component-content-code">
  26. <div class="left-vue-code component-code">
  27. <div class="code-tab-header">
  28. <div class="code-tab-left">
  29. <div class="code-tab">组件模板</div>
  30. <div class="code-tab-btn" @click="change('echart')">
  31. echarts组件
  32. </div>
  33. <div class="code-tab-btn" @click="change('g2plot')">
  34. G2Plot组件
  35. </div>
  36. <div class="code-tab-btn" @click="change('native')">
  37. 原生组件
  38. </div>
  39. <div class="code-tab-btn" @click="change('3DEchart')">
  40. 3D组件
  41. </div>
  42. </div>
  43. <div class="upload-btn">
  44. <CusBtn @click="upload('vueContent')">
  45. 上传
  46. </CusBtn>
  47. </div>
  48. </div>
  49. <div class="code-tab-content">
  50. <!-- <MonacoEditor
  51. ref="vueContent"
  52. v-model="form.vueContent"
  53. class="editor"
  54. language="html"
  55. /> -->
  56. <codemirror
  57. v-model="form.vueContent"
  58. :options="vueOptions"
  59. />
  60. </div>
  61. </div>
  62. <div class="right-setting-code component-code">
  63. <div class="code-tab-header">
  64. <div class="code-tab">
  65. 组件配置
  66. </div>
  67. <div class="upload-btn">
  68. <CusBtn @click="upload('settingContent')">
  69. 上传
  70. </CusBtn>
  71. </div>
  72. </div>
  73. <div class="code-tab-content">
  74. <!-- <MonacoEditor
  75. ref="settingContent"
  76. v-model="form.settingContent"
  77. class="editor"
  78. language="javascript"
  79. /> -->
  80. <codemirror
  81. v-model="form.settingContent"
  82. :options="settingOptions"
  83. />
  84. </div>
  85. </div>
  86. </div>
  87. <div class="bs-custom-component-content-preview">
  88. <div class="bs-preview-inner">
  89. <div class="code-tab-header">
  90. <div class="code-tab">
  91. 效果预览
  92. </div>
  93. <div class="upload-btn">
  94. <CusBtn
  95. :loading="loading"
  96. @click.native="createdImg()"
  97. >
  98. 生成图片
  99. </CusBtn>
  100. </div>
  101. </div>
  102. <BizComponentPreview
  103. :vue-content="form.vueContent"
  104. :setting-content="form.settingContent"
  105. />
  106. </div>
  107. </div>
  108. <!-- 通过计算属性发现accept有问题 -->
  109. <input
  110. ref="vueContentFile"
  111. style="display: none"
  112. type="file"
  113. name="file"
  114. accept=".vue"
  115. @change="handleBatchUpload"
  116. >
  117. <input
  118. ref="settingContentFile"
  119. style="display: none"
  120. type="file"
  121. name="file"
  122. accept=".js"
  123. @change="handleBatchUpload"
  124. >
  125. </div>
  126. </div>
  127. </template>
  128. <script>
  129. import { toJpeg, toPng } from 'html-to-image'
  130. import CusBtn from 'data-room-ui/BigScreenDesign/BtnLoading'
  131. // import MonacoEditor from 'data-room-ui/MonacoEditor'
  132. import BizComponentPreview from './Preview'
  133. import { getBizComponentInfo, updateBizComponent } from 'data-room-ui/js/api/bigScreenApi'
  134. import { defaultSettingContent, defaultVueContent } from './config/defaultBizConfig'
  135. import { defaultEchartsSettingContent, defaultEchartsVueContent } from './config/defaultEchartsConfig'
  136. import { defaultG2SettingContent, defaultG2VueContent } from './config/defaultG2Config'
  137. import { codemirror } from 'vue-codemirror'
  138. import 'codemirror/lib/codemirror.css'
  139. import 'codemirror/theme/material-darker.css'
  140. import 'codemirror/addon/selection/active-line.js'
  141. import 'codemirror/mode/vue/vue.js'
  142. import {
  143. showSize,
  144. dataURLtoBlob,
  145. translateBlobToBase64
  146. } from 'data-room-ui/js/utils/compressImg'
  147. import * as imageConversion from 'image-conversion'
  148. export default {
  149. name: 'BizComponentDesign',
  150. components: {
  151. CusBtn,
  152. // MonacoEditor,
  153. codemirror,
  154. BizComponentPreview
  155. },
  156. props: {},
  157. data () {
  158. return {
  159. form: {
  160. name: '',
  161. coverPicture: '',
  162. settingContent: '',
  163. vueContent: ''
  164. },
  165. currentContentType: 'vueContent',
  166. loading: false,
  167. vueOptions: {
  168. foldGutter: true,
  169. lineWrapping: true,
  170. gutters: [
  171. 'CodeMirror-linenumbers',
  172. 'CodeMirror-foldgutter',
  173. 'CodeMirror-lint-markers'
  174. ],
  175. theme: 'material-darker',
  176. tabSize: 4,
  177. lineNumbers: true,
  178. line: true,
  179. indentWithTabs: true,
  180. smartIndent: true,
  181. autofocus: false,
  182. matchBrackets: true,
  183. mode: 'text/x-vue',
  184. hintOptions: {
  185. completeSingle: false
  186. },
  187. lint: true
  188. },
  189. settingOptions: {
  190. foldGutter: true,
  191. lineWrapping: true,
  192. gutters: [
  193. 'CodeMirror-linenumbers',
  194. 'CodeMirror-foldgutter',
  195. 'CodeMirror-lint-markers'
  196. ],
  197. theme: 'material-darker',
  198. tabSize: 4,
  199. lineNumbers: true,
  200. line: true,
  201. indentWithTabs: true,
  202. smartIndent: true,
  203. autofocus: false,
  204. matchBrackets: true,
  205. mode: 'text/javascript',
  206. hintOptions: {
  207. completeSingle: false
  208. },
  209. lint: true
  210. }
  211. }
  212. },
  213. computed: {
  214. },
  215. mounted () {
  216. this.getBizComponentInfo()
  217. },
  218. methods: {
  219. getBizComponentInfo () {
  220. const code = this.$route.query.code
  221. const type = this.$route.query.type
  222. if (code) {
  223. getBizComponentInfo(code).then(data => {
  224. if(type&&type==='g2plot'){
  225. this.form = {
  226. ...data,
  227. name: data.name,
  228. coverPicture: data.coverPicture,
  229. settingContent: data.settingContent || defaultG2SettingContent,
  230. vueContent: data.vueContent || defaultG2VueContent
  231. }
  232. }else if(type&&type==='echart'){
  233. this.form = {
  234. ...data,
  235. name: data.name,
  236. coverPicture: data.coverPicture,
  237. settingContent: data.settingContent || defaultEchartsSettingContent,
  238. vueContent: data.vueContent || defaultEchartsVueContent
  239. }
  240. }else{
  241. this.form = {
  242. ...data,
  243. name: data.name,
  244. coverPicture: data.coverPicture,
  245. settingContent: data.settingContent || defaultSettingContent,
  246. vueContent: data.vueContent || defaultVueContent
  247. }
  248. }
  249. // this.$refs.vueContent.editor.setValue(this.form.vueContent)
  250. // this.$refs.settingContent.editor.setValue(this.form.settingContent)
  251. })
  252. }
  253. },
  254. changeTemp(val){
  255. if(val=='g2plot'){
  256. this.form.settingContent=defaultG2SettingContent
  257. this.form.vueContent=defaultG2VueContent
  258. }else if(val=='native'){
  259. this.form.settingContent=defaultSettingContent
  260. this.form.vueContent=defaultVueContent
  261. }else if(val=='echart'){
  262. this.form.settingContent= defaultEchartsSettingContent
  263. this.form.vueContent=defaultEchartsVueContent
  264. }
  265. },
  266. change(val) {
  267. if(val==='3DEchart') return this.$confirm('开发中。。。。', '提示', {
  268. distinguishCancelAndClose: true,
  269. confirmButtonText: '确定',
  270. cancelButtonText: '取消',
  271. cancelButtonClass: 'cancel-btn',
  272. type: 'warning',
  273. customClass: 'bs-el-message-box'
  274. }).then(() => {
  275. }).catch((action) => {
  276. })
  277. this.$confirm('确定替换为选中模板吗?未保存的代码将被覆盖!', '提示', {
  278. distinguishCancelAndClose: true,
  279. confirmButtonText: '确定',
  280. cancelButtonText: '取消',
  281. cancelButtonClass: 'cancel-btn',
  282. type: 'warning',
  283. customClass: 'bs-el-message-box'
  284. }).then(() => {
  285. this.changeTemp(val)
  286. }).catch((action) => {
  287. })
  288. },
  289. upload (type) {
  290. this.currentContentType = type
  291. this.$refs[`${this.currentContentType}File`].click()
  292. },
  293. handleBatchUpload (source) {
  294. const file = source.target.files
  295. const reader = new FileReader() // 新建一个FileReader
  296. reader.readAsText(file[0], 'UTF-8') // 读取文件
  297. reader.onload = (event) => {
  298. const sileString = event.target.result // 读取文件内容
  299. this.form[this.currentContentType] = sileString
  300. // input通过onchange事件来触发js代码的,由于两次文件是重复的,所以这个时候onchange事件是没有触发到的,所以需要手动清空input的值
  301. source.target.value = ''
  302. }
  303. },
  304. backManagement () {
  305. // 给出一个确认框提示,提示如下确定返回主页面吗?未保存的配置将会丢失。3个按钮 : 留在页面 、离开页面、保存后离开页面
  306. this.$confirm('确定返回主页面吗?未保存的配置将会丢失。', '提示', {
  307. distinguishCancelAndClose: true,
  308. confirmButtonText: '保存后离开页面',
  309. cancelButtonText: '离开页面',
  310. cancelButtonClass: 'cancel-btn',
  311. type: 'warning',
  312. customClass: 'bs-el-message-box'
  313. }).then(() => {
  314. this.save(true)
  315. }).catch((action) => {
  316. if (action === 'cancel') {
  317. this.pageJump()
  318. }
  319. })
  320. },
  321. save (pageJump = false) {
  322. this.loading = true
  323. const node = document.querySelector('.remote-preview-inner-wrap')
  324. // 获取node下的第一个子节点
  325. const childrenNode = node.children[0]
  326. toJpeg(childrenNode, { quality: 0.2 })
  327. .then((dataUrl) => {
  328. const that = this
  329. if (showSize(dataUrl) > 200) {
  330. const url = dataURLtoBlob(dataUrl)
  331. // 压缩到500KB,这里的500就是要压缩的大小,可自定义
  332. imageConversion
  333. .compressAccurately(url, {
  334. size: 200, // 图片大小压缩到100kb
  335. width: 1280, // 宽度压缩到1280
  336. height: 720 // 高度压缩到720
  337. })
  338. .then((res) => {
  339. translateBlobToBase64(res, function (e) {
  340. this.form.coverPicture = e.result
  341. updateBizComponent(this.form)
  342. .then(() => {
  343. this.$message({
  344. message: '保存成功',
  345. type: 'success',
  346. duration: 800,
  347. onClose: () => {
  348. // 此处写提示关闭后需要执行的函数
  349. if (pageJump) {
  350. this.pageJump()
  351. }
  352. }
  353. })
  354. })
  355. .finally(() => {
  356. that.loading = false
  357. })
  358. })
  359. })
  360. } else {
  361. this.form.coverPicture = dataUrl
  362. updateBizComponent(this.form)
  363. .then(() => {
  364. this.$message({
  365. message: '保存成功',
  366. type: 'success',
  367. duration: 800,
  368. onClose: () => {
  369. // 此处写提示关闭后需要执行的函数
  370. if (pageJump) {
  371. this.pageJump()
  372. }
  373. }
  374. })
  375. })
  376. .finally(() => {
  377. this.loading = false
  378. })
  379. }
  380. })
  381. .catch(() => {
  382. this.loading = false
  383. })
  384. },
  385. createdImg () {
  386. this.loading = true
  387. const node = document.querySelector('.remote-preview-inner-wrap')
  388. // 获取node下的第一个子节点
  389. const childrenNode = node.children[0]
  390. toPng(childrenNode)
  391. .then((dataUrl) => {
  392. const link = document.createElement('a')
  393. link.download = `${this.form.name}.png`
  394. link.href = dataUrl
  395. link.click()
  396. link.addEventListener('click', () => {
  397. link.remove()
  398. })
  399. this.loading = false
  400. })
  401. .catch(() => {
  402. this.$message.warning('出现未知错误,请重试')
  403. this.loading = false
  404. })
  405. },
  406. pageJump () {
  407. const data = { componentsManagementType: 'bizComponent' }
  408. this.$router.app.$options.globalData = data // 将数据存储在全局变量中
  409. this.$router.push({ path: window.BS_CONFIG?.routers?.componentUrl || '/big-screen-components' })
  410. }
  411. }
  412. }
  413. </script>
  414. <style lang="scss" scoped>
  415. .bs-custom-components {
  416. position: absolute;
  417. display: flex;
  418. flex-direction: column;
  419. width: 100%;
  420. height: 100vh;
  421. color: var(--bs-el-text);
  422. background: var(--bs-background-2);
  423. overflow: hidden;
  424. > * {
  425. box-sizing: border-box;
  426. }
  427. .bs-custom-component-header {
  428. display: flex;
  429. align-items: center;
  430. justify-content: space-between;
  431. height: 50px;
  432. padding: 0 16px;
  433. border-bottom: 4px solid var(--bs-background-1);
  434. background: var(--bs-background-2);
  435. .left-title {
  436. font-size: 16px;
  437. color: var(--bs-el-title);
  438. .logo-wrap {
  439. display: flex;
  440. align-items: center;
  441. }
  442. .menu-img {
  443. width: 18px;
  444. height: 18px;
  445. margin-right: 15px;
  446. margin-left: 9px;
  447. cursor: pointer;
  448. }
  449. }
  450. .right-btn-wrap {
  451. display: flex;
  452. align-items: center;
  453. height: 100%;
  454. }
  455. }
  456. .bs-custom-component-content {
  457. flex: 1;
  458. background: var(--bs-background-2);
  459. display: flex;
  460. flex-direction: column;
  461. .bs-custom-component-content-code {
  462. display: flex;
  463. justify-content: space-between;
  464. width: 100%;
  465. height: 354px;
  466. padding: 5px 16px;
  467. .left-vue-code {
  468. width: 60%;
  469. height: 100%;
  470. /* background: var(--bs-background-1); */
  471. }
  472. .right-setting-code {
  473. width: calc(40% - 16px);
  474. height: 100%;
  475. /* background: var(--bs-background-1); */
  476. }
  477. .component-code {
  478. .code-tab-header {
  479. display: flex;
  480. align-items: center;
  481. justify-content: space-between;
  482. height: 40px;
  483. .code-tab-left {
  484. height: 100%;
  485. width: 450px;
  486. display: flex;
  487. flex-direction: row;
  488. align-items: center;
  489. justify-content: space-between;
  490. .code-tab-btn{
  491. // width: 90px;
  492. cursor: pointer;
  493. text-align: center;
  494. }
  495. .code-tab {
  496. font-size: 14px;
  497. align-items: center;
  498. justify-content: center;
  499. width: 120px;
  500. height: 100%;
  501. color: var(--bs-el-title);
  502. background: var(--bs-background-1);
  503. }
  504. }
  505. .code-tab {
  506. font-size: 14px;
  507. display: flex;
  508. align-items: center;
  509. justify-content: center;
  510. width: 120px;
  511. height: 100%;
  512. color: var(--bs-el-title);
  513. background: var(--bs-background-1);
  514. }
  515. }
  516. .code-tab-content {
  517. height: calc(100% - 88px);
  518. background: var(--bs-background-1);
  519. }
  520. }
  521. }
  522. .bs-custom-component-content-preview {
  523. flex: 1;
  524. width: 100%;
  525. height: 50%;
  526. padding: 0 16px 16px;
  527. .bs-preview-inner {
  528. width: 100%;
  529. height: 100%;
  530. background: var(--bs-background-1);
  531. position: relative;
  532. .code-tab-header{
  533. height: 40px;
  534. display: flex;
  535. flex-direction: row;
  536. align-items: center;
  537. background-color: var(--bs-background-2);
  538. .code-tab {
  539. font-size: 14px;
  540. align-items: center;
  541. justify-content: center;
  542. display: flex;
  543. width: 120px;
  544. margin-right: 20px;
  545. height: 100%;
  546. color: var(--bs-el-title);
  547. background: var(--bs-background-1);
  548. }
  549. }
  550. }
  551. }
  552. }
  553. }
  554. </style>
  555. <style>
  556. .cm-s-material-darker.CodeMirror,
  557. .cm-s-material-darker .CodeMirror-gutters
  558. {
  559. background: var(--bs-background-1) !important;
  560. }
  561. .CodeMirror-scroll {
  562. background-color: var(--bs-background-1) !important;
  563. }
  564. .CodeMirror-gutters {
  565. border-right: 1px solid var(--bs-background-1) !important;
  566. background-color: var(--bs-background-1) !important;
  567. }
  568. </style>