index.vue 16 KB

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