feat: 重构颜色配置相关组件,新增颜色配置面板和带透明度的颜色选择器,优化颜色管理逻辑

This commit is contained in:
xudan 2025-09-08 15:46:34 +08:00
parent 6599216e38
commit b4ac71a717
9 changed files with 570 additions and 49 deletions

View File

@ -1,7 +1,7 @@
import type { RobotGroup, RobotInfo } from '@api/robot';
import type { Meta2dData } from '@meta2d/core';
import type { EditorColorConfig } from '../../services/color-config.service';
import type { EditorColorConfig } from '../../services/color/color-config.service';
export interface SceneInfo {
id: string; // 场景id

View File

@ -0,0 +1,108 @@
# 颜色配置组件
## 概述
这个文件夹包含了所有与颜色配置相关的组件和服务,包括颜色配置面板、颜色选择器和颜色配置服务。
## 文件结构
```
src/components/color/
├── color-config-panel.vue # 颜色配置面板组件
├── color-picker-with-alpha.vue # 带透明度的颜色选择器
└── README.md # 说明文档
src/services/color/
└── color-config.service.ts # 颜色配置服务
```
## 新增功能:区域边框颜色配置
### 功能特性
1. **全局边框设置**
- 默认边框宽度1-10px
- 默认透明度15%
- 边框样式固定为实线
2. **区域类型边框颜色**
- 库区类型1
- 互斥区类型11
- 非互斥区类型12
- 约束区类型13
- 描述区类型14
### 使用方法
#### 1. 在颜色配置面板中设置
打开颜色配置面板,在"区域颜色"部分找到"边框配置"
- 调整全局边框设置(宽度、透明度)
- 为每种区域类型设置独立的边框颜色
#### 2. 通过代码设置
```typescript
import colorConfig from '@/services/color/color-config.service';
// 设置区域边框颜色
colorConfig.setAreaBorderColor(1, '#FF0000'); // 库区红色边框
// 设置边框宽度
colorConfig.setAreaBorderWidth(1, 3); // 3px宽度
// 设置边框透明度
colorConfig.setAreaBorderOpacity(1, 0.15); // 15%透明度
// 批量设置
colorConfig.setAreaBorderConfig(1, {
color: '#FF0000',
width: 3,
opacity: 0.15,
});
```
#### 3. 获取当前配置
```typescript
// 获取边框颜色
const borderColor = colorConfig.getAreaBorderColor(1);
// 获取边框宽度
const borderWidth = colorConfig.getAreaBorderWidth(1);
// 获取边框透明度
const borderOpacity = colorConfig.getAreaBorderOpacity(1);
```
### 测试功能
在颜色配置面板中点击"测试边框颜色"按钮,系统会:
1. 在控制台输出当前所有区域类型的边框配置
2. 自动设置一些测试颜色(库区红色、互斥区绿色、非互斥区蓝色)
3. 触发编辑器重新渲染以显示新的边框颜色
### 技术实现
- **实时渲染**:配置更改后立即触发编辑器重新渲染
- **本地存储**所有配置自动保存到localStorage
- **类型安全**完整的TypeScript类型定义
- **向后兼容**保持现有API不变
### 注意事项
1. 边框颜色优先级:`area.border.colors[type]` > `area.types[type].borderColor` > 默认值
2. 边框样式固定为实线
3. 透明度范围0-10%到100%
4. 边框宽度范围1-10像素
### 故障排除
如果边框颜色不生效,请检查:
1. 确保区域类型正确1, 11, 12, 13, 14
2. 检查控制台是否有调试信息输出
3. 确认颜色值格式正确(如:#FF0000
4. 使用"测试边框颜色"功能验证配置是否正确

View File

@ -47,40 +47,119 @@
<!-- 区域颜色配置 -->
<div class="color-section">
<h4>区域颜色</h4>
<div class="color-group">
<label>库区填充</label>
<ColorPickerWithAlpha
:modelValue="config.area.fill[1]"
@update:modelValue="(value) => updateColor('area.fill.1', { target: { value } })"
/>
<!-- 区域填充颜色 -->
<div class="color-subsection">
<h5>填充颜色</h5>
<div class="color-group">
<label>库区填充</label>
<ColorPickerWithAlpha
:modelValue="config.area.fill[1]"
@update:modelValue="(value) => updateColor('area.fill.1', { target: { value } })"
/>
</div>
<div class="color-group">
<label>互斥区填充</label>
<ColorPickerWithAlpha
:modelValue="config.area.fill[11]"
@update:modelValue="(value) => updateColor('area.fill.11', { target: { value } })"
/>
</div>
<div class="color-group">
<label>非互斥区填充</label>
<ColorPickerWithAlpha
:modelValue="config.area.fill[12]"
@update:modelValue="(value) => updateColor('area.fill.12', { target: { value } })"
/>
</div>
<div class="color-group">
<label>约束区填充</label>
<ColorPickerWithAlpha
:modelValue="config.area.fill[13]"
@update:modelValue="(value) => updateColor('area.fill.13', { target: { value } })"
/>
</div>
<div class="color-group">
<label>描述区填充</label>
<ColorPickerWithAlpha
:modelValue="config.area.fill[14]"
@update:modelValue="(value) => updateColor('area.fill.14', { target: { value } })"
/>
</div>
</div>
<div class="color-group">
<label>互斥区填充</label>
<ColorPickerWithAlpha
:modelValue="config.area.fill[11]"
@update:modelValue="(value) => updateColor('area.fill.11', { target: { value } })"
/>
</div>
<div class="color-group">
<label>非互斥区填充</label>
<ColorPickerWithAlpha
:modelValue="config.area.fill[12]"
@update:modelValue="(value) => updateColor('area.fill.12', { target: { value } })"
/>
</div>
<div class="color-group">
<label>约束区填充</label>
<ColorPickerWithAlpha
:modelValue="config.area.fill[13]"
@update:modelValue="(value) => updateColor('area.fill.13', { target: { value } })"
/>
</div>
<div class="color-group">
<label>描述区填充</label>
<ColorPickerWithAlpha
:modelValue="config.area.fill[14]"
@update:modelValue="(value) => updateColor('area.fill.14', { target: { value } })"
/>
<!-- 区域边框配置 -->
<div class="color-subsection">
<h5>边框配置</h5>
<!-- 全局边框设置 -->
<div class="border-global-settings">
<div class="color-group">
<label>默认边框宽度</label>
<input
type="number"
:value="config.area.border.width"
@input="(e) => updateBorderProperty('width', e)"
min="1"
max="10"
step="1"
class="size-input"
/>
<span class="unit">px</span>
</div>
<div class="color-group">
<label>默认透明度</label>
<input
type="range"
:value="config.area.border.opacity * 100"
@input="(e) => updateBorderOpacity(e)"
min="0"
max="100"
step="5"
class="opacity-slider"
/>
<span class="opacity-value">{{ Math.round(config.area.border.opacity * 100) }}%</span>
</div>
</div>
<!-- 各类型区域边框颜色 -->
<div class="border-colors">
<div class="color-group">
<label>库区边框</label>
<ColorPickerWithAlpha
:modelValue="config.area.border.colors[1]"
@update:modelValue="(value) => updateAreaBorderColor(1, value)"
/>
</div>
<div class="color-group">
<label>互斥区边框</label>
<ColorPickerWithAlpha
:modelValue="config.area.border.colors[11]"
@update:modelValue="(value) => updateAreaBorderColor(11, value)"
/>
</div>
<div class="color-group">
<label>非互斥区边框</label>
<ColorPickerWithAlpha
:modelValue="config.area.border.colors[12]"
@update:modelValue="(value) => updateAreaBorderColor(12, value)"
/>
</div>
<div class="color-group">
<label>约束区边框</label>
<ColorPickerWithAlpha
:modelValue="config.area.border.colors[13]"
@update:modelValue="(value) => updateAreaBorderColor(13, value)"
/>
</div>
<div class="color-group">
<label>描述区边框</label>
<ColorPickerWithAlpha
:modelValue="config.area.border.colors[14]"
@update:modelValue="(value) => updateAreaBorderColor(14, value)"
/>
</div>
</div>
</div>
</div>
@ -218,9 +297,9 @@
</template>
<script setup lang="ts">
import { computed, defineAsyncComponent, ref, watch } from 'vue';
import { computed, defineAsyncComponent, nextTick, ref, watch } from 'vue';
import colorConfig from '../services/color-config.service';
import colorConfig from '../../services/color/color-config.service';
//
const ColorPickerWithAlpha = defineAsyncComponent(() => import('./color-picker-with-alpha.vue'));
@ -279,11 +358,54 @@ const resetRobotSize = () => {
tempRobotSize.value.imageHeight = config.value.robot.imageHeight;
};
const resetToDefault = () => {
//
const updateBorderProperty = (property: 'width', event: Event) => {
const target = event.target as HTMLInputElement;
if (target) {
const value = Number(target.value);
//
colorConfig.setColor(`area.border.${property}`, value.toString());
//
const areaTypes = [1, 11, 12, 13, 14];
areaTypes.forEach(type => {
colorConfig.setAreaBorderWidth(type, value);
});
}
};
//
const updateBorderOpacity = (event: Event) => {
const target = event.target as HTMLInputElement;
if (target) {
const opacity = Number(target.value) / 100;
//
colorConfig.setColor('area.border.opacity', opacity.toString());
//
const areaTypes = [1, 11, 12, 13, 14];
areaTypes.forEach(type => {
colorConfig.setAreaBorderOpacity(type, opacity);
});
}
};
//
const updateAreaBorderColor = (areaType: number, color: string) => {
colorConfig.setAreaBorderColor(areaType, color);
};
const resetToDefault = async () => {
colorConfig.resetToDefault();
// tick
await nextTick();
//
tempRobotSize.value.imageWidth = config.value.robot.imageWidth;
tempRobotSize.value.imageHeight = config.value.robot.imageHeight;
};
</script>
@ -417,6 +539,7 @@ const resetToDefault = () => {
transition: all 0.2s;
}
.btn-reset {
color: #ff4d4f;
border-color: #ff4d4f;
@ -485,6 +608,60 @@ const resetToDefault = () => {
cursor: not-allowed;
}
/* 边框配置样式 */
.border-global-settings {
margin-bottom: 16px;
padding: 12px;
background: #f8f9fa;
border-radius: 4px;
border: 1px solid #e9ecef;
}
.border-colors {
margin-top: 12px;
}
.opacity-slider {
width: 120px;
height: 6px;
border-radius: 3px;
background: #d9d9d9;
outline: none;
cursor: pointer;
-webkit-appearance: none;
appearance: none;
}
.opacity-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 18px;
height: 18px;
border-radius: 50%;
background: #1890ff;
cursor: pointer;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.opacity-slider::-moz-range-thumb {
width: 18px;
height: 18px;
border-radius: 50%;
background: #1890ff;
cursor: pointer;
border: none;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.opacity-value {
font-size: 14px;
color: #666;
margin-left: 8px;
min-width: 35px;
text-align: right;
}
/* 滚动条样式 */
.color-config-panel::-webkit-scrollbar {
width: 6px;

View File

@ -4,7 +4,7 @@ import type { EditorService } from '@core/editor.service';
import { useToolbar } from '@core/useToolbar';
import { computed, inject, type InjectionKey, onBeforeUnmount, onMounted, ref, type ShallowRef } from 'vue';
import ColorConfigPanel from './color-config-panel.vue';
import ColorConfigPanel from './color/color-config-panel.vue';
//
//

View File

@ -10,7 +10,7 @@ import { computed, onMounted, onUnmounted, provide, ref, shallowRef, watch } fro
import { useRoute } from 'vue-router';
import { autoDoorSimulationService, type AutoDoorWebSocketData } from '../services/auto-door-simulation.service';
import colorConfig from '../services/color-config.service';
import colorConfig from '../services/color/color-config.service';
import {
type ContextMenuState,
createContextMenuManager,

View File

@ -46,12 +46,22 @@ export interface EditorColorConfig {
strokeActive: string;
stroke: Record<number, string>; // 按区域类型索引
fill: Record<number, string>; // 按区域类型索引
// 边框颜色配置
border: {
width: number; // 边框宽度
opacity: number; // 边框透明度 (0-1)
// 各类型区域边框颜色
colors: Record<number, string>; // 按区域类型索引的边框颜色
};
// 各类型区域专用颜色
types: {
[key: number]: {
stroke: string;
strokeActive: string;
fill: string;
borderColor: string; // 边框颜色
borderWidth: number; // 边框宽度
borderOpacity: number; // 边框透明度
};
};
};
@ -165,13 +175,60 @@ const DEFAULT_COLORS: EditorColorConfig = {
13: '#e61e4a33',
14: '#FFD70033'
},
// 边框配置
border: {
width: 1, // 默认边框宽度
opacity: 0.15, // 默认边框透明度 15%
colors: {
1: '#1890FF', // 库区边框颜色
11: '#FF4D4F', // 互斥区边框颜色
12: '#52C41A', // 非互斥区边框颜色
13: '#FA8C16', // 约束区边框颜色
14: '#722ED1' // 描述区边框颜色
}
},
types: {
// 区域类型
1: { stroke: '#9ACDFF99', strokeActive: '#EBB214', fill: '#9ACDFF33' }, // 库区
11: { stroke: '#FF535399', strokeActive: '#EBB214', fill: '#FF9A9A33' }, // 互斥区
12: { stroke: '#0DBB8A99', strokeActive: '#EBB214', fill: '#0DBB8A33' }, // 非互斥区
13: { stroke: '#e61e4aad', strokeActive: '#EBB214', fill: '#e61e4a33' }, // 约束区
14: { stroke: '#FFD70099', strokeActive: '#EBB214', fill: '#FFD70033' }, // 描述区
1: {
stroke: '#9ACDFF99',
strokeActive: '#EBB214',
fill: '#9ACDFF33',
borderColor: '#1890FF',
borderWidth: 1,
borderOpacity: 0.15
}, // 库区
11: {
stroke: '#FF535399',
strokeActive: '#EBB214',
fill: '#FF9A9A33',
borderColor: '#FF4D4F',
borderWidth: 1,
borderOpacity: 0.15
}, // 互斥区
12: {
stroke: '#0DBB8A99',
strokeActive: '#EBB214',
fill: '#0DBB8A33',
borderColor: '#52C41A',
borderWidth: 1,
borderOpacity: 0.15
}, // 非互斥区
13: {
stroke: '#e61e4aad',
strokeActive: '#EBB214',
fill: '#e61e4a33',
borderColor: '#FA8C16',
borderWidth: 1,
borderOpacity: 0.15
}, // 约束区
14: {
stroke: '#FFD70099',
strokeActive: '#EBB214',
fill: '#FFD70033',
borderColor: '#722ED1',
borderWidth: 1,
borderOpacity: 0.15
}, // 描述区
}
},
robot: {
@ -217,6 +274,7 @@ class ColorConfigService {
private config = ref<EditorColorConfig>({ ...DEFAULT_COLORS });
private editorService: any = null;
private readonly STORAGE_KEY = 'editor-color-config';
private isResetting = false; // 标记是否正在重置
constructor() {
// 从本地存储加载配置
@ -234,7 +292,10 @@ class ColorConfigService {
watch(
() => this.config.value,
(newConfig) => {
this.saveToLocalStorage(newConfig);
// 如果正在重置,不保存到本地存储
if (!this.isResetting) {
this.saveToLocalStorage(newConfig);
}
},
{ deep: true }
);
@ -276,6 +337,11 @@ class ColorConfigService {
*
*/
private loadFromLocalStorage(): void {
// 如果正在重置,不加载本地存储
if (this.isResetting) {
return;
}
try {
const stored = localStorage.getItem(this.STORAGE_KEY);
if (stored) {
@ -336,7 +402,18 @@ class ColorConfigService {
*
*/
public resetToDefault(): void {
this.config.value = { ...DEFAULT_COLORS };
// 设置重置标记,防止自动保存
this.isResetting = true;
// 清除本地存储
localStorage.removeItem(this.STORAGE_KEY);
// 重置为默认配置,使用深度复制确保完全重置
this.config.value = JSON.parse(JSON.stringify(DEFAULT_COLORS));
// 重置标记,允许后续自动保存
this.isResetting = false;
// 触发编辑器重新渲染
this.triggerRender();
}
@ -366,10 +443,94 @@ class ColorConfigService {
this.triggerRender();
}
/**
*
* @param areaType
* @param color
*/
public setAreaBorderColor(areaType: number, color: string): void {
this.setColor(`area.border.colors.${areaType}`, color);
this.setColor(`area.types.${areaType}.borderColor`, color);
}
/**
*
* @param areaType
* @param width
*/
public setAreaBorderWidth(areaType: number, width: number): void {
this.setNestedValue(this.config.value, `area.types.${areaType}.borderWidth`, width);
this.triggerRender();
}
/**
*
* @param areaType
* @param opacity (0-1)
*/
public setAreaBorderOpacity(areaType: number, opacity: number): void {
this.setNestedValue(this.config.value, `area.types.${areaType}.borderOpacity`, opacity);
this.triggerRender();
}
/**
*
* @param areaType
*/
public getAreaBorderColor(areaType: number): string {
return this.getNestedValue(this.config.value, `area.border.colors.${areaType}`) ||
this.getNestedValue(this.config.value, `area.types.${areaType}.borderColor`) ||
this.config.value.area.border.colors[areaType] || '';
}
/**
*
* @param areaType
*/
public getAreaBorderWidth(areaType: number): number {
return this.getNestedValue(this.config.value, `area.types.${areaType}.borderWidth`) || this.config.value.area.border.width;
}
/**
*
* @param areaType
*/
public getAreaBorderOpacity(areaType: number): number {
return this.getNestedValue(this.config.value, `area.types.${areaType}.borderOpacity`) || this.config.value.area.border.opacity;
}
/**
*
* @param areaType
* @param config
*/
public setAreaBorderConfig(areaType: number, config: {
color?: string;
width?: number;
opacity?: number;
}): void {
if (config.color !== undefined) {
this.setAreaBorderColor(areaType, config.color);
}
if (config.width !== undefined) {
this.setAreaBorderWidth(areaType, config.width);
}
if (config.opacity !== undefined) {
this.setAreaBorderOpacity(areaType, config.opacity);
}
}
/**
*
*/
private loadConfig(): void {
// 如果正在重置,不加载主题配置
if (this.isResetting) {
return;
}
const theme = sTheme.editor as any;
if (theme) {
// 从主题配置中加载颜色,如果没有则使用默认值
@ -419,6 +580,59 @@ class ColorConfigService {
12: theme.area?.['fill-12'] || DEFAULT_COLORS.area.fill[12],
13: theme.area?.['fill-13'] || DEFAULT_COLORS.area.fill[13],
14: theme.area?.['fill-14'] || DEFAULT_COLORS.area.fill[14],
},
border: {
width: theme.area?.border?.width || DEFAULT_COLORS.area.border.width,
opacity: theme.area?.border?.opacity || DEFAULT_COLORS.area.border.opacity,
colors: {
1: theme.area?.border?.['color-1'] || DEFAULT_COLORS.area.border.colors[1],
11: theme.area?.border?.['color-11'] || DEFAULT_COLORS.area.border.colors[11],
12: theme.area?.border?.['color-12'] || DEFAULT_COLORS.area.border.colors[12],
13: theme.area?.border?.['color-13'] || DEFAULT_COLORS.area.border.colors[13],
14: theme.area?.border?.['color-14'] || DEFAULT_COLORS.area.border.colors[14],
}
},
types: {
1: {
stroke: theme.area?.['stroke-1'] || DEFAULT_COLORS.area.types[1].stroke,
strokeActive: theme.area?.strokeActive || DEFAULT_COLORS.area.types[1].strokeActive,
fill: theme.area?.['fill-1'] || DEFAULT_COLORS.area.types[1].fill,
borderColor: theme.area?.border?.['color-1'] || DEFAULT_COLORS.area.types[1].borderColor,
borderWidth: theme.area?.border?.width || DEFAULT_COLORS.area.types[1].borderWidth,
borderOpacity: theme.area?.border?.opacity || DEFAULT_COLORS.area.types[1].borderOpacity,
},
11: {
stroke: theme.area?.['stroke-11'] || DEFAULT_COLORS.area.types[11].stroke,
strokeActive: theme.area?.strokeActive || DEFAULT_COLORS.area.types[11].strokeActive,
fill: theme.area?.['fill-11'] || DEFAULT_COLORS.area.types[11].fill,
borderColor: theme.area?.border?.['color-11'] || DEFAULT_COLORS.area.types[11].borderColor,
borderWidth: theme.area?.border?.width || DEFAULT_COLORS.area.types[11].borderWidth,
borderOpacity: theme.area?.border?.opacity || DEFAULT_COLORS.area.types[11].borderOpacity,
},
12: {
stroke: theme.area?.['stroke-12'] || DEFAULT_COLORS.area.types[12].stroke,
strokeActive: theme.area?.strokeActive || DEFAULT_COLORS.area.types[12].strokeActive,
fill: theme.area?.['fill-12'] || DEFAULT_COLORS.area.types[12].fill,
borderColor: theme.area?.border?.['color-12'] || DEFAULT_COLORS.area.types[12].borderColor,
borderWidth: theme.area?.border?.width || DEFAULT_COLORS.area.types[12].borderWidth,
borderOpacity: theme.area?.border?.opacity || DEFAULT_COLORS.area.types[12].borderOpacity,
},
13: {
stroke: theme.area?.['stroke-13'] || DEFAULT_COLORS.area.types[13].stroke,
strokeActive: theme.area?.strokeActive || DEFAULT_COLORS.area.types[13].strokeActive,
fill: theme.area?.['fill-13'] || DEFAULT_COLORS.area.types[13].fill,
borderColor: theme.area?.border?.['color-13'] || DEFAULT_COLORS.area.types[13].borderColor,
borderWidth: theme.area?.border?.width || DEFAULT_COLORS.area.types[13].borderWidth,
borderOpacity: theme.area?.border?.opacity || DEFAULT_COLORS.area.types[13].borderOpacity,
},
14: {
stroke: theme.area?.['stroke-14'] || DEFAULT_COLORS.area.types[14].stroke,
strokeActive: theme.area?.strokeActive || DEFAULT_COLORS.area.types[14].strokeActive,
fill: theme.area?.['fill-14'] || DEFAULT_COLORS.area.types[14].fill,
borderColor: theme.area?.border?.['color-14'] || DEFAULT_COLORS.area.types[14].borderColor,
borderWidth: theme.area?.border?.width || DEFAULT_COLORS.area.types[14].borderWidth,
borderOpacity: theme.area?.border?.opacity || DEFAULT_COLORS.area.types[14].borderOpacity,
}
}
},
robot: {

View File

@ -1,7 +1,7 @@
import type { MapPen } from '@api/map';
import { LockState } from '@meta2d/core';
import colorConfig from '../color-config.service';
import colorConfig from '../color/color-config.service';
// 预加载锁定图标(仅一次)
const lockedIcon = new Image();

View File

@ -30,7 +30,7 @@ import { BehaviorSubject, debounceTime, filter, map, Subject, switchMap } from '
import { reactive, watch } from 'vue';
import { AreaOperationService } from './area-operation.service';
import colorConfig from './color-config.service';
import colorConfig from './color/color-config.service';
import {
drawStorageBackground,
drawStorageLocation,
@ -1895,10 +1895,32 @@ function drawArea(ctx: CanvasRenderingContext2D, pen: MapPen): void {
ctx.fillStyle = finalFillColor;
ctx.fill();
// 获取边框颜色 - 优先使用新的边框颜色配置
const borderColor = type ? colorConfig.getAreaBorderColor(type) : '';
const generalStrokeColor = colorConfig.getColor(active ? 'area.strokeActive' : `area.stroke.${type}`);
const typeStrokeColor = colorConfig.getColor(`area.types.${type}.stroke`);
const themeStrokeColor = get(theme, active ? 'area.strokeActive' : `area.stroke-${type}`);
ctx.strokeStyle = generalStrokeColor || typeStrokeColor || themeStrokeColor || '';
// 获取边框宽度和透明度
const borderWidth = type ? colorConfig.getAreaBorderWidth(type) : 1;
const borderOpacity = type ? colorConfig.getAreaBorderOpacity(type) : 0.15;
// 设置边框宽度和样式
ctx.lineWidth = borderWidth;
ctx.setLineDash([]); // 固定为实线
// 优先使用边框颜色,然后是通用颜色,最后是主题颜色
let finalStrokeColor = borderColor || generalStrokeColor || typeStrokeColor || themeStrokeColor || '';
// 应用透明度
if (borderOpacity < 1 && finalStrokeColor.startsWith('#')) {
const alpha = Math.round(borderOpacity * 255).toString(16).padStart(2, '0');
finalStrokeColor = finalStrokeColor + alpha;
}
ctx.strokeStyle = finalStrokeColor;
ctx.stroke();
// 如果是描述区且有描述内容,渲染描述文字