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 App from './App.vue'
|
||||
//import App from './App.vue'
|
||||
import App from './AppV2.vue'
|
||||
import ElementUI from 'element-ui'
|
||||
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>
|
||||