feat: 修改选区面板样式 多语言完善

This commit is contained in:
bighuixiang
2025-07-20 19:31:43 +08:00
parent c44aadc9e3
commit 666cbc8470
5 changed files with 2438 additions and 2165 deletions

View File

@@ -10,24 +10,33 @@
<div class="tool-types"> <div class="tool-types">
<div <div
:class="['tool-btn', { active: selectionType === OperationType.LASSO }]" :class="[
'tool-btn',
{ active: selectionType === OperationType.LASSO },
]"
@click="setSelectionType(OperationType.LASSO)" @click="setSelectionType(OperationType.LASSO)"
> >
<svg-icon name="CFree" size="26" /> <svg-icon name="CFree" size="20" />
<span>{{ $t("手绘") }}</span> <span>{{ $t("手绘") }}</span>
</div> </div>
<div <div
:class="['tool-btn', { active: selectionType === OperationType.LASSO_RECTANGLE }]" :class="[
'tool-btn',
{ active: selectionType === OperationType.LASSO_RECTANGLE },
]"
@click="setSelectionType(OperationType.LASSO_RECTANGLE)" @click="setSelectionType(OperationType.LASSO_RECTANGLE)"
> >
<svg-icon name="CRectangle" size="32" /> <svg-icon name="CRectangle" size="26" />
<span>{{ $t("矩形") }}</span> <span>{{ $t("矩形") }}</span>
</div> </div>
<div <div
:class="['tool-btn', { active: selectionType === OperationType.LASSO_ELLIPSE }]" :class="[
'tool-btn',
{ active: selectionType === OperationType.LASSO_ELLIPSE },
]"
@click="setSelectionType(OperationType.LASSO_ELLIPSE)" @click="setSelectionType(OperationType.LASSO_ELLIPSE)"
> >
<svg-icon name="CEllipse" size="30" /> <svg-icon name="CEllipse" size="24" />
<span>{{ $t("椭圆") }}</span> <span>{{ $t("椭圆") }}</span>
</div> </div>
</div> </div>
@@ -38,15 +47,15 @@
<!-- 底部选区操作工具栏 --> <!-- 底部选区操作工具栏 -->
<div class="tool-actions"> <div class="tool-actions">
<div class="action-btn" @click="copySelectionToNewLayer"> <div class="action-btn" @click="copySelectionToNewLayer">
<svg-icon name="CPaste" size="20" /> <svg-icon name="CPaste" size="16" />
<span class="btn-text">{{ $t("创建") }}</span> <span class="btn-text">{{ $t("创建") }}</span>
</div> </div>
<div class="action-btn" @click="cutSelectionToNewLayer"> <div class="action-btn" @click="cutSelectionToNewLayer">
<svg-icon name="CCut" size="30" /> <svg-icon name="CCut" size="26" />
<span class="btn-text">{{ $t("创建并拷贝") }}</span> <span class="btn-text">{{ $t("创建并拷贝") }}</span>
</div> </div>
<div class="action-btn" @click="clearSelectionContent"> <div class="action-btn" @click="clearSelectionContent">
<svg-icon name="CClear" size="22" /> <svg-icon name="CClear" size="18" />
<span class="btn-text">{{ $t("清除选择内容") }}</span> <span class="btn-text">{{ $t("清除选择内容") }}</span>
</div> </div>
<!-- <button <!-- <button
@@ -150,7 +159,9 @@
<div class="dialog-container"> <div class="dialog-container">
<div class="dialog-header"> <div class="dialog-header">
<h3>{{ $t("选择填充颜色") }}</h3> <h3>{{ $t("选择填充颜色") }}</h3>
<button class="close-dialog-btn" @click="cancelColorPicker">×</button> <button class="close-dialog-btn" @click="cancelColorPicker">
×
</button>
</div> </div>
<div class="dialog-content"> <div class="dialog-content">
<input type="color" v-model="fillColor" class="color-picker" /> <input type="color" v-model="fillColor" class="color-picker" />
@@ -311,7 +322,8 @@ function setSelectionType(type) {
*/ */
function checkSelectionStatus() { function checkSelectionStatus() {
hasSelection.value = hasSelection.value =
props.selectionManager && props.selectionManager.getSelectionObject() !== null; props.selectionManager &&
props.selectionManager.getSelectionObject() !== null;
// 同步羽化值 // 同步羽化值
if (hasSelection.value) { if (hasSelection.value) {
@@ -610,15 +622,15 @@ function confirmColorPicker() {
background-color: rgba(0, 0, 0, 0.05); background-color: rgba(0, 0, 0, 0.05);
border: none; border: none;
border-radius: 6px; border-radius: 6px;
padding: 10px 5px; padding: 6px;
color: #333; color: #333;
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.2s;
} }
.tool-btn span { .tool-btn span {
margin-top: 6px; margin-top: 0;
font-size: 14px; font-size: 12px;
} }
.tool-btn svg { .tool-btn svg {
@@ -638,7 +650,7 @@ function confirmColorPicker() {
.toolbar-divider { .toolbar-divider {
height: 1px; height: 1px;
background-color: rgba(0, 0, 0, 0.05); background-color: rgba(0, 0, 0, 0.05);
margin: 0 10px 10px; margin: 0 10px 5px 10px;
} }
.tool-actions { .tool-actions {
@@ -674,13 +686,19 @@ function confirmColorPicker() {
.action-btn { .action-btn {
display: flex; display: flex;
flex-direction: column; // flex-direction: column;
flex-direction: row;
align-items: center; align-items: center;
justify-content: center;
background: none; background: none;
border: none; border: none;
color: #333; color: #333;
cursor: pointer; cursor: pointer;
padding: 8px 2px; padding: 0;
gap: 4px;
.c-svg {
width: auto;
}
} }
.action-btn svg { .action-btn svg {
@@ -690,7 +708,8 @@ function confirmColorPicker() {
} }
.btn-text { .btn-text {
font-size: 14px; display: block;
font-size: 12px;
text-align: center; text-align: center;
} }
@@ -825,9 +844,7 @@ function confirmColorPicker() {
.fade-enter-active, .fade-enter-active,
.fade-leave-active { .fade-leave-active {
transition: transition: opacity 0.3s, transform 0.3s;
opacity 0.3s,
transform 0.3s;
} }
.fade-enter-from, .fade-enter-from,
.fade-leave-to { .fade-leave-to {

View File

@@ -34,12 +34,16 @@ import SelectionPanel from "./components/SelectionPanel.vue"; // 引入选区面
import { LayerType, OperationType } from "./utils/layerHelper.js"; import { LayerType, OperationType } from "./utils/layerHelper.js";
import { ToolManager } from "./managers/toolManager.js"; import { ToolManager } from "./managers/toolManager.js";
// import { fabric } from "fabric-with-all"; // import { fabric } from "fabric-with-all";
import { uploadImageAndCreateLayer, loadImageUrlToLayer, loadImage } from "./utils/imageHelper.js"; import {
uploadImageAndCreateLayer,
loadImageUrlToLayer,
loadImage,
} from "./utils/imageHelper.js";
// import MinimapPanel from "./components/MinimapPanel.vue"; // import MinimapPanel from "./components/MinimapPanel.vue";
import { useI18n } from 'vue-i18n' import { useI18n } from "vue-i18n";
const {t} = useI18n() const { t } = useI18n();
const KeyboardShortcutHelp = defineAsyncComponent( const KeyboardShortcutHelp = defineAsyncComponent(() =>
() => import("./components/KeyboardShortcutHelp.vue") import("./components/KeyboardShortcutHelp.vue")
); );
const emit = defineEmits([ const emit = defineEmits([
@@ -230,6 +234,7 @@ onMounted(async () => {
activeLayerId, activeLayerId,
canvasManager, // 添加对 canvasManager 的引用 canvasManager, // 添加对 canvasManager 的引用
commandManager, // 添加对命令管理器的引用 commandManager, // 添加对命令管理器的引用
t, // 国际化函数
}); });
// commandManager.setLayerManager(layerManager); // 设置命令管理器需要访问的图层数据 // commandManager.setLayerManager(layerManager); // 设置命令管理器需要访问的图层数据
@@ -321,7 +326,11 @@ onMounted(async () => {
await layerManager.initializeLayers(); await layerManager.initializeLayers();
} }
if (props.enabledRedGreenMode && props.clothingImageUrl && props.redGreenImageUrl) { if (
props.enabledRedGreenMode &&
props.clothingImageUrl &&
props.redGreenImageUrl
) {
canvasManager.canvas.fill = "#fff"; // 设置画布背景色为白色 // 初始化红绿图模式管理器 canvasManager.canvas.fill = "#fff"; // 设置画布背景色为白色 // 初始化红绿图模式管理器
redGreenModeManager = new RedGreenModeManager({ redGreenModeManager = new RedGreenModeManager({
canvas: canvasManager.canvas, canvas: canvasManager.canvas,
@@ -368,7 +377,10 @@ onMounted(async () => {
console.error("更换底图失败:", error); console.error("更换底图失败:", error);
} }
canvasManager?.centerBackgroundLayer?.(canvasManager.canvas.width, canvasManager.canvas.height); canvasManager?.centerBackgroundLayer?.(
canvasManager.canvas.width,
canvasManager.canvas.height
);
} }
// // 设置固定图层是否可擦除 // // 设置固定图层是否可擦除
@@ -516,7 +528,7 @@ function updateCanvasColor() {
} }
async function addLayer() { async function addLayer() {
await layerManager.createLayer(t('Canvas.EmptyLayer')); await layerManager.createLayer(t("Canvas.EmptyLayer"));
} }
async function addTopLayer() { async function addTopLayer() {
await layerManager.createLayer("空图层", LayerType.EMPTY, { await layerManager.createLayer("空图层", LayerType.EMPTY, {
@@ -555,7 +567,9 @@ function moveLayerDown(layerId) {
function removeLayer(layerId) { function removeLayer(layerId) {
// Check if this is the last layer - prevent deletion // Check if this is the last layer - prevent deletion
if (layers.value.length <= 2) { if (layers.value.length <= 2) {
console.warn("Cannot delete the last layer. At least one layer must remain."); console.warn(
"Cannot delete the last layer. At least one layer must remain."
);
return; return;
} }
@@ -661,7 +675,9 @@ function handleLayersReorder(reorderData) {
const success = layerManager.reorderLayers(oldIndex, newIndex, layerId); const success = layerManager.reorderLayers(oldIndex, newIndex, layerId);
if (success) { if (success) {
console.log(`图层 ${layerId} 已从位置 ${oldIndex} 移动到位置 ${newIndex}`); console.log(
`图层 ${layerId} 已从位置 ${oldIndex} 移动到位置 ${newIndex}`
);
// 更新画布渲染顺序 // 更新画布渲染顺序
if (canvasManager) { if (canvasManager) {
@@ -678,7 +694,12 @@ function handleChildLayersReorder(reorderData) {
const { parentId, oldIndex, newIndex, layerId } = reorderData; const { parentId, oldIndex, newIndex, layerId } = reorderData;
if (layerManager && layerManager.reorderChildLayers) { if (layerManager && layerManager.reorderChildLayers) {
const success = layerManager.reorderChildLayers(parentId, oldIndex, newIndex, layerId); const success = layerManager.reorderChildLayers(
parentId,
oldIndex,
newIndex,
layerId
);
if (success) { if (success) {
console.log( console.log(
@@ -838,7 +859,8 @@ defineExpose({
* @returns {Object} 优化结果统计 * @returns {Object} 优化结果统计
*/ */
optimizeLayerStructure() { optimizeLayerStructure() {
if (!layerManager) return { removedEmptyLayers: 0, mergedLayers: 0, reorderedLayers: 0 }; if (!layerManager)
return { removedEmptyLayers: 0, mergedLayers: 0, reorderedLayers: 0 };
return layerManager.optimizeLayerStructure(); return layerManager.optimizeLayerStructure();
}, },
@@ -951,7 +973,10 @@ defineExpose({
<!-- <MinimapPanel v-if="minimapEnabled" :minimapManager="minimapManager" /> --> <!-- <MinimapPanel v-if="minimapEnabled" :minimapManager="minimapManager" /> -->
<!-- 笔刷控制面板 --> <!-- 笔刷控制面板 -->
<BrushControlPanel v-if="canvasManagerLoaded" :activeTool="activeTool" /> <BrushControlPanel
v-if="canvasManagerLoaded"
:activeTool="activeTool"
/>
<!-- 文本编辑面板 --> <!-- 文本编辑面板 -->
<TextEditorPanel <TextEditorPanel
@@ -982,9 +1007,15 @@ defineExpose({
/> />
<div class="zoom-info"> <div class="zoom-info">
{{ t('Canvas.Scale') }}: {{ currentZoom }}% {{ t("Canvas.Scale") }}: {{ currentZoom }}%
<button class="reset-zoom" @click="resetZoom">{{ $t('Canvas.ResetLayer') }}</button> <button class="reset-zoom" @click="resetZoom">
<button class="help-btn" @click="toggleShortcutHelp" :title="$t('Canvas.Help')"> {{ $t("Canvas.ResetLayer") }}
</button>
<button
class="help-btn"
@click="toggleShortcutHelp"
:title="$t('Canvas.Help')"
>
? ?
</button> </button>
</div> </div>
@@ -1024,7 +1055,11 @@ defineExpose({
</div> --> </div> -->
<!-- 快捷键帮助模态框 --> <!-- 快捷键帮助模态框 -->
<div v-if="showShortcutHelp" class="modal-overlay" @click="showShortcutHelp = false"> <div
v-if="showShortcutHelp"
class="modal-overlay"
@click="showShortcutHelp = false"
>
<div class="modal-content" @click.stop> <div class="modal-content" @click.stop>
<button class="close-modal" @click="showShortcutHelp = false">×</button> <button class="close-modal" @click="showShortcutHelp = false">×</button>
<KeyboardShortcutHelp /> <KeyboardShortcutHelp />
@@ -1101,20 +1136,30 @@ defineExpose({
--offsetY: 0px; --offsetY: 0px;
--size: 8px; --size: 8px;
--color: #dedcdc; --color: #dedcdc;
background-image: background-image: -webkit-linear-gradient(
-webkit-linear-gradient(
45deg, 45deg,
var(--color) 25%, var(--color) 25%,
transparent 0, transparent 0,
transparent 75%, transparent 75%,
var(--color) 0 var(--color) 0
), ),
-webkit-linear-gradient(45deg, var(--color) 25%, transparent 0, transparent 75%, var(--color) 0); -webkit-linear-gradient(45deg, var(--color) 25%, transparent 0, transparent
background-image: 75%, var(--color) 0);
linear-gradient(45deg, var(--color) 25%, transparent 0, transparent 75%, var(--color) 0), background-image: linear-gradient(
linear-gradient(45deg, var(--color) 25%, transparent 0, transparent 75%, var(--color) 0); 45deg,
background-position: var(--color) 25%,
var(--offsetX) var(--offsetY), transparent 0,
transparent 75%,
var(--color) 0
),
linear-gradient(
45deg,
var(--color) 25%,
transparent 0,
transparent 75%,
var(--color) 0
);
background-position: var(--offsetX) var(--offsetY),
calc(var(--size) + var(--offsetX)) calc(var(--size) + var(--offsetY)); calc(var(--size) + var(--offsetX)) calc(var(--size) + var(--offsetY));
background-size: calc(var(--size) * 2) calc(var(--size) * 2); background-size: calc(var(--size) * 2) calc(var(--size) * 2);
} }
@@ -1334,9 +1379,7 @@ button:hover {
// 淡入淡出动画 // 淡入淡出动画
.fade-enter-active, .fade-enter-active,
.fade-leave-active { .fade-leave-active {
transition: transition: opacity 0.3s, transform 0.3s;
opacity 0.3s,
transform 0.3s;
} }
.fade-enter-from, .fade-enter-from,
.fade-leave-to { .fade-leave-to {

View File

@@ -52,18 +52,27 @@ import {
} from "../commands/RasterizeLayerCommand"; } from "../commands/RasterizeLayerCommand";
// 导入图层排序相关类和混入 // 导入图层排序相关类和混入
import { LayerSort, createLayerSort, LayerSortMixin, LayerSortUtils } from "../utils/LayerSort"; import {
LayerSort,
createLayerSort,
LayerSortMixin,
LayerSortUtils,
} from "../utils/LayerSort";
import CanvasConfig from "../config/canvasConfig"; import CanvasConfig from "../config/canvasConfig";
import { isBoolean, template } from "lodash-es"; import { isBoolean, template } from "lodash-es";
import { findObjectById, generateId, optimizeCanvasRendering } from "../utils/helper"; import {
findObjectById,
generateId,
optimizeCanvasRendering,
} from "../utils/helper";
import { message } from "ant-design-vue"; import { message } from "ant-design-vue";
import { fabric } from "fabric-with-all"; import { fabric } from "fabric-with-all";
import { getOriginObjectInfo } from "../utils/layerUtils"; import { getOriginObjectInfo } from "../utils/layerUtils";
import { restoreFabricObject } from "../utils/objectHelper"; import { restoreFabricObject } from "../utils/objectHelper";
import { UpdateGroupMaskPositionCommand } from "../commands/UpdateGroupMaskPositionCommand"; import { UpdateGroupMaskPositionCommand } from "../commands/UpdateGroupMaskPositionCommand";
import { useI18n } from 'vue-i18n' // import { useI18n } from 'vue-i18n'
const {t} = useI18n() // const {t} = useI18n()
/** /**
* 图层管理器 - 负责管理画布上的所有图层 * 图层管理器 - 负责管理画布上的所有图层
* 包含图层的创建、删除、修改、排序等操作 * 包含图层的创建、删除、修改、排序等操作
@@ -83,6 +92,7 @@ export class LayerManager {
* @param {Number} options.canvasWidth 画布宽度 * @param {Number} options.canvasWidth 画布宽度
* @param {Number} options.canvasHeight 画布高度 * @param {Number} options.canvasHeight 画布高度
* @param {String} options.backgroundColor 背景颜色 * @param {String} options.backgroundColor 背景颜色
* @param {Function} options.t 国际化函数
*/ */
constructor(options) { constructor(options) {
this.canvas = options.canvas; this.canvas = options.canvas;
@@ -91,6 +101,7 @@ export class LayerManager {
this.commandManager = options.commandManager; this.commandManager = options.commandManager;
this.canvasManager = options.canvasManager || null; this.canvasManager = options.canvasManager || null;
this.lastSelectLayerId = options.lastSelectLayerId || { value: null }; // 上次选中的图层ID this.lastSelectLayerId = options.lastSelectLayerId || { value: null }; // 上次选中的图层ID
this.t = options.t || ((key) => key); // 国际化函数默认为直接返回key
this.backgroundFillManager = new BackgroundFillManager({ this.backgroundFillManager = new BackgroundFillManager({
canvas: this.canvas, canvas: this.canvas,
@@ -221,7 +232,11 @@ export class LayerManager {
// 基于 fabric-with-erasing 库的 erasable 属性设置擦除权限 // 基于 fabric-with-erasing 库的 erasable 属性设置擦除权限
// 只有活动图层、可见、非锁定、非背景、非固定图层的对象才可擦除 // 只有活动图层、可见、非锁定、非背景、非固定图层的对象才可擦除
obj.erasable = obj.erasable =
isInActiveLayer && layer.visible && !layer.locked && !layer.isBackground && !layer.isFixed; isInActiveLayer &&
layer.visible &&
!layer.locked &&
!layer.isBackground &&
!layer.isFixed;
// 图层状态决定交互性 // 图层状态决定交互性
if (layer.isBackground || obj.isBackground || layer.isFixed) { if (layer.isBackground || obj.isBackground || layer.isFixed) {
@@ -268,7 +283,11 @@ export class LayerManager {
if (this.isRedGreenMode) { if (this.isRedGreenMode) {
// 红绿图模式下 所有普通图层都可擦除 // 红绿图模式下 所有普通图层都可擦除
obj.erasable = layer.visible && !layer.locked && !layer.isBackground && !layer.isFixed; obj.erasable =
layer.visible &&
!layer.locked &&
!layer.isBackground &&
!layer.isFixed;
} }
// 平移模式下,禁用多选和擦除 // 平移模式下,禁用多选和擦除
@@ -319,7 +338,9 @@ export class LayerManager {
// 设置子图层对象的交互性 // 设置子图层对象的交互性
layer?.childLayer?.forEach(async (childLayer) => { layer?.childLayer?.forEach(async (childLayer) => {
const childObj = this.canvas.getObjects().find((o) => o.layerId === childLayer.id); const childObj = this.canvas
.getObjects()
.find((o) => o.layerId === childLayer.id);
if (childObj) { if (childObj) {
await this._setObjectInteractivity(childObj, childLayer, editorMode); await this._setObjectInteractivity(childObj, childLayer, editorMode);
} }
@@ -331,7 +352,10 @@ export class LayerManager {
let clippingMaskFabricObject = null; let clippingMaskFabricObject = null;
if (layer.clippingMask) { if (layer.clippingMask) {
// 反序列化 clippingMask // 反序列化 clippingMask
clippingMaskFabricObject = await restoreFabricObject(layer.clippingMask, this.canvas); clippingMaskFabricObject = await restoreFabricObject(
layer.clippingMask,
this.canvas
);
clippingMaskFabricObject.clipPath = null; clippingMaskFabricObject.clipPath = null;
clippingMaskFabricObject.set({ clippingMaskFabricObject.set({
@@ -351,7 +375,9 @@ export class LayerManager {
// } // }
// } // }
layer.children.forEach((childLayer) => { layer.children.forEach((childLayer) => {
const childObj = this.canvas.getObjects().find((o) => o.layerId === childLayer.id); const childObj = this.canvas
.getObjects()
.find((o) => o.layerId === childLayer.id);
if (childObj) { if (childObj) {
childObj.clipPath = clippingMaskFabricObject; childObj.clipPath = clippingMaskFabricObject;
childObj.dirty = true; // 标记为脏对象 childObj.dirty = true; // 标记为脏对象
@@ -369,7 +395,9 @@ export class LayerManager {
}); });
} else { } else {
layer.fabricObjects?.forEach((obj) => { layer.fabricObjects?.forEach((obj) => {
const fabricObject = this.canvas.getObjects().find((o) => o.id === obj.id); const fabricObject = this.canvas
.getObjects()
.find((o) => o.id === obj.id);
if (fabricObject) { if (fabricObject) {
fabricObject.clipPath = clippingMaskFabricObject; fabricObject.clipPath = clippingMaskFabricObject;
fabricObject.dirty = true; // 标记为脏对象 fabricObject.dirty = true; // 标记为脏对象
@@ -397,7 +425,11 @@ export class LayerManager {
*/ */
async fillLayerBackground(layerId, fillColor, undoable = true) { async fillLayerBackground(layerId, fillColor, undoable = true) {
layerId = this.activeLayerId.value || layerId; layerId = this.activeLayerId.value || layerId;
await this.backgroundFillManager.fillLayerBackground(layerId, fillColor, undoable); await this.backgroundFillManager.fillLayerBackground(
layerId,
fillColor,
undoable
);
} }
/** /**
@@ -470,7 +502,9 @@ export class LayerManager {
*/ */
createBackgroundLayer(name = "背景") { createBackgroundLayer(name = "背景") {
// 检查是否已有背景图层 // 检查是否已有背景图层
const hasBackgroundLayer = this.layers.value.some((layer) => layer.isBackground); const hasBackgroundLayer = this.layers.value.some(
(layer) => layer.isBackground
);
if (hasBackgroundLayer) { if (hasBackgroundLayer) {
console.warn("已存在背景层,不再创建新的背景层"); console.warn("已存在背景层,不再创建新的背景层");
@@ -523,7 +557,9 @@ export class LayerManager {
} }
// 生成唯一ID // 生成唯一ID
const layerId = `fixed_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`; const layerId = `fixed_layer_${Date.now()}_${Math.floor(
Math.random() * 1000
)}`;
// 创建固定图层 // 创建固定图层
const fixedLayer = createFixedLayer({ const fixedLayer = createFixedLayer({
@@ -574,19 +610,21 @@ export class LayerManager {
// 如果没有任何图层,创建背景层、固定图层和一个空白图层 // 如果没有任何图层,创建背景层、固定图层和一个空白图层
if (this.layers.value.length === 0) { if (this.layers.value.length === 0) {
// 创建背景图层 // 创建背景图层
this.createBackgroundLayer(); this.createBackgroundLayer(this.t("Canvas.Background"));
// 创建固定图层,位于背景图层之上 // 创建固定图层,位于背景图层之上
this.createFixedLayer(); this.createFixedLayer(this.t("Canvas.FixedLayer"));
// 创建一个空白图层(默认位于背景图层和固定图层之上) // 创建一个空白图层(默认位于背景图层和固定图层之上)
await this.createLayer(t('Canvas.Layer1')); await this.createLayer(this.t("Canvas.Layer1"));
} else { } else {
// 检查是否已有背景层 // 检查是否已有背景层
const hasBackgroundLayer = this.layers.value.some((layer) => layer.isBackground); const hasBackgroundLayer = this.layers.value.some(
(layer) => layer.isBackground
);
if (!hasBackgroundLayer) { if (!hasBackgroundLayer) {
this.createBackgroundLayer(); this.createBackgroundLayer(this.t("Canvas.Background"));
} }
// 检查是否已有固定图层 // 检查是否已有固定图层
@@ -602,7 +640,7 @@ export class LayerManager {
); );
if (!hasNormalLayer) { if (!hasNormalLayer) {
await this.createLayer(t('Canvas.Layer1')); await this.createLayer(this.t("Canvas.Layer1"));
} }
} }
@@ -629,7 +667,10 @@ export class LayerManager {
} }
// 验证目标图层是否存在 // 验证目标图层是否存在
const { layer: targetLayer } = findLayerRecursively(this.layers.value, targetLayerId); const { layer: targetLayer } = findLayerRecursively(
this.layers.value,
targetLayerId
);
if (!targetLayer) { if (!targetLayer) {
console.error(`目标图层 ${targetLayerId} 不存在`); console.error(`目标图层 ${targetLayerId} 不存在`);
return null; return null;
@@ -683,7 +724,8 @@ export class LayerManager {
*/ */
removeObjectFromLayer(objectOrId) { removeObjectFromLayer(objectOrId) {
// 获取对象ID // 获取对象ID
const objectId = typeof objectOrId === "string" ? objectOrId : objectOrId.id; const objectId =
typeof objectOrId === "string" ? objectOrId : objectOrId.id;
if (!objectId) { if (!objectId) {
console.error("无效的对象ID"); console.error("无效的对象ID");
@@ -743,7 +785,9 @@ export class LayerManager {
*/ */
getActiveLayer() { getActiveLayer() {
if (!this.activeLayerId.value) { if (!this.activeLayerId.value) {
console.warn("没有活动图层ID无法获取活动图层 ==== 默认设置第一个图层为活动图层"); console.warn(
"没有活动图层ID无法获取活动图层 ==== 默认设置第一个图层为活动图层"
);
this.activeLayerId.value = this.layers.value[0]?.id || null; this.activeLayerId.value = this.layers.value[0]?.id || null;
} }
@@ -848,8 +892,13 @@ export class LayerManager {
} }
const tempFabricObject = const tempFabricObject =
fabricObject?.toObject?.(["id", "layerId", "layerName", "isBackgroud", "isFixed"]) || fabricObject?.toObject?.([
fabricObject; "id",
"layerId",
"layerName",
"isBackgroud",
"isFixed",
]) || fabricObject;
if (layer.isFixed || layer.isBackground) { if (layer.isFixed || layer.isBackground) {
layer.fabricObject = tempFabricObject; layer.fabricObject = tempFabricObject;
} else { } else {
@@ -875,7 +924,9 @@ export class LayerManager {
// 如果是背景层或固定层,不允许移动 // 如果是背景层或固定层,不允许移动
if (layer && (layer.isBackground || layer.isFixed)) { if (layer && (layer.isBackground || layer.isFixed)) {
console.warn(layer.isBackground ? $t("背景层不可移动") : $t("固定层不可移动")); console.warn(
layer.isBackground ? this.t("背景层不可移动") : this.t("固定层不可移动")
);
return false; return false;
} }
@@ -1300,7 +1351,11 @@ export class LayerManager {
async ungroupLayers(groupId) { async ungroupLayers(groupId) {
// 查找组图层 // 查找组图层
const groupLayer = this.layers.value.find((l) => l.id === groupId); const groupLayer = this.layers.value.find((l) => l.id === groupId);
if (!groupLayer || !groupLayer.children || groupLayer.children.length === 0) { if (
!groupLayer ||
!groupLayer.children ||
groupLayer.children.length === 0
) {
console.error(`${groupId} 不是有效的组图层或不包含子图层`); console.error(`${groupId} 不是有效的组图层或不包含子图层`);
return null; return null;
} }
@@ -1357,7 +1412,9 @@ export class LayerManager {
*/ */
resizeCanvasWithScale(width, height, options = {}) { resizeCanvasWithScale(width, height, options = {}) {
// 检查是否有除背景层外的其他元素 // 检查是否有除背景层外的其他元素
const hasOtherElements = this.canvas.getObjects().some((obj) => !obj.isBackground); const hasOtherElements = this.canvas
.getObjects()
.some((obj) => !obj.isBackground);
if (hasOtherElements) { if (hasOtherElements) {
// 有其他元素时使用带缩放的命令 // 有其他元素时使用带缩放的命令
@@ -1426,7 +1483,9 @@ export class LayerManager {
if (!this.canvas) return; if (!this.canvas) return;
// 获取画布上的所有对象 // 获取画布上的所有对象
const canvasObjects = [...this.canvas.getObjects(["id", "layerId", "layerName"])]; const canvasObjects = [
...this.canvas.getObjects(["id", "layerId", "layerName"]),
];
// 清空画布 // 清空画布
this.canvas.clear(); this.canvas.clear();
@@ -1441,13 +1500,19 @@ export class LayerManager {
if (layer.isBackground && layer.fabricObject) { if (layer.isBackground && layer.fabricObject) {
// 背景图层 // 背景图层
const originalObj = canvasObjects.find((o) => o.id === layer.fabricObject.id); const originalObj = canvasObjects.find(
(o) => o.id === layer.fabricObject.id
);
if (originalObj) { if (originalObj) {
this.canvas.add(originalObj); this.canvas.add(originalObj);
} else { } else {
this.canvas.add(layer.fabricObject); this.canvas.add(layer.fabricObject);
} }
} else if (layer.isFixed && layer.fabricObjects && layer.fabricObjects.length > 0) { } else if (
layer.isFixed &&
layer.fabricObjects &&
layer.fabricObjects.length > 0
) {
// 固定图层 // 固定图层
layer.fabricObjects.forEach((obj) => { layer.fabricObjects.forEach((obj) => {
const originalObj = canvasObjects.find((o) => o.id === obj.id); const originalObj = canvasObjects.find((o) => o.id === obj.id);
@@ -1457,7 +1522,10 @@ export class LayerManager {
this.canvas.add(obj); this.canvas.add(obj);
} }
}); });
} else if (Array.isArray(layer.fabricObjects) && layer.fabricObjects.length > 0) { } else if (
Array.isArray(layer.fabricObjects) &&
layer.fabricObjects.length > 0
) {
// 普通图层添加所有fabricObjects // 普通图层添加所有fabricObjects
layer.fabricObjects.forEach((obj) => { layer.fabricObjects.forEach((obj) => {
const originalObj = canvasObjects.find((o) => o.id === obj.id); const originalObj = canvasObjects.find((o) => o.id === obj.id);
@@ -1486,7 +1554,9 @@ export class LayerManager {
if (layer.isBackground) { if (layer.isBackground) {
// 背景图层处理 // 背景图层处理
if (layer.fabricObject) { if (layer.fabricObject) {
const existsOnCanvas = canvasObjects.some((obj) => obj.id === layer.fabricObject.id); const existsOnCanvas = canvasObjects.some(
(obj) => obj.id === layer.fabricObject.id
);
if (!existsOnCanvas) { if (!existsOnCanvas) {
this.canvas.add(layer.fabricObject); this.canvas.add(layer.fabricObject);
} }
@@ -1621,7 +1691,8 @@ export class LayerManager {
layerCopy.children = layer.children.map((child) => { layerCopy.children = layer.children.map((child) => {
const childCopy = JSON.parse(JSON.stringify(child)); const childCopy = JSON.parse(JSON.stringify(child));
if (child.fabricObjects && child.fabricObjects.length > 0) { if (child.fabricObjects && child.fabricObjects.length > 0) {
childCopy.serializedObjects = this.getCurrLayerSerializedObjects(child); childCopy.serializedObjects =
this.getCurrLayerSerializedObjects(child);
} }
return childCopy; return childCopy;
}); });
@@ -1667,7 +1738,9 @@ export class LayerManager {
if (layer.fabricObjects && layer.fabricObjects.length > 0) { if (layer.fabricObjects && layer.fabricObjects.length > 0) {
layerCopy.serializedObjects = layer.fabricObjects layerCopy.serializedObjects = layer.fabricObjects
.map((obj) => .map((obj) =>
typeof obj.toObject === "function" ? obj.toObject(["id", "layerId", "layerName"]) : null typeof obj.toObject === "function"
? obj.toObject(["id", "layerId", "layerName"])
: null
) )
.filter(Boolean); .filter(Boolean);
} }
@@ -1800,7 +1873,8 @@ export class LayerManager {
// 查找第一个非背景、非锁定的图层,排除指定的图层 // 查找第一个非背景、非锁定的图层,排除指定的图层
return ( return (
this.layers.value.find( this.layers.value.find(
(layer) => layer.id !== excludeLayerId && !layer.isBackground && !layer.locked (layer) =>
layer.id !== excludeLayerId && !layer.isBackground && !layer.locked
) || null ) || null
); );
} }
@@ -1935,7 +2009,9 @@ export class LayerManager {
* @param {string} backgroundColor 背景颜色 * @param {string} backgroundColor 背景颜色
*/ */
updateBackgroundColor(backgroundColor, options = {}) { updateBackgroundColor(backgroundColor, options = {}) {
const backgroundLayer = this.layers.value.find((layer) => layer.isBackground); const backgroundLayer = this.layers.value.find(
(layer) => layer.isBackground
);
if (!backgroundLayer) { if (!backgroundLayer) {
console.warn("没有找到背景图层"); console.warn("没有找到背景图层");
@@ -2135,7 +2211,8 @@ export class LayerManager {
if (!this.canvas || !textObject) return null; if (!this.canvas || !textObject) return null;
// 确保对象有ID // 确保对象有ID
textObject.id = textObject.id || `text_${Date.now()}_${Math.floor(Math.random() * 1000)}`; textObject.id =
textObject.id || `text_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
// 创建文本图层 // 创建文本图层
const layerName = options.name || "文本图层"; const layerName = options.name || "文本图层";
@@ -2152,7 +2229,9 @@ export class LayerManager {
overline: options.overline || textObject.overline || false, overline: options.overline || textObject.overline || false,
fill: options.fill || textObject.fill || "#000000", fill: options.fill || textObject.fill || "#000000",
textBackgroundColor: textBackgroundColor:
options.textBackgroundColor || textObject.textBackgroundColor || "transparent", options.textBackgroundColor ||
textObject.textBackgroundColor ||
"transparent",
lineHeight: options.lineHeight || textObject.lineHeight || 1.16, lineHeight: options.lineHeight || textObject.lineHeight || 1.16,
charSpacing: options.charSpacing || textObject.charSpacing || 0, charSpacing: options.charSpacing || textObject.charSpacing || 0,
}, },
@@ -2163,7 +2242,9 @@ export class LayerManager {
textObject.layerName = layerName; textObject.layerName = layerName;
// 添加到画布,如果还未添加 // 添加到画布,如果还未添加
const isOnCanvas = this.canvas.getObjects().some((obj) => obj.id === textObject.id); const isOnCanvas = this.canvas
.getObjects()
.some((obj) => obj.id === textObject.id);
if (!isOnCanvas) { if (!isOnCanvas) {
this.canvas.add(textObject); this.canvas.add(textObject);
} }
@@ -2172,7 +2253,9 @@ export class LayerManager {
const layer = this.getLayerById(layerId); const layer = this.getLayerById(layerId);
if (layer) { if (layer) {
layer.fabricObjects = layer.fabricObjects || []; layer.fabricObjects = layer.fabricObjects || [];
layer.fabricObjects.push(textObject.toObject(["id", "layerId", "layerName"])); layer.fabricObjects.push(
textObject.toObject(["id", "layerId", "layerName"])
);
} }
// 设置此图层为活动图层 // 设置此图层为活动图层
@@ -2205,7 +2288,9 @@ export class LayerManager {
// 检查普通图层 // 检查普通图层
if (layer.fabricObjects && Array.isArray(layer.fabricObjects)) { if (layer.fabricObjects && Array.isArray(layer.fabricObjects)) {
const foundObject = layer.fabricObjects.find((obj) => obj.id === fabricObject.id); const foundObject = layer.fabricObjects.find(
(obj) => obj.id === fabricObject.id
);
if (foundObject) { if (foundObject) {
return layer; return layer;
} }
@@ -2286,7 +2371,12 @@ export class LayerManager {
* @returns {boolean} 是否排序成功 * @returns {boolean} 是否排序成功
*/ */
reorderChildLayers(parentId, oldIndex, newIndex, layerId) { reorderChildLayers(parentId, oldIndex, newIndex, layerId) {
return this.layerSort?.reorderChildLayers(parentId, oldIndex, newIndex, layerId); return this.layerSort?.reorderChildLayers(
parentId,
oldIndex,
newIndex,
layerId
);
} }
/** /**
@@ -2467,13 +2557,19 @@ export class LayerManager {
const layersCount = this.layers?.value?.length || 0; const layersCount = this.layers?.value?.length || 0;
const shouldUseAsync = const shouldUseAsync =
async !== null ? async : LayerSortUtils.shouldUseAsyncProcessing(objectsCount, layersCount); async !== null
? async
: LayerSortUtils.shouldUseAsyncProcessing(objectsCount, layersCount);
if (shouldUseAsync) { if (shouldUseAsync) {
console.log(`使用异步排序处理 ${objectsCount} 个对象, ${layersCount} 个图层`); console.log(
`使用异步排序处理 ${objectsCount} 个对象, ${layersCount} 个图层`
);
return this.layerSort.rearrangeObjectsAsync(); return this.layerSort.rearrangeObjectsAsync();
} else { } else {
console.log(`使用同步排序处理 ${objectsCount} 个对象, ${layersCount} 个图层`); console.log(
`使用同步排序处理 ${objectsCount} 个对象, ${layersCount} 个图层`
);
this.layerSort.rearrangeObjects(); this.layerSort.rearrangeObjects();
} }
} }
@@ -2582,7 +2678,10 @@ export class LayerManager {
moveLayerToIndex({ parentId, oldIndex, newIndex, layerId }) { moveLayerToIndex({ parentId, oldIndex, newIndex, layerId }) {
if (!this.layerSort) { if (!this.layerSort) {
console.warn("图层排序工具未初始化,使用基础移动方法"); console.warn("图层排序工具未初始化,使用基础移动方法");
return this.moveLayer(layerId, newIndex > this.getLayerIndex(layerId) ? "down" : "up"); return this.moveLayer(
layerId,
newIndex > this.getLayerIndex(layerId) ? "down" : "up"
);
} }
const result = this.layerSort.moveLayerToIndex({ const result = this.layerSort.moveLayerToIndex({
@@ -2593,7 +2692,9 @@ export class LayerManager {
}); });
if (result) { if (result) {
console.log(`图层 ${layerId} - oldIndex: ${oldIndex} 已移动到位置 ${newIndex}`); console.log(
`图层 ${layerId} - oldIndex: ${oldIndex} 已移动到位置 ${newIndex}`
);
// 更新对象交互性 // 更新对象交互性
// this.updateLayersObjectsInteractivity(); // this.updateLayersObjectsInteractivity();
} }
@@ -2637,7 +2738,9 @@ export class LayerManager {
return -1; return -1;
} }
let layerIndex = this.layers.value.findIndex((layer) => layer.id === layerId); let layerIndex = this.layers.value.findIndex(
(layer) => layer.id === layerId
);
if (layerIndex >= 0) return layerIndex; if (layerIndex >= 0) return layerIndex;
// 如果未找到,尝试在子图层中查找 // 如果未找到,尝试在子图层中查找
const { parent } = findLayerRecursively(this.layers.value, layerId); const { parent } = findLayerRecursively(this.layers.value, layerId);
@@ -2668,7 +2771,9 @@ export class LayerManager {
const result = this.layerSort.reorderLayers(oldIndex, newIndex, layerId); const result = this.layerSort.reorderLayers(oldIndex, newIndex, layerId);
if (result) { if (result) {
console.log(`高级排序完成: 图层 ${layerId} 从位置 ${oldIndex} 移动到 ${newIndex}`); console.log(
`高级排序完成: 图层 ${layerId} 从位置 ${oldIndex} 移动到 ${newIndex}`
);
// 更新对象交互性 // 更新对象交互性
this.updateLayersObjectsInteractivity(); this.updateLayersObjectsInteractivity();
} }
@@ -2685,7 +2790,12 @@ export class LayerManager {
* @returns {boolean} 是否排序成功 * @returns {boolean} 是否排序成功
*/ */
advancedReorderChildLayers(parentId, oldIndex, newIndex, layerId) { advancedReorderChildLayers(parentId, oldIndex, newIndex, layerId) {
const result = this.reorderChildLayers(parentId, oldIndex, newIndex, layerId); const result = this.reorderChildLayers(
parentId,
oldIndex,
newIndex,
layerId
);
if (result) { if (result) {
console.log( console.log(
@@ -2802,8 +2912,12 @@ export class LayerManager {
async mergeGroupLayers(groupId) { async mergeGroupLayers(groupId) {
// 查找组图层 // 查找组图层
const groupLayer = this.layers.value.find((l) => l.id === groupId); const groupLayer = this.layers.value.find((l) => l.id === groupId);
if (!groupLayer || !groupLayer.children || groupLayer.children.length === 0) { if (
console.warn($t("找不到有效的组图层或组图层为空")); !groupLayer ||
!groupLayer.children ||
groupLayer.children.length === 0
) {
console.warn(this.t("找不到有效的组图层或组图层为空"));
return []; return [];
} }
@@ -2837,22 +2951,25 @@ export class LayerManager {
const targetLayerId = layerId || this.activeLayerId.value; const targetLayerId = layerId || this.activeLayerId.value;
if (!targetLayerId) { if (!targetLayerId) {
console.warn($t("没有指定要栅格化的图层")); console.warn(this.t("没有指定要栅格化的图层"));
return false; return false;
} }
// 查找目标图层 // 查找目标图层
// const targetLayer = this.getLayerById(targetLayerId); // const targetLayer = this.getLayerById(targetLayerId);
const { layer: targetLayer } = findLayerRecursively(this.layers.value, targetLayerId); const { layer: targetLayer } = findLayerRecursively(
this.layers.value,
targetLayerId
);
if (!targetLayer) { if (!targetLayer) {
console.error($t("图层不存在", { layerId: targetLayerId })); console.error(this.t("图层不存在", { layerId: targetLayerId }));
return false; return false;
} }
// 不允许栅格化背景图层和固定图层 // 不允许栅格化背景图层和固定图层
if (targetLayer.isBackground || targetLayer.isFixed) { if (targetLayer.isBackground || targetLayer.isFixed) {
console.warn($t("背景图层和固定图层不能栅格化")); console.warn(this.t("背景图层和固定图层不能栅格化"));
return false; return false;
} }
@@ -2898,7 +3015,9 @@ export class LayerManager {
// 检查图层是否有内容可以栅格化 // 检查图层是否有内容可以栅格化
if (layer.type === "group" || layer.children.length > 0) { if (layer.type === "group" || layer.children.length > 0) {
// 组图层:检查是否有子图层且子图层有内容 // 组图层:检查是否有子图层且子图层有内容
return layer.children.some((child) => child.fabricObjects && child.fabricObjects.length > 0); return layer.children.some(
(child) => child.fabricObjects && child.fabricObjects.length > 0
);
} else { } else {
// 普通图层:检查是否有对象 // 普通图层:检查是否有对象
return layer.fabricObjects && layer.fabricObjects.length > 0; return layer.fabricObjects && layer.fabricObjects.length > 0;
@@ -2913,16 +3032,19 @@ export class LayerManager {
const targetLayerId = layerId || this.activeLayerId.value; const targetLayerId = layerId || this.activeLayerId.value;
if (!targetLayerId) { if (!targetLayerId) {
console.warn($t("没有指定要栅格化的图层")); console.warn(this.t("没有指定要栅格化的图层"));
return false; return false;
} }
// 查找目标图层 // 查找目标图层
// const targetLayer = this.getLayerById(targetLayerId); // const targetLayer = this.getLayerById(targetLayerId);
const { layer: targetLayer } = findLayerRecursively(this.layers.value, targetLayerId); const { layer: targetLayer } = findLayerRecursively(
this.layers.value,
targetLayerId
);
if (!targetLayer) { if (!targetLayer) {
console.error($t("图层不存在", { layerId: targetLayerId })); console.error(this.t("图层不存在", { layerId: targetLayerId }));
return false; return false;
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff