初始化

This commit is contained in:
砂糖
2025-11-08 10:38:36 +08:00
commit 3beeec7296
1626 changed files with 198488 additions and 0 deletions

View File

@@ -0,0 +1,215 @@
<template>
<div
v-loading="loading"
class="bs-remote-preview"
element-loading-text="远程组件加载中..."
>
<div class="remote-preview-inner-wrap">
<component
:is="remoteComponent"
:config="config"
/>
</div>
</div>
</template>
<script>
import remoteVueLoader from 'remote-vue-loader'
import * as _echarts from 'echarts'
import * as g2Plot from '@antv/g2plot'
import { getBizComponentInfo } from 'data-room-ui/js/api/bigScreenApi'
import innerRemoteComponents, { getRemoteComponents } from 'data-room-ui/RemoteComponents/remoteComponentsList'
export default {
name: 'BsComponentPreview',
props: {
vueContent: {
type: String,
default: ''
},
settingContent: {
type: String,
default: ''
}
},
computed: {
config: {
get () {
// eslint-disable-next-line prefer-const
let option = {}
// eslint-disable-next-line prefer-const
let setting = []
// eslint-disable-next-line prefer-const, no-unused-vars
let title = ''
// eslint-disable-next-line prefer-const, no-unused-vars
let data = []
// eslint-disable-next-line prefer-const, no-unused-vars
let optionHandler = ''
const g2Plots = g2Plot
const echarts = _echarts
// eslint-disable-next-line prefer-const
let settingContent = this.settingContentInner?.replaceAll('const ', '')
// 去掉 export default及后面代码
settingContent = settingContent?.replace(/export default[\s\S]*/, '')
eval(settingContent)
return {
title,
option,
setting,
echarts,
g2Plots,
optionHandler
}
},
set (val) {}
}
},
watch: {
settingContentInner () {
this.getRemoteComponent()
},
vueContentInner () {
this.getRemoteComponent()
},
vueContent (newVal) {
this.vueContentInner = newVal
},
settingContent (newVal) {
this.settingContentInner = newVal
}
},
data () {
return {
loading: false,
remoteComponent: null,
vueContentInner: this.vueContent,
settingContentInner: this.settingContent?.replaceAll('const ', '')
}
},
created () {
this.viewComponent()
},
methods: {
async viewComponent () {
// 如果有编码,则获取组件信息
if (this.$route.query?.code) {
const data = await getBizComponentInfo(this.$route.query?.code)
this.vueContentInner = data.vueContent
this.settingContentInner = data.settingContent
this.config = this.dataFormatting(this.config)
this.remoteComponent = remoteVueLoader('data:text/plain,' + encodeURIComponent(this.vueContentInner))
this.loading = false
}
// 如果有组件的dirName则获取系统组件信息
if (this.$route.query?.dirName) {
const dirName = this.$route.query?.dirName
const remoteComponentList = [...innerRemoteComponents, ...getRemoteComponents()]
const config = remoteComponentList?.find(item => item.customize.vueSysComponentDirName === dirName)
this.config.option = config?.option
this.config.title = config?.title
const vueFile = config.customize?.vueFile
this.remoteComponent = vueFile
this.loading = false
}
},
// 尝试渲染远程文件或远程字符串
getRemoteComponent () {
this.loading = true
this.config = this.dataFormatting(this.config, { success: false })
this.remoteComponent = remoteVueLoader('data:text/plain,' + encodeURIComponent(this.vueContentInner))
this.loading = false
},
/**
* 组件的配置
* @returns {Promise<unknown>}
*/
// 将config.setting的配置转化为option里的配置这里之所以将转化的方法提出来是因为在改变维度指标和样式的时候都需要转化
transformSettingToOption (config, type) {
let option = null
config.setting.forEach(set => {
if (set.optionField) {
const optionField = set.optionField.split('.')
option = config.option
optionField.forEach((field, index) => {
if (index === optionField.length - 1) {
// 数据配置时,必须有值才更新
if ((set.tabName === type && type === 'data' && set.value) || (set.tabName === type && type === 'custom')) {
option[field] = set.value
}
} else {
// 如果没有这个属性,则创建该属性,并赋值为空对值
if (!option[field]) {
option[field] = {}
}
option = option[field]
}
})
}
})
return config
},
dataFormatting (config, data) {
// 数据返回成功则赋值
if (data?.success) {
data = data.data
config = this.transformSettingToOption(config, 'data')
// 获取到后端返回的数据,有则赋值
// const option = config.option
// const setting = config.setting
if (config.dataHandler) {
try {
// 此处函数处理data
eval(config.dataHandler)
} catch (e) {
console.error(e)
}
}
config.option.data = data
} else {
// 数据返回失败则赋前端的模拟数据
config.option.data = this.plotList?.find(plot => plot.name === config.name)?.option?.data || config.option.data
}
return config
},
// 组件的样式改变返回改变后的config
changeStyle (config) {
config = { ...this.config, ...config }
config = this.transformSettingToOption(config, 'custom')
// 这里定义了option和setting是为了保证在执行eval时,optionHandler、dataHandler里面可能会用到
// const option = config.option
// const setting = config.setting
if (this.config.optionHandler) {
try {
// 此处函数处理config
eval(this.config.optionHandler)
} catch (e) {
console.error(e)
}
}
if (this.chart) {
this.chart.update(config.option)
}
this.changeChartConfig(config)
return config
}
}
}
</script>
<style lang="scss" scoped>
.bs-remote-preview {
position: absolute;
min-height: 100%;
min-width: 100%;
overflow: hidden;
box-sizing: border-box;
.remote-preview-inner-wrap {
position: absolute;
height: calc(100% - 40px);
width: 100%;
overflow: auto;
padding: 5px 20px;
background-color: var(--bs-background-1);
}
}
</style>

