del
This commit is contained in:
@@ -1,502 +0,0 @@
|
||||
<template>
|
||||
<div class="app">
|
||||
<!-- 左侧组件列表 -->
|
||||
<div class="left-panel">
|
||||
<!-- 分辨率设置 -->
|
||||
<div class="resolution-settings">
|
||||
<h3>画布分辨率</h3>
|
||||
<div class="resolution-inputs">
|
||||
<div class="input-group">
|
||||
<label>宽度(px):</label>
|
||||
<el-input
|
||||
v-model.number="canvasWidth"
|
||||
type="number"
|
||||
:min="100"
|
||||
@change="handleResolutionChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>高度(px):</label>
|
||||
<el-input
|
||||
v-model.number="canvasHeight"
|
||||
type="number"
|
||||
:min="100"
|
||||
@change="handleResolutionChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 厂区选择下拉框 -->
|
||||
<div class="factory-selector">
|
||||
<el-select
|
||||
v-model="selectedFactory"
|
||||
placeholder="请选择厂区"
|
||||
@change="handleFactoryChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in factoryOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<h3>组件列表</h3>
|
||||
<draggable
|
||||
v-model="components"
|
||||
:options="{ group: { name: 'components', pull: 'clone', put: false }, sort: false }"
|
||||
@start="onDragStart"
|
||||
@end="onDragEnd"
|
||||
>
|
||||
<div v-for="(component, index) in components" :key="index" class="component-item">
|
||||
{{ component.name }}
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
|
||||
<!-- 中间编辑框 -->
|
||||
<div class="editor-container">
|
||||
<!-- 标尺部分 -->
|
||||
<div class="ruler-horizontal" :style="{ width: canvasWidth + 'px' }">
|
||||
<div
|
||||
v-for="i in horizontalRulerTicks"
|
||||
:key="i"
|
||||
class="ruler-tick"
|
||||
:style="{ left: `${i * rulerUnit}px` }"
|
||||
>
|
||||
{{ i * rulerUnit }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ruler-vertical" :style="{ height: canvasHeight + 'px' }">
|
||||
<div
|
||||
v-for="i in verticalRulerTicks"
|
||||
:key="i"
|
||||
class="ruler-tick"
|
||||
:style="{ top: `${i * rulerUnit}px` }"
|
||||
>
|
||||
{{ i * rulerUnit }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 编辑画布 -->
|
||||
<div
|
||||
class="editor-canvas"
|
||||
:style="{
|
||||
width: canvasWidth + 'px',
|
||||
height: canvasHeight + 'px'
|
||||
}"
|
||||
@drop="onDrop"
|
||||
@dragover.prevent
|
||||
ref="editorCanvas"
|
||||
>
|
||||
<div
|
||||
v-for="(component, index) in canvasComponents"
|
||||
:key="index"
|
||||
:style="getComponentStyle(component)"
|
||||
class="canvas-component"
|
||||
@mousedown="startDrag(component, $event)"
|
||||
@click="selectComponent(component)"
|
||||
>
|
||||
<img
|
||||
v-if="component.imageUrl"
|
||||
:src="component.imageUrl"
|
||||
alt="组件图片"
|
||||
style="width: 100%; max-height: 80%;"
|
||||
/>
|
||||
<div style="width: 100%; height: 40%; font-size: 14px;"><span class="circle" :style="'background-color:' + component.status + ';'"></span>{{ component.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧属性编辑面板 -->
|
||||
<div class="right-panel">
|
||||
<h3>属性编辑</h3>
|
||||
<div v-if="selectedComponent">
|
||||
<label>名称:</label>
|
||||
<el-input v-model="selectedComponent.name" type="value" />
|
||||
<label>X 坐标:</label>
|
||||
<el-input v-model.number="selectedComponent.x" type="number" />
|
||||
<label>Y 坐标:</label>
|
||||
<el-input v-model.number="selectedComponent.y" type="number" />
|
||||
<label>宽度:</label>
|
||||
<el-input v-model.number="selectedComponent.width" type="number" />
|
||||
<label>高度:</label>
|
||||
<el-input v-model.number="selectedComponent.height" type="number" />
|
||||
<label>状态:</label>
|
||||
<el-input v-model="selectedComponent.status" type="value" />
|
||||
<!-- <button @click="toSave()">保存</button> -->
|
||||
<label>上传图片:</label>
|
||||
<el-upload
|
||||
action="https://jsonplaceholder.typicode.com/posts"
|
||||
:on-success="handleUploadSuccess"
|
||||
:before-upload="beforeUpload"
|
||||
:show-file-list="false"
|
||||
>
|
||||
<el-button type="primary">点击上传</el-button>
|
||||
</el-upload>
|
||||
<label></label>
|
||||
<el-button type="primary" @click="toSave()">配置保存</el-button>
|
||||
</div>
|
||||
<div v-else>
|
||||
请选择一个组件
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from 'vuedraggable';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
draggable,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 新增分辨率设置
|
||||
canvasWidth: 1920,
|
||||
canvasHeight: 1080,
|
||||
// 新增厂区选择相关数据
|
||||
factoryOptions: [
|
||||
{ id: 1, name: '一楼厂区' },
|
||||
{ id: 2, name: '二楼厂区' },
|
||||
{ id: 3, name: '三楼厂区' }
|
||||
],
|
||||
selectedFactory: 1, // 默认选中第一个厂区
|
||||
components: [
|
||||
{ id: 1, name: '组件1', type: 'component1' },
|
||||
{ id: 2, name: '组件2', type: 'component2' },
|
||||
{ id: 3, name: '组件3', type: 'component3' },
|
||||
],
|
||||
canvasComponents: [],
|
||||
// canvasComponents: [
|
||||
// {"id":1111,"type":"","name":"组件11","x":275,"y":155,"width":100,"height":100,"imageUrl":"blob:http://localhost:8013/b883480e-814a-4803-8cf7-bb74967463bf","status":"#f00","desc":"有货"},
|
||||
// {"id":2222,"type":"","name":"组件22","x":475,"y":255,"width":100,"height":100,"imageUrl":"blob:http://localhost:8013/b883480e-814a-4803-8cf7-bb74967463bf","status":"#24f38a","desc":"有货"},
|
||||
// {"id":3333,"type":"","name":"组件33","x":575,"y":325,"width":100,"height":100,"imageUrl":"blob:http://localhost:8013/b883480e-814a-4803-8cf7-bb74967463bf","status":"关机","desc":"无货"},
|
||||
// ],
|
||||
selectedComponent: null,
|
||||
dragOffset: { x: 0, y: 0 },
|
||||
isDragging: false, // 是否正在拖拽
|
||||
rulerUnit: 50,
|
||||
horizontalRulerTicks: [],
|
||||
verticalRulerTicks: [],
|
||||
saveList:[]
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
// 监听分辨率变化
|
||||
canvasWidth(newVal) {
|
||||
this.updateRulerTicks();
|
||||
this.adjustComponentsPosition();
|
||||
},
|
||||
canvasHeight(newVal) {
|
||||
this.updateRulerTicks();
|
||||
this.adjustComponentsPosition();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.updateRulerTicks();
|
||||
},
|
||||
methods: {
|
||||
// 分辨率变更处理
|
||||
handleResolutionChange() {
|
||||
if (this.canvasWidth < 100) this.canvasWidth = 100;
|
||||
if (this.canvasHeight < 100) this.canvasHeight = 100;
|
||||
this.updateRulerTicks();
|
||||
this.adjustComponentsPosition();
|
||||
},
|
||||
|
||||
// 更新标尺刻度
|
||||
updateRulerTicks() {
|
||||
this.horizontalRulerTicks = Array.from(
|
||||
{ length: Math.ceil(this.canvasWidth / this.rulerUnit) },
|
||||
(_, i) => i
|
||||
);
|
||||
this.verticalRulerTicks = Array.from(
|
||||
{ length: Math.ceil(this.canvasHeight / this.rulerUnit) },
|
||||
(_, i) => i
|
||||
);
|
||||
},
|
||||
|
||||
// 调整组件位置
|
||||
adjustComponentsPosition() {
|
||||
this.canvasComponents.forEach(component => {
|
||||
// 限制X坐标
|
||||
const maxX = this.canvasWidth - component.width;
|
||||
if (component.x > maxX) {
|
||||
component.x = Math.max(0, maxX);
|
||||
}
|
||||
|
||||
// 限制Y坐标
|
||||
const maxY = this.canvasHeight - component.height;
|
||||
if (component.y > maxY) {
|
||||
component.y = Math.max(0, maxY);
|
||||
}
|
||||
});
|
||||
},
|
||||
// 新增厂区变更处理方法
|
||||
handleFactoryChange(factoryId) {
|
||||
console.log(`切换到厂区: ${factoryId}`);
|
||||
// 这里可以添加加载厂区配置的逻辑
|
||||
// 例如:清空当前画布或加载对应厂区配置
|
||||
this.canvasComponents = [];
|
||||
this.selectedComponent = null;
|
||||
},
|
||||
toSave () {
|
||||
console.log(this.selectedComponent)
|
||||
console.log(JSON.stringify(this.canvasComponents))
|
||||
},
|
||||
onDragStart(event) {
|
||||
event.item.dataset.type = this.components[event.oldIndex].type;
|
||||
},
|
||||
onDragEnd(event) {
|
||||
// 拖拽结束后的逻辑
|
||||
},
|
||||
onDrop(event) {
|
||||
const type = event.dataTransfer.getData('type');
|
||||
const newComponent = {
|
||||
id: Date.now(),
|
||||
type,
|
||||
name: `组件${this.canvasComponents.length + 1}`,
|
||||
x: event.offsetX - this.dragOffset.x,
|
||||
y: event.offsetY - this.dragOffset.y,
|
||||
width: 100,
|
||||
height: 50,
|
||||
imageUrl: '',
|
||||
};
|
||||
this.canvasComponents.push(newComponent);
|
||||
},
|
||||
getComponentStyle(component) {
|
||||
return {
|
||||
position: 'absolute',
|
||||
left: `${component.x}px`,
|
||||
top: `${component.y}px`,
|
||||
width: `${component.width}px`,
|
||||
height: `${component.height}px`,
|
||||
border: '1px solid #ccc',
|
||||
cursor: this.isDragging ? 'grabbing' : 'move',
|
||||
};
|
||||
},
|
||||
// 开始拖拽
|
||||
startDrag(component, event) {
|
||||
this.selectedComponent = component;
|
||||
this.isDragging = true;
|
||||
|
||||
// 计算鼠标在组件内部的偏移量
|
||||
const rect = event.target.getBoundingClientRect();
|
||||
this.dragOffset = {
|
||||
x: event.clientX - rect.left,
|
||||
y: event.clientY - rect.top,
|
||||
};
|
||||
|
||||
// 绑定全局事件
|
||||
document.addEventListener('mousemove', this.onDrag);
|
||||
document.addEventListener('mouseup', this.stopDrag);
|
||||
},
|
||||
// 拖拽中
|
||||
onDrag(event) {
|
||||
if (!this.isDragging || !this.selectedComponent) return;
|
||||
|
||||
// 计算组件的新位置(相对于画布)
|
||||
const canvasRect = this.$refs.editorCanvas.getBoundingClientRect();
|
||||
const newX = event.clientX - canvasRect.left - this.dragOffset.x;
|
||||
const newY = event.clientY - canvasRect.top - this.dragOffset.y;
|
||||
|
||||
// 更新组件位置
|
||||
this.selectedComponent.x = Math.max(0, newX);
|
||||
this.selectedComponent.y = Math.max(0, newY);
|
||||
},
|
||||
// 停止拖拽
|
||||
stopDrag() {
|
||||
this.isDragging = false;
|
||||
document.removeEventListener('mousemove', this.onDrag);
|
||||
document.removeEventListener('mouseup', this.stopDrag);
|
||||
},
|
||||
selectComponent(component) {
|
||||
this.selectedComponent = component;
|
||||
},
|
||||
handleUploadSuccess(response, file) {
|
||||
if (this.selectedComponent) {
|
||||
this.selectedComponent.imageUrl = URL.createObjectURL(file.raw);
|
||||
}
|
||||
},
|
||||
beforeUpload(file) {
|
||||
const isImage = file.type.startsWith('image/');
|
||||
if (!isImage) {
|
||||
this.$message.error('只能上传图片文件!');
|
||||
}
|
||||
return isImage;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 全局样式 */
|
||||
.app {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
}
|
||||
/* 分辨率设置样式 */
|
||||
.resolution-settings {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.resolution-inputs {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
width: 60px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
|
||||
/* 新增厂区选择器样式 */
|
||||
.factory-selector {
|
||||
margin-bottom: 20px;
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.factory-selector .el-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 调整左侧面板布局 */
|
||||
.left-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.left-panel > h3 {
|
||||
padding: 0 12px;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.left-panel {
|
||||
width: 200px;
|
||||
padding: 10px;
|
||||
border-right: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.component-item {
|
||||
padding: 10px;
|
||||
margin-bottom: 5px;
|
||||
border: 1px solid #ccc;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.editor-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
margin: 0 20px;
|
||||
height: 600px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.ruler-horizontal {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 30px;
|
||||
width: calc(100% - 30px);
|
||||
height: 20px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
background-color: #f9f9f9;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.ruler-vertical {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 0;
|
||||
width: 30px;
|
||||
height: calc(100% - 20px);
|
||||
border-right: 1px solid #ccc;
|
||||
background-color: #f9f9f9;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.ruler-tick {
|
||||
position: absolute;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 标尺尺寸调整 */
|
||||
/* .ruler-horizontal {
|
||||
left: 30px;
|
||||
width: calc(100% - 30px);
|
||||
}
|
||||
|
||||
.ruler-vertical {
|
||||
top: 20px;
|
||||
height: calc(100% - 20px);
|
||||
} */
|
||||
|
||||
/* .editor-canvas {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 30px;
|
||||
width: calc(100% - 30px);
|
||||
height: calc(100% - 20px);
|
||||
border: 1px solid #ccc;
|
||||
background: center / 100% auto url("~@/assets/images/background.jpg") no-repeat;
|
||||
} */
|
||||
|
||||
/* 画布容器样式调整 */
|
||||
.editor-canvas {
|
||||
position: relative;
|
||||
background: #ff0;
|
||||
border: 1px solid #ddd;
|
||||
margin: 20px 0 0 30px;
|
||||
/* background: center / 100% auto url("~@/assets/images/background.jpg") no-repeat; */
|
||||
}
|
||||
|
||||
.canvas-component {
|
||||
background-color: #f0f0f0;
|
||||
text-align: center;
|
||||
/* line-height: 50px; */
|
||||
}
|
||||
|
||||
.right-panel {
|
||||
width: 200px;
|
||||
padding: 10px;
|
||||
border-left: 1px solid #ccc;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
/* add */
|
||||
.circle {
|
||||
display:inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
margin-right: 6px;
|
||||
text-align: 0px;
|
||||
/* background-color: #ff0; */
|
||||
}
|
||||
</style>
|
||||
@@ -1,333 +0,0 @@
|
||||
<template>
|
||||
<div class="app">
|
||||
<!-- 左侧组件列表 -->
|
||||
<div class="left-panel">
|
||||
<h3>组件列表</h3>
|
||||
<draggable
|
||||
v-model="components"
|
||||
:options="{ group: { name: 'components', pull: 'clone', put: false }, sort: false }"
|
||||
@start="onDragStart"
|
||||
@end="onDragEnd"
|
||||
>
|
||||
<div v-for="(component, index) in components" :key="index" class="component-item">
|
||||
{{ component.name }}
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
|
||||
<!-- 中间编辑框 -->
|
||||
<div class="editor-container">
|
||||
<!-- 水平标尺 -->
|
||||
<div class="ruler-horizontal">
|
||||
<div
|
||||
v-for="i in horizontalRulerTicks"
|
||||
:key="i"
|
||||
class="ruler-tick"
|
||||
:style="{ left: `${i * rulerUnit}px` }"
|
||||
>
|
||||
{{ i * rulerUnit }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 垂直标尺 -->
|
||||
<div class="ruler-vertical">
|
||||
<div
|
||||
v-for="i in verticalRulerTicks"
|
||||
:key="i"
|
||||
class="ruler-tick"
|
||||
:style="{ top: `${i * rulerUnit}px` }"
|
||||
>
|
||||
{{ i * rulerUnit }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 编辑画布 -->
|
||||
<div
|
||||
class="editor-canvas"
|
||||
@drop="onDrop"
|
||||
@dragover.prevent
|
||||
ref="editorCanvas"
|
||||
style="background: url(@/assets/images/background.jpg);"
|
||||
>
|
||||
<div
|
||||
v-for="(component, index) in canvasComponents"
|
||||
:key="index"
|
||||
:style="getComponentStyle(component)"
|
||||
class="canvas-component"
|
||||
@mousedown="startDrag(component, $event)"
|
||||
@click="selectComponent(component)"
|
||||
>
|
||||
<img
|
||||
v-if="component.imageUrl"
|
||||
:src="component.imageUrl"
|
||||
alt="组件图片"
|
||||
style="width: 100%; height: 100%; object-fit: cover;"
|
||||
/>
|
||||
<span v-else>{{ component.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧属性编辑面板 -->
|
||||
<div class="right-panel">
|
||||
<h3>属性编辑</h3>
|
||||
<div v-if="selectedComponent">
|
||||
<label>{{ selectedComponent.name }}</label>
|
||||
<label>X 坐标:</label>
|
||||
<el-input v-model.number="selectedComponent.x" type="number" />
|
||||
<label>Y 坐标:</label>
|
||||
<el-input v-model.number="selectedComponent.y" type="number" />
|
||||
<label>宽度:</label>
|
||||
<el-input v-model.number="selectedComponent.width" type="number" />
|
||||
<label>高度:</label>
|
||||
<el-input v-model.number="selectedComponent.height" type="number" />
|
||||
<label>状态:</label>
|
||||
<el-input v-model.number="selectedComponent.status" type="value" />
|
||||
<label>详情:</label>
|
||||
<el-input v-model.number="selectedComponent.desc" type="value" />
|
||||
<button @click="toSave()">保存</button>
|
||||
<label>上传图片:</label>
|
||||
<el-upload
|
||||
action="https://jsonplaceholder.typicode.com/posts"
|
||||
:on-success="handleUploadSuccess"
|
||||
:before-upload="beforeUpload"
|
||||
:show-file-list="false"
|
||||
>
|
||||
<el-button type="primary">点击上传</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
<div v-else>
|
||||
请选择一个组件
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from 'vuedraggable';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
draggable,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
components: [
|
||||
{ id: 1, name: '组件1', type: 'component1' },
|
||||
{ id: 2, name: '组件2', type: 'component2' },
|
||||
{ id: 3, name: '组件3', type: 'component3' },
|
||||
],
|
||||
canvasComponents: [],
|
||||
// canvasComponents: [
|
||||
// {"id":1111,"type":"","name":"组件11","x":275,"y":155,"width":100,"height":50,"imageUrl":"blob:http://localhost:8013/b883480e-814a-4803-8cf7-bb74967463bf","status":"运行","desc":"有货"},
|
||||
// {"id":2222,"type":"","name":"组件22","x":475,"y":255,"width":100,"height":100,"imageUrl":"blob:http://localhost:8013/b883480e-814a-4803-8cf7-bb74967463bf","status":"关机","desc":"有货"},
|
||||
// {"id":3333,"type":"","name":"组件33","x":575,"y":325,"width":100,"height":50,"imageUrl":"blob:http://localhost:8013/b883480e-814a-4803-8cf7-bb74967463bf","status":"关机","desc":"无货"},
|
||||
// ],
|
||||
selectedComponent: null,
|
||||
dragOffset: { x: 0, y: 0 },
|
||||
isDragging: false, // 是否正在拖拽
|
||||
rulerUnit: 50,
|
||||
horizontalRulerTicks: [],
|
||||
verticalRulerTicks: [],
|
||||
saveList:[]
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.updateRulerTicks();
|
||||
},
|
||||
methods: {
|
||||
toSave () {
|
||||
console.log(this.selectedComponent)
|
||||
console.log(JSON.stringify(this.canvasComponents))
|
||||
},
|
||||
updateRulerTicks() {
|
||||
const canvasWidth = 800;
|
||||
const canvasHeight = 600;
|
||||
this.horizontalRulerTicks = Array.from(
|
||||
{ length: Math.ceil(canvasWidth / this.rulerUnit) },
|
||||
(_, i) => i
|
||||
);
|
||||
this.verticalRulerTicks = Array.from(
|
||||
{ length: Math.ceil(canvasHeight / this.rulerUnit) },
|
||||
(_, i) => i
|
||||
);
|
||||
},
|
||||
onDragStart(event) {
|
||||
event.item.dataset.type = this.components[event.oldIndex].type;
|
||||
},
|
||||
onDragEnd(event) {
|
||||
// 拖拽结束后的逻辑
|
||||
},
|
||||
onDrop(event) {
|
||||
const type = event.dataTransfer.getData('type');
|
||||
const newComponent = {
|
||||
id: Date.now(),
|
||||
type,
|
||||
name: `组件${this.canvasComponents.length + 1}`,
|
||||
x: event.offsetX - this.dragOffset.x,
|
||||
y: event.offsetY - this.dragOffset.y,
|
||||
width: 100,
|
||||
height: 50,
|
||||
imageUrl: '',
|
||||
};
|
||||
this.canvasComponents.push(newComponent);
|
||||
},
|
||||
getComponentStyle(component) {
|
||||
return {
|
||||
position: 'absolute',
|
||||
left: `${component.x}px`,
|
||||
top: `${component.y}px`,
|
||||
width: `${component.width}px`,
|
||||
height: `${component.height}px`,
|
||||
border: '1px solid #ccc',
|
||||
cursor: this.isDragging ? 'grabbing' : 'move',
|
||||
};
|
||||
},
|
||||
// 开始拖拽
|
||||
startDrag(component, event) {
|
||||
this.selectedComponent = component;
|
||||
this.isDragging = true;
|
||||
|
||||
// 计算鼠标在组件内部的偏移量
|
||||
const rect = event.target.getBoundingClientRect();
|
||||
this.dragOffset = {
|
||||
x: event.clientX - rect.left,
|
||||
y: event.clientY - rect.top,
|
||||
};
|
||||
|
||||
// 绑定全局事件
|
||||
document.addEventListener('mousemove', this.onDrag);
|
||||
document.addEventListener('mouseup', this.stopDrag);
|
||||
},
|
||||
// 拖拽中
|
||||
onDrag(event) {
|
||||
if (!this.isDragging || !this.selectedComponent) return;
|
||||
|
||||
// 计算组件的新位置(相对于画布)
|
||||
const canvasRect = this.$refs.editorCanvas.getBoundingClientRect();
|
||||
const newX = event.clientX - canvasRect.left - this.dragOffset.x;
|
||||
const newY = event.clientY - canvasRect.top - this.dragOffset.y;
|
||||
|
||||
// 更新组件位置
|
||||
this.selectedComponent.x = Math.max(0, newX);
|
||||
this.selectedComponent.y = Math.max(0, newY);
|
||||
},
|
||||
// 停止拖拽
|
||||
stopDrag() {
|
||||
this.isDragging = false;
|
||||
document.removeEventListener('mousemove', this.onDrag);
|
||||
document.removeEventListener('mouseup', this.stopDrag);
|
||||
},
|
||||
selectComponent(component) {
|
||||
this.selectedComponent = component;
|
||||
},
|
||||
handleUploadSuccess(response, file) {
|
||||
if (this.selectedComponent) {
|
||||
this.selectedComponent.imageUrl = URL.createObjectURL(file.raw);
|
||||
}
|
||||
},
|
||||
beforeUpload(file) {
|
||||
const isImage = file.type.startsWith('image/');
|
||||
if (!isImage) {
|
||||
this.$message.error('只能上传图片文件!');
|
||||
}
|
||||
return isImage;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 全局样式 */
|
||||
.app {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.left-panel {
|
||||
width: 200px;
|
||||
padding: 10px;
|
||||
border-right: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.component-item {
|
||||
padding: 10px;
|
||||
margin-bottom: 5px;
|
||||
border: 1px solid #ccc;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.editor-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
margin: 0 20px;
|
||||
height: 600px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ruler-horizontal {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 30px;
|
||||
width: calc(100% - 30px);
|
||||
height: 20px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
background-color: #f9f9f9;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.ruler-vertical {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 0;
|
||||
width: 30px;
|
||||
height: calc(100% - 20px);
|
||||
border-right: 1px solid #ccc;
|
||||
background-color: #f9f9f9;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.ruler-tick {
|
||||
position: absolute;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.editor-canvas {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 30px;
|
||||
width: calc(100% - 30px);
|
||||
height: calc(100% - 20px);
|
||||
border: 1px solid #ccc;
|
||||
/* background-color: #fff; */
|
||||
background-color: #0077ff;
|
||||
/* background: center / 100% auto url(../../../../../../assets/images/background.jpg) no-repeat; */
|
||||
/* background: url(../../../../../../assets/images/background.jpg); */
|
||||
}
|
||||
|
||||
.canvas-component {
|
||||
background-color: #f0f0f0;
|
||||
text-align: center;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
.right-panel {
|
||||
width: 200px;
|
||||
padding: 10px;
|
||||
border-left: 1px solid #ccc;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,350 +0,0 @@
|
||||
<template>
|
||||
<div class="app">
|
||||
<!-- 左侧组件列表 -->
|
||||
<div class="left-panel">
|
||||
<h3>组件列表</h3>
|
||||
<draggable
|
||||
v-model="components"
|
||||
:options="{ group: { name: 'components', pull: 'clone', put: false }, sort: false }"
|
||||
@start="onDragStart"
|
||||
@end="onDragEnd"
|
||||
>
|
||||
<div v-for="(component, index) in components" :key="index" class="component-item">
|
||||
{{ component.name }}
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
|
||||
<!-- 中间编辑框 -->
|
||||
<div class="editor-container">
|
||||
<!-- 水平标尺 -->
|
||||
<div class="ruler-horizontal">
|
||||
<div
|
||||
v-for="i in horizontalRulerTicks"
|
||||
:key="i"
|
||||
class="ruler-tick"
|
||||
:style="{ left: `${i * rulerUnit}px` }"
|
||||
>
|
||||
{{ i * rulerUnit }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 垂直标尺 -->
|
||||
<div class="ruler-vertical">
|
||||
<div
|
||||
v-for="i in verticalRulerTicks"
|
||||
:key="i"
|
||||
class="ruler-tick"
|
||||
:style="{ top: `${i * rulerUnit}px` }"
|
||||
>
|
||||
{{ i * rulerUnit }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 编辑画布 -->
|
||||
<div
|
||||
class="editor-canvas"
|
||||
@drop="onDrop"
|
||||
@dragover.prevent
|
||||
ref="editorCanvas"
|
||||
>
|
||||
<div
|
||||
v-for="(component, index) in canvasComponents"
|
||||
:key="index"
|
||||
:style="getComponentStyle(component)"
|
||||
class="canvas-component"
|
||||
@mousedown="startDrag(component, $event)"
|
||||
@click="selectComponent(component)"
|
||||
>
|
||||
<img
|
||||
v-if="component.imageUrl"
|
||||
:src="component.imageUrl"
|
||||
alt="组件图片"
|
||||
style="width: 100%; max-height: 80%;"
|
||||
/>
|
||||
<!-- <span v-else>{{ component.name }}</span> -->
|
||||
<!-- <div style="width: 100%; height: 60%; object-fit: cover;" :style="'background-image:url('+ component.imageUrl +');'"></div> -->
|
||||
<!-- <div class="login" :style="'background-image:url('+ Background +');'"> -->
|
||||
|
||||
<div style="width: 100%; height: 40%; font-size: 14px;"><span class="circle" :style="'background-color:' + component.status + ';'"></span>{{ component.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧属性编辑面板 -->
|
||||
<div class="right-panel">
|
||||
<h3>属性编辑</h3>
|
||||
<div v-if="selectedComponent">
|
||||
<label>名称:</label>
|
||||
<el-input v-model="selectedComponent.name" type="value" />
|
||||
<label>X 坐标:</label>
|
||||
<el-input v-model.number="selectedComponent.x" type="number" />
|
||||
<label>Y 坐标:</label>
|
||||
<el-input v-model.number="selectedComponent.y" type="number" />
|
||||
<label>宽度:</label>
|
||||
<el-input v-model.number="selectedComponent.width" type="number" />
|
||||
<label>高度:</label>
|
||||
<el-input v-model.number="selectedComponent.height" type="number" />
|
||||
<label>状态:</label>
|
||||
<el-input v-model="selectedComponent.status" type="value" />
|
||||
<label>详情:</label>
|
||||
<el-input v-model="selectedComponent.desc" type="value" />
|
||||
<!-- <button @click="toSave()">保存</button> -->
|
||||
<label>上传图片:</label>
|
||||
<el-upload
|
||||
action="https://jsonplaceholder.typicode.com/posts"
|
||||
:on-success="handleUploadSuccess"
|
||||
:before-upload="beforeUpload"
|
||||
:show-file-list="false"
|
||||
>
|
||||
<el-button type="primary">点击上传</el-button>
|
||||
</el-upload>
|
||||
<label></label>
|
||||
<el-button type="primary" @click="toSave()">配置保存</el-button>
|
||||
</div>
|
||||
<div v-else>
|
||||
请选择一个组件
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from 'vuedraggable';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
draggable,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
components: [
|
||||
{ id: 1, name: '组件1', type: 'component1' },
|
||||
{ id: 2, name: '组件2', type: 'component2' },
|
||||
{ id: 3, name: '组件3', type: 'component3' },
|
||||
],
|
||||
// canvasComponents: [],
|
||||
canvasComponents: [
|
||||
{"id":1111,"type":"","name":"组件11","x":275,"y":155,"width":100,"height":100,"imageUrl":"blob:http://localhost:8013/b883480e-814a-4803-8cf7-bb74967463bf","status":"#f00","desc":"有货"},
|
||||
{"id":2222,"type":"","name":"组件22","x":475,"y":255,"width":100,"height":100,"imageUrl":"blob:http://localhost:8013/b883480e-814a-4803-8cf7-bb74967463bf","status":"#24f38a","desc":"有货"},
|
||||
{"id":3333,"type":"","name":"组件33","x":575,"y":325,"width":100,"height":100,"imageUrl":"blob:http://localhost:8013/b883480e-814a-4803-8cf7-bb74967463bf","status":"关机","desc":"无货"},
|
||||
],
|
||||
selectedComponent: null,
|
||||
dragOffset: { x: 0, y: 0 },
|
||||
isDragging: false, // 是否正在拖拽
|
||||
rulerUnit: 50,
|
||||
horizontalRulerTicks: [],
|
||||
verticalRulerTicks: [],
|
||||
saveList:[]
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.updateRulerTicks();
|
||||
},
|
||||
methods: {
|
||||
toSave () {
|
||||
console.log(this.selectedComponent)
|
||||
console.log(JSON.stringify(this.canvasComponents))
|
||||
},
|
||||
updateRulerTicks() {
|
||||
const canvasWidth = 800;
|
||||
const canvasHeight = 600;
|
||||
this.horizontalRulerTicks = Array.from(
|
||||
{ length: Math.ceil(canvasWidth / this.rulerUnit) },
|
||||
(_, i) => i
|
||||
);
|
||||
this.verticalRulerTicks = Array.from(
|
||||
{ length: Math.ceil(canvasHeight / this.rulerUnit) },
|
||||
(_, i) => i
|
||||
);
|
||||
},
|
||||
onDragStart(event) {
|
||||
event.item.dataset.type = this.components[event.oldIndex].type;
|
||||
},
|
||||
onDragEnd(event) {
|
||||
// 拖拽结束后的逻辑
|
||||
},
|
||||
onDrop(event) {
|
||||
const type = event.dataTransfer.getData('type');
|
||||
const newComponent = {
|
||||
id: Date.now(),
|
||||
type,
|
||||
name: `组件${this.canvasComponents.length + 1}`,
|
||||
x: event.offsetX - this.dragOffset.x,
|
||||
y: event.offsetY - this.dragOffset.y,
|
||||
width: 100,
|
||||
height: 50,
|
||||
imageUrl: '',
|
||||
};
|
||||
this.canvasComponents.push(newComponent);
|
||||
},
|
||||
getComponentStyle(component) {
|
||||
return {
|
||||
position: 'absolute',
|
||||
left: `${component.x}px`,
|
||||
top: `${component.y}px`,
|
||||
width: `${component.width}px`,
|
||||
height: `${component.height}px`,
|
||||
border: '1px solid #ccc',
|
||||
cursor: this.isDragging ? 'grabbing' : 'move',
|
||||
};
|
||||
},
|
||||
// 开始拖拽
|
||||
startDrag(component, event) {
|
||||
this.selectedComponent = component;
|
||||
this.isDragging = true;
|
||||
|
||||
// 计算鼠标在组件内部的偏移量
|
||||
const rect = event.target.getBoundingClientRect();
|
||||
this.dragOffset = {
|
||||
x: event.clientX - rect.left,
|
||||
y: event.clientY - rect.top,
|
||||
};
|
||||
|
||||
// 绑定全局事件
|
||||
document.addEventListener('mousemove', this.onDrag);
|
||||
document.addEventListener('mouseup', this.stopDrag);
|
||||
},
|
||||
// 拖拽中
|
||||
onDrag(event) {
|
||||
if (!this.isDragging || !this.selectedComponent) return;
|
||||
|
||||
// 计算组件的新位置(相对于画布)
|
||||
const canvasRect = this.$refs.editorCanvas.getBoundingClientRect();
|
||||
const newX = event.clientX - canvasRect.left - this.dragOffset.x;
|
||||
const newY = event.clientY - canvasRect.top - this.dragOffset.y;
|
||||
|
||||
// 更新组件位置
|
||||
this.selectedComponent.x = Math.max(0, newX);
|
||||
this.selectedComponent.y = Math.max(0, newY);
|
||||
},
|
||||
// 停止拖拽
|
||||
stopDrag() {
|
||||
this.isDragging = false;
|
||||
document.removeEventListener('mousemove', this.onDrag);
|
||||
document.removeEventListener('mouseup', this.stopDrag);
|
||||
},
|
||||
selectComponent(component) {
|
||||
this.selectedComponent = component;
|
||||
},
|
||||
handleUploadSuccess(response, file) {
|
||||
if (this.selectedComponent) {
|
||||
this.selectedComponent.imageUrl = URL.createObjectURL(file.raw);
|
||||
}
|
||||
},
|
||||
beforeUpload(file) {
|
||||
const isImage = file.type.startsWith('image/');
|
||||
if (!isImage) {
|
||||
this.$message.error('只能上传图片文件!');
|
||||
}
|
||||
return isImage;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 全局样式 */
|
||||
.app {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.left-panel {
|
||||
width: 200px;
|
||||
padding: 10px;
|
||||
border-right: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.component-item {
|
||||
padding: 10px;
|
||||
margin-bottom: 5px;
|
||||
border: 1px solid #ccc;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.editor-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
margin: 0 20px;
|
||||
height: 600px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ruler-horizontal {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 30px;
|
||||
width: calc(100% - 30px);
|
||||
height: 20px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
background-color: #f9f9f9;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.ruler-vertical {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 0;
|
||||
width: 30px;
|
||||
height: calc(100% - 20px);
|
||||
border-right: 1px solid #ccc;
|
||||
background-color: #f9f9f9;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.ruler-tick {
|
||||
position: absolute;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.editor-canvas {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 30px;
|
||||
width: calc(100% - 30px);
|
||||
height: calc(100% - 20px);
|
||||
border: 1px solid #ccc;
|
||||
/* background-color: #fff; */
|
||||
/* // background-color: #0077ff; */
|
||||
background: center / 100% auto url("~@/assets/images/background.jpg") no-repeat;
|
||||
/* background: url("~@/assets/images/background.jpg"); */
|
||||
}
|
||||
|
||||
.canvas-component {
|
||||
background-color: #f0f0f0;
|
||||
text-align: center;
|
||||
/* line-height: 50px; */
|
||||
}
|
||||
|
||||
.right-panel {
|
||||
width: 200px;
|
||||
padding: 10px;
|
||||
border-left: 1px solid #ccc;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
/* add */
|
||||
.circle {
|
||||
display:inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
margin-right: 6px;
|
||||
text-align: 0px;
|
||||
/* background-color: #ff0; */
|
||||
}
|
||||
</style>
|
||||
@@ -1,579 +0,0 @@
|
||||
<template>
|
||||
<div class="app">
|
||||
<!-- 左侧组件列表 -->
|
||||
<div class="left-panel">
|
||||
<!-- 分辨率设置 -->
|
||||
<div class="resolution-settings">
|
||||
<el-button type="primary" @click="toSave()">保存</el-button>
|
||||
<h3>画布分辨率</h3>
|
||||
<div class="resolution-inputs">
|
||||
<div class="input-group">
|
||||
<label>宽度(px):</label>
|
||||
<el-input
|
||||
v-model.number="canvasWidth"
|
||||
type="number"
|
||||
:min="100"
|
||||
@change="handleResolutionChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>高度(px):</label>
|
||||
<el-input
|
||||
v-model.number="canvasHeight"
|
||||
type="number"
|
||||
:min="100"
|
||||
@change="handleResolutionChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-settings">
|
||||
<el-upload
|
||||
action="#"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
:on-change="handleFileChange"
|
||||
accept="image/*"
|
||||
>
|
||||
<el-button type="primary">导入背景图</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 厂区选择下拉框 -->
|
||||
<div class="factory-selector">
|
||||
<el-select
|
||||
v-model="selectedFactory"
|
||||
placeholder="请选择厂区"
|
||||
@change="handleFactoryChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in factoryOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<h3>点位列表</h3>
|
||||
<draggable
|
||||
v-model="components"
|
||||
:options="{ group: { name: 'components', pull: 'clone', put: false }, sort: false }"
|
||||
@start="onDragStart"
|
||||
@end="onDragEnd"
|
||||
>
|
||||
<div v-for="(component, index) in components" :key="index" class="component-item">
|
||||
{{ component.name }}
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
|
||||
<!-- 中间编辑框 -->
|
||||
<div class="editor-container">
|
||||
<!-- 标尺部分 -->
|
||||
<div class="ruler-horizontal" :style="{ width: canvasWidth + 'px' }">
|
||||
<div
|
||||
v-for="i in horizontalRulerTicks"
|
||||
:key="i"
|
||||
class="ruler-tick"
|
||||
:style="{ left: `${i * rulerUnit}px` }"
|
||||
>
|
||||
{{ i * rulerUnit }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ruler-vertical" :style="{ height: canvasHeight + 'px' }">
|
||||
<div
|
||||
v-for="i in verticalRulerTicks"
|
||||
:key="i"
|
||||
class="ruler-tick"
|
||||
:style="{ top: `${i * rulerUnit}px` }"
|
||||
>
|
||||
{{ i * rulerUnit }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 编辑画布 -->
|
||||
<div
|
||||
class="editor-canvas"
|
||||
:style="{
|
||||
width: canvasWidth + 'px',
|
||||
height: canvasHeight + 'px',
|
||||
backgroundImage: 'url(' + bgUrl + ')'
|
||||
}"
|
||||
@drop="onDrop"
|
||||
@dragover.prevent
|
||||
ref="editorCanvas"
|
||||
>
|
||||
<div
|
||||
v-for="(component, index) in canvasComponents"
|
||||
:key="index"
|
||||
:style="getComponentStyle(component)"
|
||||
class="canvas-component"
|
||||
@mousedown="startDrag(component, $event)"
|
||||
@click="selectComponent(component)"
|
||||
>
|
||||
<!-- <div class="pbox" :style="{background: '#fc0 url('+ component.imageUrl +') no-repeat center;'}"> -->
|
||||
<div class="pbox" :style="{backgroundImage: 'url(' + component.imageUrl + ')'}">
|
||||
<div style="line-height: 100%; font-size:12px; color: #000;">{{ component.name}}</div>
|
||||
</div>
|
||||
<!-- <img
|
||||
v-if="component.imageUrl"
|
||||
:src="component.imageUrl"
|
||||
alt="组件图片"
|
||||
style="width: 100%; max-height: 80%;"
|
||||
/>
|
||||
<div style="width: 100%; height: 40%; font-size: 14px;"><span class="circle" :style="'background-color:' + component.status + ';'"></span>{{ component.name }}</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧属性编辑面板 -->
|
||||
<div class="right-panel">
|
||||
<h3>属性编辑</h3>
|
||||
<div v-if="selectedComponent">
|
||||
<label>名称:</label>
|
||||
<el-input v-model="selectedComponent.name" type="value" :disabled="true"/>
|
||||
<label>X 坐标:</label>
|
||||
<el-input v-model.number="selectedComponent.x" type="number" />
|
||||
<label>Y 坐标:</label>
|
||||
<el-input v-model.number="selectedComponent.y" type="number" />
|
||||
<label>宽度:</label>
|
||||
<el-input v-model.number="selectedComponent.width" type="number" />
|
||||
<label>高度:</label>
|
||||
<el-input v-model.number="selectedComponent.height" type="number" />
|
||||
<label>状态:</label>
|
||||
<el-input v-model="selectedComponent.status" type="value" />
|
||||
<label>图标:</label>
|
||||
<div class="factory-selector">
|
||||
<el-select
|
||||
v-model="selectedPointImg"
|
||||
placeholder="请选择点位图标"
|
||||
@change="handlePointImgChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in pointSetOptions"
|
||||
:key="item.id"
|
||||
:label="item.code"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<!-- <el-upload
|
||||
action="https://jsonplaceholder.typicode.com/posts"
|
||||
:on-success="handleUploadSuccess"
|
||||
:before-upload="beforeUpload"
|
||||
:show-file-list="false"
|
||||
>
|
||||
<el-button type="primary">点击上传</el-button>
|
||||
</el-upload> -->
|
||||
<label></label>
|
||||
</div>
|
||||
<div v-else>
|
||||
请选择一个组件
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from 'vuedraggable';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
draggable,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 新增分辨率设置
|
||||
canvasWidth: 1920,
|
||||
canvasHeight: 1080,
|
||||
// 新增厂区选择相关数据
|
||||
factoryOptions: [
|
||||
{ id: 1, name: '一楼厂区' },
|
||||
{ id: 2, name: '二楼厂区' },
|
||||
{ id: 3, name: '三楼厂区' }
|
||||
],
|
||||
selectedFactory: 1, // 默认选中第一个厂区
|
||||
components: [
|
||||
{ id: 1, name: '点位1', type: 'component1' },
|
||||
{ id: 2, name: '点位2', type: 'component2' },
|
||||
{ id: 3, name: '点位3', type: 'component3' },
|
||||
],
|
||||
pointSetOptions: [
|
||||
{ id: 1, name: '点位1', code: '029-01', pointImg: 'https://img2.baidu.com/it/u=2914671817,628443499&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=889' },
|
||||
{ id: 2, name: '点位2', code: '029-02', pointImg: 'https://img0.baidu.com/it/u=3613398591,98570664&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=500' },
|
||||
{ id: 3, name: '点位3', code: '029-03', pointImg: 'https://img0.baidu.com/it/u=1796365119,3995265789&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=667' }
|
||||
],
|
||||
selectedPointImg: '',
|
||||
bgUrl: '', // 画布区域背景图片
|
||||
canvasComponents: [],
|
||||
// canvasComponents: [
|
||||
// {"id":1111,"type":"","name":"组件11","x":275,"y":155,"width":100,"height":100,"imageUrl":"blob:http://localhost:8013/b883480e-814a-4803-8cf7-bb74967463bf","status":"#f00","desc":"有货"},
|
||||
// {"id":2222,"type":"","name":"组件22","x":475,"y":255,"width":100,"height":100,"imageUrl":"blob:http://localhost:8013/b883480e-814a-4803-8cf7-bb74967463bf","status":"#24f38a","desc":"有货"},
|
||||
// {"id":3333,"type":"","name":"组件33","x":575,"y":325,"width":100,"height":100,"imageUrl":"blob:http://localhost:8013/b883480e-814a-4803-8cf7-bb74967463bf","status":"关机","desc":"无货"},
|
||||
// ],
|
||||
selectedComponent: null,
|
||||
dragOffset: { x: 0, y: 0 },
|
||||
isDragging: false, // 是否正在拖拽
|
||||
rulerUnit: 50,
|
||||
horizontalRulerTicks: [],
|
||||
verticalRulerTicks: [],
|
||||
saveList:[]
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
// 监听分辨率变化
|
||||
canvasWidth(newVal) {
|
||||
this.updateRulerTicks();
|
||||
this.adjustComponentsPosition();
|
||||
},
|
||||
canvasHeight(newVal) {
|
||||
this.updateRulerTicks();
|
||||
this.adjustComponentsPosition();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.updateRulerTicks();
|
||||
// const box = document.querySelector('.ruler-horizontal')
|
||||
// const vox = document.querySelector('.ruler-vertical')
|
||||
// window.addEventListener('scroll', () => {
|
||||
// const scrollX = window.scrollX; // 获取水平滚动值
|
||||
// box.style.left = `${30 + scrollX}px`; // 更新left值
|
||||
// const scrollY = window.scrollY; // 获取垂直滚动值
|
||||
// vox.style.top = `${20 + scrollY}px`; // 更新top值
|
||||
// })
|
||||
},
|
||||
methods: {
|
||||
handleFileChange(file) {
|
||||
// 验证文件类型
|
||||
if (!file.raw.type.startsWith('image/')) {
|
||||
this.$message.error('只能上传图片文件!');
|
||||
return;
|
||||
}
|
||||
|
||||
// 读取文件生成预览
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
this.bgUrl = e.target.result;
|
||||
};
|
||||
reader.readAsDataURL(file.raw);
|
||||
},
|
||||
// 分辨率变更处理
|
||||
handleResolutionChange() {
|
||||
if (this.canvasWidth < 100) this.canvasWidth = 100;
|
||||
if (this.canvasHeight < 100) this.canvasHeight = 100;
|
||||
this.updateRulerTicks();
|
||||
this.adjustComponentsPosition();
|
||||
},
|
||||
|
||||
// 更新标尺刻度
|
||||
updateRulerTicks() {
|
||||
this.horizontalRulerTicks = Array.from(
|
||||
{ length: Math.ceil(this.canvasWidth / this.rulerUnit) },
|
||||
(_, i) => i
|
||||
);
|
||||
this.verticalRulerTicks = Array.from(
|
||||
{ length: Math.ceil(this.canvasHeight / this.rulerUnit) },
|
||||
(_, i) => i
|
||||
);
|
||||
},
|
||||
|
||||
// 调整组件位置
|
||||
adjustComponentsPosition() {
|
||||
this.canvasComponents.forEach(component => {
|
||||
// 限制X坐标
|
||||
const maxX = this.canvasWidth - component.width;
|
||||
if (component.x > maxX) {
|
||||
component.x = Math.max(0, maxX);
|
||||
}
|
||||
|
||||
// 限制Y坐标
|
||||
const maxY = this.canvasHeight - component.height;
|
||||
if (component.y > maxY) {
|
||||
component.y = Math.max(0, maxY);
|
||||
}
|
||||
});
|
||||
},
|
||||
// 新增厂区变更处理方法
|
||||
handleFactoryChange(factoryId) {
|
||||
console.log(`切换到厂区: ${factoryId}`);
|
||||
// 这里可以添加加载厂区配置的逻辑
|
||||
// 例如:清空当前画布或加载对应厂区配置
|
||||
this.canvasComponents = [];
|
||||
this.selectedComponent = null;
|
||||
},
|
||||
handlePointImgChange(PointSetId) {
|
||||
console.log(`切换到点位图片: ${PointSetId}`);
|
||||
this.pointSetOptions.map(el => {
|
||||
if (el.id == PointSetId) {
|
||||
this.selectedComponent.imageUrl = el.pointImg;
|
||||
}
|
||||
})
|
||||
},
|
||||
toSave () {
|
||||
console.log(this.selectedComponent)
|
||||
console.log(JSON.stringify(this.canvasComponents))
|
||||
},
|
||||
onDragStart(event) {
|
||||
event.item.dataset.type = this.components[event.oldIndex].type;
|
||||
},
|
||||
onDragEnd(event) {
|
||||
// 拖拽结束后的逻辑
|
||||
},
|
||||
onDrop(event) {
|
||||
const type = event.dataTransfer.getData('type');
|
||||
const newComponent = {
|
||||
id: Date.now(),
|
||||
type,
|
||||
name: `点位${this.canvasComponents.length + 1}`,
|
||||
x: event.offsetX - this.dragOffset.x,
|
||||
y: event.offsetY - this.dragOffset.y,
|
||||
width: 100,
|
||||
height: 50,
|
||||
imageUrl: '',
|
||||
};
|
||||
this.canvasComponents.push(newComponent);
|
||||
},
|
||||
getComponentStyle(component) {
|
||||
return {
|
||||
position: 'absolute',
|
||||
left: `${component.x}px`,
|
||||
top: `${component.y}px`,
|
||||
width: `${component.width}px`,
|
||||
height: `${component.height}px`,
|
||||
border: '1px solid #ccc',
|
||||
cursor: this.isDragging ? 'grabbing' : 'move',
|
||||
};
|
||||
},
|
||||
// 开始拖拽
|
||||
startDrag(component, event) {
|
||||
this.selectedComponent = component;
|
||||
this.isDragging = true;
|
||||
|
||||
// 计算鼠标在组件内部的偏移量
|
||||
const rect = event.target.getBoundingClientRect();
|
||||
this.dragOffset = {
|
||||
x: event.clientX - rect.left,
|
||||
y: event.clientY - rect.top,
|
||||
};
|
||||
|
||||
// 绑定全局事件
|
||||
document.addEventListener('mousemove', this.onDrag);
|
||||
document.addEventListener('mouseup', this.stopDrag);
|
||||
},
|
||||
// 拖拽中
|
||||
onDrag(event) {
|
||||
if (!this.isDragging || !this.selectedComponent) return;
|
||||
|
||||
// 计算组件的新位置(相对于画布)
|
||||
const canvasRect = this.$refs.editorCanvas.getBoundingClientRect();
|
||||
const newX = event.clientX - canvasRect.left - this.dragOffset.x;
|
||||
const newY = event.clientY - canvasRect.top - this.dragOffset.y;
|
||||
|
||||
// 更新组件位置
|
||||
this.selectedComponent.x = Math.max(0, newX);
|
||||
this.selectedComponent.y = Math.max(0, newY);
|
||||
},
|
||||
// 停止拖拽
|
||||
stopDrag() {
|
||||
this.isDragging = false;
|
||||
document.removeEventListener('mousemove', this.onDrag);
|
||||
document.removeEventListener('mouseup', this.stopDrag);
|
||||
},
|
||||
selectComponent(component) {
|
||||
this.selectedComponent = component;
|
||||
},
|
||||
handleUploadSuccess(response, file) {
|
||||
if (this.selectedComponent) {
|
||||
this.selectedComponent.imageUrl = URL.createObjectURL(file.raw);
|
||||
}
|
||||
},
|
||||
beforeUpload(file) {
|
||||
const isImage = file.type.startsWith('image/');
|
||||
if (!isImage) {
|
||||
this.$message.error('只能上传图片文件!');
|
||||
}
|
||||
return isImage;
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 全局样式 */
|
||||
.app {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
}
|
||||
/* 分辨率设置样式 */
|
||||
.resolution-settings {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.resolution-inputs {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
width: 60px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
|
||||
/* 新增厂区选择器样式 */
|
||||
.factory-selector {
|
||||
margin-bottom: 20px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.factory-selector .el-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 调整左侧面板布局 */
|
||||
.left-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.left-panel > h3 {
|
||||
padding: 0 12px;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.left-panel {
|
||||
width: 200px;
|
||||
padding: 10px;
|
||||
border-right: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.component-item {
|
||||
padding: 6px;
|
||||
margin-bottom: 5px;
|
||||
border: 1px solid #c0c4cc;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.editor-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
margin: 0 20px;
|
||||
height: 600px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.ruler-horizontal {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 30px;
|
||||
width: calc(100% - 30px);
|
||||
height: 20px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
background-color: #f9f9f9;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.ruler-vertical {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 0;
|
||||
width: 30px;
|
||||
height: calc(100% - 20px);
|
||||
border-right: 1px solid #ccc;
|
||||
background-color: #f9f9f9;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.ruler-tick {
|
||||
position: absolute;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 标尺尺寸调整 */
|
||||
/* .ruler-horizontal {
|
||||
left: 30px;
|
||||
width: calc(100% - 30px);
|
||||
}
|
||||
|
||||
.ruler-vertical {
|
||||
top: 20px;
|
||||
height: calc(100% - 20px);
|
||||
} */
|
||||
|
||||
/* .editor-canvas {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 30px;
|
||||
width: calc(100% - 30px);
|
||||
height: calc(100% - 20px);
|
||||
border: 1px solid #ccc;
|
||||
background: center / 100% auto url("~@/assets/images/background.jpg") no-repeat;
|
||||
} */
|
||||
|
||||
/* 画布容器样式调整 */
|
||||
.editor-canvas {
|
||||
position: relative;
|
||||
background: #160064;
|
||||
border: 1px solid #ddd;
|
||||
margin: 20px 0 0 30px;
|
||||
/* background: center / 100% auto url("~@/assets/images/background.jpg") no-repeat; */
|
||||
}
|
||||
|
||||
.canvas-component {
|
||||
background-color: #f0f0f0;
|
||||
text-align: center;
|
||||
/* box-sizing: border-box;
|
||||
border: 1px solid #0073e1 !important; */
|
||||
/* line-height: 50px; */
|
||||
}
|
||||
|
||||
.right-panel {
|
||||
width: 200px;
|
||||
padding: 10px;
|
||||
border-left: 1px solid #ccc;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
/* add */
|
||||
.circle {
|
||||
display:inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
margin-right: 6px;
|
||||
text-align: 0px;
|
||||
/* background-color: #ff0; */
|
||||
}
|
||||
.bg-settings {
|
||||
padding-left: 52px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
.pbox {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,652 +0,0 @@
|
||||
<template>
|
||||
<div class="app">
|
||||
<!-- 左侧组件列表 -->
|
||||
<div class="left-panel">
|
||||
<!-- 分辨率设置 -->
|
||||
<div class="resolution-settings">
|
||||
<el-button type="primary" @click="toSave()">保存</el-button>
|
||||
<h3>画布分辨率</h3>
|
||||
<div class="resolution-inputs">
|
||||
<div class="input-group">
|
||||
<label>宽度(px):</label>
|
||||
<el-input
|
||||
v-model.number="canvasWidth"
|
||||
type="number"
|
||||
:min="100"
|
||||
@change="handleResolutionChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>高度(px):</label>
|
||||
<el-input
|
||||
v-model.number="canvasHeight"
|
||||
type="number"
|
||||
:min="100"
|
||||
@change="handleResolutionChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-settings">
|
||||
<el-upload
|
||||
action="#"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
:on-change="handleFileChange"
|
||||
accept="image/*"
|
||||
>
|
||||
<el-button type="primary">导入背景图</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 厂区选择下拉框 -->
|
||||
<div class="factory-selector">
|
||||
<el-select
|
||||
v-model="selectedFactory"
|
||||
placeholder="请选择厂区"
|
||||
@change="handleFactoryChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in factoryOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<h3>{{ selFacName }}</h3>
|
||||
<draggable
|
||||
v-model="components"
|
||||
:options="{ group: { name: 'components', pull: 'clone', put: false }, sort: false }"
|
||||
@start="onDragStart"
|
||||
@end="onDragEnd"
|
||||
>
|
||||
<div v-for="(component, index) in components" :key="index" class="component-item">
|
||||
{{ component.name }}
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
|
||||
<!-- 中间编辑框 -->
|
||||
<div class="editor-container" @scroll="handleScroll">
|
||||
<!-- 标尺部分 -->
|
||||
<div class="ruler-horizontal" :style="horizontalRulerStyle">
|
||||
<div
|
||||
v-for="i in horizontalRulerTicks"
|
||||
:key="i"
|
||||
class="ruler-tick"
|
||||
:style="{ left: `${i * rulerUnit}px` }"
|
||||
>
|
||||
{{ i * rulerUnit }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ruler-vertical" :style="verticalRulerStyle">
|
||||
<div
|
||||
v-for="i in verticalRulerTicks"
|
||||
:key="i"
|
||||
class="ruler-tick"
|
||||
:style="{ top: `${i * rulerUnit}px` }"
|
||||
>
|
||||
{{ i * rulerUnit }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 编辑画布 -->
|
||||
<div
|
||||
class="editor-canvas"
|
||||
:style="{
|
||||
width: canvasWidth + 'px',
|
||||
height: canvasHeight + 'px',
|
||||
backgroundImage: 'url(' + bgUrl + ')'
|
||||
}"
|
||||
@drop="onDrop"
|
||||
@dragover.prevent
|
||||
ref="editorCanvas"
|
||||
>
|
||||
<div
|
||||
v-for="(component, index) in canvasComponents"
|
||||
:key="index"
|
||||
:style="getComponentStyle(component)"
|
||||
class="canvas-component"
|
||||
ref="targetDiv"
|
||||
:class="{ 'highlight-border': isActive }"
|
||||
@mousedown="startDrag(component, $event)"
|
||||
@click="selectComponent(component)"
|
||||
>
|
||||
<!-- <div class="pbox" :style="{background: '#fc0 url('+ component.imageUrl +') no-repeat center;'}"> -->
|
||||
<div class="pbox" :style="{backgroundImage: 'url(' + component.imageUrl + ')'}">
|
||||
<div style="line-height: 100%; font-size:12px; color: #000;">{{ component.name}}</div>
|
||||
</div>
|
||||
<!-- <img
|
||||
v-if="component.imageUrl"
|
||||
:src="component.imageUrl"
|
||||
alt="组件图片"
|
||||
style="width: 100%; max-height: 80%;"
|
||||
/>
|
||||
<div style="width: 100%; height: 40%; font-size: 14px;"><span class="circle" :style="'background-color:' + component.status + ';'"></span>{{ component.name }}</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧属性编辑面板 -->
|
||||
<div class="right-panel">
|
||||
<h3>属性编辑</h3>
|
||||
<div v-if="selectedComponent">
|
||||
<label>名称:</label>
|
||||
<el-input v-model="selectedComponent.name" type="value" :disabled="true"/>
|
||||
<label>X 坐标:</label>
|
||||
<el-input v-model.number="selectedComponent.x" type="number" />
|
||||
<label>Y 坐标:</label>
|
||||
<el-input v-model.number="selectedComponent.y" type="number" />
|
||||
<label>宽度:</label>
|
||||
<el-input v-model.number="selectedComponent.width" type="number" />
|
||||
<label>高度:</label>
|
||||
<el-input v-model.number="selectedComponent.height" type="number" />
|
||||
<!-- <label>状态:</label>
|
||||
<el-input v-model="selectedComponent.status" type="value" /> -->
|
||||
<label>图标:</label>
|
||||
<div class="factory-selector">
|
||||
<el-select
|
||||
v-model="selectedPointImg"
|
||||
placeholder="请选择点位图标"
|
||||
@change="handlePointImgChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in pointSetOptions"
|
||||
:key="item.id"
|
||||
:label="item.code"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<!-- <el-upload
|
||||
action="https://jsonplaceholder.typicode.com/posts"
|
||||
:on-success="handleUploadSuccess"
|
||||
:before-upload="beforeUpload"
|
||||
:show-file-list="false"
|
||||
>
|
||||
<el-button type="primary">点击上传</el-button>
|
||||
</el-upload> -->
|
||||
<label></label>
|
||||
</div>
|
||||
<div v-else>
|
||||
请选择一个组件
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from 'vuedraggable';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
draggable,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 新增分辨率设置
|
||||
canvasWidth: 1920,
|
||||
canvasHeight: 1080,
|
||||
// 新增厂区选择相关数据
|
||||
factoryOptions: [
|
||||
{ id: 1, name: '一楼厂区' },
|
||||
{ id: 2, name: '二楼厂区' },
|
||||
{ id: 3, name: '三楼厂区' }
|
||||
],
|
||||
selectedFactory: 1, // 默认选中第一个厂区
|
||||
selFacName: '',
|
||||
components: [
|
||||
{ id: 1, name: '点位1', type: 'component1' },
|
||||
{ id: 2, name: '点位2', type: 'component2' },
|
||||
{ id: 3, name: '点位3', type: 'component3' },
|
||||
],
|
||||
pointSetOptions: [
|
||||
{ id: 1, name: '点位1', code: '029-01', pointImg: 'https://img2.baidu.com/it/u=2914671817,628443499&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=889' },
|
||||
{ id: 2, name: '点位2', code: '029-02', pointImg: 'https://img0.baidu.com/it/u=3613398591,98570664&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=500' },
|
||||
{ id: 3, name: '点位3', code: '029-03', pointImg: 'https://img0.baidu.com/it/u=1796365119,3995265789&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=667' }
|
||||
],
|
||||
selectedPointImg: '',
|
||||
bgUrl: '', // 画布区域背景图片
|
||||
isActive: false,
|
||||
canvasComponents: [],
|
||||
// canvasComponents: [
|
||||
// {"id":1111,"type":"","name":"组件11","x":275,"y":155,"width":100,"height":100,"imageUrl":"blob:http://localhost:8013/b883480e-814a-4803-8cf7-bb74967463bf","status":"#f00","desc":"有货"},
|
||||
// {"id":2222,"type":"","name":"组件22","x":475,"y":255,"width":100,"height":100,"imageUrl":"blob:http://localhost:8013/b883480e-814a-4803-8cf7-bb74967463bf","status":"#24f38a","desc":"有货"},
|
||||
// {"id":3333,"type":"","name":"组件33","x":575,"y":325,"width":100,"height":100,"imageUrl":"blob:http://localhost:8013/b883480e-814a-4803-8cf7-bb74967463bf","status":"关机","desc":"无货"},
|
||||
// ],
|
||||
selectedComponent: null,
|
||||
dragOffset: { x: 0, y: 0 },
|
||||
isDragging: false, // 是否正在拖拽
|
||||
rulerUnit: 50,
|
||||
horizontalRulerTicks: [],
|
||||
verticalRulerTicks: [],
|
||||
saveList:[],
|
||||
scrollLeft: 0,
|
||||
scrollTop: 0,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
// 监听分辨率变化
|
||||
canvasWidth(newVal) {
|
||||
this.updateRulerTicks();
|
||||
this.adjustComponentsPosition();
|
||||
},
|
||||
canvasHeight(newVal) {
|
||||
this.updateRulerTicks();
|
||||
this.adjustComponentsPosition();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 添加全局点击监听
|
||||
document.addEventListener('click', this.handleDocumentClick);
|
||||
this.updateRulerTicks();
|
||||
// const box = document.querySelector('.ruler-horizontal')
|
||||
// const vox = document.querySelector('.ruler-vertical')
|
||||
// window.addEventListener('scroll', () => {
|
||||
// const scrollX = window.scrollX; // 获取水平滚动值
|
||||
// box.style.left = `${30 + scrollX}px`; // 更新left值
|
||||
// const scrollY = window.scrollY; // 获取垂直滚动值
|
||||
// vox.style.top = `${20 + scrollY}px`; // 更新top值
|
||||
// })
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 移除监听,防止内存泄漏
|
||||
document.removeEventListener('click', this.handleDocumentClick);
|
||||
},
|
||||
computed: {
|
||||
// 顶部标尺样式
|
||||
horizontalRulerStyle() {
|
||||
return {
|
||||
top: this.scrollTop + 'px',
|
||||
width: this.canvasWidth + 'px',
|
||||
}
|
||||
},
|
||||
// 左侧标尺样式
|
||||
verticalRulerStyle() {
|
||||
return {
|
||||
left: this.scrollLeft + 'px',
|
||||
height: this.canvasHeight + 'px',
|
||||
}
|
||||
},
|
||||
// // 左侧标尺样式
|
||||
// verticalRulerStyle() {
|
||||
// return {
|
||||
// backgroundPositionY: -this.scrollTop % 100 + 'px',
|
||||
// backgroundSize: `20px 100px`,
|
||||
// backgroundImage: this.getVerticalRulerGradient()
|
||||
// }
|
||||
// },
|
||||
// // 内容区域样式
|
||||
// contentStyle() {
|
||||
// return {
|
||||
// width: this.contentWidth + 'px',
|
||||
// height: this.contentHeight + 'px'
|
||||
// }
|
||||
// }
|
||||
},
|
||||
methods: {
|
||||
handleDocumentClick(event) {
|
||||
// 判断点击是否发生在目标 div 外部
|
||||
if (this.$refs.targetDiv && !this.$refs.targetDiv.contains(event.target)) {
|
||||
this.isActive = false;
|
||||
}
|
||||
},
|
||||
handleFileChange(file) {
|
||||
// 验证文件类型
|
||||
if (!file.raw.type.startsWith('image/')) {
|
||||
this.$message.error('只能上传图片文件!');
|
||||
return;
|
||||
}
|
||||
|
||||
// 读取文件生成预览
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
this.bgUrl = e.target.result;
|
||||
};
|
||||
reader.readAsDataURL(file.raw);
|
||||
},
|
||||
// 分辨率变更处理
|
||||
handleResolutionChange() {
|
||||
if (this.canvasWidth < 100) this.canvasWidth = 100;
|
||||
if (this.canvasHeight < 100) this.canvasHeight = 100;
|
||||
this.updateRulerTicks();
|
||||
this.adjustComponentsPosition();
|
||||
},
|
||||
|
||||
// 更新标尺刻度
|
||||
updateRulerTicks() {
|
||||
this.horizontalRulerTicks = Array.from(
|
||||
{ length: Math.ceil(this.canvasWidth / this.rulerUnit) },
|
||||
(_, i) => i
|
||||
);
|
||||
this.verticalRulerTicks = Array.from(
|
||||
{ length: Math.ceil(this.canvasHeight / this.rulerUnit) },
|
||||
(_, i) => i
|
||||
);
|
||||
},
|
||||
|
||||
// 调整组件位置
|
||||
adjustComponentsPosition() {
|
||||
this.canvasComponents.forEach(component => {
|
||||
// 限制X坐标
|
||||
const maxX = this.canvasWidth - component.width;
|
||||
if (component.x > maxX) {
|
||||
component.x = Math.max(0, maxX);
|
||||
}
|
||||
|
||||
// 限制Y坐标
|
||||
const maxY = this.canvasHeight - component.height;
|
||||
if (component.y > maxY) {
|
||||
component.y = Math.max(0, maxY);
|
||||
}
|
||||
});
|
||||
},
|
||||
// 新增厂区变更处理方法
|
||||
handleFactoryChange(factoryId) {
|
||||
console.log(`切换到厂区: ${factoryId}`);
|
||||
console.log('selectedFactory', this.selectedFactory)
|
||||
this.factoryOptions.map(el => {
|
||||
if (el.id == factoryId) {
|
||||
this.selFacName = el.name;
|
||||
}
|
||||
})
|
||||
// 这里可以添加加载厂区配置的逻辑
|
||||
// 例如:清空当前画布或加载对应厂区配置
|
||||
this.canvasComponents = [];
|
||||
this.selectedComponent = null;
|
||||
},
|
||||
handlePointImgChange(PointSetId) {
|
||||
// console.log(`切换到点位图片: ${PointSetId}`);
|
||||
this.pointSetOptions.map(el => {
|
||||
if (el.id == PointSetId) {
|
||||
this.selectedComponent.imageUrl = el.pointImg;
|
||||
}
|
||||
})
|
||||
},
|
||||
toSave () {
|
||||
console.log(this.selectedComponent)
|
||||
console.log(JSON.stringify(this.canvasComponents))
|
||||
},
|
||||
onDragStart(event) {
|
||||
event.item.dataset.type = this.components[event.oldIndex].type;
|
||||
},
|
||||
onDragEnd(event) {
|
||||
// 拖拽结束后的逻辑
|
||||
},
|
||||
onDrop(event) {
|
||||
const type = event.dataTransfer.getData('type');
|
||||
const newComponent = {
|
||||
id: Date.now(),
|
||||
type,
|
||||
name: `点位${this.canvasComponents.length + 1}`,
|
||||
x: event.offsetX - this.dragOffset.x,
|
||||
y: event.offsetY - this.dragOffset.y,
|
||||
width: 100,
|
||||
height: 50,
|
||||
imageUrl: '',
|
||||
};
|
||||
this.canvasComponents.push(newComponent);
|
||||
},
|
||||
getComponentStyle(component) {
|
||||
return {
|
||||
position: 'absolute',
|
||||
left: `${component.x}px`,
|
||||
top: `${component.y}px`,
|
||||
width: `${component.width}px`,
|
||||
height: `${component.height}px`,
|
||||
border: '1px solid #ccc',
|
||||
cursor: this.isDragging ? 'grabbing' : 'move',
|
||||
};
|
||||
},
|
||||
// 开始拖拽
|
||||
startDrag(component, event) {
|
||||
this.selectedComponent = component;
|
||||
this.isDragging = true;
|
||||
|
||||
// 计算鼠标在组件内部的偏移量
|
||||
const rect = event.target.getBoundingClientRect();
|
||||
this.dragOffset = {
|
||||
x: event.clientX - rect.left,
|
||||
y: event.clientY - rect.top,
|
||||
};
|
||||
|
||||
// 绑定全局事件
|
||||
document.addEventListener('mousemove', this.onDrag);
|
||||
document.addEventListener('mouseup', this.stopDrag);
|
||||
},
|
||||
// 拖拽中
|
||||
onDrag(event) {
|
||||
if (!this.isDragging || !this.selectedComponent) return;
|
||||
|
||||
// 计算组件的新位置(相对于画布)
|
||||
const canvasRect = this.$refs.editorCanvas.getBoundingClientRect();
|
||||
const newX = event.clientX - canvasRect.left - this.dragOffset.x;
|
||||
const newY = event.clientY - canvasRect.top - this.dragOffset.y;
|
||||
|
||||
// 更新组件位置
|
||||
this.selectedComponent.x = Math.max(0, newX);
|
||||
this.selectedComponent.y = Math.max(0, newY);
|
||||
},
|
||||
// 停止拖拽
|
||||
stopDrag() {
|
||||
this.isDragging = false;
|
||||
document.removeEventListener('mousemove', this.onDrag);
|
||||
document.removeEventListener('mouseup', this.stopDrag);
|
||||
},
|
||||
selectComponent(component) {
|
||||
this.selectedComponent = component;
|
||||
this.isActive = true;
|
||||
this.selectedPointImg = '';
|
||||
},
|
||||
handleUploadSuccess(response, file) {
|
||||
if (this.selectedComponent) {
|
||||
this.selectedComponent.imageUrl = URL.createObjectURL(file.raw);
|
||||
}
|
||||
},
|
||||
beforeUpload(file) {
|
||||
const isImage = file.type.startsWith('image/');
|
||||
if (!isImage) {
|
||||
this.$message.error('只能上传图片文件!');
|
||||
}
|
||||
return isImage;
|
||||
},
|
||||
handleScroll(e) {
|
||||
this.scrollLeft = e.target.scrollLeft;
|
||||
this.scrollTop = e.target.scrollTop;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 全局样式 */
|
||||
.app {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
}
|
||||
/* 分辨率设置样式 */
|
||||
.resolution-settings {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.resolution-inputs {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
width: 60px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
|
||||
/* 新增厂区选择器样式 */
|
||||
.factory-selector {
|
||||
margin-bottom: 20px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.factory-selector .el-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 调整左侧面板布局 */
|
||||
.left-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.left-panel > h3 {
|
||||
padding: 0 12px;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.left-panel {
|
||||
width: 200px;
|
||||
padding: 10px;
|
||||
border-right: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.component-item {
|
||||
padding: 6px;
|
||||
margin-bottom: 5px;
|
||||
border: 1px solid #c0c4cc;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.editor-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
margin: 0 20px;
|
||||
/* min-height: 600px;
|
||||
max-height: 1080px; */
|
||||
height: 650px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.ruler-horizontal {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 30px;
|
||||
width: calc(100% - 30px);
|
||||
height: 20px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
background-color: #f9f9f9;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.ruler-vertical {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 0;
|
||||
width: 30px;
|
||||
height: calc(100% - 20px);
|
||||
border-right: 1px solid #ccc;
|
||||
background-color: #f9f9f9;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.ruler-tick {
|
||||
position: absolute;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 标尺尺寸调整 */
|
||||
/* .ruler-horizontal {
|
||||
left: 30px;
|
||||
width: calc(100% - 30px);
|
||||
}
|
||||
|
||||
.ruler-vertical {
|
||||
top: 20px;
|
||||
height: calc(100% - 20px);
|
||||
} */
|
||||
|
||||
/* .editor-canvas {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 30px;
|
||||
width: calc(100% - 30px);
|
||||
height: calc(100% - 20px);
|
||||
border: 1px solid #ccc;
|
||||
background: center / 100% auto url("~@/assets/images/background.jpg") no-repeat;
|
||||
} */
|
||||
|
||||
/* 画布容器样式调整 */
|
||||
.editor-canvas {
|
||||
position: relative;
|
||||
background: #160064;
|
||||
border: 1px solid #ddd;
|
||||
margin: 20px 0 0 30px;
|
||||
/* background: center / 100% auto url("~@/assets/images/background.jpg") no-repeat; */
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.canvas-component {
|
||||
background-color: #f0f0f0;
|
||||
text-align: center;
|
||||
/* box-sizing: border-box;
|
||||
border: 1px solid #0073e1 !important; */
|
||||
/* line-height: 50px; */
|
||||
box-sizing: border-box;
|
||||
/* border: 2px dashed rgb(77 187 249); */
|
||||
}
|
||||
|
||||
.right-panel {
|
||||
width: 200px;
|
||||
padding: 10px;
|
||||
border-left: 1px solid #ccc;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
/* add */
|
||||
.circle {
|
||||
display:inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
margin-right: 6px;
|
||||
text-align: 0px;
|
||||
/* background-color: #ff0; */
|
||||
}
|
||||
.bg-settings {
|
||||
padding-left: 52px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
.pbox {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
.highlight-border {
|
||||
border: 3px solid #2196f3; /* 蓝色高亮边框 */
|
||||
/* 可选:添加阴影增强效果 */
|
||||
box-shadow: 0 0 8px rgba(33, 150, 243, 0.5);
|
||||
}
|
||||
</style>
|
||||
@@ -1,413 +0,0 @@
|
||||
<template>
|
||||
<div class="app">
|
||||
<!-- 左侧组件列表 -->
|
||||
<div class="left-panel">
|
||||
<!-- 分辨率设置 -->
|
||||
<div class="resolution-settings">
|
||||
<h3>画布分辨率</h3>
|
||||
<div class="resolution-inputs">
|
||||
<div class="input-group">
|
||||
<label>宽度(px):</label>
|
||||
<el-input
|
||||
v-model.number="canvasWidth"
|
||||
type="number"
|
||||
:min="100"
|
||||
@change="handleResolutionChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>高度(px):</label>
|
||||
<el-input
|
||||
v-model.number="canvasHeight"
|
||||
type="number"
|
||||
:min="100"
|
||||
@change="handleResolutionChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 厂区选择下拉框 -->
|
||||
<div class="factory-selector">
|
||||
<el-select
|
||||
v-model="selectedFactory"
|
||||
placeholder="请选择厂区"
|
||||
@change="handleFactoryChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in factoryOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<h3>组件列表</h3>
|
||||
<draggable
|
||||
v-model="components"
|
||||
:options="{ group: { name: 'components', pull: 'clone', put: false }, sort: false }"
|
||||
@start="onDragStart"
|
||||
@end="onDragEnd"
|
||||
>
|
||||
<div v-for="(component, index) in components" :key="index" class="component-item">
|
||||
{{ component.name }}
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
|
||||
<!-- 中间编辑框 -->
|
||||
<div class="editor-container">
|
||||
<!-- 水平标尺 -->
|
||||
<div class="ruler-horizontal">
|
||||
<div
|
||||
v-for="i in horizontalRulerTicks"
|
||||
:key="i"
|
||||
class="ruler-tick"
|
||||
:style="{ left: `${i * rulerUnit}px` }"
|
||||
>
|
||||
{{ i * rulerUnit }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 垂直标尺 -->
|
||||
<div class="ruler-vertical">
|
||||
<div
|
||||
v-for="i in verticalRulerTicks"
|
||||
:key="i"
|
||||
class="ruler-tick"
|
||||
:style="{ top: `${i * rulerUnit}px` }"
|
||||
>
|
||||
{{ i * rulerUnit }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 编辑画布 -->
|
||||
<div
|
||||
class="editor-canvas"
|
||||
@drop="onDrop"
|
||||
@dragover.prevent
|
||||
ref="editorCanvas"
|
||||
>
|
||||
<div
|
||||
v-for="(component, index) in canvasComponents"
|
||||
:key="index"
|
||||
:style="getComponentStyle(component)"
|
||||
class="canvas-component"
|
||||
@mousedown="startDrag(component, $event)"
|
||||
@click="selectComponent(component)"
|
||||
>
|
||||
<img
|
||||
v-if="component.imageUrl"
|
||||
:src="component.imageUrl"
|
||||
alt="组件图片"
|
||||
style="width: 100%; max-height: 80%;"
|
||||
/>
|
||||
<div style="width: 100%; height: 40%; font-size: 14px;"><span class="circle" :style="'background-color:' + component.status + ';'"></span>{{ component.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧属性编辑面板 -->
|
||||
<div class="right-panel">
|
||||
<h3>属性编辑</h3>
|
||||
<div v-if="selectedComponent">
|
||||
<label>名称:</label>
|
||||
<el-input v-model="selectedComponent.name" type="value" />
|
||||
<label>X 坐标:</label>
|
||||
<el-input v-model.number="selectedComponent.x" type="number" />
|
||||
<label>Y 坐标:</label>
|
||||
<el-input v-model.number="selectedComponent.y" type="number" />
|
||||
<label>宽度:</label>
|
||||
<el-input v-model.number="selectedComponent.width" type="number" />
|
||||
<label>高度:</label>
|
||||
<el-input v-model.number="selectedComponent.height" type="number" />
|
||||
<label>状态:</label>
|
||||
<el-input v-model="selectedComponent.status" type="value" />
|
||||
<!-- <button @click="toSave()">保存</button> -->
|
||||
<label>上传图片:</label>
|
||||
<el-upload
|
||||
action="https://jsonplaceholder.typicode.com/posts"
|
||||
:on-success="handleUploadSuccess"
|
||||
:before-upload="beforeUpload"
|
||||
:show-file-list="false"
|
||||
>
|
||||
<el-button type="primary">点击上传</el-button>
|
||||
</el-upload>
|
||||
<label></label>
|
||||
<el-button type="primary" @click="toSave()">配置保存</el-button>
|
||||
</div>
|
||||
<div v-else>
|
||||
请选择一个组件
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from 'vuedraggable';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
draggable,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 新增厂区选择相关数据
|
||||
factoryOptions: [
|
||||
{ id: 1, name: '一楼厂区' },
|
||||
{ id: 2, name: '二楼厂区' },
|
||||
{ id: 3, name: '三楼厂区' }
|
||||
],
|
||||
selectedFactory: 1, // 默认选中第一个厂区
|
||||
components: [
|
||||
{ id: 1, name: '组件1', type: 'component1' },
|
||||
{ id: 2, name: '组件2', type: 'component2' },
|
||||
{ id: 3, name: '组件3', type: 'component3' },
|
||||
],
|
||||
canvasComponents: [],
|
||||
// canvasComponents: [
|
||||
// {"id":1111,"type":"","name":"组件11","x":275,"y":155,"width":100,"height":100,"imageUrl":"blob:http://localhost:8013/b883480e-814a-4803-8cf7-bb74967463bf","status":"#f00","desc":"有货"},
|
||||
// {"id":2222,"type":"","name":"组件22","x":475,"y":255,"width":100,"height":100,"imageUrl":"blob:http://localhost:8013/b883480e-814a-4803-8cf7-bb74967463bf","status":"#24f38a","desc":"有货"},
|
||||
// {"id":3333,"type":"","name":"组件33","x":575,"y":325,"width":100,"height":100,"imageUrl":"blob:http://localhost:8013/b883480e-814a-4803-8cf7-bb74967463bf","status":"关机","desc":"无货"},
|
||||
// ],
|
||||
selectedComponent: null,
|
||||
dragOffset: { x: 0, y: 0 },
|
||||
isDragging: false, // 是否正在拖拽
|
||||
rulerUnit: 50,
|
||||
horizontalRulerTicks: [],
|
||||
verticalRulerTicks: [],
|
||||
saveList:[]
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.updateRulerTicks();
|
||||
},
|
||||
methods: {
|
||||
toSave () {
|
||||
console.log(this.selectedComponent)
|
||||
console.log(JSON.stringify(this.canvasComponents))
|
||||
},
|
||||
updateRulerTicks() {
|
||||
const canvasWidth = 800;
|
||||
const canvasHeight = 600;
|
||||
this.horizontalRulerTicks = Array.from(
|
||||
{ length: Math.ceil(canvasWidth / this.rulerUnit) },
|
||||
(_, i) => i
|
||||
);
|
||||
this.verticalRulerTicks = Array.from(
|
||||
{ length: Math.ceil(canvasHeight / this.rulerUnit) },
|
||||
(_, i) => i
|
||||
);
|
||||
},
|
||||
onDragStart(event) {
|
||||
event.item.dataset.type = this.components[event.oldIndex].type;
|
||||
},
|
||||
onDragEnd(event) {
|
||||
// 拖拽结束后的逻辑
|
||||
},
|
||||
onDrop(event) {
|
||||
const type = event.dataTransfer.getData('type');
|
||||
const newComponent = {
|
||||
id: Date.now(),
|
||||
type,
|
||||
name: `组件${this.canvasComponents.length + 1}`,
|
||||
x: event.offsetX - this.dragOffset.x,
|
||||
y: event.offsetY - this.dragOffset.y,
|
||||
width: 100,
|
||||
height: 50,
|
||||
imageUrl: '',
|
||||
};
|
||||
this.canvasComponents.push(newComponent);
|
||||
},
|
||||
getComponentStyle(component) {
|
||||
return {
|
||||
position: 'absolute',
|
||||
left: `${component.x}px`,
|
||||
top: `${component.y}px`,
|
||||
width: `${component.width}px`,
|
||||
height: `${component.height}px`,
|
||||
border: '1px solid #ccc',
|
||||
cursor: this.isDragging ? 'grabbing' : 'move',
|
||||
};
|
||||
},
|
||||
// 开始拖拽
|
||||
startDrag(component, event) {
|
||||
this.selectedComponent = component;
|
||||
this.isDragging = true;
|
||||
|
||||
// 计算鼠标在组件内部的偏移量
|
||||
const rect = event.target.getBoundingClientRect();
|
||||
this.dragOffset = {
|
||||
x: event.clientX - rect.left,
|
||||
y: event.clientY - rect.top,
|
||||
};
|
||||
|
||||
// 绑定全局事件
|
||||
document.addEventListener('mousemove', this.onDrag);
|
||||
document.addEventListener('mouseup', this.stopDrag);
|
||||
},
|
||||
// 拖拽中
|
||||
onDrag(event) {
|
||||
if (!this.isDragging || !this.selectedComponent) return;
|
||||
|
||||
// 计算组件的新位置(相对于画布)
|
||||
const canvasRect = this.$refs.editorCanvas.getBoundingClientRect();
|
||||
const newX = event.clientX - canvasRect.left - this.dragOffset.x;
|
||||
const newY = event.clientY - canvasRect.top - this.dragOffset.y;
|
||||
|
||||
// 更新组件位置
|
||||
this.selectedComponent.x = Math.max(0, newX);
|
||||
this.selectedComponent.y = Math.max(0, newY);
|
||||
},
|
||||
// 停止拖拽
|
||||
stopDrag() {
|
||||
this.isDragging = false;
|
||||
document.removeEventListener('mousemove', this.onDrag);
|
||||
document.removeEventListener('mouseup', this.stopDrag);
|
||||
},
|
||||
selectComponent(component) {
|
||||
this.selectedComponent = component;
|
||||
},
|
||||
handleUploadSuccess(response, file) {
|
||||
if (this.selectedComponent) {
|
||||
this.selectedComponent.imageUrl = URL.createObjectURL(file.raw);
|
||||
}
|
||||
},
|
||||
beforeUpload(file) {
|
||||
const isImage = file.type.startsWith('image/');
|
||||
if (!isImage) {
|
||||
this.$message.error('只能上传图片文件!');
|
||||
}
|
||||
return isImage;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 全局样式 */
|
||||
.app {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
/* 新增厂区选择器样式 */
|
||||
.factory-selector {
|
||||
margin-bottom: 20px;
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.factory-selector .el-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 调整左侧面板布局 */
|
||||
.left-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.left-panel > h3 {
|
||||
padding: 0 12px;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.left-panel {
|
||||
width: 200px;
|
||||
padding: 10px;
|
||||
border-right: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.component-item {
|
||||
padding: 10px;
|
||||
margin-bottom: 5px;
|
||||
border: 1px solid #ccc;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.editor-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
margin: 0 20px;
|
||||
height: 600px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ruler-horizontal {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 30px;
|
||||
width: calc(100% - 30px);
|
||||
height: 20px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
background-color: #f9f9f9;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.ruler-vertical {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 0;
|
||||
width: 30px;
|
||||
height: calc(100% - 20px);
|
||||
border-right: 1px solid #ccc;
|
||||
background-color: #f9f9f9;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.ruler-tick {
|
||||
position: absolute;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.editor-canvas {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 30px;
|
||||
width: calc(100% - 30px);
|
||||
height: calc(100% - 20px);
|
||||
border: 1px solid #ccc;
|
||||
/* background-color: #fff; */
|
||||
/* // background-color: #0077ff; */
|
||||
background: center / 100% auto url("~@/assets/images/background.jpg") no-repeat;
|
||||
/* background: url("~@/assets/images/background.jpg"); */
|
||||
}
|
||||
|
||||
.canvas-component {
|
||||
background-color: #f0f0f0;
|
||||
text-align: center;
|
||||
/* line-height: 50px; */
|
||||
}
|
||||
|
||||
.right-panel {
|
||||
width: 200px;
|
||||
padding: 10px;
|
||||
border-left: 1px solid #ccc;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
/* add */
|
||||
.circle {
|
||||
display:inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
margin-right: 6px;
|
||||
text-align: 0px;
|
||||
/* background-color: #ff0; */
|
||||
}
|
||||
</style>
|
||||
@@ -1,137 +0,0 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<!-- 顶部标尺 -->
|
||||
<div
|
||||
class="horizontal-ruler"
|
||||
:style="horizontalRulerStyle"
|
||||
></div>
|
||||
|
||||
<!-- 左侧标尺 -->
|
||||
<div
|
||||
class="vertical-ruler"
|
||||
:style="verticalRulerStyle"
|
||||
></div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div
|
||||
class="content"
|
||||
ref="content"
|
||||
@scroll="handleScroll"
|
||||
>
|
||||
<!-- 实际内容尺寸需要足够大才能滚动 -->
|
||||
<div
|
||||
class="content-inner"
|
||||
:style="contentStyle"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
scrollLeft: 0,
|
||||
scrollTop: 0,
|
||||
// 内容区域尺寸(示例值,根据实际需求调整)
|
||||
contentWidth: 3000,
|
||||
contentHeight: 2000
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 顶部标尺样式
|
||||
horizontalRulerStyle() {
|
||||
return {
|
||||
backgroundPositionX: -this.scrollLeft % 100 + 'px',
|
||||
backgroundSize: `100px 20px`,
|
||||
backgroundImage: this.getHorizontalRulerGradient()
|
||||
}
|
||||
},
|
||||
// 左侧标尺样式
|
||||
verticalRulerStyle() {
|
||||
return {
|
||||
backgroundPositionY: -this.scrollTop % 100 + 'px',
|
||||
backgroundSize: `20px 100px`,
|
||||
backgroundImage: this.getVerticalRulerGradient()
|
||||
}
|
||||
},
|
||||
// 内容区域样式
|
||||
contentStyle() {
|
||||
return {
|
||||
width: this.contentWidth + 'px',
|
||||
height: this.contentHeight + 'px'
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 生成水平标尺渐变
|
||||
getHorizontalRulerGradient() {
|
||||
const lines = []
|
||||
// 每10px一个小刻度
|
||||
for(let i = 0; i <= 100; i += 10) {
|
||||
const color = i % 100 === 0 ? '#000' : '#999' // 大刻度黑色
|
||||
lines.push(`${color} ${i}px ${i+1}px`)
|
||||
}
|
||||
return `linear-gradient(to right, ${lines.join(', ')})`
|
||||
},
|
||||
// 生成垂直标尺渐变
|
||||
getVerticalRulerGradient() {
|
||||
const lines = []
|
||||
for(let i = 0; i <= 100; i += 10) {
|
||||
const color = i % 100 === 0 ? '#000' : '#999'
|
||||
lines.push(`${color} ${i}px ${i+1}px`)
|
||||
}
|
||||
return `linear-gradient(to bottom, ${lines.join(', ')})`
|
||||
},
|
||||
// 处理滚动事件
|
||||
handleScroll(e) {
|
||||
console.log(e)
|
||||
this.scrollLeft = e.target.scrollLeft
|
||||
this.scrollTop = e.target.scrollTop
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
position: relative;
|
||||
width: 600px;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.horizontal-ruler {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 20px; /* 留出左侧标尺位置 */
|
||||
width: calc(100% - 20px);
|
||||
height: 20px;
|
||||
background-repeat: repeat;
|
||||
}
|
||||
|
||||
.vertical-ruler {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 20px; /* 留出顶部标尺位置 */
|
||||
height: calc(100% - 20px);
|
||||
width: 20px;
|
||||
background-repeat: repeat;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
width: calc(100% - 20px);
|
||||
height: calc(100% - 20px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.content-inner {
|
||||
/* 通过伪元素创建网格背景 */
|
||||
background-image:
|
||||
linear-gradient(#eee 1px, transparent 1px),
|
||||
linear-gradient(90deg, #eee 1px, transparent 1px);
|
||||
background-size: 100px 100px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,57 +0,0 @@
|
||||
<template>
|
||||
<div class="editor-container">
|
||||
<vue-sketch-ruler
|
||||
:horizontal="true"
|
||||
:vertical="true"
|
||||
:width="editorWidth"
|
||||
:height="editorHeight"
|
||||
:zoom="zoom"
|
||||
></vue-sketch-ruler>
|
||||
<div class="editor" :style="{ width: editorWidth + 'px', height: editorHeight + 'px' }">
|
||||
<!-- 这里可以放置编辑器的内容 -->
|
||||
<textarea placeholder="在此输入内容"></textarea>
|
||||
<div style="width: 100px; height: 100px; border: 1px solid #f00;">22</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VueSketchRuler from 'vue-sketch-ruler';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
VueSketchRuler
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editorWidth: 800,
|
||||
editorHeight: 600,
|
||||
zoom: 1
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.editor-container {
|
||||
position: relative;
|
||||
width: 800px;
|
||||
height: 800px;
|
||||
}
|
||||
|
||||
.editor {
|
||||
position: absolute;
|
||||
top: 20px; /* 留出标尺的空间 */
|
||||
left: 20px; /* 留出标尺的空间 */
|
||||
border: 1px solid #ccc;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user