v2版本初稿
|
|
@ -0,0 +1,24 @@
|
||||||
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
<IndexV2 />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import IndexV2 from '@/v2/design.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AppV2',
|
||||||
|
components: {
|
||||||
|
IndexV2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#app {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
After Width: | Height: | Size: 535 B |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 909 B |
|
|
@ -1,5 +1,6 @@
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import App from './App.vue'
|
//import App from './App.vue'
|
||||||
|
import App from './AppV2.vue'
|
||||||
import ElementUI from 'element-ui'
|
import ElementUI from 'element-ui'
|
||||||
import 'element-ui/lib/theme-chalk/index.css'
|
import 'element-ui/lib/theme-chalk/index.css'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
<template>
|
||||||
|
<div class="canvas">
|
||||||
|
<div class="canvas-content">
|
||||||
|
<component
|
||||||
|
v-for="(element, index) in elements"
|
||||||
|
:key="index"
|
||||||
|
:is="element.component"
|
||||||
|
:option="element.option"
|
||||||
|
:style="{
|
||||||
|
position: 'absolute',
|
||||||
|
left: (element.option && element.option.left) ? element.option.left + 'px' :
|
||||||
|
(element.option && element.option.x) ? element.option.x + 'px' : '100px',
|
||||||
|
top: (element.option && element.option.top) ? element.option.top + 'px' :
|
||||||
|
(element.option && element.option.y) ? element.option.y + 'px' : '100px',
|
||||||
|
width: (element.option && element.option.width) ? element.option.width + 'px' : 'auto',
|
||||||
|
height: (element.option && element.option.height) ? element.option.height + 'px' : 'auto',
|
||||||
|
border: activeIndex === index ? '2px solid #1890ff' : '1px dashed #ccc'
|
||||||
|
}"
|
||||||
|
@click.native="selectElement(index)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Canvas',
|
||||||
|
props: {
|
||||||
|
elements: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [] // 默认空数组
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeIndex: -1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectElement(index) {
|
||||||
|
this.activeIndex = index
|
||||||
|
this.$emit('element-selected', index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.canvas {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas-content {
|
||||||
|
width: 800px;
|
||||||
|
height: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移除.canvas-element样式,由组件自身控制 */
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
<template>
|
||||||
|
<div class="layer-panel">
|
||||||
|
<div class="panel-header">
|
||||||
|
<h3>图层</h3>
|
||||||
|
</div>
|
||||||
|
<div class="layer-list">
|
||||||
|
<div
|
||||||
|
class="layer-item"
|
||||||
|
v-for="(element, index) in elements"
|
||||||
|
:key="index"
|
||||||
|
@click="$emit('layer-selected', index)"
|
||||||
|
>
|
||||||
|
{{ element.name || `元素${index + 1}` }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'LayerPanel',
|
||||||
|
props: {
|
||||||
|
elements: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.layer-panel {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-bottom: 1px solid #e1e4e8;
|
||||||
|
height: 40px; /* 固定高度 */
|
||||||
|
min-height: 40px; /* 确保不会压缩 */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-item {
|
||||||
|
padding: 10px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-item:hover {
|
||||||
|
background: #e9e9e9;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
<template>
|
||||||
|
<div class="property-panel">
|
||||||
|
<div class="panel-header">
|
||||||
|
<h3>属性设置</h3>
|
||||||
|
</div>
|
||||||
|
<div class="tabs">
|
||||||
|
<button v-for="tab in tabs" :key="tab" @click="currentTab = tab" :class="{ active: currentTab === tab }">
|
||||||
|
{{ tab }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-content" v-if="element">
|
||||||
|
<div v-for="(tab, tabIndex) in tabs" :key="tabIndex" v-show="currentTab === tab">
|
||||||
|
<div class="form-group" v-for="(prop, propIndex) in groupedProps[tab]" :key="propIndex">
|
||||||
|
<label>{{ prop.label }}</label>
|
||||||
|
<input
|
||||||
|
:type="prop.type"
|
||||||
|
v-model="element.option[prop.name]"
|
||||||
|
:disabled="prop.disabled"
|
||||||
|
@change="updateElementOption">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'PropertyPanel',
|
||||||
|
props: {
|
||||||
|
element: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
name: '未选择',
|
||||||
|
type: 'none',
|
||||||
|
props: [],
|
||||||
|
option: {
|
||||||
|
width: 100,
|
||||||
|
height: 50,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
backgroundColor: '#ffffff'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
index: Number
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
groupedProps() {
|
||||||
|
if(!this.element){
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const groups = {}
|
||||||
|
// 基础属性组
|
||||||
|
groups['基础'] = [
|
||||||
|
{ name: 'name', type: 'string', label: '名称' },
|
||||||
|
{ name: 'type', type: 'string', label: '类型', disabled: true }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 样式属性组
|
||||||
|
groups['样式'] = [
|
||||||
|
{ name: 'width', type: 'number', label: '宽度', group: '样式' },
|
||||||
|
{ name: 'height', type: 'number', label: '高度', group: '样式' },
|
||||||
|
{ name: 'x', type: 'number', label: 'X坐标', group: '样式' },
|
||||||
|
{ name: 'y', type: 'number', label: 'Y坐标', group: '样式' },
|
||||||
|
{ name: 'backgroundColor', type: 'color', label: '背景色', group: '样式' }
|
||||||
|
]
|
||||||
|
this.element.props = this.element.props || [];
|
||||||
|
// 动态添加element.props中的属性
|
||||||
|
this.element.props.forEach(prop => {
|
||||||
|
if (!groups[prop.group]) {
|
||||||
|
groups[prop.group] = []
|
||||||
|
}
|
||||||
|
groups[prop.group].push(prop)
|
||||||
|
})
|
||||||
|
|
||||||
|
return groups
|
||||||
|
},
|
||||||
|
tabs() {
|
||||||
|
return Object.keys(this.groupedProps)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateElementOption() {
|
||||||
|
console.log('updateElementOption', this.element)
|
||||||
|
this.$emit('update-element', {
|
||||||
|
index: this.index,
|
||||||
|
element: this.element
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentTab: '基础'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.property-panel {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
font-size: 14px;
|
||||||
|
/* 添加固定字体大小 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-bottom: 1px solid #e1e4e8;
|
||||||
|
height: 40px;
|
||||||
|
min-height: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 16px;
|
||||||
|
/* 标题稍大一点 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs button {
|
||||||
|
flex: 1;
|
||||||
|
padding: 10px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs button.active {
|
||||||
|
background: #e9e9e9;
|
||||||
|
border-bottom: 2px solid #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 5px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
<template>
|
||||||
|
<div class="toolbar">
|
||||||
|
<div class="toolbar-left">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 修改为鼠标经过效果 -->
|
||||||
|
<div class="component-selector">
|
||||||
|
<div
|
||||||
|
class="component-category"
|
||||||
|
v-for="category in componentCategories"
|
||||||
|
:key="category.name"
|
||||||
|
@mouseenter="activeCategory = category.name"
|
||||||
|
@mouseleave="activeCategory = ''"
|
||||||
|
>
|
||||||
|
{{ category.name }}
|
||||||
|
<div
|
||||||
|
class="component-list"
|
||||||
|
v-if="activeCategory === category.name"
|
||||||
|
@mouseenter="activeCategory = category.name"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="component-item"
|
||||||
|
v-for="item in category.items"
|
||||||
|
:key="item.name"
|
||||||
|
@click="addToCanvas(item)"
|
||||||
|
>
|
||||||
|
<img :src="item.icon" :alt="item.name">
|
||||||
|
<span>{{ item.name }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toolbar-right">
|
||||||
|
<button @click="clearCache">清除</button>
|
||||||
|
<button>保存</button>
|
||||||
|
<button>预览</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getComponentCategories } from './componentData'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Toolbar',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeCategory: '',
|
||||||
|
componentCategories: getComponentCategories() // 从外部文件加载
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addToCanvas(item) {
|
||||||
|
const newItem = {
|
||||||
|
...item,
|
||||||
|
option: {
|
||||||
|
...item.defaultOption,
|
||||||
|
name : item.name,
|
||||||
|
type : item.type,
|
||||||
|
...item.option
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.$emit('add-component', newItem)
|
||||||
|
this.activeCategory = ''
|
||||||
|
},
|
||||||
|
clearCache() {
|
||||||
|
localStorage.removeItem('canvasElements')
|
||||||
|
this.$emit('clear-canvas') // 通知父组件清空画布
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 20px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-selector {
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-category {
|
||||||
|
position: relative;
|
||||||
|
padding: 8px 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: #2c3e50;
|
||||||
|
color: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移除悬停显示样式,改回点击显示 */
|
||||||
|
/* .component-category:hover .component-list {
|
||||||
|
display: block;
|
||||||
|
} */
|
||||||
|
|
||||||
|
.component-list {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
background: #2c3e50;
|
||||||
|
border: 1px solid #3d5166;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
||||||
|
z-index: 100;
|
||||||
|
min-width: 150px;
|
||||||
|
padding: 10px;
|
||||||
|
/* 移除display:none,由v-if控制显示 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
color: white; /* 确保文字为白色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-item:hover {
|
||||||
|
background: #3d5166; /* 悬停背景色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-item img {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
margin-right: 8px;
|
||||||
|
filter: brightness(0) invert(1); /* 使图标变为白色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 原有按钮样式保持不变 */
|
||||||
|
.toolbar button {
|
||||||
|
margin: 0 5px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
background: #333;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar button:hover {
|
||||||
|
background: #555;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
export const componentList = [
|
||||||
|
{
|
||||||
|
id: 'chart-line',
|
||||||
|
name: '线图',
|
||||||
|
type: 'line-chart',
|
||||||
|
icon: require('@/assets/chart-line.png'),
|
||||||
|
groupName: '图表',
|
||||||
|
component: () => import('./elements/chart-line.vue') // 新增组件引用
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'chart-bar',
|
||||||
|
name: '柱图',
|
||||||
|
type: 'bar-chart',
|
||||||
|
icon: require('@/assets/chart-bar.png'),
|
||||||
|
groupName: '图表',
|
||||||
|
component: () => import('./elements/chart-bar.vue'),
|
||||||
|
props: [
|
||||||
|
{
|
||||||
|
name: 'title',
|
||||||
|
type: 'string',
|
||||||
|
label: '标题',
|
||||||
|
default: '柱图标题',
|
||||||
|
group: '基础'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dbName',
|
||||||
|
type: 'string',
|
||||||
|
label: '数据源对象',
|
||||||
|
default: '',
|
||||||
|
group: '数据'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'chart-pie',
|
||||||
|
name: '饼图',
|
||||||
|
type: 'pie-chart',
|
||||||
|
icon: require('@/assets/chart-pie.png'),
|
||||||
|
groupName: '图表',
|
||||||
|
component: () => import('./elements/chart-pie.vue'),
|
||||||
|
props: [ // 新增props数组
|
||||||
|
{
|
||||||
|
name: 'title',
|
||||||
|
type: 'string',
|
||||||
|
label: '标题',
|
||||||
|
default: '饼图标题',
|
||||||
|
group: '基础'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dbName',
|
||||||
|
type: 'string',
|
||||||
|
label: '数据源对象',
|
||||||
|
default: '',
|
||||||
|
group: '数据'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultOption: {
|
||||||
|
x: 100,
|
||||||
|
y: 100,
|
||||||
|
width: 300,
|
||||||
|
height: 300
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'table-basic',
|
||||||
|
name: '基础表格',
|
||||||
|
type: 'basic-table',
|
||||||
|
icon: require('@/assets/table-basic.png'),
|
||||||
|
groupName: '表格'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'table-paged',
|
||||||
|
name: '分页表格',
|
||||||
|
type: 'paged-table',
|
||||||
|
icon: require('@/assets/table-paged.png'),
|
||||||
|
groupName: '表格'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'text-title',
|
||||||
|
name: '标题',
|
||||||
|
type: 'title-text',
|
||||||
|
icon: require('@/assets/text-title.png'),
|
||||||
|
groupName: '文字'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'text-paragraph',
|
||||||
|
name: '正文',
|
||||||
|
type: 'paragraph',
|
||||||
|
icon: require('@/assets/text-paragraph.png'),
|
||||||
|
groupName: '文字'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 根据groupName分组
|
||||||
|
export function getComponentCategories() {
|
||||||
|
const groups = {}
|
||||||
|
componentList.forEach(item => {
|
||||||
|
if (!groups[item.groupName]) {
|
||||||
|
groups[item.groupName] = {
|
||||||
|
name: item.groupName,
|
||||||
|
items: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
groups[item.groupName].items.push(item)
|
||||||
|
})
|
||||||
|
return Object.values(groups)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<template>
|
||||||
|
<div class="chart-bar">
|
||||||
|
<h3>{{ option }}</h3>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'ChartBar',
|
||||||
|
props: {
|
||||||
|
// 组件属性
|
||||||
|
option: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.chart-bar {
|
||||||
|
/* 组件样式 */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<template>
|
||||||
|
<div class="chart-line">
|
||||||
|
<!-- 线图组件内容 -->
|
||||||
|
<h3>{{ option }}</h3>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'ChartLine',
|
||||||
|
props: {
|
||||||
|
// 组件属性
|
||||||
|
option: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.chart-line {
|
||||||
|
/* 组件样式 */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
<template>
|
||||||
|
<div class="chart-pie" ref="chartDom" style="width: 100%; height: 100%;"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ChartPie',
|
||||||
|
props: {
|
||||||
|
// 组件属性
|
||||||
|
// 组件属性
|
||||||
|
option: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartInstance: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initChart();
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
if (this.chartInstance) {
|
||||||
|
this.chartInstance.dispose();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initChart() {
|
||||||
|
this.chartInstance = echarts.init(this.$refs.chartDom);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
title: {
|
||||||
|
text: this.option.name,
|
||||||
|
left: 'center'
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item'
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
orient: 'vertical',
|
||||||
|
left: 'left'
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '访问来源',
|
||||||
|
type: 'pie',
|
||||||
|
radius: '50%',
|
||||||
|
data: [
|
||||||
|
{ value: 1048, name: '搜索引擎' },
|
||||||
|
{ value: 735, name: '直接访问' },
|
||||||
|
{ value: 580, name: '邮件营销' },
|
||||||
|
{ value: 484, name: '联盟广告' },
|
||||||
|
{ value: 300, name: '视频广告' }
|
||||||
|
],
|
||||||
|
emphasis: {
|
||||||
|
itemStyle: {
|
||||||
|
shadowBlur: 10,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
this.chartInstance.setOption(option);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.chart-pie {
|
||||||
|
width: 100%;
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,145 @@
|
||||||
|
<template>
|
||||||
|
<div class="bi-designer">
|
||||||
|
<!-- 顶部工具栏 -->
|
||||||
|
<toolbar
|
||||||
|
@add-component="handleAddComponent"
|
||||||
|
@clear-canvas="handleClearCanvas"
|
||||||
|
></toolbar>
|
||||||
|
|
||||||
|
<div class="main-content">
|
||||||
|
<!-- 左侧图层面板 -->
|
||||||
|
<layer-panel
|
||||||
|
:elements="elements"
|
||||||
|
@layer-selected="handleElementSelected"
|
||||||
|
></layer-panel>
|
||||||
|
|
||||||
|
<!-- 中间画布区域 -->
|
||||||
|
<div class="canvas-container">
|
||||||
|
<canvas-component
|
||||||
|
:elements="elements"
|
||||||
|
@element-selected="handleElementSelected"
|
||||||
|
></canvas-component>
|
||||||
|
<property-panel
|
||||||
|
:element="selectedElement"
|
||||||
|
:index="selectedIndex"
|
||||||
|
@update-element="handleUpdateElement"
|
||||||
|
></property-panel>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Toolbar from './components/Toolbar.vue'
|
||||||
|
import LayerPanel from './components/LayerPanel.vue'
|
||||||
|
import CanvasComponent from './components/Canvas.vue'
|
||||||
|
import PropertyPanel from './components/PropertyPanel.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Toolbar,
|
||||||
|
LayerPanel,
|
||||||
|
CanvasComponent,
|
||||||
|
PropertyPanel
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
selectedElement:null,
|
||||||
|
selectedIndex:-1,
|
||||||
|
elements: JSON.parse(localStorage.getItem('canvasElements')) || [] // 从缓存加载或默认空数组
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleAddComponent(component) {
|
||||||
|
console.log(component)
|
||||||
|
this.elements.push({
|
||||||
|
...component,
|
||||||
|
x: 100,
|
||||||
|
y: 100
|
||||||
|
})
|
||||||
|
this.saveToStorage()
|
||||||
|
},
|
||||||
|
saveToStorage() {
|
||||||
|
localStorage.setItem('canvasElements', JSON.stringify(this.elements))
|
||||||
|
}
|
||||||
|
,
|
||||||
|
handleClearCanvas() {
|
||||||
|
this.elements = []
|
||||||
|
this.saveToStorage()
|
||||||
|
}, // 这里添加缺少的逗号
|
||||||
|
handleElementSelected(index) {
|
||||||
|
this.selectedIndex = index
|
||||||
|
this.selectedElement = index >= 0 ? {...this.elements[index]} : null
|
||||||
|
},
|
||||||
|
handleUpdateElement({index, element}) {
|
||||||
|
this.$set(this.elements, index, element)
|
||||||
|
this.saveToStorage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 全局基础字体大小 */
|
||||||
|
html, body {
|
||||||
|
font-size: 14px !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.bi-designer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
height: 48px;
|
||||||
|
background: #1f2329;
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #f5f6f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-panel {
|
||||||
|
width: 240px;
|
||||||
|
background: #fff;
|
||||||
|
border-right: 1px solid #e1e4e8;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas-container {
|
||||||
|
flex: 1;
|
||||||
|
padding: 16px;
|
||||||
|
overflow: auto;
|
||||||
|
background: #f5f6f7;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.property-panel {
|
||||||
|
width: 280px;
|
||||||
|
background: #fff;
|
||||||
|
border-left: 1px solid #e1e4e8;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
<template>
|
||||||
|
<div class="bi-designer">
|
||||||
|
<canvas-component
|
||||||
|
:elements="elements"
|
||||||
|
></canvas-component>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import CanvasComponent from './components/Canvas.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
CanvasComponent
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
elements: JSON.parse(localStorage.getItem('canvasElements')) || [] // 从缓存加载或默认空数组
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleAddComponent(component) {
|
||||||
|
console.log(component)
|
||||||
|
this.elements.push({
|
||||||
|
...component,
|
||||||
|
x: 100,
|
||||||
|
y: 100
|
||||||
|
})
|
||||||
|
this.saveToStorage()
|
||||||
|
},
|
||||||
|
saveToStorage() {
|
||||||
|
localStorage.setItem('canvasElements', JSON.stringify(this.elements))
|
||||||
|
}
|
||||||
|
,
|
||||||
|
handleClearCanvas() {
|
||||||
|
this.elements = []
|
||||||
|
this.saveToStorage()
|
||||||
|
}, // 这里添加缺少的逗号
|
||||||
|
handleElementSelected(index) {
|
||||||
|
this.selectedIndex = index
|
||||||
|
this.selectedElement = index >= 0 ? {...this.elements[index]} : null
|
||||||
|
},
|
||||||
|
handleUpdateElement({index, element}) {
|
||||||
|
this.$set(this.elements, index, element)
|
||||||
|
this.saveToStorage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 全局基础字体大小 */
|
||||||
|
html, body {
|
||||||
|
font-size: 14px !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.bi-designer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
height: 48px;
|
||||||
|
background: #1f2329;
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #f5f6f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-panel {
|
||||||
|
width: 240px;
|
||||||
|
background: #fff;
|
||||||
|
border-right: 1px solid #e1e4e8;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas-container {
|
||||||
|
flex: 1;
|
||||||
|
padding: 16px;
|
||||||
|
overflow: auto;
|
||||||
|
background: #f5f6f7;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.property-panel {
|
||||||
|
width: 280px;
|
||||||
|
background: #fff;
|
||||||
|
border-left: 1px solid #e1e4e8;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
</style>
|
||||||