View File

@@ -0,0 +1,165 @@
/*
* @description: 此处是业务组件的代码案例
* @Date: 2023-06-06 15:45:07
*/
// vue 组件片段
export const defaultVueContent = `
<!-- 这是一个代码案例 -->
<template>
<div class="div-test-container" @click="testClick">
<p>点击测试下点击事件</p>
<br />
{{ customize.text }}
</div>
</template>
<script>
export default {
name: 'TestA',
components: {
},
// 业务组件提供的props
props: {
config: {
type: Object,
default: () => ({})
}
},
data () {
return {
}
},
// 计算属性
computed: {
option () {
return this.config.option
},
optionData () {
return this.option.data
},
customize () {
return this.option.customize
}
},
methods: {
// 联动需要调用次接口
linkage (row) {
this.$emit('linkage', row)
},
// 自己随便写的方法
testClick () {
this.$message.success('点击了边框')
}
}
}
</script>
<style lang="scss" scoped>
// 此处书写样式支持scss
.div-test-container {
width: 200px;
height: 200px;
border: 4px solid #f00;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
flex-direction: column;
.item {
width: 100%;
height: 50px;
line-height: 50px;
text-align: center;
}
}
</style>
`
// 配置 片段
export const defaultSettingContent = `
// 这是一个配置案例
// 组件备注名称
const title = '边框案例'
// 右侧配置项
const setting = [
{
label: '维度',
// 设置组件类型, select / input / colorPicker
type: 'select',
// 字段
field: 'xField',
optionField: 'xField', // 对应options中的字段
// 是否多选
multiple: false,
// 绑定的值
value: '',
// tab页。 data: 数据, custom: 自定义
tabName: 'data'
},
{
label: '指标',
// 设置组件类型
type: 'select',
// 字段
field: 'yField',
// 对应options中的字段
optionField: 'yField',
// 是否多选
multiple: false,
value: '',
tabName: 'data'
},
{
label: '用户名',
// 设置组件类型, select / input / colorPicker
type: 'input',
// 字段
field: 'customize_username',
optionField: 'customize.username', // 对应options中的字段
// 是否多选
multiple: false,
// 绑定的值
value: '',
// tab页。 data: 数据, custom: 自定义
tabName: 'custom'
},
{
label: '手机号',
// 设置组件类型, select / input / colorPicker
type: 'input',
// 字段
field: 'customize_phone',
optionField: 'customize.phone', // 对应options中的字段
// 是否多选
multiple: false,
// 绑定的值
value: '',
// tab页。 data: 数据, custom: 自定义
tabName: 'custom'
}
]
// 模拟数据
const data = []
const option = {
// 数据
data: data,
// 数据的字段相关属性
xField: '',
yField: '',
seriesField: '',
// 自定义组件其他属性
customize: {
text: '这是一个边框'
}
}
export default {
title,
option,
setting
}
`

