index.vue 16 KB

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