This commit is contained in:
2025-04-11 09:37:38 +08:00
parent 6432f36d31
commit 342ef26549
8 changed files with 0 additions and 3023 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>