You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

659 lines
19 KiB
Vue

<template>
<div class="bs-custom-components">
<div class="bs-custom-component-header">
<div class="left-title">
<div class="logo-wrap item-wrap">
<img
class="menu-img"
src="../BigScreenDesign/images/app.png"
alt="返回"
@click="backManagement"
>
<span class="logo-text name-span">{{ form.name }}</span>
</div>
</div>
<div class="right-btn-wrap">
<CusBtn
:loading="loading"
@click="save"
>
保存
</CusBtn>
</div>
</div>
<div class="bs-custom-component-content">
<div class="bs-custom-component-content-code">
<div class="left-vue-code component-code">
<div class="code-tab-header">
<div class="code-tab-left">
<div class="code-tab">
组件模板
</div>
<div
class="code-tab-btn"
@click="change('echart')"
>
echarts组件
</div>
<div
class="code-tab-btn"
@click="change('g2plot')"
>
G2Plot组件
</div>
<div
class="code-tab-btn"
@click="change('native')"
>
原生组件
</div>
<div
class="code-tab-btn"
@click="change('3DEchart')"
>
3D组件
</div>
</div>
<!-- <div class="upload-btn">
<CusBtn @click="upload('vueContent')">
上传
</CusBtn>
</div> -->
</div>
<div class="code-tab-content">
<!-- <MonacoEditor
ref="vueContent"
v-model="form.vueContent"
class="editor"
language="html"
/> -->
<codemirror
v-model="form.vueContent"
:options="vueOptions"
/>
</div>
</div>
<div class="right-setting-code component-code">
<div class="code-tab-header">
<div class="code-tab">
组件配置
</div>
<!-- <div class="upload-btn">
<CusBtn @click="upload('settingContent')">
上传
</CusBtn>
</div> -->
</div>
<div class="code-tab-content">
<!-- <MonacoEditor
ref="settingContent"
v-model="form.settingContent"
class="editor"
language="javascript"
/> -->
<codemirror
v-model="form.settingContent"
:options="settingOptions"
/>
</div>
</div>
</div>
<div class="bs-custom-component-content-preview">
<div class="bs-preview-inner">
<div class="code-tab-header">
<div class="code-tab">
效果预览
</div>
<div class="upload-btn">
<CusBtn
:loading="loading"
@click.native="createdImg()"
>
生成图片
</CusBtn>
</div>
</div>
<BizComponentPreview
:vue-content="form.vueContent"
:setting-content="form.settingContent"
/>
</div>
</div>
<!-- 通过计算属性发现accept有问题 -->
<input
ref="vueContentFile"
style="display: none"
type="file"
name="file"
accept=".vue"
@change="handleBatchUpload"
>
<input
ref="settingContentFile"
style="display: none"
type="file"
name="file"
accept=".js"
@change="handleBatchUpload"
>
</div>
</div>
</template>
<script>
import { toJpeg } from 'html-to-image'
import CusBtn from 'data-room-ui/BigScreenDesign/BtnLoading'
// import MonacoEditor from 'data-room-ui/MonacoEditor'
import BizComponentPreview from './Preview'
import { getBizComponentInfo, updateBizComponent } from 'data-room-ui/js/api/bigScreenApi'
import { defaultSettingContent, defaultVueContent } from './config/defaultBizConfig'
import { defaultEchartsSettingContent, defaultEchartsVueContent } from './config/defaultEchartsConfig'
import { defaultG2SettingContent, defaultG2VueContent } from './config/defaultG2Config'
import { codemirror } from 'vue-codemirror'
import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/material-darker.css'
import 'codemirror/addon/selection/active-line.js'
import 'codemirror/mode/vue/vue.js'
import {
showSize,
compressImage
// dataURLtoBlob,
// translateBlobToBase64
} from 'data-room-ui/js/utils/compressImg'
// import * as imageConversion from 'image-conversion'
export default {
name: 'BizComponentDesign',
components: {
CusBtn,
// MonacoEditor,
codemirror,
BizComponentPreview
},
props: {},
data () {
return {
initialCoverPicture: '',
form: {
name: '',
coverPicture: '',
settingContent: '',
vueContent: ''
},
currentContentType: 'vueContent',
loading: false,
vueOptions: {
foldGutter: true,
lineWrapping: true,
gutters: [
'CodeMirror-linenumbers',
'CodeMirror-foldgutter',
'CodeMirror-lint-markers'
],
theme: 'material-darker',
tabSize: 4,
lineNumbers: true,
line: true,
indentWithTabs: true,
smartIndent: true,
autofocus: false,
matchBrackets: true,
mode: 'text/x-vue',
hintOptions: {
completeSingle: false
},
lint: true
},
settingOptions: {
foldGutter: true,
lineWrapping: true,
gutters: [
'CodeMirror-linenumbers',
'CodeMirror-foldgutter',
'CodeMirror-lint-markers'
],
theme: 'material-darker',
tabSize: 4,
lineNumbers: true,
line: true,
indentWithTabs: true,
smartIndent: true,
autofocus: false,
matchBrackets: true,
mode: 'text/javascript',
hintOptions: {
completeSingle: false
},
lint: true
}
}
},
computed: {
},
mounted () {
this.getBizComponentInfo()
},
methods: {
getBizComponentInfo () {
const code = this.$route.query.code
const type = this.$route.query.type
if (code) {
getBizComponentInfo(code).then(data => {
this.initialCoverPicture = data.coverPicture || ''
if (type && type === 'g2plot') {
this.form = {
...data,
name: data.name,
coverPicture: data.coverPicture,
settingContent: data.settingContent || defaultG2SettingContent,
vueContent: data.vueContent || defaultG2VueContent
}
} else if (type && type === 'echart') {
this.form = {
...data,
name: data.name,
coverPicture: data.coverPicture,
settingContent: data.settingContent || defaultEchartsSettingContent,
vueContent: data.vueContent || defaultEchartsVueContent
}
} else {
this.form = {
...data,
name: data.name,
coverPicture: data.coverPicture,
settingContent: data.settingContent || defaultSettingContent,
vueContent: data.vueContent || defaultVueContent
}
}
// this.$refs.vueContent.editor.setValue(this.form.vueContent)
// this.$refs.settingContent.editor.setValue(this.form.settingContent)
})
}
},
changeTemp (val) {
if (val === 'g2plot') {
this.form.settingContent = defaultG2SettingContent
this.form.vueContent = defaultG2VueContent
} else if (val === 'native') {
this.form.settingContent = defaultSettingContent
this.form.vueContent = defaultVueContent
} else if (val === 'echart') {
this.form.settingContent = defaultEchartsSettingContent
this.form.vueContent = defaultEchartsVueContent
}
},
change (val) {
if (val === '3DEchart') {
return this.$confirm('开发中。。。。', '提示', {
distinguishCancelAndClose: true,
confirmButtonText: '确定',
cancelButtonText: '取消',
cancelButtonClass: 'cancel-btn',
type: 'warning',
customClass: 'bs-el-message-box'
}).then(() => {
}).catch((action) => {
})
}
this.$confirm('确定替换为选中模板吗?未保存的代码将被覆盖!', '提示', {
distinguishCancelAndClose: true,
confirmButtonText: '确定',
showCancelButton: false,
cancelButtonClass: 'cancel-btn',
type: 'warning',
customClass: 'bs-el-message-box'
}).then(() => {
this.changeTemp(val)
}).catch((action) => {
})
},
// upload (type) {
// this.currentContentType = type
// this.$refs[`${this.currentContentType}File`].click()
// },
handleBatchUpload (source) {
const file = source.target.files
const reader = new FileReader() // 新建一个FileReader
reader.readAsText(file[0], 'UTF-8') // 读取文件
reader.onload = (event) => {
const sileString = event.target.result // 读取文件内容
this.form[this.currentContentType] = sileString
// input通过onchange事件来触发js代码的由于两次文件是重复的所以这个时候onchange事件是没有触发到的所以需要手动清空input的值
source.target.value = ''
}
},
backManagement () {
// 给出一个确认框提示提示如下确定返回主页面吗未保存的配置将会丢失。3个按钮 留在页面 、离开页面、保存后离开页面
this.$confirm('确定返回主页面吗?未保存的配置将会丢失。', '提示', {
distinguishCancelAndClose: true,
confirmButtonText: '保存后离开页面',
cancelButtonText: '离开页面',
cancelButtonClass: 'cancel-btn',
type: 'warning',
customClass: 'bs-el-message-box'
}).then(() => {
this.save(true)
}).catch((action) => {
if (action === 'cancel') {
this.pageJump()
}
})
},
async save (pageJump = false) {
this.loading = true
let dataUrl = ''
const node = document.querySelector('.remote-preview-inner-wrap')
// 获取node下的第一个子节点
const childrenNode = node.children[0]
try {
dataUrl = await toJpeg(childrenNode, { quality: 0.2 })
} catch (error) {
console.info(error)
}
if (dataUrl) {
if (showSize(dataUrl) > 200) {
// const url = dataURLtoBlob(dataUrl)
// 压缩到500KB,这里的500就是要压缩的大小,可自定义
// imageConversion.compressAccurately(
// url,
// {
// size: 200, // 图片大小压缩到100kb
// width: 1280, // 宽度压缩到1280
// height: 720 // 高度压缩到720
// }
// ).then((res) => {
// translateBlobToBase64(res, (e) => {
// this.form.coverPicture = e.result
// })
// })
this.$message.info('由于封面图片过大,进行压缩中')
this.form.coverPicture = await compressImage(dataUrl, { width: 1280, height: 720, size: 400, quality: 1 })
} else {
this.form.coverPicture = dataUrl
}
} else {
this.$message.warning('保存封面失败,将使用上次保存的封面')
this.form.coverPicture = this.initialCoverPicture
}
updateBizComponent(this.form).then(() => {
this.$message({
message: '保存成功',
type: 'success',
duration: 800,
onClose: () => {
// 此处写提示关闭后需要执行的函数
if (pageJump) {
this.pageJump()
}
}
})
this.loading = false
}).catch((error) => {
console.info(error)
this.loading = false
})
},
createdImg () {
this.loading = true
const node = document.querySelector('.remote-preview-inner-wrap')
// 获取node下的第一个子节点
const childrenNode = node.children[0]
// 为childrenNode添加一个背景颜色
childrenNode.style.backgroundColor = 'var(--bs-background-1)'
toJpeg(childrenNode)
.then((dataUrl) => {
const link = document.createElement('a')
link.download = `${this.form.name}.png`
link.href = dataUrl
link.click()
link.addEventListener('click', () => {
link.remove()
})
this.loading = false
})
.catch((error) => {
console.info(error)
this.loading = false
// 判断的error.currentTarget是img标签如果是的就弹出消息说是图片跨域
// 确认框
this.$confirm('图片、视频资源跨域导致使用toDataURL API生成图片失败请将资源上传到资源库然后在组件中使用资源库中的图片资源确保没有跨域问题。', '提示', {
confirmButtonText: '确定',
showCancelButton: false,
type: 'warning',
customClass: 'bs-el-message-box'
}).then(() => { }).catch(() => { })
})
},
pageJump () {
const data = { componentsManagementType: 'bizComponent' }
this.$router.app.$options.globalData = data // 将数据存储在全局变量中
this.$router.push({ path: window.BS_CONFIG?.routers?.componentUrl || '/big-screen-components' })
}
}
}
</script>
<style lang="scss" scoped>
.bs-custom-components {
position: absolute;
display: flex;
flex-direction: column;
width: 100%;
height: 100vh;
color: var(--bs-el-text);
background: var(--bs-background-2);
overflow: hidden;
>* {
box-sizing: border-box;
}
.bs-custom-component-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 50px;
padding: 0 16px;
border-bottom: 4px solid var(--bs-background-1);
background: var(--bs-background-2);
.left-title {
font-size: 16px;
color: var(--bs-el-title);
.logo-wrap {
display: flex;
align-items: center;
}
.menu-img {
width: 18px;
height: 18px;
margin-right: 15px;
margin-left: 9px;
cursor: pointer;
}
}
.right-btn-wrap {
display: flex;
align-items: center;
height: 100%;
}
}
.bs-custom-component-content {
flex: 1;
background: var(--bs-background-2);
display: flex;
flex-direction: column;
.bs-custom-component-content-code {
display: flex;
justify-content: space-between;
width: 100%;
height: 354px;
padding: 5px 16px;
.left-vue-code {
width: 60%;
height: 100%;
/* background: var(--bs-background-1); */
}
.right-setting-code {
width: calc(40% - 16px);
height: 100%;
/* background: var(--bs-background-1); */
}
.component-code {
.code-tab-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 40px;
.code-tab-left {
height: 100%;
width: 450px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
.code-tab-btn {
// width: 90px;
cursor: pointer;
text-align: center;
}
.code-tab {
font-size: 14px;
align-items: center;
justify-content: center;
width: 120px;
height: 100%;
color: var(--bs-el-title);
background: var(--bs-background-1);
}
}
.code-tab {
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
width: 120px;
height: 100%;
color: var(--bs-el-title);
background: var(--bs-background-1);
}
}
.code-tab-content {
height: calc(100% - 88px);
background: var(--bs-background-1);
}
}
}
.bs-custom-component-content-preview {
flex: 1;
width: 100%;
height: 50%;
padding: 0 16px 16px;
.bs-preview-inner {
width: 100%;
height: 100%;
background: var(--bs-background-1);
position: relative;
.code-tab-header {
height: 40px;
display: flex;
flex-direction: row;
align-items: center;
background-color: var(--bs-background-2);
.code-tab {
font-size: 14px;
align-items: center;
justify-content: center;
display: flex;
width: 120px;
margin-right: 20px;
height: 100%;
color: var(--bs-el-title);
background: var(--bs-background-1);
}
}
}
}
}
}
</style>
<style>
.cm-s-material-darker.CodeMirror,
.cm-s-material-darker .CodeMirror-gutters {
background: var(--bs-background-1) !important;
}
.CodeMirror-scroll {
background-color: var(--bs-background-1) !important;
}
.CodeMirror-gutters {
border-right: 1px solid var(--bs-background-1) !important;
background-color: var(--bs-background-1) !important;
}
.CodeMirror-vscrollbar {
right: 0;
top: 0;
overflow-x: hidden;
overflow-y: scroll;
margin-right: 4px;
}
/* Webkit浏览器滚动条样式 */
.CodeMirror-vscrollbar::-webkit-scrollbar {
width: 6px;
/* 滚动条宽度 */
}
.CodeMirror-vscrollbar::-webkit-scrollbar-thumb {
background-color: #444851;
/* 滚动条滑块颜色 */
border-radius: 4px;
/* 滚动条滑块圆角 */
}
.CodeMirror-vscrollbar::-webkit-scrollbar-thumb:hover {
background-color: #444851;
/* 滚动条滑块悬停时颜色 */
}
/* Firefox和新版Chrome浏览器滚动条样式 */
.CodeMirror-vscrollbar {
scrollbar-width: thin;
/* 滚动条宽度 */
scrollbar-color: #444851 #444851;
/* 滚动条颜色 */
}
.CodeMirror-vscrollbar::-webkit-scrollbar-thumb {
background-color: #444851;
/* 滚动条滑块颜色 */
}
.CodeMirror-vscrollbar::-webkit-scrollbar-thumb:hover {
background-color: #444851;
/* 滚动条滑块悬停时颜色 */
}
</style>