导出图片添加印花平铺判断
This commit is contained in:
31
src/assets/icons/CPoint.svg
Normal file
31
src/assets/icons/CPoint.svg
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="96.000000pt" height="96.000000pt" viewBox="0 0 96.000000 96.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
|
||||||
|
<g transform="translate(0.000000,96.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M346 936 c-21 -13 -49 -41 -62 -62 -41 -67 -27 -180 27 -218 47 -32
|
||||||
|
53 -12 12 40 -71 94 -2 229 116 229 37 0 58 -7 87 -28 32 -22 40 -24 42 -12
|
||||||
|
14 64 -142 100 -222 51z"/>
|
||||||
|
<path d="M368 877 c-33 -28 -48 -57 -48 -96 0 -39 9 -61 26 -61 10 0 14 13 14
|
||||||
|
46 0 57 12 79 50 93 61 21 110 -22 110 -96 0 -48 14 -56 31 -19 31 67 -35 156
|
||||||
|
-114 156 -29 0 -50 -7 -69 -23z"/>
|
||||||
|
<path d="M580 794 c0 -58 -9 -84 -43 -121 -19 -21 -19 -23 -2 -29 34 -13 85
|
||||||
|
75 85 148 0 37 -10 58 -26 58 -10 0 -14 -15 -14 -56z"/>
|
||||||
|
<path d="M410 749 c-13 -6 -28 -15 -32 -22 -4 -7 -8 -106 -8 -222 l-1 -210
|
||||||
|
-27 34 c-62 80 -89 101 -126 101 -27 0 -39 -6 -52 -25 -15 -24 -15 -28 0 -68
|
||||||
|
21 -53 78 -123 94 -113 18 11 16 17 -28 75 -44 58 -48 72 -25 91 21 18 54 -6
|
||||||
|
93 -68 31 -47 74 -78 93 -67 5 4 9 101 9 224 0 225 3 241 42 241 10 0 19 -1
|
||||||
|
19 -2 1 -2 5 -86 8 -188 5 -165 8 -185 24 -188 15 -3 17 5 17 61 0 72 17 100
|
||||||
|
45 77 10 -9 15 -32 15 -77 0 -56 2 -64 18 -61 12 2 16 11 14 34 -5 48 14 86
|
||||||
|
41 82 20 -3 22 -9 25 -66 3 -53 6 -63 20 -60 12 2 19 16 22 43 4 32 10 41 27
|
||||||
|
43 36 5 46 -36 39 -166 -6 -127 -19 -160 -75 -195 -48 -29 -176 -34 -246 -10
|
||||||
|
-48 18 -56 25 -130 128 -16 22 -25 26 -37 19 -15 -8 -11 -18 31 -75 78 -105
|
||||||
|
94 -113 236 -117 119 -3 121 -3 167 27 69 44 82 74 86 214 6 179 -8 221 -72
|
||||||
|
216 -22 -2 -39 4 -55 20 -17 17 -32 22 -55 19 -19 -2 -38 2 -45 9 -6 7 -25 13
|
||||||
|
-40 13 l-29 0 -4 100 c-3 103 -11 122 -53 133 -11 3 -31 1 -45 -4z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -872,13 +872,13 @@ export class ToggleChildLayerVisibilityCommand extends Command {
|
|||||||
// this.oldVisibility = this.childLayer ? this.childLayer.visible : null;
|
// this.oldVisibility = this.childLayer ? this.childLayer.visible : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute() {
|
async execute(visible) {
|
||||||
if (!this.childLayer) {
|
if (!this.childLayer) {
|
||||||
throw new Error("找不到要切换可见性的子图层");
|
throw new Error("找不到要切换可见性的子图层");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切换可见性
|
// 切换可见性
|
||||||
this.childLayer.visible = !this.childLayer.visible;
|
this.childLayer.visible = typeof visible === "boolean" ? visible : !this.childLayer.visible;
|
||||||
|
|
||||||
// 更新画布上图层对象的可见性
|
// 更新画布上图层对象的可见性
|
||||||
if (this.canvas) {
|
if (this.canvas) {
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ const canDeleteComputed = computed(() => {
|
|||||||
:is-child="isChild"
|
:is-child="isChild"
|
||||||
:is-active="layer.id === activeLayerId"
|
:is-active="layer.id === activeLayerId"
|
||||||
:is-selected="isLayerSelected(layer.id)"
|
:is-selected="isLayerSelected(layer.id)"
|
||||||
:is-multi-select-mode="isMultiSelectMode && !layer.specialType"
|
:is-multi-select-mode="isMultiSelectMode && !(layer.isPrintTrims || layer.isPrintTrimsGroup)"
|
||||||
:is-editing="editingLayerId === layer.id"
|
:is-editing="editingLayerId === layer.id"
|
||||||
:editing-name="editingLayerName"
|
:editing-name="editingLayerName"
|
||||||
:can-delete="
|
:can-delete="
|
||||||
@@ -296,7 +296,7 @@ const canDeleteComputed = computed(() => {
|
|||||||
:expanded-group-ids="expandedGroupIds"
|
:expanded-group-ids="expandedGroupIds"
|
||||||
@click="(...args) => forwardEvent('layer-click', ...args)"
|
@click="(...args) => forwardEvent('layer-click', ...args)"
|
||||||
@double-click="(...args) => forwardEvent('layer-double-click', ...args)"
|
@double-click="(...args) => forwardEvent('layer-double-click', ...args)"
|
||||||
@context-menu="(...args) => !layer.specialType && forwardEvent('context-menu', ...args)"
|
@context-menu="(...args) => !(layer.isPrintTrims || layer.isPrintTrimsGroup) && forwardEvent('context-menu', ...args)"
|
||||||
@checkbox-change="(...args) => forwardEvent('checkbox-change', ...args)"
|
@checkbox-change="(...args) => forwardEvent('checkbox-change', ...args)"
|
||||||
@toggle-visibility="(...args) => forwardEvent('toggle-visibility', ...args)"
|
@toggle-visibility="(...args) => forwardEvent('toggle-visibility', ...args)"
|
||||||
@toggle-lock="(...args) => forwardEvent('toggle-lock', ...args)"
|
@toggle-lock="(...args) => forwardEvent('toggle-lock', ...args)"
|
||||||
@@ -337,7 +337,7 @@ const canDeleteComputed = computed(() => {
|
|||||||
:expanded-group-ids="expandedGroupIds"
|
:expanded-group-ids="expandedGroupIds"
|
||||||
:isChild="true"
|
:isChild="true"
|
||||||
:parentLayerId="layer.id"
|
:parentLayerId="layer.id"
|
||||||
:group-name="layer.specialType || groupName"
|
:group-name="groupName"
|
||||||
@layer-click="(...args) => forwardEvent('layer-click', ...args)"
|
@layer-click="(...args) => forwardEvent('layer-click', ...args)"
|
||||||
@layer-double-click="(...args) => forwardEvent('layer-double-click', ...args)"
|
@layer-double-click="(...args) => forwardEvent('layer-double-click', ...args)"
|
||||||
@context-menu="(...args) => forwardEvent('context-menu', ...args)"
|
@context-menu="(...args) => forwardEvent('context-menu', ...args)"
|
||||||
|
|||||||
@@ -1242,7 +1242,7 @@ async function handleCrossLevelMove(moveData) {
|
|||||||
try {
|
try {
|
||||||
const layer = findLayerRecursively(layers.value, layerId).layer;
|
const layer = findLayerRecursively(layers.value, layerId).layer;
|
||||||
const toLayer = findLayerRecursively(layers.value, toParentId).layer;
|
const toLayer = findLayerRecursively(layers.value, toParentId).layer;
|
||||||
if(layer?.specialType || toLayer?.specialType) {
|
if(layer?.isPrintTrims || layer?.isPrintTrimsGroup || toLayer?.isPrintTrims || toLayer?.isPrintTrimsGroup) {
|
||||||
console.warn("当前图层不可移动到外部");
|
console.warn("当前图层不可移动到外部");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,34 @@
|
|||||||
<template>
|
<template>
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<div class="part-selector-toolbar" v-if="visible" :class="{active:!closePanel}">
|
<div
|
||||||
<div class="btn" @click="setClosePanel"><i class="fi fi-br-angle-left"></i></div>
|
class="part-selector-toolbar"
|
||||||
|
v-if="visible"
|
||||||
|
:class="{ active: !closePanel }"
|
||||||
|
>
|
||||||
|
<div class="btn" @click="setClosePanel">
|
||||||
|
<i class="fi fi-br-angle-left"></i>
|
||||||
|
</div>
|
||||||
<!-- 顶部选区类型工具栏 -->
|
<!-- 顶部选区类型工具栏 -->
|
||||||
<div class="toolbar-section">
|
<div class="toolbar-section">
|
||||||
<div class="toolbar-header">
|
<div class="toolbar-header">
|
||||||
<div class="header-title">{{ t("Canvas.GarmentPartSelector") }}</div>
|
<div class="header-title">
|
||||||
|
{{ t("Canvas.GarmentPartSelector") }}
|
||||||
|
</div>
|
||||||
<!-- 移除关闭按钮,完全通过工具切换控制显示隐藏 -->
|
<!-- 移除关闭按钮,完全通过工具切换控制显示隐藏 -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tool-types">
|
<div class="tool-types">
|
||||||
<div
|
<div
|
||||||
|
v-for="item in toolList"
|
||||||
|
:key="item.type"
|
||||||
:class="[
|
:class="[
|
||||||
'tool-btn',
|
'tool-btn',
|
||||||
{ active: selectionType === OperationType.LASSO },
|
{ active: selectionType === item.type },
|
||||||
]"
|
]"
|
||||||
@click="setSelectionType(OperationType.LASSO)"
|
@click="setSelectionType(item.type)"
|
||||||
>
|
>
|
||||||
<svg-icon name="CFree" size="20" />
|
<svg-icon :name="item.icon" :size="item.size" />
|
||||||
<span>{{ $t("Canvas.freehandSketching") }}</span>
|
<span>{{ item.label }}</span>
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
:class="[
|
|
||||||
'tool-btn',
|
|
||||||
{ active: selectionType === OperationType.LASSO_RECTANGLE },
|
|
||||||
]"
|
|
||||||
@click="setSelectionType(OperationType.LASSO_RECTANGLE)"
|
|
||||||
>
|
|
||||||
<svg-icon name="CRectangle" size="26" />
|
|
||||||
<span>{{ $t("Canvas.rectangle") }}</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
:class="[
|
|
||||||
'tool-btn',
|
|
||||||
{ active: selectionType === OperationType.LASSO_ELLIPSE },
|
|
||||||
]"
|
|
||||||
@click="setSelectionType(OperationType.LASSO_ELLIPSE)"
|
|
||||||
>
|
|
||||||
<svg-icon name="CEllipse" size="24" />
|
|
||||||
<span>{{ $t("Canvas.ellipse") }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -49,131 +39,21 @@
|
|||||||
<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="16" />
|
<svg-icon name="CPaste" size="16" />
|
||||||
<span class="btn-text">{{ $t("Canvas.creation") }}</span>
|
<span class="btn-text">{{
|
||||||
|
$t("Canvas.creation")
|
||||||
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="action-btn" @click="cutSelectionToNewLayer">
|
<div class="action-btn" @click="cutSelectionToNewLayer">
|
||||||
<svg-icon name="CCut" size="26" />
|
<svg-icon name="CCut" size="26" />
|
||||||
<span class="btn-text">{{ $t("Canvas.CreateAndCopy") }}</span>
|
<span class="btn-text">{{
|
||||||
|
$t("Canvas.CreateAndCopy")
|
||||||
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="action-btn" @click="clearSelectionContent">
|
<div class="action-btn" @click="clearSelectionContent">
|
||||||
<svg-icon name="CClear" size="18" />
|
<svg-icon name="CClear" size="18" />
|
||||||
<span class="btn-text">{{ $t("Canvas.TheClearlySelectedContent") }}</span>
|
<span class="btn-text">{{
|
||||||
</div>
|
$t("Canvas.TheClearlySelectedContent")
|
||||||
<!-- <button
|
}}</span>
|
||||||
class="action-btn"
|
|
||||||
@click="addSelection"
|
|
||||||
:disabled="!hasSelection"
|
|
||||||
title="添加"
|
|
||||||
>
|
|
||||||
<svg-icon name="plus" />
|
|
||||||
<span class="btn-text">{{ $t("添加") }}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="action-btn"
|
|
||||||
@click="removeSelection"
|
|
||||||
:disabled="!hasSelection"
|
|
||||||
title="移除"
|
|
||||||
>
|
|
||||||
<svg-icon name="minus" />
|
|
||||||
<span class="btn-text">{{ $t("移除") }}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="action-btn"
|
|
||||||
@click="invertSelection"
|
|
||||||
:disabled="!hasSelection"
|
|
||||||
title="反转"
|
|
||||||
>
|
|
||||||
<svg-icon name="flip-horizontal" />
|
|
||||||
<span class="btn-text">{{ $t("反转") }}</span>
|
|
||||||
</button> -->
|
|
||||||
<!-- <button
|
|
||||||
class="action-btn"
|
|
||||||
@click="copySelectionToNewLayer"
|
|
||||||
:disabled="!hasSelection"
|
|
||||||
title="拷贝并粘贴"
|
|
||||||
>
|
|
||||||
<svg-icon name="copy" />
|
|
||||||
<span class="btn-text">{{ $t("拷贝并粘贴") }}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="action-btn"
|
|
||||||
@click="openFeatherDialog"
|
|
||||||
:disabled="!hasSelection"
|
|
||||||
title="羽化"
|
|
||||||
>
|
|
||||||
<svg-icon name="feather" />
|
|
||||||
<span class="btn-text">{{ $t("羽化") }}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="action-btn"
|
|
||||||
@click="fillSelection"
|
|
||||||
:disabled="!hasSelection"
|
|
||||||
title="颜色填充"
|
|
||||||
>
|
|
||||||
<svg-icon name="fill-color" />
|
|
||||||
<span class="btn-text">{{ $t("颜色填充") }}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="action-btn"
|
|
||||||
@click="clearSelection"
|
|
||||||
:disabled="!hasSelection"
|
|
||||||
title="清除"
|
|
||||||
>
|
|
||||||
<svg-icon name="trash" />
|
|
||||||
<span class="btn-text">{{ $t("清除") }}</span>
|
|
||||||
</button> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 羽化设置弹窗 -->
|
|
||||||
<div v-if="showFeatherDialog" class="dialog-overlay">
|
|
||||||
<div class="dialog-container">
|
|
||||||
<div class="dialog-header">
|
|
||||||
<h3>{{ $t("羽化") }}</h3>
|
|
||||||
<button class="close-dialog-btn" @click="cancelFeather">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="dialog-content">
|
|
||||||
<div class="feather-control">
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
min="0"
|
|
||||||
max="50"
|
|
||||||
v-model.number="featherAmount"
|
|
||||||
class="slider-control"
|
|
||||||
/>
|
|
||||||
<div class="feather-value">{{ featherAmount }}px</div>
|
|
||||||
</div>
|
|
||||||
<div class="dialog-buttons">
|
|
||||||
<button class="cancel-btn" @click="cancelFeather">
|
|
||||||
{{ $t("Canvas.close") }}
|
|
||||||
</button>
|
|
||||||
<button class="confirm-btn" @click="applyFeather">
|
|
||||||
{{ $t("Canvas.confirmEdit") }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 颜色选择器 -->
|
|
||||||
<div v-if="showColorPicker" class="dialog-overlay">
|
|
||||||
<div class="dialog-container">
|
|
||||||
<div class="dialog-header">
|
|
||||||
<h3>{{ $t("Canvas.SelectFillColor") }}</h3>
|
|
||||||
<button class="close-dialog-btn" @click="cancelColorPicker">
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="dialog-content">
|
|
||||||
<input type="color" v-model="fillColor" class="color-picker" />
|
|
||||||
<div class="dialog-buttons">
|
|
||||||
<button class="cancel-btn" @click="cancelColorPicker">
|
|
||||||
{{ $t("Canvas.close") }}
|
|
||||||
</button>
|
|
||||||
<button class="confirm-btn" @click="confirmColorPicker">
|
|
||||||
{{ $t("Canvas.confirmEdit") }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -182,28 +62,26 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, watch } from "vue";
|
import { ref, onMounted, watch } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import {
|
import {
|
||||||
CreateSelectionCommand,
|
CreateSelectionCommand,
|
||||||
InvertSelectionCommand,
|
InvertSelectionCommand,
|
||||||
FeatherSelectionCommand,
|
FeatherSelectionCommand,
|
||||||
FillSelectionCommand,
|
FillSelectionCommand,
|
||||||
// CopySelectionToNewLayerCommand,
|
} from "../commands/SelectionCommands";
|
||||||
// ClearSelectionContentCommand,
|
import { ToolCommand } from "../commands/ToolCommands";
|
||||||
} from "../commands/SelectionCommands";
|
import {
|
||||||
import { ToolCommand } from "../commands/ToolCommands";
|
|
||||||
import {
|
|
||||||
LassoCutoutCommand,
|
LassoCutoutCommand,
|
||||||
ClearSelectionCommand,
|
ClearSelectionCommand,
|
||||||
// CutSelectionToNewLayerCommand,
|
// CutSelectionToNewLayerCommand,
|
||||||
} from "../commands/LassoCutoutCommand";
|
} from "../commands/LassoCutoutCommand";
|
||||||
|
|
||||||
import { OperationType } from "../utils/layerHelper";
|
import { OperationType } from "../utils/layerHelper";
|
||||||
import { ClearSelectionContentCommand } from "../commands/ClearSelectionContentCommand";
|
import { ClearSelectionContentCommand } from "../commands/ClearSelectionContentCommand";
|
||||||
import { CutSelectionToNewLayerCommand } from "../commands/CutSelectionToNewLayerCommand";
|
import { CutSelectionToNewLayerCommand } from "../commands/CutSelectionToNewLayerCommand";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
canvas: {
|
canvas: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
@@ -229,41 +107,51 @@ const props = defineProps({
|
|||||||
required: false,
|
required: false,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const visible = ref(false);
|
const visible = ref(false);
|
||||||
const selectionType = ref("rectangle");
|
const selectionType = ref("rectangle");
|
||||||
const featherAmount = ref(0);
|
//打开隐藏操作面板
|
||||||
const fillColor = ref("#000000");
|
const closePanel = ref(false);
|
||||||
const hasSelection = ref(false);
|
const setClosePanel = () => {
|
||||||
const showFeatherDialog = ref(false);
|
closePanel.value = !closePanel.value;
|
||||||
const showColorPicker = ref(false);
|
|
||||||
//打开隐藏操作面板
|
|
||||||
const closePanel = ref(false)
|
|
||||||
const setClosePanel = ()=>{
|
|
||||||
closePanel.value = !closePanel.value
|
|
||||||
}
|
|
||||||
|
|
||||||
// 国际化
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
// 为选区管理器添加监听,以便在选区变化时更新状态
|
|
||||||
if (props.selectionManager) {
|
|
||||||
// 在选区管理器中添加选区变化的监听
|
|
||||||
checkSelectionStatus();
|
|
||||||
|
|
||||||
// 设置选区状态变化的回调
|
|
||||||
// eslint-disable-next-line vue/no-mutating-props
|
|
||||||
props.selectionManager.onSelectionChanged = () => {
|
|
||||||
checkSelectionStatus();
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 监听 activeTool 变化
|
const toolList = [
|
||||||
watch(
|
{
|
||||||
|
type: OperationType.PART,
|
||||||
|
label: "Point Selection",
|
||||||
|
icon: "CPoint",
|
||||||
|
size: "20",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: OperationType.PART_RECTANGLE,
|
||||||
|
label: "Marquee Selection",
|
||||||
|
icon: "CRectangle",
|
||||||
|
size: "26",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: OperationType.PART_BRUSH,
|
||||||
|
label: "Brush Selection",
|
||||||
|
icon: "CBrush",
|
||||||
|
size: "24",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: OperationType.PART_ERASER,
|
||||||
|
label: "Erase",
|
||||||
|
icon: "CEraser",
|
||||||
|
size: "24",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 国际化
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
onMounted(() => {});
|
||||||
|
|
||||||
|
// 监听 activeTool 变化
|
||||||
|
watch(
|
||||||
() => props.activeTool,
|
() => props.activeTool,
|
||||||
(newTool) => {
|
(newTool) => {
|
||||||
// 当工具为LASSO或AREA类型时显示选区面板
|
// 当工具为LASSO或AREA类型时显示选区面板
|
||||||
@@ -271,7 +159,7 @@ watch(
|
|||||||
OperationType.PART,
|
OperationType.PART,
|
||||||
OperationType.PART_RECTANGLE,
|
OperationType.PART_RECTANGLE,
|
||||||
OperationType.PART_BRUSH,
|
OperationType.PART_BRUSH,
|
||||||
OperationType.PART_ERASER
|
OperationType.PART_ERASER,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (selectionTools.includes(newTool)) {
|
if (selectionTools.includes(newTool)) {
|
||||||
@@ -289,28 +177,27 @@ watch(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 显示面板
|
* 显示面板
|
||||||
*/
|
*/
|
||||||
function show() {
|
function show() {
|
||||||
visible.value = true;
|
visible.value = true;
|
||||||
closePanel.value = true
|
closePanel.value = true;
|
||||||
checkSelectionStatus();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关闭面板
|
* 关闭面板
|
||||||
*/
|
*/
|
||||||
function close() {
|
function close() {
|
||||||
visible.value = false;
|
visible.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置选区类型
|
* 设置选区类型
|
||||||
*/
|
*/
|
||||||
function setSelectionType(type) {
|
function setSelectionType(type) {
|
||||||
selectionType.value = type;
|
selectionType.value = type;
|
||||||
|
|
||||||
// 通过 ToolManager 切换工具,这会自动通知 SelectionManager
|
// 通过 ToolManager 切换工具,这会自动通知 SelectionManager
|
||||||
@@ -323,193 +210,16 @@ function setSelectionType(type) {
|
|||||||
props.selectionManager.setSelectionType(type);
|
props.selectionManager.setSelectionType(type);
|
||||||
props.selectionManager.setupSelectionEvents();
|
props.selectionManager.setupSelectionEvents();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查选区状态
|
|
||||||
*/
|
|
||||||
function checkSelectionStatus() {
|
|
||||||
hasSelection.value =
|
|
||||||
props.selectionManager &&
|
|
||||||
props.selectionManager.getSelectionObject() !== null;
|
|
||||||
|
|
||||||
// 同步羽化值
|
|
||||||
if (hasSelection.value) {
|
|
||||||
featherAmount.value = props.selectionManager.getFeatherAmount();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加选区
|
|
||||||
*/
|
|
||||||
function addSelection() {
|
|
||||||
// TODO: 实现添加选区功能
|
|
||||||
console.log("添加选区功能尚未实现");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移除选区
|
|
||||||
*/
|
|
||||||
function removeSelection() {
|
|
||||||
// TODO: 实现移除选区功能
|
|
||||||
console.log("移除选区功能尚未实现");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 反转选区
|
|
||||||
*/
|
|
||||||
function invertSelection() {
|
|
||||||
if (!hasSelection.value) return;
|
|
||||||
|
|
||||||
props.commandManager.execute(
|
|
||||||
new InvertSelectionCommand({
|
|
||||||
canvas: props.canvas,
|
|
||||||
selectionManager: props.selectionManager,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
checkSelectionStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除选区
|
|
||||||
*/
|
|
||||||
function clearSelection() {
|
|
||||||
if (!hasSelection.value) return;
|
|
||||||
|
|
||||||
props.commandManager.execute(
|
|
||||||
new ClearSelectionCommand({
|
|
||||||
selectionManager: props.selectionManager,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
checkSelectionStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 应用羽化效果
|
|
||||||
*/
|
|
||||||
function applyFeather() {
|
|
||||||
if (!hasSelection.value) return;
|
|
||||||
|
|
||||||
props.commandManager.execute(
|
|
||||||
new FeatherSelectionCommand({
|
|
||||||
selectionManager: props.selectionManager,
|
|
||||||
featherAmount: featherAmount.value,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 填充选区
|
|
||||||
*/
|
|
||||||
function fillSelection() {
|
|
||||||
if (!hasSelection.value) return;
|
|
||||||
showColorPicker.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 套索抠图到新图层
|
|
||||||
*/
|
|
||||||
function copySelectionToNewLayer() {
|
|
||||||
if (!hasSelection.value) return;
|
|
||||||
|
|
||||||
props.commandManager.execute(
|
|
||||||
new LassoCutoutCommand({
|
|
||||||
canvas: props.canvas,
|
|
||||||
layerManager: props.layerManager,
|
|
||||||
selectionManager: props.selectionManager,
|
|
||||||
toolManager: props.toolManager,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
checkSelectionStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 剪切选区到新图层
|
|
||||||
*/
|
|
||||||
function cutSelectionToNewLayer() {
|
|
||||||
if (!hasSelection.value) return;
|
|
||||||
props.commandManager.execute(
|
|
||||||
new CutSelectionToNewLayerCommand({
|
|
||||||
canvas: props.canvas,
|
|
||||||
layerManager: props.layerManager,
|
|
||||||
selectionManager: props.selectionManager,
|
|
||||||
toolManager: props.toolManager,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
checkSelectionStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除选区内容
|
|
||||||
*/
|
|
||||||
function clearSelectionContent() {
|
|
||||||
if (!hasSelection.value) return;
|
|
||||||
|
|
||||||
props.commandManager.execute(
|
|
||||||
new ClearSelectionContentCommand({
|
|
||||||
canvas: props.canvas,
|
|
||||||
layerManager: props.layerManager,
|
|
||||||
selectionManager: props.selectionManager,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
checkSelectionStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 打开羽化设置弹窗
|
|
||||||
*/
|
|
||||||
function openFeatherDialog() {
|
|
||||||
showFeatherDialog.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 取消羽化设置
|
|
||||||
*/
|
|
||||||
function cancelFeather() {
|
|
||||||
showFeatherDialog.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 确认羽化设置
|
|
||||||
*/
|
|
||||||
function confirmFeather() {
|
|
||||||
applyFeather();
|
|
||||||
showFeatherDialog.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 取消颜色选择
|
|
||||||
*/
|
|
||||||
function cancelColorPicker() {
|
|
||||||
showColorPicker.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 确认颜色选择
|
|
||||||
*/
|
|
||||||
function confirmColorPicker() {
|
|
||||||
if (!hasSelection.value) return;
|
|
||||||
|
|
||||||
props.commandManager.execute(
|
|
||||||
new FillSelectionCommand({
|
|
||||||
canvas: props.canvas,
|
|
||||||
layerManager: props.layerManager,
|
|
||||||
selectionManager: props.selectionManager,
|
|
||||||
color: fillColor.value,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
checkSelectionStatus();
|
|
||||||
showColorPicker.value = false;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.part-selector-toolbar {
|
.part-selector-toolbar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 22px;
|
bottom: 22px;
|
||||||
left: 20px;
|
left: 20px;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
max-width: min(90vw, 640px);
|
max-width: min(90vw, 700px);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
background-color: rgba(255, 255, 255, 0.95);
|
background-color: rgba(255, 255, 255, 0.95);
|
||||||
backdrop-filter: blur(15px);
|
backdrop-filter: blur(15px);
|
||||||
@@ -520,15 +230,15 @@ function confirmColorPicker() {
|
|||||||
color: #333;
|
color: #333;
|
||||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
&.active{
|
&.active {
|
||||||
transform: translateY(100%);
|
transform: translateY(100%);
|
||||||
> .btn{
|
> .btn {
|
||||||
> i{
|
> i {
|
||||||
transform: rotate(90deg);
|
transform: rotate(90deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
> .btn{
|
> .btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -537,15 +247,15 @@ function confirmColorPicker() {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
|
|
||||||
> i{
|
> i {
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
transform: rotate(270deg);
|
transform: rotate(270deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 平板和手机适配 */
|
/* 平板和手机适配 */
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
.part-selector-toolbar {
|
.part-selector-toolbar {
|
||||||
bottom: 15px;
|
bottom: 15px;
|
||||||
left: 15px;
|
left: 15px;
|
||||||
@@ -553,38 +263,38 @@ function confirmColorPicker() {
|
|||||||
max-width: calc(100vw - 30px);
|
max-width: calc(100vw - 30px);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 480px) {
|
@media screen and (max-width: 480px) {
|
||||||
.part-selector-toolbar {
|
.part-selector-toolbar {
|
||||||
bottom: 10px;
|
bottom: 10px;
|
||||||
left: 10px;
|
left: 10px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
max-width: calc(100vw - 20px);
|
max-width: calc(100vw - 20px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.part-selector-toolbar.is-active {
|
.part-selector-toolbar.is-active {
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar-header {
|
.toolbar-header {
|
||||||
// display: flex;
|
// display: flex;
|
||||||
// justify-content: center;
|
// justify-content: center;
|
||||||
// align-items: center;
|
// align-items: center;
|
||||||
padding: 8px 0;
|
padding: 8px 0;
|
||||||
// border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
// border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
background-color: rgba(255, 255, 255, 0.8);
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
border-radius: 8px 8px 0 0;
|
border-radius: 8px 8px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-title {
|
.header-title {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #333;
|
color: #333;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
.header-btn {
|
.header-btn {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
color: #333;
|
color: #333;
|
||||||
@@ -594,28 +304,28 @@ function confirmColorPicker() {
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
transition: background-color 0.2s ease;
|
transition: background-color 0.2s ease;
|
||||||
min-width: 32px;
|
min-width: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-btn:hover {
|
.header-btn:hover {
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-btn {
|
.close-btn {
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar-section {
|
.toolbar-section {
|
||||||
padding: 0 3rem 1.2rem;
|
padding: 0 3rem 1.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool-types {
|
.tool-types {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(4, 1fr);
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool-btn {
|
.tool-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -627,51 +337,51 @@ function confirmColorPicker() {
|
|||||||
color: #333;
|
color: #333;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool-btn span {
|
.tool-btn span {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool-btn svg {
|
.tool-btn svg {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool-btn:hover {
|
.tool-btn:hover {
|
||||||
background-color: rgba(0, 0, 0, 0.08);
|
background-color: rgba(0, 0, 0, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool-btn.active {
|
.tool-btn.active {
|
||||||
background-color: #007aff;
|
background-color: #007aff;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool-actions {
|
.tool-actions {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 平板适配 - 每行4个按钮 */
|
/* 平板适配 - 每行4个按钮 */
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
.tool-actions {
|
.tool-actions {
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
gap: 8px 6px;
|
gap: 8px 6px;
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 手机适配 - 每行3个按钮 */
|
/* 手机适配 - 每行3个按钮 */
|
||||||
@media screen and (max-width: 480px) {
|
@media screen and (max-width: 480px) {
|
||||||
.tool-actions {
|
.tool-actions {
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
gap: 6px 4px;
|
gap: 6px 4px;
|
||||||
@@ -683,9 +393,9 @@ function confirmColorPicker() {
|
|||||||
padding: 2px 4px;
|
padding: 2px 4px;
|
||||||
min-width: 28px;
|
min-width: 28px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
// flex-direction: column;
|
// flex-direction: column;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@@ -700,156 +410,27 @@ function confirmColorPicker() {
|
|||||||
.c-svg {
|
.c-svg {
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn svg {
|
.action-btn svg {
|
||||||
width: 22px;
|
width: 22px;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-text {
|
.btn-text {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn:hover {
|
.action-btn:hover {
|
||||||
color: #007aff;
|
color: #007aff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn:disabled {
|
.action-btn:disabled {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 对话框样式 */
|
|
||||||
.dialog-overlay {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
|
||||||
backdrop-filter: blur(5px);
|
|
||||||
-webkit-backdrop-filter: blur(5px);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 2000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-container {
|
|
||||||
background-color: #ffffff;
|
|
||||||
border-radius: 12px;
|
|
||||||
width: 280px;
|
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
|
||||||
overflow: hidden;
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 12px 0;
|
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-header h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 15px;
|
|
||||||
color: #333;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-dialog-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: #666;
|
|
||||||
font-size: 18px;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-content {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feather-control {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider-control {
|
|
||||||
flex: 1;
|
|
||||||
height: 4px;
|
|
||||||
background: rgba(0, 0, 0, 0.1);
|
|
||||||
border-radius: 2px;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
appearance: none;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider-control::-webkit-slider-thumb {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: #007aff;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feather-value {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #333;
|
|
||||||
min-width: 40px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-buttons {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancel-btn,
|
|
||||||
.confirm-btn {
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 14px;
|
|
||||||
cursor: pointer;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancel-btn {
|
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-btn {
|
|
||||||
background-color: #007aff;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-picker {
|
|
||||||
width: 100%;
|
|
||||||
height: 40px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-enter-active,
|
|
||||||
.fade-leave-active {
|
|
||||||
transition: opacity 0.3s, transform 0.3s;
|
|
||||||
}
|
|
||||||
.fade-enter-from,
|
|
||||||
.fade-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(30px);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -178,11 +178,7 @@
|
|||||||
import { ref, onMounted, watch, onUnmounted, reactive } from "vue";
|
import { ref, onMounted, watch, onUnmounted, reactive } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
import {
|
import { OperationType, SpecialLayerId } from "../../utils/layerHelper";
|
||||||
OperationType,
|
|
||||||
SpecialLayerId,
|
|
||||||
SpecialType,
|
|
||||||
} from "../../utils/layerHelper";
|
|
||||||
import { loadImageUrlToLayer } from "../../utils/imageHelper";
|
import { loadImageUrlToLayer } from "../../utils/imageHelper";
|
||||||
import {
|
import {
|
||||||
calculateRotatedTopLeftDeg,
|
calculateRotatedTopLeftDeg,
|
||||||
@@ -283,9 +279,6 @@
|
|||||||
const getActiveObject = (e) => {
|
const getActiveObject = (e) => {
|
||||||
console.log("==========切换激活对象", e, activeObjects);
|
console.log("==========切换激活对象", e, activeObjects);
|
||||||
activeObjects.value = [...e.selected];
|
activeObjects.value = [...e.selected];
|
||||||
// .filter((v) =>
|
|
||||||
// v.specialType ? v.specialType === SpecialType.REPEAT_O : true
|
|
||||||
// );// 过滤出印花对象
|
|
||||||
activeObjects.value.forEach((v) => {
|
activeObjects.value.forEach((v) => {
|
||||||
v.layer = props.layerManager.getLayerById(v.layerId);
|
v.layer = props.layerManager.getLayerById(v.layerId);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -166,19 +166,19 @@ const normalToolsList = ref([
|
|||||||
icon: { name: "CFont", size: "20" },
|
icon: { name: "CFont", size: "20" },
|
||||||
class: "text-btn",
|
class: "text-btn",
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
id: OperationType.PART,
|
// id: OperationType.PART,
|
||||||
title: t("Canvas.GarmentPartSelector"),
|
// title: t("Canvas.GarmentPartSelector"),
|
||||||
action: () => selectTool(OperationType.PART),
|
// action: () => selectTool(OperationType.PART),
|
||||||
icon: { name: "CPart", size: "28" },
|
// icon: { name: "CPart", size: "28" },
|
||||||
class: "part-btn",
|
// class: "part-btn",
|
||||||
activeList: [
|
// activeList: [
|
||||||
OperationType.PART,
|
// OperationType.PART,
|
||||||
OperationType.PART_RECTANGLE,
|
// OperationType.PART_RECTANGLE,
|
||||||
OperationType.PART_BRUSH,
|
// OperationType.PART_BRUSH,
|
||||||
OperationType.PART_ERASER,
|
// OperationType.PART_ERASER,
|
||||||
],
|
// ],
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
id: "help",
|
id: "help",
|
||||||
title: t("Canvas.help"),
|
title: t("Canvas.help"),
|
||||||
|
|||||||
@@ -1000,6 +1000,8 @@ defineExpose({
|
|||||||
isContainBg = false, // 是否包含背景图层
|
isContainBg = false, // 是否包含背景图层
|
||||||
isContainFixed = false, // 是否包含固定图层
|
isContainFixed = false, // 是否包含固定图层
|
||||||
isContainFixedOther = false, // 是否包含其他固定图层
|
isContainFixedOther = false, // 是否包含其他固定图层
|
||||||
|
isPrintTrimsNoRepeat = true, // 是否包含印花图层的不平铺
|
||||||
|
isPrintTrimsRepeat = true, // 是否包含印花图层的平铺
|
||||||
isCropByBg = false, // 是否使用背景大小裁剪 // 如果为true,则导出时裁剪到背景图层大小
|
isCropByBg = false, // 是否使用背景大小裁剪 // 如果为true,则导出时裁剪到背景图层大小
|
||||||
layerId = "", // 导出具体图层ID
|
layerId = "", // 导出具体图层ID
|
||||||
layerIdArray = [], // 导出多个图层ID数组
|
layerIdArray = [], // 导出多个图层ID数组
|
||||||
@@ -1010,6 +1012,8 @@ defineExpose({
|
|||||||
isContainBg,
|
isContainBg,
|
||||||
isContainFixed,
|
isContainFixed,
|
||||||
isContainFixedOther,
|
isContainFixedOther,
|
||||||
|
isPrintTrimsNoRepeat,
|
||||||
|
isPrintTrimsRepeat,
|
||||||
isCropByBg,
|
isCropByBg,
|
||||||
layerId,
|
layerId,
|
||||||
layerIdArray,
|
layerIdArray,
|
||||||
@@ -1250,7 +1254,7 @@ defineExpose({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 部件选取面板 -->
|
<!-- 部件选取面板 -->
|
||||||
<PartSelectorPanel
|
<!-- <PartSelectorPanel
|
||||||
v-if="canvasManagerLoaded && !enabledRedGreenMode"
|
v-if="canvasManagerLoaded && !enabledRedGreenMode"
|
||||||
:canvas="canvasManager && canvasManager.canvas"
|
:canvas="canvasManager && canvasManager.canvas"
|
||||||
:commandManager="commandManager"
|
:commandManager="commandManager"
|
||||||
@@ -1259,7 +1263,7 @@ defineExpose({
|
|||||||
:canvasManager="canvasManager"
|
:canvasManager="canvasManager"
|
||||||
:toolManager="toolManager"
|
:toolManager="toolManager"
|
||||||
:activeTool="activeTool"
|
:activeTool="activeTool"
|
||||||
/>
|
/> -->
|
||||||
|
|
||||||
<!-- 文本编辑面板 -->
|
<!-- 文本编辑面板 -->
|
||||||
<TextEditorPanel
|
<TextEditorPanel
|
||||||
@@ -1414,6 +1418,7 @@ defineExpose({
|
|||||||
/* background-color: #f8f8f8; */
|
/* background-color: #f8f8f8; */
|
||||||
:deep(.canvas-container) {
|
:deep(.canvas-container) {
|
||||||
position: absolute !important;
|
position: absolute !important;
|
||||||
|
filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1426,33 +1431,31 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.background-grid {
|
.background-grid {
|
||||||
--offsetX: 0px;
|
--offsetX: 50%;
|
||||||
--offsetY: 0px;
|
--offsetY: 50%;
|
||||||
--size: 8px;
|
--size: 10px;
|
||||||
--color: #dedcdc;
|
--color: rgba(229, 229,229,0.5);
|
||||||
background-image: -webkit-linear-gradient(
|
background-image: -webkit-linear-gradient(
|
||||||
45deg,
|
90deg,
|
||||||
var(--color) 25%,
|
var(--color) 1px,
|
||||||
transparent 0,
|
transparent 0,
|
||||||
transparent 75%,
|
|
||||||
var(--color) 0
|
|
||||||
),
|
),
|
||||||
-webkit-linear-gradient(45deg, var(--color) 25%, transparent 0, transparent
|
-webkit-linear-gradient(
|
||||||
75%, var(--color) 0);
|
0,
|
||||||
background-image: linear-gradient(
|
var(--color) 1px,
|
||||||
45deg,
|
transparent 0,
|
||||||
var(--color) 25%,
|
);
|
||||||
|
background-image:linear-gradient(
|
||||||
|
90deg,
|
||||||
|
var(--color) 1px,
|
||||||
transparent 0,
|
transparent 0,
|
||||||
transparent 75%,
|
|
||||||
var(--color) 0
|
|
||||||
),
|
),
|
||||||
linear-gradient(
|
linear-gradient(
|
||||||
45deg,
|
0,
|
||||||
var(--color) 25%,
|
var(--color) 1px,
|
||||||
transparent 0,
|
transparent 0,
|
||||||
transparent 75%,
|
|
||||||
var(--color) 0
|
|
||||||
);
|
);
|
||||||
|
background-color: #fafafa;
|
||||||
background-position: var(--offsetX) var(--offsetY),
|
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);
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
createLayer,
|
createLayer,
|
||||||
LayerType,
|
LayerType,
|
||||||
SpecialLayerId,
|
SpecialLayerId,
|
||||||
SpecialType,
|
|
||||||
BlendMode,
|
BlendMode,
|
||||||
} from "../utils/layerHelper";
|
} from "../utils/layerHelper";
|
||||||
import { ObjectMoveCommand } from "../commands/ObjectCommands";
|
import { ObjectMoveCommand } from "../commands/ObjectCommands";
|
||||||
@@ -45,7 +44,7 @@ import {
|
|||||||
} from "../utils/layerUtils";
|
} from "../utils/layerUtils";
|
||||||
import { imageModeHandler } from "../utils/imageHelper";
|
import { imageModeHandler } from "../utils/imageHelper";
|
||||||
import { getObjectAlphaToCanvas } from "../utils/objectHelper";
|
import { getObjectAlphaToCanvas } from "../utils/objectHelper";
|
||||||
import { AddLayerCommand, RemoveLayerCommand } from "../commands/LayerCommands";
|
import { AddLayerCommand, RemoveLayerCommand, ToggleChildLayerVisibilityCommand } from "../commands/LayerCommands";
|
||||||
import { fa, id } from "element-plus/es/locales.mjs";
|
import { fa, id } from "element-plus/es/locales.mjs";
|
||||||
import i18n from "@/lang/index.ts";
|
import i18n from "@/lang/index.ts";
|
||||||
const {t} = i18n.global;
|
const {t} = i18n.global;
|
||||||
@@ -717,6 +716,8 @@ export class CanvasManager {
|
|||||||
originX: backgroundLayerObject.originX || "left",
|
originX: backgroundLayerObject.originX || "left",
|
||||||
originY: backgroundLayerObject.originY || "top",
|
originY: backgroundLayerObject.originY || "top",
|
||||||
absolutePositioned: true,
|
absolutePositioned: true,
|
||||||
|
rx: 15,
|
||||||
|
ry: 15,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
getBackgroundLayer() {
|
getBackgroundLayer() {
|
||||||
@@ -827,7 +828,7 @@ export class CanvasManager {
|
|||||||
updateMaskPosition(backgroundLayerObject) {
|
updateMaskPosition(backgroundLayerObject) {
|
||||||
if (!backgroundLayerObject || !this.maskLayer || !this.canvas.clipPath)
|
if (!backgroundLayerObject || !this.maskLayer || !this.canvas.clipPath)
|
||||||
return;
|
return;
|
||||||
|
console.log("backgroundLayerObject");
|
||||||
const left = backgroundLayerObject.left;
|
const left = backgroundLayerObject.left;
|
||||||
const top = backgroundLayerObject.top;
|
const top = backgroundLayerObject.top;
|
||||||
|
|
||||||
@@ -922,6 +923,8 @@ export class CanvasManager {
|
|||||||
* @param {Boolean} options.isContainBg 是否包含背景图层
|
* @param {Boolean} options.isContainBg 是否包含背景图层
|
||||||
* @param {Boolean} options.isContainFixed 是否包含固定图层
|
* @param {Boolean} options.isContainFixed 是否包含固定图层
|
||||||
* @param {Boolean} options.isContainFixedOther 是否包含其他固定图层
|
* @param {Boolean} options.isContainFixedOther 是否包含其他固定图层
|
||||||
|
* @param {Boolean} options.isPrintTrimsNoRepeat 是否包含印花图层的不平铺
|
||||||
|
* @param {Boolean} options.isPrintTrimsRepeat 是否包含印花图层的平铺
|
||||||
* @param {String} options.layerId 导出具体图层ID
|
* @param {String} options.layerId 导出具体图层ID
|
||||||
* @param {Array} options.layerIdArray 导出多个图层ID数组
|
* @param {Array} options.layerIdArray 导出多个图层ID数组
|
||||||
* @param {String} options.expPicType 导出图片类型 (png/jpg/svg)
|
* @param {String} options.expPicType 导出图片类型 (png/jpg/svg)
|
||||||
@@ -942,6 +945,8 @@ export class CanvasManager {
|
|||||||
// this.canvas.renderAll(); // 重新渲染画布
|
// this.canvas.renderAll(); // 重新渲染画布
|
||||||
// 自动设置红绿图模式相关参数
|
// 自动设置红绿图模式相关参数
|
||||||
const enhancedOptions = {
|
const enhancedOptions = {
|
||||||
|
isPrintTrimsNoRepeat: true,
|
||||||
|
isPrintTrimsRepeat: true,
|
||||||
...options,
|
...options,
|
||||||
// 如果没有明确指定,则根据当前模式自动设置
|
// 如果没有明确指定,则根据当前模式自动设置
|
||||||
restoreOpacityInRedGreen:
|
restoreOpacityInRedGreen:
|
||||||
@@ -972,7 +977,46 @@ export class CanvasManager {
|
|||||||
console.log("红绿图模式导出图层:", normalLayerIds);
|
console.log("红绿图模式导出图层:", normalLayerIds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return await this.exportManager.exportImage(enhancedOptions);
|
|
||||||
|
// 处理特殊图层的显示状态
|
||||||
|
const ptlids = [];
|
||||||
|
if(!enhancedOptions.isPrintTrimsNoRepeat || !enhancedOptions.isPrintTrimsRepeat){
|
||||||
|
let layers = this.layers?.value?.find((layer) => layer.isPrintTrimsGroup)?.children || [];
|
||||||
|
for(let layer of layers){
|
||||||
|
if(!layer.visible) continue;
|
||||||
|
let repeat = layer.fabricObjects?.[0]?.fill?.repeat || "no-repeat";
|
||||||
|
if(typeof repeat !== "string") repeat = "no-repeat";
|
||||||
|
if(repeat === "no-repeat"){
|
||||||
|
if(enhancedOptions.isPrintTrimsNoRepeat) continue;
|
||||||
|
}else{
|
||||||
|
if(enhancedOptions.isPrintTrimsRepeat) continue;
|
||||||
|
}
|
||||||
|
ptlids.push(layer.id);
|
||||||
|
const command = new ToggleChildLayerVisibilityCommand({
|
||||||
|
canvas: this.canvas,
|
||||||
|
layers: this.layers,
|
||||||
|
layerId: layer.id,
|
||||||
|
layerManager: this.layerManager,
|
||||||
|
});
|
||||||
|
await command.execute(false);
|
||||||
|
}
|
||||||
|
await this.changeCanvas();
|
||||||
|
}
|
||||||
|
const res = await this.exportManager.exportImage(enhancedOptions);
|
||||||
|
// 恢复特殊图层的显示状态
|
||||||
|
if(ptlids.length > 0){
|
||||||
|
for(let id of ptlids){
|
||||||
|
const command = new ToggleChildLayerVisibilityCommand({
|
||||||
|
canvas: this.canvas,
|
||||||
|
layers: this.layers,
|
||||||
|
layerId: id,
|
||||||
|
layerManager: this.layerManager,
|
||||||
|
});
|
||||||
|
await command.execute(true);
|
||||||
|
}
|
||||||
|
await this.changeCanvas();
|
||||||
|
}
|
||||||
|
return res;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("CanvasManager导出图片失败:", error);
|
console.warn("CanvasManager导出图片失败:", error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -1533,7 +1577,7 @@ export class CanvasManager {
|
|||||||
selectable: true,
|
selectable: true,
|
||||||
hasControls: true,
|
hasControls: true,
|
||||||
hasBorders: true,
|
hasBorders: true,
|
||||||
specialType: SpecialType.PRINT_TRIMS_O,
|
isPrintTrims: true,
|
||||||
globalCompositeOperation: BlendMode.MULTIPLY,
|
globalCompositeOperation: BlendMode.MULTIPLY,
|
||||||
});
|
});
|
||||||
resolve(fabricImage);
|
resolve(fabricImage);
|
||||||
@@ -1547,7 +1591,7 @@ export class CanvasManager {
|
|||||||
visible: true,
|
visible: true,
|
||||||
locked: false,
|
locked: false,
|
||||||
opacity: 1.0,
|
opacity: 1.0,
|
||||||
specialType: SpecialType.PRINT_TRIMS_L,
|
isPrintTrims: true,
|
||||||
blendMode: BlendMode.MULTIPLY,
|
blendMode: BlendMode.MULTIPLY,
|
||||||
fabricObjects: [image.toObject(["id", "layerId", "layerName"])],
|
fabricObjects: [image.toObject(["id", "layerId", "layerName"])],
|
||||||
metadata: {sourceData: item},
|
metadata: {sourceData: item},
|
||||||
@@ -1601,7 +1645,7 @@ export class CanvasManager {
|
|||||||
width: image.width,
|
width: image.width,
|
||||||
height: image.height,
|
height: image.height,
|
||||||
},
|
},
|
||||||
specialType: SpecialType.REPEAT_O,
|
isPrintTrims: true,
|
||||||
});
|
});
|
||||||
this.canvas.add(rect);
|
this.canvas.add(rect);
|
||||||
let layer = createLayer({
|
let layer = createLayer({
|
||||||
@@ -1611,7 +1655,7 @@ export class CanvasManager {
|
|||||||
visible: true,
|
visible: true,
|
||||||
locked: true,
|
locked: true,
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
specialType: SpecialType.REPEAT_L,
|
isPrintTrims: true,
|
||||||
blendMode: BlendMode.MULTIPLY,
|
blendMode: BlendMode.MULTIPLY,
|
||||||
fabricObjects: [rect.toObject(["id", "layerId", "layerName"])],
|
fabricObjects: [rect.toObject(["id", "layerId", "layerName"])],
|
||||||
metadata: {sourceData: item},
|
metadata: {sourceData: item},
|
||||||
@@ -1630,7 +1674,7 @@ export class CanvasManager {
|
|||||||
// })
|
// })
|
||||||
// children.push(layer);
|
// children.push(layer);
|
||||||
// }
|
// }
|
||||||
// if(children.length === 0) return;
|
if(children.length === 0) return;
|
||||||
const groupRect = new fabric.Rect({});
|
const groupRect = new fabric.Rect({});
|
||||||
await this.setObjecCliptInfo(groupRect);
|
await this.setObjecCliptInfo(groupRect);
|
||||||
// 插入组图层
|
// 插入组图层
|
||||||
@@ -1646,11 +1690,8 @@ export class CanvasManager {
|
|||||||
children: children,
|
children: children,
|
||||||
clippingMask: groupRect.toObject(),
|
clippingMask: groupRect.toObject(),
|
||||||
isPrintTrimsGroup: true,
|
isPrintTrimsGroup: true,
|
||||||
specialType: SpecialType.PRINT_TRIMS_G,
|
|
||||||
});
|
});
|
||||||
this.layers.value.splice(groupIndex, 0, groupLayer);
|
this.layers.value.splice(groupIndex, 0, groupLayer);
|
||||||
console.log("==========layers", [...this.layers.value]);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -502,7 +502,6 @@ export class ToolManager {
|
|||||||
if (!this.canvas) return;
|
if (!this.canvas) return;
|
||||||
this.canvas.isDrawingMode = false;
|
this.canvas.isDrawingMode = false;
|
||||||
this.canvas.selection = true;
|
this.canvas.selection = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -511,7 +510,7 @@ export class ToolManager {
|
|||||||
*/
|
*/
|
||||||
checkToolCanOperateSelectedObject() {
|
checkToolCanOperateSelectedObject() {
|
||||||
const layer = this.layerManager?.getActiveLayer();
|
const layer = this.layerManager?.getActiveLayer();
|
||||||
const isSpecialLayer = !!layer?.specialType;
|
const isSpecialLayer = !!layer?.isPrintTrims || !!layer?.isPrintTrimsGroup;
|
||||||
if (isSpecialLayer) {
|
if (isSpecialLayer) {
|
||||||
this._disableBrushIndicator();
|
this._disableBrushIndicator();
|
||||||
this.canvas.defaultCursor = "not-allowed";
|
this.canvas.defaultCursor = "not-allowed";
|
||||||
|
|||||||
@@ -25,18 +25,6 @@ export const SpecialLayerId = {
|
|||||||
SPECIAL_GROUP: "group_special", // 特殊组
|
SPECIAL_GROUP: "group_special", // 特殊组
|
||||||
COLOR: "special_color", // 颜色图层
|
COLOR: "special_color", // 颜色图层
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* 特殊类型
|
|
||||||
*/
|
|
||||||
export const SpecialType = {
|
|
||||||
PRINT_TRIMS_G: "print_trims_group", // 印花和元素图层组
|
|
||||||
PRINT_TRIMS_L: "print_trims_layer", // 印花和元素图层
|
|
||||||
PRINT_TRIMS_O: "print_trims_object", // 印花和元素图层对象
|
|
||||||
REPEAT_L: "repeat_layer",// 平铺图层
|
|
||||||
REPEAT_O: "repeat_object",// 平铺图层对象
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -335,20 +335,20 @@ const otherData = {
|
|||||||
color: {rgba: {r:255,g:0,b:0,a:1}},
|
color: {rgba: {r:255,g:0,b:0,a:1}},
|
||||||
printObject: {
|
printObject: {
|
||||||
prints: [
|
prints: [
|
||||||
// {
|
|
||||||
// ifSingle: false,
|
|
||||||
// level2Type: "Pattern",
|
|
||||||
// designType: "Library",
|
|
||||||
// path: "/src/assets/images/canvas/yinhua1.jpg",
|
|
||||||
// location: [250, 780],
|
|
||||||
// scale: [0.3, 0.4],
|
|
||||||
// angle: 0,
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
ifSingle: false,
|
ifSingle: false,
|
||||||
level2Type: "Pattern",
|
level2Type: "Pattern",
|
||||||
designType: "Library",
|
designType: "Library",
|
||||||
path: "/src/assets/images/canvas/yinhua1.jpg",
|
path: "/src/assets/images/canvas/yinhua1.jpg",
|
||||||
|
location: [250, 780],
|
||||||
|
scale: [0.3, 0.4],
|
||||||
|
angle: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ifSingle: true,
|
||||||
|
level2Type: "Pattern",
|
||||||
|
designType: "Library",
|
||||||
|
path: "/src/assets/images/canvas/yinhua1.jpg",
|
||||||
location: [550, 650],
|
location: [550, 650],
|
||||||
scale: [0.15, 0.2],
|
scale: [0.15, 0.2],
|
||||||
angle: 0,
|
angle: 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user