488 lines
9.1 KiB
Vue
488 lines
9.1 KiB
Vue
<template>
|
||
<transition name="fade">
|
||
<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-header">
|
||
<div class="header-title">
|
||
{{ t("Canvas.GarmentPartSelector") }}
|
||
</div>
|
||
<!-- 移除关闭按钮,完全通过工具切换控制显示隐藏 -->
|
||
<div class="tip">
|
||
<div>
|
||
<img
|
||
src="/src/assets/images/canvas/shubiao-l.png"
|
||
/>
|
||
<span>Left Click: Add</span>
|
||
</div>
|
||
<div>
|
||
<img
|
||
src="/src/assets/images/canvas/shubiao-r.png"
|
||
/>
|
||
<span>Right Click: Remove</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="tool-types">
|
||
<div
|
||
v-for="item in toolList"
|
||
:key="item.type"
|
||
:class="[
|
||
'tool-btn',
|
||
{ active: toolType === item.type },
|
||
]"
|
||
@click="setPartType(item.type)"
|
||
>
|
||
<svg-icon :name="item.icon" :size="item.size" />
|
||
<span>{{ item.label }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 分割线 -->
|
||
<div class="toolbar-divider"></div>
|
||
|
||
<!-- 底部选区操作工具栏 -->
|
||
<div class="tool-actions">
|
||
<div class="action-btn" @click="onCreate">
|
||
<svg-icon name="CPaste" size="16" />
|
||
<span class="btn-text">{{
|
||
$t("Canvas.creation")
|
||
}}</span>
|
||
</div>
|
||
<div class="action-btn" @click="onCopyCreate">
|
||
<svg-icon name="CCut" size="26" />
|
||
<span class="btn-text">{{
|
||
$t("Canvas.CreateAndCopy")
|
||
}}</span>
|
||
</div>
|
||
<div class="action-btn" @click="onReset">
|
||
<svg-icon name="CCut" size="26" />
|
||
<span class="btn-text">清空当前点位</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</transition>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, watch } from "vue";
|
||
import { useI18n } from "vue-i18n";
|
||
import {
|
||
CreateSelectionCommand,
|
||
InvertSelectionCommand,
|
||
FeatherSelectionCommand,
|
||
FillSelectionCommand,
|
||
} from "../commands/SelectionCommands";
|
||
import { ToolCommand } from "../commands/ToolCommands";
|
||
import {
|
||
LassoCutoutCommand,
|
||
ClearSelectionCommand,
|
||
// CutSelectionToNewLayerCommand,
|
||
} from "../commands/LassoCutoutCommand";
|
||
|
||
import { OperationType } from "../utils/layerHelper";
|
||
import { ClearSelectionContentCommand } from "../commands/ClearSelectionContentCommand";
|
||
import { CutSelectionToNewLayerCommand } from "../commands/CutSelectionToNewLayerCommand";
|
||
|
||
const props = defineProps({
|
||
canvas: {
|
||
type: Object,
|
||
required: true,
|
||
},
|
||
commandManager: {
|
||
type: Object,
|
||
required: true,
|
||
},
|
||
partManager: {
|
||
type: Object,
|
||
required: true,
|
||
},
|
||
partManager: {
|
||
type: Object,
|
||
required: true,
|
||
},
|
||
layerManager: {
|
||
type: Object,
|
||
required: true,
|
||
},
|
||
toolManager: {
|
||
type: Object,
|
||
required: true,
|
||
},
|
||
activeTool: {
|
||
type: String,
|
||
required: false,
|
||
default: null,
|
||
},
|
||
});
|
||
|
||
// 响应式数据
|
||
const visible = ref(false);
|
||
const toolType = ref(OperationType.PART);
|
||
//打开隐藏操作面板
|
||
const closePanel = ref(false);
|
||
const setClosePanel = () => {
|
||
closePanel.value = !closePanel.value;
|
||
};
|
||
|
||
const toolList = [
|
||
{
|
||
type: OperationType.PART,
|
||
label: "Point Selection",
|
||
icon: "CPoint",
|
||
size: "20",
|
||
},
|
||
{
|
||
type: OperationType.PART_RECTANGLE,
|
||
label: "Marquee Selection",
|
||
icon: "CMarquee",
|
||
size: "20",
|
||
},
|
||
{
|
||
type: OperationType.PART_BRUSH,
|
||
label: "Brush Selection",
|
||
icon: "CBrush2",
|
||
size: "16",
|
||
},
|
||
{
|
||
type: OperationType.PART_ERASER,
|
||
label: "Erase",
|
||
icon: "CEraser2",
|
||
size: "22",
|
||
},
|
||
];
|
||
|
||
// 国际化
|
||
const { t } = useI18n();
|
||
|
||
onMounted(() => {});
|
||
|
||
// 监听 activeTool 变化
|
||
watch(
|
||
() => props.activeTool,
|
||
(newTool) => {
|
||
// 当工具为LASSO或AREA类型时显示选区面板
|
||
const selectionTools = [
|
||
OperationType.PART,
|
||
OperationType.PART_RECTANGLE,
|
||
OperationType.PART_BRUSH,
|
||
OperationType.PART_ERASER,
|
||
];
|
||
|
||
if (selectionTools.includes(newTool)) {
|
||
show();
|
||
// 根据工具类型设置选区类型
|
||
toolType.value = newTool;
|
||
|
||
// 更新选区管理器的选区类型
|
||
// if (props.partManager) {
|
||
// props.partManager.setPartType(toolType.value);
|
||
// props.partManager.setupPartEvents();
|
||
// }
|
||
} else {
|
||
close();
|
||
}
|
||
},
|
||
{ immediate: true }
|
||
);
|
||
|
||
/**
|
||
* 显示面板
|
||
*/
|
||
function show() {
|
||
visible.value = true;
|
||
closePanel.value = true;
|
||
}
|
||
|
||
/**
|
||
* 关闭面板
|
||
*/
|
||
function close() {
|
||
visible.value = false;
|
||
}
|
||
|
||
/**
|
||
* 设置选区类型
|
||
*/
|
||
function setPartType(type) {
|
||
toolType.value = type;
|
||
|
||
// 通过 ToolManager 切换工具,这会自动通知 partManager
|
||
if (props.toolManager) {
|
||
props.toolManager.setToolWithCommand(type);
|
||
}
|
||
|
||
// // 备用方案:如果没有 toolManager,直接更新 partManager
|
||
// else if (props.partManager) {
|
||
// props.partManager.setPartType(type);
|
||
// props.partManager.setupPartEvents();
|
||
// }
|
||
}
|
||
|
||
// 创建
|
||
function onCreate() {
|
||
props.partManager.createPart();
|
||
}
|
||
// 复制并创建
|
||
function onCopyCreate() {
|
||
|
||
}
|
||
// 清空当前点位
|
||
function onReset() {
|
||
props.partManager.clearPart();
|
||
}
|
||
|
||
</script>
|
||
|
||
<style scoped lang="less">
|
||
.part-selector-toolbar {
|
||
position: absolute;
|
||
bottom: 22px;
|
||
left: 20px;
|
||
right: 20px;
|
||
max-width: min(90vw, 700px);
|
||
margin: 0 auto;
|
||
background-color: rgba(255, 255, 255, 0.95);
|
||
backdrop-filter: blur(15px);
|
||
-webkit-backdrop-filter: blur(15px);
|
||
border-radius: 8px;
|
||
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.1);
|
||
z-index: 1000;
|
||
color: #333;
|
||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||
user-select: none;
|
||
&.active {
|
||
transform: translateY(100%);
|
||
> .btn {
|
||
> i {
|
||
transform: rotate(90deg);
|
||
}
|
||
}
|
||
}
|
||
> .btn {
|
||
width: 100%;
|
||
text-align: center;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 22px;
|
||
|
||
> i {
|
||
font-size: 1.4rem;
|
||
transform: rotate(270deg);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 平板和手机适配 */
|
||
@media screen and (max-width: 768px) {
|
||
.part-selector-toolbar {
|
||
bottom: 15px;
|
||
left: 15px;
|
||
right: 15px;
|
||
max-width: calc(100vw - 30px);
|
||
border-radius: 6px;
|
||
}
|
||
}
|
||
|
||
@media screen and (max-width: 480px) {
|
||
.part-selector-toolbar {
|
||
bottom: 10px;
|
||
left: 10px;
|
||
right: 10px;
|
||
max-width: calc(100vw - 20px);
|
||
}
|
||
}
|
||
|
||
.part-selector-toolbar.is-active {
|
||
transform: translateY(0);
|
||
}
|
||
|
||
.toolbar-header {
|
||
// display: flex;
|
||
// justify-content: center;
|
||
// align-items: center;
|
||
padding: 8px 0;
|
||
// border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||
background-color: rgba(255, 255, 255, 0.8);
|
||
border-radius: 8px 8px 0 0;
|
||
position: relative;
|
||
> .tip {
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
display: flex;
|
||
> div {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-left: 10px;
|
||
> img {
|
||
width: 12px;
|
||
height: 12px;
|
||
margin-right: 5px;
|
||
}
|
||
> span {
|
||
font-size: 10px;
|
||
color: #333;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.header-title {
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
color: #333;
|
||
text-align: left;
|
||
}
|
||
.header-btn {
|
||
background: none;
|
||
border: none;
|
||
color: #333;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
padding: 3px 0;
|
||
border-radius: 3px;
|
||
transition: background-color 0.2s ease;
|
||
min-width: 32px;
|
||
}
|
||
|
||
.header-btn:hover {
|
||
background-color: rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.close-btn {
|
||
color: #666;
|
||
}
|
||
|
||
.toolbar-section {
|
||
padding: 0 3rem 1.2rem;
|
||
}
|
||
|
||
.tool-types {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 8px;
|
||
padding: 10px 0;
|
||
}
|
||
|
||
.tool-btn {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: rgba(0, 0, 0, 0.05);
|
||
border: none;
|
||
border-radius: 6px;
|
||
padding: 6px;
|
||
color: #333;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.tool-btn span {
|
||
margin-top: 0;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.tool-btn svg {
|
||
width: 24px;
|
||
height: 24px;
|
||
}
|
||
|
||
.tool-btn:hover {
|
||
background-color: rgba(0, 0, 0, 0.08);
|
||
}
|
||
|
||
.tool-btn.active {
|
||
background-color: #007aff;
|
||
color: white;
|
||
}
|
||
|
||
.toolbar-divider {
|
||
height: 1px;
|
||
background-color: rgba(0, 0, 0, 0.05);
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.tool-actions {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 5px;
|
||
padding: 0 30px;
|
||
}
|
||
|
||
/* 平板适配 - 每行4个按钮 */
|
||
@media screen and (max-width: 768px) {
|
||
.tool-actions {
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 8px 6px;
|
||
padding: 0 8px;
|
||
}
|
||
}
|
||
|
||
/* 手机适配 - 每行3个按钮 */
|
||
@media screen and (max-width: 480px) {
|
||
.tool-actions {
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 6px 4px;
|
||
padding: 0 6px;
|
||
}
|
||
|
||
.header-btn {
|
||
font-size: 11px;
|
||
padding: 2px 4px;
|
||
min-width: 28px;
|
||
}
|
||
}
|
||
|
||
.action-btn {
|
||
display: flex;
|
||
// flex-direction: column;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: none;
|
||
border: none;
|
||
color: #333;
|
||
cursor: pointer;
|
||
padding: 0;
|
||
gap: 4px;
|
||
.c-svg {
|
||
width: auto;
|
||
}
|
||
}
|
||
|
||
.action-btn svg {
|
||
width: 22px;
|
||
height: 22px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.btn-text {
|
||
display: block;
|
||
font-size: 12px;
|
||
text-align: center;
|
||
}
|
||
|
||
.action-btn:hover {
|
||
color: #007aff;
|
||
}
|
||
|
||
.action-btn:disabled {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
}
|
||
</style>
|