From fd9b1721c1b101a6659925703654c6cdf82f58cf Mon Sep 17 00:00:00 2001
From: bighuixiang <472705331@qq.com>
Date: Thu, 26 Jun 2025 23:26:50 +0800
Subject: [PATCH] feat: Add texture management features and update UI
components
---
src/assets/icons/{fonticon => }/CFcenter.svg | 0
.../{fonticon/CFtretch.svg => CFjustify.svg} | 0
src/assets/icons/{fonticon => }/CFleft.svg | 0
src/assets/icons/{fonticon => }/CFright.svg | 0
.../CanvasEditor/components/BrushPanel.vue | 228 ++++++++++++++----
.../components/TextEditorPanel.vue | 100 ++++----
src/component/Canvas/CanvasEditor/index.vue | 2 +
.../managers/brushes/types/TextureBrush.js | 148 ++++++------
.../Canvas/CanvasEditor/store/BrushStore.js | 32 +++
9 files changed, 337 insertions(+), 173 deletions(-)
rename src/assets/icons/{fonticon => }/CFcenter.svg (100%)
rename src/assets/icons/{fonticon/CFtretch.svg => CFjustify.svg} (100%)
rename src/assets/icons/{fonticon => }/CFleft.svg (100%)
rename src/assets/icons/{fonticon => }/CFright.svg (100%)
diff --git a/src/assets/icons/fonticon/CFcenter.svg b/src/assets/icons/CFcenter.svg
similarity index 100%
rename from src/assets/icons/fonticon/CFcenter.svg
rename to src/assets/icons/CFcenter.svg
diff --git a/src/assets/icons/fonticon/CFtretch.svg b/src/assets/icons/CFjustify.svg
similarity index 100%
rename from src/assets/icons/fonticon/CFtretch.svg
rename to src/assets/icons/CFjustify.svg
diff --git a/src/assets/icons/fonticon/CFleft.svg b/src/assets/icons/CFleft.svg
similarity index 100%
rename from src/assets/icons/fonticon/CFleft.svg
rename to src/assets/icons/CFleft.svg
diff --git a/src/assets/icons/fonticon/CFright.svg b/src/assets/icons/CFright.svg
similarity index 100%
rename from src/assets/icons/fonticon/CFright.svg
rename to src/assets/icons/CFright.svg
diff --git a/src/component/Canvas/CanvasEditor/components/BrushPanel.vue b/src/component/Canvas/CanvasEditor/components/BrushPanel.vue
index 7a9055c7..8c977f92 100644
--- a/src/component/Canvas/CanvasEditor/components/BrushPanel.vue
+++ b/src/component/Canvas/CanvasEditor/components/BrushPanel.vue
@@ -34,11 +34,11 @@
+
-
{{ texture.label }}
+
+
+
+
+ {{ $t("上传的纹理") }}
+
+
+
+
+
+
-
上传纹理
+
{{ $t("上传纹理") }}
+
+
![]()
+
+
+ ×
+
+
+
-
-
+ -->
@@ -373,6 +405,9 @@ const toolManager = inject("toolManager");
// 命令管理器
const commandManager = inject("commandManager");
+// 注入纹理预设管理器
+const texturePresetManager = inject("texturePresetManager");
+
// 计算属性:按分类获取当前笔刷可配置属性
const propertiesByCategory = computed(() => {
return BrushStore.getPropertiesByCategory();
@@ -460,7 +495,11 @@ function handleTextureSelect(textureId) {
// 触发纹理上传文件选择
function triggerTextureUpload() {
- handleFileSelect("texturePath");
+ if (textureFileInput.value.length) {
+ textureFileInput.value[0].click();
+ return;
+ }
+ textureFileInput.value.click();
}
// 处理纹理文件上传
@@ -482,8 +521,7 @@ async function handleTextureUpload(event) {
return;
}
- // 获取纹理预设管理器和笔刷管理器
- const texturePresetManager = toolManager?.texturePresetManager;
+ // 获取笔刷管理器
const brushManager = toolManager?.brushManager;
if (!texturePresetManager || !brushManager) {
@@ -501,7 +539,14 @@ async function handleTextureUpload(event) {
brushManager: brushManager,
});
- await commandManager.execute(command);
+ const result = await commandManager.execute(command);
+
+ // 上传成功后,将纹理ID添加到缓存列表
+ if (result && result.textureId) {
+ brushStore.cacheUploadedTexture(result.textureId);
+ // 自动选择新上传的纹理
+ handleTextureSelect(result.textureId);
+ }
// 清空文件输入,允许重复上传同一文件
event.target.value = "";
@@ -511,6 +556,42 @@ async function handleTextureUpload(event) {
}
}
+// 获取上传纹理的预览图
+function getUploadedTexturePreview(textureId) {
+ const texture = texturePresetManager?.getTextureById?.(textureId);
+ return texture?.preview || texture?.path || "/placeholder-texture.png";
+}
+
+// 获取上传纹理的名称
+function getUploadedTextureName(textureId) {
+ const texture = texturePresetManager?.getTextureById?.(textureId);
+ return texture?.name || "未知纹理";
+}
+
+// 删除上传的纹理
+function removeUploadedTexture(textureId) {
+ try {
+ // 从缓存中移除
+ brushStore.removeCachedTexture(textureId);
+
+ // 从纹理预设管理器中删除
+ if (texturePresetManager?.removeCustomTexture) {
+ texturePresetManager.removeCustomTexture(textureId);
+ }
+
+ // 如果当前选中的是被删除的纹理,清空选择
+ const currentProperty = propertiesByCategory.value?.["纹理"]?.find(
+ (p) => p.id === "textureSelector"
+ );
+ if (currentProperty?.value === textureId) {
+ handlePropertyChange("textureSelector", null);
+ }
+ } catch (error) {
+ console.error("删除纹理失败:", error);
+ alert(`删除失败: ${error.message}`);
+ }
+}
+
// 格式化属性值显示
function formatPropertyValue(prop) {
if (prop.type === "slider") {
@@ -560,30 +641,6 @@ function setBrushTypeWithCommand(type) {
commandManager.execute(command, { nonUndoable: true }); // 不需要撤销
}
-// 处理文件选择
-function handleFileSelect(propId) {
- // 创建一个文件输入元素
- const input = document.createElement("input");
- input.type = "file";
- input.accept = "image/*";
-
- input.onchange = (event) => {
- if (event.target.files && event.target.files[0]) {
- const file = event.target.files[0];
- const reader = new FileReader();
-
- reader.onload = (e) => {
- // 更新属性值
- handlePropertyChange(propId, e.target.result);
- };
-
- reader.readAsDataURL(file);
- }
- };
-
- input.click();
-}
-
// 获取笔刷预览样式
function getBrushPreviewStyle(brush) {
const baseStyle = {
@@ -1360,7 +1417,7 @@ const brushStore = BrushStore;
.texture-thumbnail {
width: 100%;
- height: 45px;
+ height: 100%;
object-fit: cover;
border-radius: 4px;
margin-bottom: 8px;
@@ -1611,10 +1668,97 @@ const brushStore = BrushStore;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
-/* 移除内部动画,只保留整体动画 */
-.brush-type-grid,
-.property-list,
-.presets-container {
- animation: none;
+/* 上传纹理缓存区域样式 */
+.uploaded-textures-section {
+ width: 100%;
+ margin-top: 15px;
+}
+
+.uploaded-textures-divider {
+ display: flex;
+ align-items: center;
+ margin-bottom: 12px;
+ padding: 0 4px;
+}
+
+.uploaded-textures-divider::before,
+.uploaded-textures-divider::after {
+ content: "";
+ flex: 1;
+ height: 1px;
+ background: linear-gradient(
+ to right,
+ transparent,
+ rgba(0, 0, 0, 0.08),
+ transparent
+ );
+}
+
+.uploaded-textures-divider span {
+ padding: 0 12px;
+ font-size: 13px;
+ color: #666;
+ background-color: #fff;
+ white-space: nowrap;
+ font-weight: 500;
+}
+
+.uploaded-textures-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(75px, 1fr));
+ gap: 10px;
+ margin-bottom: 15px;
+}
+
+.uploaded-texture-item {
+ position: relative;
+ border: 1px solid rgba(108, 117, 125, 0.2);
+ background-color: rgba(248, 249, 250, 0.8);
+}
+
+.uploaded-texture-item:hover {
+ border-color: rgba(66, 133, 244, 0.3);
+ background-color: rgba(66, 133, 244, 0.05);
+}
+
+.uploaded-texture-item.active {
+ background-color: rgba(66, 133, 244, 0.15);
+ border-color: rgba(66, 133, 244, 0.4);
+}
+
+.texture-remove-btn {
+ position: absolute;
+ top: -6px;
+ right: -6px;
+ width: 18px;
+ height: 18px;
+ background-color: rgba(244, 67, 54, 0.9);
+ border: 1px solid #fff;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ opacity: 0;
+ transition: all 0.2s ease;
+ z-index: 10;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
+}
+
+.texture-remove-btn span {
+ color: #fff;
+ font-size: 12px;
+ font-weight: bold;
+ line-height: 1;
+}
+
+.uploaded-texture-item:hover .texture-remove-btn {
+ opacity: 1;
+ transform: scale(1.1);
+}
+
+.texture-remove-btn:hover {
+ background-color: rgba(244, 67, 54, 1);
+ transform: scale(1.2) !important;
}
diff --git a/src/component/Canvas/CanvasEditor/components/TextEditorPanel.vue b/src/component/Canvas/CanvasEditor/components/TextEditorPanel.vue
index af15a51b..7756828c 100644
--- a/src/component/Canvas/CanvasEditor/components/TextEditorPanel.vue
+++ b/src/component/Canvas/CanvasEditor/components/TextEditorPanel.vue
@@ -6,12 +6,10 @@
:class="{ 'is-active': visible }"
>
@@ -76,22 +74,6 @@
-
-
字符间距
-
-
-
{{ textSpacing.toFixed(1) }}%
-
-
-
-
+
不透明度
@@ -145,71 +127,76 @@
字体属性
+
+
+
-
-
@@ -248,9 +235,7 @@
{{
colorPickerMode === "text" ? "选择文字颜色" : "选择背景颜色"
}}
-
- ×
-
+ ×
-
+
确定
-
+
@@ -991,6 +976,7 @@ export default {
font-size: 14px;
margin-bottom: 10px;
color: #333;
+ text-align: left;
}
.bg-options {
diff --git a/src/component/Canvas/CanvasEditor/index.vue b/src/component/Canvas/CanvasEditor/index.vue
index 9567733a..cd9c4505 100644
--- a/src/component/Canvas/CanvasEditor/index.vue
+++ b/src/component/Canvas/CanvasEditor/index.vue
@@ -18,6 +18,7 @@ import CanvasConfig from "./config/canvasConfig.js";
import { LiquifyManager } from "./managers/liquify/LiquifyManager";
import { SelectionManager } from "./managers/selection/SelectionManager";
import { RedGreenModeManager } from "./managers/RedGreenModeManager";
+import texturePresetManager from "./managers/brushes/TexturePresetManager";
// import { MinimapManager } from "./managers/minimap/MinimapManager";
@@ -271,6 +272,7 @@ onMounted(async () => {
provide("keyboardManager", keyboardManager); // 提供给子组件使用
provide("activeTool", activeTool); // 提供给子组件使用
provide("liquifyManager", () => liquifyManager); // 提供液化管理器
+ provide("texturePresetManager", texturePresetManager); // 提供纹理预设管理器
provide("layers", layers); // 提供图层数据
// 初始化网格设置
// toggleGridVisibility(gridEnabled.value);
diff --git a/src/component/Canvas/CanvasEditor/managers/brushes/types/TextureBrush.js b/src/component/Canvas/CanvasEditor/managers/brushes/types/TextureBrush.js
index 16523d74..8c0a720a 100644
--- a/src/component/Canvas/CanvasEditor/managers/brushes/types/TextureBrush.js
+++ b/src/component/Canvas/CanvasEditor/managers/brushes/types/TextureBrush.js
@@ -478,80 +478,80 @@ export class TextureBrush extends BaseBrush {
order: 100,
hidden: allTextures.length === 0,
},
- {
- id: "textureRepeat",
- name: "纹理重复模式",
- type: "select",
- defaultValue: this.textureRepeat,
- options: [
- { value: "repeat", label: "双向重复" },
- { value: "repeat-x", label: "水平重复" },
- { value: "repeat-y", label: "垂直重复" },
- { value: "no-repeat", label: "不重复" },
- ],
- description: "设置纹理的重复模式",
- category: "纹理设置",
- order: 110,
- },
- {
- id: "textureScale",
- name: "纹理缩放",
- type: "slider",
- defaultValue: this.textureScale,
- min: 0.1,
- max: 5,
- step: 0.1,
- description: "调整纹理的缩放比例",
- category: "纹理设置",
- order: 120,
- },
- {
- id: "textureAngle",
- name: "纹理旋转",
- type: "slider",
- defaultValue: this.textureAngle,
- min: 0,
- max: 360,
- step: 5,
- description: "调整纹理的旋转角度",
- category: "纹理设置",
- order: 130,
- },
- {
- id: "textureOpacity",
- name: "纹理透明度",
- type: "slider",
- defaultValue: this.textureOpacity,
- min: 0,
- max: 1,
- step: 0.05,
- description: "调整纹理的透明度",
- category: "纹理设置",
- order: 140,
- },
- {
- id: "uploadTexture",
- name: "上传纹理",
- type: "button",
- action: "uploadTexture",
- description: "上传自定义纹理",
- category: "纹理设置",
- order: 150,
- },
- {
- id: "texturePreview",
- name: "纹理预览",
- type: "preview",
- description: "当前纹理预览",
- category: "纹理设置",
- order: 160,
- getValue: () => {
- const currentTexture = this.getCurrentTexture();
- return currentTexture
- ? texturePresetManager.getTexturePreviewUrl(currentTexture)
- : null;
- },
- },
+ // {
+ // id: "textureRepeat",
+ // name: "纹理重复模式",
+ // type: "select",
+ // defaultValue: this.textureRepeat,
+ // options: [
+ // { value: "repeat", label: "双向重复" },
+ // { value: "repeat-x", label: "水平重复" },
+ // { value: "repeat-y", label: "垂直重复" },
+ // { value: "no-repeat", label: "不重复" },
+ // ],
+ // description: "设置纹理的重复模式",
+ // category: "纹理设置",
+ // order: 110,
+ // },
+ // {
+ // id: "textureScale",
+ // name: "纹理缩放",
+ // type: "slider",
+ // defaultValue: this.textureScale,
+ // min: 0.1,
+ // max: 5,
+ // step: 0.1,
+ // description: "调整纹理的缩放比例",
+ // category: "纹理设置",
+ // order: 120,
+ // },
+ // {
+ // id: "textureAngle",
+ // name: "纹理旋转",
+ // type: "slider",
+ // defaultValue: this.textureAngle,
+ // min: 0,
+ // max: 360,
+ // step: 5,
+ // description: "调整纹理的旋转角度",
+ // category: "纹理设置",
+ // order: 130,
+ // },
+ // {
+ // id: "textureOpacity",
+ // name: "纹理透明度",
+ // type: "slider",
+ // defaultValue: this.textureOpacity,
+ // min: 0,
+ // max: 1,
+ // step: 0.05,
+ // description: "调整纹理的透明度",
+ // category: "纹理设置",
+ // order: 140,
+ // },
+ // {
+ // id: "uploadTexture",
+ // name: "上传纹理",
+ // type: "file",
+ // action: "uploadTexture",
+ // description: "上传自定义纹理",
+ // category: "纹理设置",
+ // order: 150,
+ // },
+ // {
+ // id: "texturePreview",
+ // name: "纹理预览",
+ // type: "preview",
+ // description: "当前纹理预览",
+ // category: "纹理设置",
+ // order: 160,
+ // getValue: () => {
+ // const currentTexture = this.getCurrentTexture();
+ // return currentTexture
+ // ? texturePresetManager.getTexturePreviewUrl(currentTexture)
+ // : null;
+ // },
+ // },
];
// 合并并返回所有属性
diff --git a/src/component/Canvas/CanvasEditor/store/BrushStore.js b/src/component/Canvas/CanvasEditor/store/BrushStore.js
index d3fd10ce..9687c816 100644
--- a/src/component/Canvas/CanvasEditor/store/BrushStore.js
+++ b/src/component/Canvas/CanvasEditor/store/BrushStore.js
@@ -72,6 +72,9 @@ const state = reactive({
},
],
+ // 上传的纹理缓存列表
+ uploadedTextures: [],
+
// 最近使用的颜色
recentColors: ["#000000", "#ffffff", "#ff0000", "#00ff00", "#0000ff"],
@@ -559,6 +562,35 @@ const actions = {
}
});
},
+
+ /**
+ * 清空上传的纹理缓存
+ */
+ clearUploadedTextures() {
+ state.uploadedTextures = [];
+ },
+
+ /**
+ * 添加纹理到缓存
+ * @param {String} textureId 材质ID
+ */
+ cacheUploadedTexture(textureId) {
+ const texture = texturePresetManager.getTextureById(textureId);
+ if (texture && !state.uploadedTextures.includes(textureId)) {
+ state.uploadedTextures.push(textureId);
+ }
+ },
+
+ /**
+ * 从缓存中移除纹理
+ * @param {String} textureId 材质ID
+ */
+ removeCachedTexture(textureId) {
+ const index = state.uploadedTextures.indexOf(textureId);
+ if (index !== -1) {
+ state.uploadedTextures.splice(index, 1);
+ }
+ },
};
// 暴露给组件使用的Store对象