feat: 添加机器人图片尺寸配置功能,支持动态调整和应用,优化相关样式和逻辑

This commit is contained in:
xudan 2025-09-05 15:02:11 +08:00
parent 2174e8350b
commit 7708bcfaa1
3 changed files with 236 additions and 7 deletions

View File

@ -141,6 +141,53 @@
@update:modelValue="(value) => updateColor('robot.line', { target: { value } })"
/>
</div>
<!-- 机器人图片尺寸配置 -->
<div class="robot-size-section">
<div class="color-group">
<label>图片宽度</label>
<input
type="number"
:value="tempRobotSize.imageWidth"
@input="(e) => updateTempRobotSize('imageWidth', e)"
min="20"
max="120"
step="2"
class="size-input"
/>
<span class="unit">px</span>
</div>
<div class="color-group">
<label>图片高度</label>
<input
type="number"
:value="tempRobotSize.imageHeight"
@input="(e) => updateTempRobotSize('imageHeight', e)"
min="40"
max="150"
step="2"
class="size-input"
/>
<span class="unit">px</span>
</div>
<div class="size-actions">
<button
@click="applyRobotSize"
class="btn-apply"
:disabled="!hasRobotSizeChanged"
>
确定
</button>
<button
@click="resetRobotSize"
class="btn-reset-size"
:disabled="!hasRobotSizeChanged"
>
重置
</button>
</div>
</div>
</div>
<!-- 库位颜色配置 -->
@ -171,7 +218,7 @@
</template>
<script setup lang="ts">
import { computed, defineAsyncComponent } from 'vue';
import { computed, defineAsyncComponent, ref, watch } from 'vue';
import colorConfig from '../services/color-config.service';
//
@ -184,6 +231,27 @@ defineOptions({
const config = computed(() => colorConfig.currentConfig);
//
const tempRobotSize = ref({
imageWidth: config.value.robot.imageWidth,
imageHeight: config.value.robot.imageHeight
});
//
watch(() => config.value.robot.imageWidth, (newVal) => {
tempRobotSize.value.imageWidth = newVal;
});
watch(() => config.value.robot.imageHeight, (newVal) => {
tempRobotSize.value.imageHeight = newVal;
});
//
const hasRobotSizeChanged = computed(() => {
return tempRobotSize.value.imageWidth !== config.value.robot.imageWidth ||
tempRobotSize.value.imageHeight !== config.value.robot.imageHeight;
});
const updateColor = (path: string, event: Event | { target: { value: string } }) => {
const target = event.target as HTMLInputElement;
if (target) {
@ -191,8 +259,31 @@ const updateColor = (path: string, event: Event | { target: { value: string } })
}
};
//
const updateTempRobotSize = (key: 'imageWidth' | 'imageHeight', event: Event) => {
const target = event.target as HTMLInputElement;
if (target) {
tempRobotSize.value[key] = Number(target.value);
}
};
//
const applyRobotSize = () => {
colorConfig.setColor('robot.imageWidth', tempRobotSize.value.imageWidth.toString());
colorConfig.setColor('robot.imageHeight', tempRobotSize.value.imageHeight.toString());
};
//
const resetRobotSize = () => {
tempRobotSize.value.imageWidth = config.value.robot.imageWidth;
tempRobotSize.value.imageHeight = config.value.robot.imageHeight;
};
const resetToDefault = () => {
colorConfig.resetToDefault();
//
tempRobotSize.value.imageWidth = config.value.robot.imageWidth;
tempRobotSize.value.imageHeight = config.value.robot.imageHeight;
};
</script>
@ -279,6 +370,33 @@ const resetToDefault = () => {
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.size-input {
width: 80px;
height: 36px;
padding: 0 8px;
border: 2px solid #d9d9d9;
border-radius: 6px;
font-size: 14px;
text-align: center;
transition: border-color 0.2s;
}
.size-input:hover {
border-color: #40a9ff;
}
.size-input:focus {
border-color: #1890ff;
outline: none;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.unit {
font-size: 14px;
color: #666;
margin-left: 4px;
}
.actions {
display: flex;
@ -314,6 +432,59 @@ const resetToDefault = () => {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
/* 机器人尺寸配置样式 */
.robot-size-section {
margin-top: 16px;
padding: 16px;
background: #fff;
border-radius: 6px;
border: 1px solid #e0e0e0;
}
.size-actions {
display: flex;
gap: 8px;
margin-top: 16px;
padding-top: 12px;
border-top: 1px solid #f0f0f0;
}
.size-actions button {
padding: 8px 16px;
border: 1px solid #d9d9d9;
border-radius: 4px;
background: white;
cursor: pointer;
font-size: 13px;
font-weight: 500;
transition: all 0.2s;
}
.btn-apply {
color: #1890ff;
border-color: #1890ff;
}
.btn-apply:hover:not(:disabled) {
background: #e6f7ff;
border-color: #40a9ff;
}
.btn-reset-size {
color: #666;
border-color: #d9d9d9;
}
.btn-reset-size:hover:not(:disabled) {
background: #f5f5f5;
border-color: #bfbfbf;
}
.size-actions button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* 滚动条样式 */
.color-config-panel::-webkit-scrollbar {
width: 6px;

View File

@ -56,7 +56,7 @@ export interface EditorColorConfig {
};
};
// 机器人颜色
// 机器人配置
robot: {
stroke: string;
fill: string;
@ -67,6 +67,9 @@ export interface EditorColorConfig {
fillWarning: string;
strokeFault: string;
fillFault: string;
// 机器人图片尺寸
imageWidth: number; // 机器人图片宽度
imageHeight: number; // 机器人图片高度
};
// 库位颜色
@ -180,7 +183,9 @@ const DEFAULT_COLORS: EditorColorConfig = {
strokeWarning: '#FF851B99',
fillWarning: '#FF851B33',
strokeFault: '#FF4D4F99',
fillFault: '#FF4D4F33'
fillFault: '#FF4D4F33',
imageWidth: 42, // 机器人图片宽度 - 42像素
imageHeight: 76 // 机器人图片高度 - 76像素
},
storage: {
occupied: '#ff4d4f',
@ -258,6 +263,15 @@ class ColorConfigService {
}
}
/**
*
*/
private updateAllRobotImageSizes(): void {
if (this.editorService && typeof this.editorService.updateAllRobotImageSizes === 'function') {
this.editorService.updateAllRobotImageSizes();
}
}
/**
*
*/
@ -342,6 +356,12 @@ class ColorConfigService {
*/
public setColor(path: string, value: string): void {
this.setNestedValue(this.config.value, path, value);
// 如果是机器人图片尺寸配置更新,需要特殊处理
if (path === 'robot.imageWidth' || path === 'robot.imageHeight') {
this.updateAllRobotImageSizes();
}
// 触发编辑器重新渲染
this.triggerRender();
}

View File

@ -1098,6 +1098,28 @@ export class EditorService extends Meta2d {
}
}
/**
*
*
*/
public updateAllRobotImageSizes(): void {
this.robots.forEach(({ id, type }) => {
const pen = this.getPenById(id);
if (pen && pen.robot) {
const imageConfig = this.#mapRobotImage(type, pen.robot.active);
this.setValue(
{
id,
iconWidth: imageConfig.iconWidth,
iconHeight: imageConfig.iconHeight,
iconTop: imageConfig.iconTop,
},
{ render: true, history: false, doEvent: false },
);
}
});
}
#mapRobotImage(
type: RobotType,
active?: boolean,
@ -1106,10 +1128,16 @@ export class EditorService extends Meta2d {
const image =
import.meta.env.BASE_URL + (active ? `/robot/${type}-active-${theme}.png` : `/robot/${type}-${theme}.png`);
// 使用配置的机器人图片尺寸,如果没有配置则使用默认值
const iconWidth = colorConfig.getColor('robot.imageWidth') ? Number(colorConfig.getColor('robot.imageWidth')) : 42;
const iconHeight = colorConfig.getColor('robot.imageHeight')
? Number(colorConfig.getColor('robot.imageHeight'))
: 76;
// 使用优化的像素对齐算法,确保小车和光圈精确重合
const iconTop = this.#calculatePixelAlignedOffset(-16);
return { image, iconWidth: 42, iconHeight: 76, iconTop };
return { image, iconWidth, iconHeight, iconTop };
}
//#endregion
@ -1967,13 +1995,23 @@ function drawRobot(ctx: CanvasRenderingContext2D, pen: MapPen): void {
// 根据机器人状态获取颜色
const status = getRobotStatus(isWaring, isFault);
// 基于机器人图片大小计算光圈半径光圈比图片大20%,并随画布缩放
const imageWidth = colorConfig.getColor('robot.imageWidth')
? Number(colorConfig.getColor('robot.imageWidth')) : 60;
const imageHeight = colorConfig.getColor('robot.imageHeight')
? Number(colorConfig.getColor('robot.imageHeight')) : 80;
// 取图片的较大边作为基准,计算圆形光圈的半径
const imageSize = Math.max(imageWidth, imageHeight);
const robotRadius = (imageSize * 1.2 * s) / 2; // 光圈半径比图片大20%,并随画布缩放
const ox = x + w / 2;
const oy = y + h / 2;
ctx.save();
ctx.ellipse(ox, oy, w / 2, h / 2, 0, 0, Math.PI * 2);
ctx.fillStyle = colorConfig.getColor(`robot.fill${status === 'normal' ? '' : `-${status}`}`) || (get(theme, `robot.fill-${status}`) ?? (get(theme, 'robot.fill') ?? ''));
ctx.beginPath();
ctx.arc(ox, oy, robotRadius, 0, Math.PI * 2);
ctx.fillStyle = colorConfig.getColor(`robot.fill${status === 'normal' ? 'Normal' : status === 'warning' ? 'Warning' : 'Fault'}`) || (get(theme, `robot.fill-${status}`) ?? (get(theme, 'robot.fill') ?? ''));
ctx.fill();
ctx.strokeStyle = colorConfig.getColor(`robot.stroke${status === 'normal' ? '' : `-${status}`}`) || (get(theme, `robot.stroke-${status}`) ?? (get(theme, 'robot.stroke') ?? ''));
ctx.strokeStyle = colorConfig.getColor(`robot.stroke${status === 'normal' ? 'Normal' : status === 'warning' ? 'Warning' : 'Fault'}`) || (get(theme, `robot.stroke-${status}`) ?? (get(theme, 'robot.stroke') ?? ''));
ctx.stroke();
if (path?.length) {
ctx.strokeStyle = colorConfig.getColor('robot.line') || (get(theme, 'robot.line') ?? '');