View File

@@ -0,0 +1,232 @@
/*
* @description: 此处是业务组件的代码案例
* @Date: 2023-06-06 15:45:07
*/
// vue 组件片段
export const defaultEchartsVueContent = `
<!-- 这是一个代码案例 -->
<template>
<div
:id="chatId"
style="width: 100%;height: 100%"
/>
</template>
<script>
export default {
name: 'TestA',
components: {
},
// 业务组件提供的props
props: {
config: {
type: Object,
default: () => ({})
}
},
data () {
return {
chart: null,
}
},
// 计算属性
computed: {
chatId(){
return 'echarts' + this.config.code
}
},
methods: {
//响应式变化组件大小方法,无需改动
onResize () {
this.chart.resize({
animation: {
duration: 300,
easing: 'linear'
// delay: 500,
}
})
},
// 初始化图表
newChart (config) {
let option = config.option
const xList=config.option.data.map(item=> item[config.option.xField])
const yList=config.option.data.map(item=> item[config.option.yField])
option.xAxis.data=xList
option.series[0].data=yList
const dom = document.getElementById(this.chatId)
this.chart = config.echarts.init(dom)
this.chart.setOption(option,true)
},
},
mounted(){
this.newChart(this.config)
//响应式变化组件大小,无需改动
const dragSelect = document.querySelector("#"+this.chatId)
let pre = Date.now()
const wait = 300
const resizeObserver = new ResizeObserver(entries => {
const now = Date.now()
if (now - pre >= wait) {
setTimeout(() => {
this.onResize()
}, wait)
pre = Date.now()
}
})
resizeObserver.observe(dragSelect)
},
}
</script>
<style lang="scss" scoped>
// 此处书写样式支持scss
</style>
`
// 配置 片段
export const defaultEchartsSettingContent = `
// 这是一个配置案例
// 组件备注名称
const title = 'echarts案例'
// 右侧配置项
const setting = [
{
label: '维度',
// 设置组件类型, select / input / colorPicker
type: 'select',
// 字段
field: 'xField',
optionField: 'xField', // 对应options中的字段
// 是否多选
multiple: false,
// 绑定的值
value: '',
// tab页。 data: 数据, custom: 自定义
tabName: 'data'
},
{
label: '指标',
// 设置组件类型
type: 'select',
// 字段
field: 'yField',
// 对应options中的字段
optionField: 'yField',
// 是否多选
multiple: false,
value: '',
tabName: 'data'
},
{
label: '柱形颜色',
type: 'colorPicker',
field: 'color',
optionField: 'color',
value: '#007aff',
tabName: 'custom',
groupName: 'graph'
},
{
label: 'x轴类型',
type: 'input',
field: 'xAxis_type',
optionField: 'xAxis.type',
value: 'category',
tabName: 'custom',
groupName: 'xAxis'
},
{
label: '是否显示标签',
type: 'switch',
field: 'xAxis_axisLabel_show',
optionField: 'xAxis.axisLabel.show',
value: true,
tabName: 'custom',
groupName: 'xAxis'
},
{
label: '标签旋转角度',
type: 'inputNumber',
field: 'xAxis_axisLabel_rotate',
optionField: 'xAxis.axisLabel.rotate',
value: 0,
tabName: 'custom',
groupName: 'xAxis'
},
{
label: '标签颜色',
type: 'colorPicker',
field: 'xAxis_axisLabel_color',
optionField: 'xAxis.axisLabel.color',
value: '#fff',
tabName: 'custom',
groupName: 'xAxis'
},
{
label: 'y轴类型',
type: 'input',
field: 'yAxis_type',
optionField: 'yAxis.type',
value: 'value',
tabName: 'custom',
groupName: 'yAxis'
}
]
// 模拟数据
const data = [
{ Date: '2010-01', scales: 1998 },
{ Date: '2010-02', scales: 1850 },
{ Date: '2010-03', scales: 1720 },
{ Date: '2010-04', scales: 1818 },
{ Date: '2010-05', scales: 1920 },
{ Date: '2010-06', scales: 1802 },
{ Date: '2010-07', scales: 1945 },
{ Date: '2010-08', scales: 1856 },
{ Date: '2010-09', scales: 2107 },
{ Date: '2010-10', scales: 2140 }
]
const option = {
// 数据将要放入到哪个字段中
dataKey: 'data',
// 图表内边距
appendPadding: [0, 0, 0, 0],
data,
//柱状图颜色
color: '#007aff',
appendPadding: [16, 16, 16, 16], // 设置图标的边距
xField: 'Date',
yField: 'scales',
xAxis: {
type:'category',
data: [],
axisLabel:{
show:true,
color:'#fff',
rotate:0
}
},
yAxis: {
type: 'value'
},
series:[
{
data: [],
type: 'bar',
backgroundStyle: {
color: '#fff'
}
}
]
}
export default {
title,
option,
setting
}
`

