566 lines
14 KiB
Vue
566 lines
14 KiB
Vue
<script setup>
|
||
import { inject, ref, provide, onMounted, computed, watch, onUnmounted } from "vue";
|
||
import { OperationType } from "../utils/layerHelper";
|
||
import BrushPanel from "./BrushPanel.vue";
|
||
import { BrushStore } from "../store/BrushStore";
|
||
// 提供brushStore给子组件
|
||
provide("brushStore", BrushStore);
|
||
|
||
const toolManager = inject("toolManager");
|
||
const layerManager = inject("layerManager");
|
||
const isShowLayerPanel = inject("isShowLayerPanel", ref(false));
|
||
|
||
const props = defineProps({
|
||
activeTool: String,
|
||
canvasWidth: Number,
|
||
canvasHeight: Number,
|
||
canvasColor: String,
|
||
brushSize: Number,
|
||
enabledRedGreenMode: Boolean,
|
||
showLayersPanel: {
|
||
type: Boolean,
|
||
default: true, // 是否显示图层面板
|
||
},
|
||
isBackgroundChangeable: Boolean,
|
||
isChangeCanvasSize: Boolean,
|
||
});
|
||
|
||
const emit = defineEmits([
|
||
"update:canvasWidth",
|
||
"update:canvasHeight",
|
||
"update:canvasColor",
|
||
"update:brushSize",
|
||
"canvas-size-change",
|
||
"canvas-color-change",
|
||
]);
|
||
|
||
// 笔刷面板相关状态
|
||
const showBrushPanel = ref(false);
|
||
const brushPanelRef = ref(null);
|
||
const lastColor = ref("#ffffff");
|
||
|
||
// 计算属性
|
||
// const shouldShowBrushSettings = computed(() => {
|
||
// return props.activeTool === OperationType.DRAW;
|
||
// });
|
||
|
||
function updateCanvasSize({ width, height } = { width: props.width, height: props.height }) {
|
||
if (!layerManager) {
|
||
console.warn("LayerManager 未初始化,无法调整背景层尺寸");
|
||
return;
|
||
}
|
||
|
||
layerManager.resizeCanvas(width, height);
|
||
|
||
// // 检查画布上是否有除了背景层的其他元素
|
||
// const hasOtherElements = layerManager.layers.value.some((layer) => {
|
||
// if (layer.isBackground) return false;
|
||
// // 检查普通图层是否有对象
|
||
// if (layer.fabricObjects && layer.fabricObjects.length > 0) return true;
|
||
// // 检查固定图层是否有对象
|
||
// if (layer.isFixed && layer.fabricObjects && layer.fabricObjects.length > 0)
|
||
// return true;
|
||
// return false;
|
||
// });
|
||
|
||
// if (hasOtherElements) {
|
||
// // 有其他元素时使用等比缩放命令
|
||
// layerManager.resizeCanvasWithScale(width, height);
|
||
// } else {
|
||
// // 只有背景层时使用普通调整命令
|
||
// layerManager.resizeCanvas(width, height);
|
||
// }
|
||
|
||
emit("canvas-size-change");
|
||
}
|
||
|
||
function updateCanvasColor() {
|
||
console.log("更新画布颜色:", props.canvasColor);
|
||
if (!layerManager) {
|
||
console.warn("LayerManager 未初始化,无法更改背景色");
|
||
return;
|
||
}
|
||
|
||
// 更新背景层颜色而不是画布颜色
|
||
layerManager.updateBackgroundColor(props.canvasColor, {
|
||
oldColor: lastColor.value,
|
||
undoable: true,
|
||
});
|
||
lastColor.value = props.canvasColor;
|
||
emit("canvas-color-change");
|
||
}
|
||
|
||
watch(
|
||
() => props.canvasColor,
|
||
(newColor) => {
|
||
// 更新背景层颜色而不是画布颜色
|
||
layerManager.updateBackgroundColor(newColor, {
|
||
oldColor: lastColor.value,
|
||
undoable: false, // 不需要撤销
|
||
});
|
||
}
|
||
);
|
||
|
||
// 切换笔刷面板显示状态
|
||
function toggleBrushPanel() {
|
||
// 如果笔刷没有激活 则激活笔刷工具
|
||
if (toolManager?.activeTool !== OperationType.DRAW) {
|
||
toolManager.setToolWithCommand(OperationType.DRAW);
|
||
}
|
||
|
||
showBrushPanel.value = !showBrushPanel.value;
|
||
}
|
||
|
||
// 处理笔刷大小变化
|
||
function handleBrushSizeChange(event) {
|
||
const newSize = parseFloat(event.target.value);
|
||
emit("update:brushSize", newSize);
|
||
}
|
||
|
||
// 处理笔刷设置变化,将BrushStore的数据同步到brushManager
|
||
function syncBrushStoreToManager() {
|
||
if (!toolManager?.brushManager) return;
|
||
|
||
const brushManager = toolManager.brushManager;
|
||
|
||
// 检查画笔是否正在更新中
|
||
if (brushManager.isUpdatingBrush) {
|
||
console.warn("画笔正在更新中,请稍候...");
|
||
// 延迟重试,确保在画笔更新完成后应用最新设置
|
||
setTimeout(syncBrushStoreToManager, 100);
|
||
return;
|
||
}
|
||
|
||
// 监听BrushStore的变化,更新brushManager
|
||
const size = BrushStore.state.size;
|
||
const color = BrushStore.state.color;
|
||
const type = BrushStore.state.type;
|
||
const opacity = BrushStore.state.opacity;
|
||
const textureEnabled = BrushStore.state.textureEnabled;
|
||
const texturePath = BrushStore.state.texturePath;
|
||
const textureScale = BrushStore.state.textureScale;
|
||
|
||
// 将所有更改一次性应用,减少updateBrush调用次数
|
||
let needsUpdate = false;
|
||
|
||
if (
|
||
brushManager.brushSize &&
|
||
typeof brushManager.setBrushSize === "function" &&
|
||
brushManager.getBrushSize() !== size
|
||
) {
|
||
brushManager.brushSize.value = size; // 直接设置值,避免触发updateBrush
|
||
needsUpdate = true;
|
||
}
|
||
|
||
if (
|
||
brushManager.brushColor &&
|
||
typeof brushManager.setBrushColor === "function" &&
|
||
brushManager.getBrushColor() !== color
|
||
) {
|
||
brushManager.brushColor.value = color; // 直接设置值,避免触发updateBrush
|
||
needsUpdate = true;
|
||
}
|
||
|
||
if (
|
||
typeof brushManager.setBrushType === "function" &&
|
||
brushManager.getCurrentBrushType() !== type
|
||
) {
|
||
brushManager.setBrushType(type);
|
||
needsUpdate = true;
|
||
}
|
||
|
||
if (typeof brushManager.setBrushOpacity === "function") {
|
||
brushManager.setBrushOpacity(opacity);
|
||
needsUpdate = true;
|
||
}
|
||
|
||
// 同步材质相关设置
|
||
if (textureEnabled && texturePath) {
|
||
if (typeof brushManager.setTexturePath === "function") {
|
||
brushManager.setTexturePath(texturePath);
|
||
needsUpdate = true;
|
||
}
|
||
|
||
if (
|
||
typeof brushManager.setTextureScale === "function" &&
|
||
brushManager.getTextureScale() !== textureScale
|
||
) {
|
||
brushManager.textureScale.value = textureScale; // 直接设置值,避免触发updateBrush
|
||
needsUpdate = true;
|
||
}
|
||
}
|
||
|
||
// 只在有变化时调用一次updateBrush,减少重绘次数
|
||
if (needsUpdate && typeof brushManager.updateBrush === "function") {
|
||
brushManager.updateBrush();
|
||
}
|
||
}
|
||
|
||
// 点击外部时关闭笔刷面板
|
||
function handleClickOutside(event) {
|
||
// if (isShowLayerPanel.value) {
|
||
// // 如果点击的是图层面板或其内部元素,则不关闭
|
||
// if (event.target.closest(".layers-panel")) {
|
||
// return;
|
||
// }
|
||
// // 关闭图层面板
|
||
// isShowLayerPanel.value = false;
|
||
// }
|
||
|
||
if (showBrushPanel.value) {
|
||
// 检查是否点击了笔刷选择器按钮
|
||
if (event.target.closest(".brush-selector")) {
|
||
return;
|
||
}
|
||
|
||
// 检查是否点击了笔刷面板或其内部元素
|
||
if (event.target.closest(".brush-panel")) {
|
||
return;
|
||
}
|
||
|
||
// 如果都不是,则关闭面板
|
||
if (showBrushPanel.value) {
|
||
showBrushPanel.value = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
function showLayerPanel() {
|
||
isShowLayerPanel.value = !isShowLayerPanel.value;
|
||
}
|
||
|
||
onMounted(() => {
|
||
lastColor.value = props.canvasColor;
|
||
// 获取工具管理器和笔刷管理器
|
||
const brushManager = toolManager?.brushManager;
|
||
|
||
// 设置初始的可用笔刷类型
|
||
if (brushManager) {
|
||
const availableBrushes = brushManager.getBrushTypes();
|
||
BrushStore.setAvailableBrushes(availableBrushes);
|
||
|
||
// 初始化BrushStore与brushManager的数据同步
|
||
BrushStore.setBrushSize(brushManager.brushSize?.value || 5);
|
||
BrushStore.setBrushColor(brushManager.brushColor?.value || "#000000");
|
||
BrushStore.setBrushType(brushManager.getCurrentBrushType() || "pencil");
|
||
}
|
||
|
||
// 添加点击外部关闭面板的事件监听
|
||
document.addEventListener("mousedown", handleClickOutside);
|
||
|
||
// 监听BrushStore的变化,同步到brushManager
|
||
const unwatch = watch(
|
||
() => [
|
||
BrushStore.state.size,
|
||
BrushStore.state.color,
|
||
BrushStore.state.type,
|
||
BrushStore.state.opacity,
|
||
BrushStore.state.textureEnabled,
|
||
BrushStore.state.texturePath,
|
||
BrushStore.state.textureScale,
|
||
],
|
||
syncBrushStoreToManager,
|
||
{ deep: true }
|
||
);
|
||
|
||
// 组件卸载时移除事件监听
|
||
onUnmounted(() => {
|
||
document.removeEventListener("mousedown", handleClickOutside);
|
||
unwatch();
|
||
});
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<div class="canvas-header">
|
||
<div class="canvas-header-wrapper">
|
||
<span class="canvas-title">{{ $t('Canvas.Canvas') }}</span>
|
||
<!-- 默认设置 -->
|
||
<!-- v-if="
|
||
!activeTool ||
|
||
activeTool === OperationType.SELECT ||
|
||
activeTool === OperationType.PAN
|
||
" -->
|
||
<div class="canvas-settings" v-if="!props.enabledRedGreenMode">
|
||
<div class="setting-group">
|
||
<span class="setting-label">{{ $t("Canvas.width") }}</span>
|
||
<a-input-number
|
||
:disabled="!isChangeCanvasSize"
|
||
:value="canvasWidth?.toFixed(2)"
|
||
class="setting-input"
|
||
:min="1"
|
||
:max="4999"
|
||
:step="1"
|
||
@change="
|
||
(value) => {
|
||
$emit('update:canvasWidth', value);
|
||
updateCanvasSize({ width: value, height: canvasHeight });
|
||
}
|
||
"
|
||
/>
|
||
</div>
|
||
<div class="setting-group">
|
||
<span class="setting-label">{{ $t("Canvas.height") }}</span>
|
||
<a-input-number
|
||
:disabled="!isChangeCanvasSize"
|
||
:value="canvasHeight?.toFixed(2)"
|
||
class="setting-input"
|
||
:min="1"
|
||
:max="4999"
|
||
:step="1"
|
||
@change="
|
||
(value) => {
|
||
$emit('update:canvasHeight', value);
|
||
updateCanvasSize({ width: canvasWidth, height: value });
|
||
}
|
||
"
|
||
/>
|
||
</div>
|
||
<div class="setting-group" v-if="isBackgroundChangeable">
|
||
<span class="setting-label">{{ $t("Canvas.color") }}</span>
|
||
<div class="color-picker-wrapper">
|
||
<input
|
||
type="color"
|
||
:value="canvasColor"
|
||
class="color-picker"
|
||
@input="$emit('update:canvasColor', $event.target.value)"
|
||
@change="updateCanvasColor"
|
||
/>
|
||
<span class="color-dropdown">▼</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 菜单扩展插槽 -->
|
||
<template v-if="$slots.existsImageList">
|
||
<div class="setting-group">
|
||
<slot name="existsImageList" />
|
||
</div>
|
||
</template>
|
||
</div>
|
||
|
||
<!-- <div class="setting-group">
|
||
<span class="setting-label">颜色:</span>
|
||
<div class="color-picker-wrapper">
|
||
<input
|
||
type="color"
|
||
:value="BrushStore.state.color"
|
||
class="color-picker"
|
||
@input="BrushStore.setBrushColor($event.target.value)"
|
||
/>
|
||
<span class="color-dropdown">▼</span>
|
||
</div>
|
||
</div> -->
|
||
</div>
|
||
|
||
<!-- 导出设置 -->
|
||
<!-- <div class="setting-group export-group">
|
||
<span class="export-model-select">exportModel.select:</span>
|
||
<span class="export-model-dropdown">▼</span>
|
||
</div> -->
|
||
|
||
<!-- 绘图工具设置 -->
|
||
<div class="canvas-settings gap-20" v-if="!props.enabledRedGreenMode">
|
||
<div class="btn" :class="{ active: showBrushPanel }" @click="toggleBrushPanel">
|
||
<!-- <span class="setting-label">笔刷:</span>/ -->
|
||
<div class="brush-selector">
|
||
<SvgIcon name="CBrushTop" size="22"></SvgIcon>
|
||
<!-- <div
|
||
class="brush-preview"
|
||
:style="{
|
||
backgroundColor: BrushStore.state.color,
|
||
height: BrushStore.state.type === 'marker' ? '4px' : '2px',
|
||
opacity: BrushStore.state.opacity,
|
||
}"
|
||
></div> -->
|
||
<!-- <span class="brush-dropdown">▼</span> -->
|
||
</div>
|
||
<!-- 笔刷面板 -->
|
||
<div v-if="showBrushPanel" class="brush-panel-container" ref="brushPanelRef">
|
||
<Teleport to="body">
|
||
<BrushPanel />
|
||
</Teleport>
|
||
</div>
|
||
</div>
|
||
<div
|
||
v-if="showLayersPanel"
|
||
class="btn"
|
||
:class="{ active: isShowLayerPanel }"
|
||
@click="showLayerPanel"
|
||
>
|
||
<SvgIcon name="CLayout" size="26"></SvgIcon>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped lang="less">
|
||
.canvas-header {
|
||
display: flex;
|
||
align-items: center;
|
||
border-bottom: 1px solid #e0e0e0;
|
||
user-select: none;
|
||
height: 5.2rem;
|
||
overflow: hidden;
|
||
width: 100%;
|
||
.canvas-header-wrapper {
|
||
display: flex;
|
||
align-items: center;
|
||
flex: auto;
|
||
}
|
||
}
|
||
|
||
.canvas-title {
|
||
font-size: 1.6rem;
|
||
font-weight: 500;
|
||
margin-right: 3rem;
|
||
display: flex;
|
||
align-items: center;
|
||
color: #333;
|
||
// &:before {
|
||
// // /* content: "⟳";
|
||
// // margin-right: 5px;
|
||
// // font-size: 14px; */
|
||
// }
|
||
}
|
||
|
||
.canvas-settings {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: .8rem;
|
||
color: #213547;
|
||
.btn {
|
||
width: 3.2rem;
|
||
height: 3.2rem;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
cursor: pointer;
|
||
background-color: #f0f0f0;
|
||
|
||
&.active,
|
||
&:active {
|
||
background-color: #e6f7ff;
|
||
color: #1890ff;
|
||
}
|
||
&:hover {
|
||
background-color: #e6f7ff;
|
||
// color: #1890ff;
|
||
}
|
||
}
|
||
}
|
||
|
||
.gap-20 {
|
||
gap: 2rem;
|
||
}
|
||
|
||
.setting-group {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: .5rem;
|
||
position: relative;
|
||
}
|
||
|
||
.setting-label {
|
||
font-size: 1.4rem;
|
||
color: #333;
|
||
}
|
||
|
||
.setting-input {
|
||
width: 8rem;
|
||
}
|
||
|
||
.setting-input :deep(.ant-input-number-input) {
|
||
padding: .4rem .8rem;
|
||
font-size: 1.4rem;
|
||
}
|
||
|
||
.setting-select {
|
||
padding: .4rem .8rem;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
font-size: 1.4rem;
|
||
}
|
||
|
||
.font-select {
|
||
width: 15rem;
|
||
padding: .4rem .8rem;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.color-picker-wrapper {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.color-picker {
|
||
width: 3rem;
|
||
height: 3rem;
|
||
border: none;
|
||
padding: 0;
|
||
background: none;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.color-dropdown {
|
||
font-size: 1rem;
|
||
margin-left: .5rem;
|
||
color: #666;
|
||
}
|
||
|
||
.size-slider {
|
||
width: 10rem;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.size-value {
|
||
font-size: 1.2rem;
|
||
color: #666;
|
||
min-width: 3rem;
|
||
}
|
||
|
||
.brush-selector {
|
||
display: block;
|
||
}
|
||
|
||
.brush-preview {
|
||
width: 2rem;
|
||
height: .2rem;
|
||
background-color: #000;
|
||
border-radius: 1px;
|
||
}
|
||
|
||
.brush-dropdown,
|
||
.export-model-dropdown {
|
||
font-size: 1rem;
|
||
margin-left: .5rem;
|
||
color: #666;
|
||
}
|
||
|
||
.export-model-select {
|
||
font-size: 1.4rem;
|
||
color: #333;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.export-group {
|
||
margin-left: auto;
|
||
}
|
||
|
||
/* 笔刷面板 */
|
||
.brush-panel-container {
|
||
position: absolute;
|
||
top: calc(100% + 5px);
|
||
left: 0;
|
||
z-index: 1000;
|
||
width: 60rem;
|
||
box-shadow: 0 2px 1rem rgba(0, 0, 0, 0.2);
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
}
|
||
</style>
|