这只是一个简单的echart生成功能进一步的效果优化暂无,大家也可以借鉴 form-generator 的方案进行改造(这是开源的动态生成页面项目)
组合页面代码
<template>
<div class="hello">
<div style="height: 100%;flex: 1;overflow-y: auto">
<vuedraggable :group="{name:'menu',pull:'clone',put:false}" :sort="false" :list="echartArr" @end="addOne" :clone="cloneArr">
<div v-for="(item ,index) in echartArr" :key="index">
{{item.name}}
</div>
</vuedraggable>
</div>
<div class="right">
<el-button @click="sumbitEchart">保存Echart</el-button>
<el-button @click="seeEchart=true">预览</el-button>
<el-button @click="exportJSON">导出JSON</el-button>
<vuedraggable group='menu' :animation="340" :list="echartShow" style="height: 90%;overflow-y: auto;border: 1px solid #47b347;width: 100%">
<el-col v-for="(item ,index) in echartShow" :key="index" :span="item.style.span"
:style="{marginTop:item.style.marginTop+'px',marginBottom:item.style.marginBottom,marginLeft:item.style.marginLeft+'px',marginRight:item.style.marginRight+'px'}"
>
<div @click="clickEchart(item)">
<echart :options="item.option" :span="item.style.span" :height="item.height" :id="item.id" />
</div>
</el-col>
</vuedraggable>
</div>
<div style="flex: 1;padding-right: 15px;height: 100%;overflow-y: auto;padding-left: 15px">
<el-tabs v-model="activeName">
<el-tab-pane label="组件" name="zujian">
<el-form v-if="jsonEchart" label-position="top">
<el-form-item label="ID">
<el-input v-model="currentEchart.id" style="width: 80%" :disabled="false"/>
</el-form-item>
<el-form-item label="图表名称">
<el-input v-model="currentEchart.option.title.text" @blur="handleName" style="width: 80%"/>
</el-form-item>
<el-form-item label="高度">
<el-input-number v-model="currentEchart.height" />
</el-form-item>
<el-form-item label="栅格宽度">
<el-input-number v-model="currentEchart.style.span" />
</el-form-item>
<el-form-item label="外边距">
<el-row>上<el-input-number v-model="currentEchart.style.marginTop" /></el-row>
<el-row>下<el-input-number v-model="currentEchart.style.marginBottom" /></el-row>
<el-row>左<el-input-number v-model="currentEchart.style.marginLeft" /></el-row>
<el-row>右<el-input-number v-model="currentEchart.style.marginRight" /></el-row>
</el-form-item>
</el-form>
<el-empty v-if="!jsonEchart" description="暂无数据"></el-empty>
</el-tab-pane>
<el-tab-pane label="Echart" name="echart">
<el-button @click="drawer=true">更改数据</el-button>
</el-tab-pane>
</el-tabs>
</div>
<el-drawer
title="Echart图表数据"
:visible.sync="drawer"
:size="800"
>
<b-code-editor
v-if="jsonEchart"
v-model="jsonEchart"
:auto-format="true"
:smart-indent="true"
theme="dracula"
:indent-unit="2"
:line-wrap="true"
:height="'800px'"
ref="editor"
/>
<el-button type="primary" @click="onSubumit">保存</el-button>
</el-drawer>
<el-dialog title="预览" :visible.sync="seeEchart" width="60%">
<el-row>
<el-col v-for="(item ,index) in echartShow" :key="index" :span="item.style.span"
:style="{marginTop:item.style.marginTop+'px',marginBottom:item.style.marginBottom,marginLeft:item.style.marginLeft+'px',marginRight:item.style.marginRight+'px'}"
>
<echart v-if="seeEchart" :options="item.option" :span="item.style.span" :height="item.height" :id="item.id" />
</el-col>
</el-row>
</el-dialog>
</div>
</template>
<script>
import vuedraggable from 'vuedraggable';
import FileSaver from 'file-saver'
import echart from "./echart";
export default {
name: 'HelloWorld',
components: { vuedraggable ,echart},
data(){
return{
activeName:'zujian',
echartArr:[
{
name:'柱状图',
id:'zzt',
option:{
title: {
text: '柱状图',
left: 'center'
},
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar',
showBackground: true,
backgroundStyle: {
color: 'rgba(180, 180, 180, 0.2)'
}
}
]
},
style:{
marginTop:'0',
marginBottom:'0',
marginLeft:'0',
marginRight:'0',
span:24
},
height:300
},
{
name:'折线图',
id:'zxt',
style:{
marginTop:'0',
marginBottom:'0',
marginLeft:'0',
marginRight:'0',
span:24
},
option: {
title: {
text: '折线图',
left: 'center'
},
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [
{
data: [150, 230, 224, 218, 135, 147, 260],
type: 'line'
}
]
},
height:300
},
{
name:'饼图',
id:'bt',
style:{
marginTop:'0',
marginBottom:'0',
marginLeft:'0',
marginRight:'0',
span:24
},
option: {
title: {
text: '饼图',
subtext: 'Fake Data',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: 'Access From',
type: 'pie',
radius: '50%',
data: [
{ value: 1048, name: 'Search Engine' },
{ value: 735, name: 'Direct' },
{ value: 580, name: 'Email' },
{ value: 484, name: 'Union Ads' },
{ value: 300, name: 'Video Ads' }
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
},
height:300
},
{
name:'折柱混合图',
id:'zzhh',
style: {
marginTop:'0',
marginBottom:'0',
marginLeft:'0',
marginRight:'0',
span:24
},
option : {
title: {
text: '折柱混合图',
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
crossStyle: {
color: '#999'
}
}
},
legend: {
data: ['Evaporation', 'Precipitation', 'Temperature'],
left: 'left'
},
xAxis: [
{
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
axisPointer: {
type: 'shadow'
}
}
],
yAxis: [
{
type: 'value',
name: 'Precipitation',
min: 0,
max: 250,
interval: 50,
axisLabel: {
formatter: '{value} ml'
}
},
{
type: 'value',
name: 'Temperature',
min: 0,
max: 25,
interval: 5,
axisLabel: {
formatter: '{value} °C'
}
}
],
series: [
{
name: 'Evaporation',
type: 'bar',
tooltip: {
valueFormatter: function (value) {
return value + ' ml';
}
},
data: [
2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3
]
},
{
name: 'Precipitation',
type: 'bar',
tooltip: {
valueFormatter: function (value) {
return value + ' ml';
}
},
data: [
2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3
]
},
{
name: 'Temperature',
type: 'line',
yAxisIndex: 1,
tooltip: {
valueFormatter: function (value) {
return value + ' °C';
}
},
data: [2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2]
}
]
},
height: 300
}
],
echartShow:[],
currentEchart:'',
jsonEchart:'',
oneId:'',
drawer:false,
seeEchart:false
}
},
mounted() {
// vuedraggle 拖拽在火狐浏览器的阻断
document.body.ondrop = function (event) { event.preventDefault(); event.stopPropagation();}
},
methods:{
cloneArr(e){
let arr=JSON.parse(JSON.stringify(e))
// 动态生成随机id
arr.id=Math.random().toString(36).substr(2)
this.oneId=JSON.parse(JSON.stringify(arr.id))
this.jsonEchart=''
this.echartShow.push(arr)
this.currentEchart=arr
this.$nextTick(()=>{
this.jsonEchart=JSON.stringify(arr.option)
})
},
clickEchart(item){
this.jsonEchart=''
this.currentEchart=item
this.oneId=JSON.parse(JSON.stringify(item.id))
this.activeName='zujian'
this.$nextTick(()=>{
this.jsonEchart=JSON.stringify(item.option)
})
},
// 检测json格式
isJSON(str) {
if (typeof str == 'string') {
try {
var obj=JSON.parse(str);
if(typeof obj == 'object' && obj ){
return true;
}else{
return false;
}
} catch(e) {
return false;
}
}else if (typeof str == 'object' && str) {
return true;
}
},
onSubumit(){
if (!this.isJSON(this.jsonEchart)){
this.$message.error(`json格式错误`)
return false
}
this.echartShow.map((item)=>{
if(item.id===this.oneId){
item.option=JSON.parse(this.jsonEchart)
}
})
this.$message.success('json格式正确')
},
// 更改名称
handleName(){
this.jsonEchart=''
this.$nextTick(()=>{
this.jsonEchart=JSON.stringify(this.currentEchart.option)
console.log(this.jsonEchart)
})
},
// 保存echarts图
sumbitEchart(){
console.log(this.echartShow)
},
// 导出JSON
exportJSON(){
// const
const data=JSON.stringify(this.echartShow,null,2)
const name = 'Echart' + new Date().getTime() + '.json'
console.log(new Date("month dd,yyyy hh:mm:ss"),1111)
const blob = new Blob([data], {type: ''})
FileSaver.saveAs(blob, name)
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped >
.hello{
width: 100%;
height: 100%;
padding: 0 10px;
display: flex;
}
.right{
flex: 4;
height: 100%;
/*background: navy;*/
/*overflow-y: auto;*/
}
.right >div{
/*min-height: 200px;*/
width: 100%;
}
.CodeMirror {
font-family: monospace;
height: 800px !important;
color: black;
direction: ltr;
}
</style>
echart组件
<template>
<div class="chart-zone" :style="{ height: height+'px' }">
<div ref="echartdiv" :style="{ height: height+'px', width: width }"></div>
</div>
</template>
<script>
import * as echarts from "echarts";
// let chart = null // chart不能在此定义,不然同一个页面有多个chart时,可能会导致数据渲染错乱
export default {
name: "echart",
props: {
className: {
type: String,
default: "chart",
},
span:{
type:Number
},
id: {
type: String,
default: "chart",
},
width: {
type: String,
default: "100%",
},
height: {
type: Number,
},
options: {
type: Object,
default: () => ({}),
},
},
data() {
return {
// chart: null, // chart不能是响应式的,不然tooltip中的trigger为axis时会没有效果
};
},
watch: {
options: {
handler(options) {
// 设置true清空echart缓存
this.chart.setOption(options, true);
},
deep: true,
},
height(val){
this.chart.dispose()
this.$nextTick(()=>{
this.initChart();
})
},
span(val){
this.chart.dispose()
this.$nextTick(()=>{
this.initChart();
})
}
},
mounted() {
this.initChart();
},
beforeUnmount() {
this.chart = null;
},
methods: {
initChart() {
this.chart = null;
// 初始化echart
this.chart = echarts.init(this.$refs.echartdiv);
this.chart.setOption(this.options, true);
},
},
};
</script>
<style scoped>
.chart-zone {
position: relative;
width: 100%;
}
</style>
效果
导出json文件内容
在线更改echart内容