View File

@@ -0,0 +1,513 @@
/*
* @description: 此处是业务组件的代码案例
* @Date: 2023-06-06 15:45:07
*/
// vue 组件片段
export const defaultG2VueContent = `
<!-- 这是一个代码案例 -->
<template>
<div
:id="chatId"
style="width: 100%;height: 100%"
/>
</template>
<script>
export default {
name: 'TestA',
components: {
},
// 业务组件提供的props
props: {
config: {
type: Object,
default: () => ({})
}
},
data () {
return {
chart: null,
}
},
// 计算属性
computed: {
chatId(){
return 'g2' + this.config.code
}
},
methods: {
// 联动需要调用次接口
newChart (config) {
this.chart = new config.g2Plots['Line'](this.chatId, {
renderer: 'svg',
// 仪表盘缩放状态下,点击准确
supportCSSTransform: true,
...config.option
})
this.chart.render()
},
},
mounted(){
this.newChart(this.config)
},
}
</script>
<style lang="scss" scoped>
// 此处书写样式支持scss
</style>
`
// 配置 片段
export const defaultG2SettingContent = `
// 这是一个配置案例
// 组件备注名称
const title = 'g2案例'
// 右侧配置项
const setting = [
{
label: '维度',
// 设置组件类型, select / input / colorPicker
type: 'select',
// 字段
field: 'xField',
optionField: 'xField', // 对应options中的字段
// 是否多选
multiple: false,
// 绑定的值
value: '',
// tab页。 data: 数据, custom: 自定义
tabName: 'data'
},
{
label: '指标',
// 设置组件类型
type: 'select',
// 字段
field: 'yField',
// 对应options中的字段
optionField: 'yField',
// 是否多选
multiple: false,
value: '',
tabName: 'data'
},
/** 样式配置 **/
// 图表 graph
{
label: '曲线宽度',
type: 'inputNumber',
field: 'lineStyle_lineWidth',
optionField: 'lineStyle.lineWidth',
value: 2,
tabName: 'custom',
groupName: 'graph'
},
{
label: '曲线颜色',
type: 'gradual',
field: 'lineStyle_stroke',
optionField: 'lineStyle.stroke',
value: 'l(0) 0:#5F92F9 1:#62FF00',
tabName: 'custom',
groupName: 'graph'
},
// 网格线 grid
{
label: '虚线',
type: 'switch',
field: 'yAxis_grid_line_style_lineDash',
optionField: 'yAxis.grid.line.style.lineDash',
value: 0,
active: 5,
inactive: 0,
tabName: 'custom',
groupName: 'grid'
},
{
label: '宽度',
type: 'inputNumber',
field: 'yAxis_grid_line_style_lineWidth',
optionField: 'yAxis.grid.line.style.lineWidth',
value: 1,
tabName: 'custom',
groupName: 'grid'
},
{
label: '颜色',
type: 'colorPicker',
field: 'yAxis_grid_line_style_stroke',
optionField: 'yAxis.grid.line.style.stroke',
value: '#E5E6EB10',
tabName: 'custom',
groupName: 'grid'
},
// 图例 legend
// X轴 xAxis
{
label: '标题',
type: 'input',
field: 'xAxis_title_text',
optionField: 'xAxis.title.text',
value: '',
tabName: 'custom',
groupName: 'xAxis'
},
{
label: '标题位置',
type: 'select',
field: 'xAxis_title_position',
optionField: 'xAxis.title.position',
value: 'end',
tabName: 'custom',
options: [
{
label: '左',
value: 'start'
},
{
label: '中',
value: 'center'
},
{
label: '右',
value: 'end'
}],
groupName: 'xAxis'
},
{
label: '标题字体大小',
type: 'inputNumber',
field: 'xAxis_title_style_fontSize',
optionField: 'xAxis.title.style.fontSize',
value: 12,
tabName: 'custom',
groupName: 'xAxis'
},
{
label: '标题颜色',
type: 'colorPicker',
field: 'xAxis_title_style_fill',
optionField: 'xAxis.title.style.fill',
// 是否多选
multiple: false,
value: '#e9e9e9',
tabName: 'custom',
groupName: 'xAxis'
},
{
label: '标签大小',
type: 'inputNumber',
field: 'xAxis_label_style_fontSize',
optionField: 'xAxis.label.style.fontSize',
value: 12,
tabName: 'custom',
groupName: 'xAxis'
},
{
label: '标签颜色',
type: 'colorPicker',
field: 'xAxis_label_style_fill',
optionField: 'xAxis.label.style.fill',
// 是否多选
multiple: false,
value: '#e9e9e9',
tabName: 'custom',
groupName: 'xAxis'
},
{
label: '轴线宽度',
type: 'inputNumber',
field: 'xAxis_line_style_lineWidth',
optionField: 'xAxis.line.style.lineWidth',
value: 1,
tabName: 'custom',
groupName: 'xAxis'
},
{
label: '轴线颜色',
type: 'colorPicker',
field: 'xAxis_line_style_stroke',
optionField: 'xAxis.line.style.stroke',
// 是否多选
multiple: false,
value: '#C9CDD4',
tabName: 'custom',
groupName: 'xAxis'
},
{
label: '刻度线宽度',
type: 'inputNumber',
field: 'xAxis_tickLine_style_lineWidth',
optionField: 'xAxis.tickLine.style.lineWidth',
value: 1,
tabName: 'custom',
groupName: 'xAxis'
},
{
label: '刻度线颜色',
type: 'colorPicker',
field: 'xAxis_tickLine_style_stroke',
optionField: 'xAxis.tickLine.style.stroke',
// 是否多选
multiple: false,
value: '#C9CDD4',
tabName: 'custom',
groupName: 'xAxis'
},
{
label: '标签过多时旋转',
type: 'switch',
field: 'xAxis_label_autoRotate',
optionField: 'xAxis.label.autoRotate',
value: true,
active: true,
inactive: false,
tabName: 'custom',
groupName: 'xAxis'
},
{
label: '标签过多时隐藏',
type: 'switch',
field: 'xAxis_label_autoHide',
optionField: 'xAxis.label.autoHide',
value: true,
active: true,
inactive: false,
tabName: 'custom',
groupName: 'xAxis'
},
{
label: '标签过长时省略',
type: 'switch',
field: 'xAxis_label_autoEllipsis',
optionField: 'xAxis.label.autoEllipsis',
value: false,
active: true,
inactive: false,
tabName: 'custom',
groupName: 'xAxis'
},
// Y轴 yAxis
{
label: '标题',
type: 'input',
field: 'yAxis_title_text',
optionField: 'yAxis.title.text',
value: '',
tabName: 'custom',
groupName: 'yAxis'
},
{
label: '标题位置',
type: 'select',
field: 'yAxis_title_position',
optionField: 'yAxis.title.position',
value: 'end',
tabName: 'custom',
options: [
{
label: '上',
value: 'end'
},
{
label: '中',
value: 'center'
},
{
label: '下',
value: 'start'
}],
groupName: 'yAxis'
},
{
label: '标题字体大小',
type: 'inputNumber',
field: 'yAxis_title_style_fontSize',
optionField: 'yAxis.title.style.fontSize',
value: 12,
tabName: 'custom',
groupName: 'yAxis'
},
{
label: '标题颜色',
type: 'colorPicker',
field: 'yAxis_title_style_fill',
optionField: 'yAxis.title.style.fill',
// 是否多选
multiple: false,
value: '#e9e9e9',
tabName: 'custom',
groupName: 'yAxis'
},
{
label: '显示标签',
type: 'switch',
field: 'yAxis_label_style_opacity',
optionField: 'yAxis.label.style.opacity',
value: 1,
active: 1,
inactive: 0,
tabName: 'custom',
groupName: 'yAxis'
},
{
label: '标签字体大小',
type: 'inputNumber',
field: 'yAxis_label_style_fontSize',
optionField: 'yAxis.label.style.fontSize',
value: 12,
tabName: 'custom',
groupName: 'yAxis'
},
{
label: '标签颜色',
type: 'colorPicker',
field: 'yAxis_label_style_fill',
optionField: 'yAxis.label.style.fill',
// 是否多选
multiple: false,
value: '#e9e9e9',
tabName: 'custom',
groupName: 'yAxis'
},
{
label: '轴线宽度',
type: 'inputNumber',
field: 'yAxis_line_lineWidth',
optionField: 'yAxis.line.style.lineWidth',
value: 0,
tabName: 'custom',
groupName: 'yAxis'
},
{
label: '轴线颜色',
type: 'colorPicker',
field: 'yAxis_line_stroke',
optionField: 'yAxis.line.style.stroke',
// 是否多选
multiple: false,
value: '#C9CDD4',
tabName: 'custom',
groupName: 'yAxis'
},
// 内边距 appendPadding
{
label: '图表边距',
type: 'padding',
field: 'appendPadding',
optionField: 'appendPadding',
value: [16, 16, 16, 16],
tabName: 'custom',
groupName: 'padding'
}
]
// 模拟数据
const data = [
{ Date: '2010-01', scales: 1998 },
{ Date: '2010-02', scales: 1850 },
{ Date: '2010-03', scales: 1720 },
{ Date: '2010-04', scales: 1818 },
{ Date: '2010-05', scales: 1920 },
{ Date: '2010-06', scales: 1802 },
{ Date: '2010-07', scales: 1945 },
{ Date: '2010-08', scales: 1856 },
{ Date: '2010-09', scales: 2107 },
{ Date: '2010-10', scales: 2140 }
]
const option = {
// 数据将要放入到哪个字段中
dataKey: 'data',
// 图表内边距
appendPadding: [0, 0, 0, 0],
data,
color: '',
appendPadding: [16, 16, 16, 16], // 设置图标的边距
xField: 'Date',
yField: 'scales',
smooth: true,
lineStyle: {
lineWidth: 2,
stroke: 'l(0) 0:#6b74e4 1:#4391f4'
},
xAxis: {
title: {
text: '',
position: 'end',
style: {
fill: '#e9e9e9',
fontSize: 12
}
},
label: {
autoRotate: false,
autoHide: false,
autoEllipsis: true,
style: {
fill: '#e9e9e9',
fontSize: 12
}
},
line: {
style: {
stroke: '#C9CDD4',
lineWidth: 1
}
},
tickLine: {
style: {
stroke: '#C9CDD4',
lineWidth: 1
}
}
},
yAxis: {
title: {
text: '',
position: 'end',
autoRotate: false,
// rotation: Math.PI / 2,
style: {
fill: '#8C8C8C',
fontSize: 12
}
},
grid: {
line: {
style: {
stroke: '#E5E6EB10',
lineWidth: 1,
lineDash: [4, 5],
strokeOpacity: 0.7
}
}
},
label: {
style: {
fill: '#e9e9e9',
fontSize: 12,
opacity: 1
}
},
line: {
style: {
stroke: '#C9CDD4',
lineWidth: 0
}
}
}
}
export default {
title,
option,
setting
}
`

View File

@@ -0,0 +1,660 @@
<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>