feat: Add texture management features and update UI components
|
Before Width: | Height: | Size: 820 B After Width: | Height: | Size: 820 B |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 830 B After Width: | Height: | Size: 830 B |
|
Before Width: | Height: | Size: 812 B After Width: | Height: | Size: 812 B |
@@ -34,11 +34,11 @@
|
|||||||
<div class="brush-section">
|
<div class="brush-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<span>{{ category }}</span>
|
<span>{{ category }}</span>
|
||||||
<div class="section-actions" v-if="category === '材质'">
|
<!-- <div class="section-actions" v-if="category === '纹理'">
|
||||||
<button class="action-btn" @click="showLibrary = !showLibrary">
|
<button class="action-btn" @click="showLibrary = !showLibrary">
|
||||||
<i class="icon-library">📚</i> 材质库
|
<i class="icon-library">📚</i> 材质库
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 针对每个属性,根据其类型渲染合适的控件 -->
|
<!-- 针对每个属性,根据其类型渲染合适的控件 -->
|
||||||
@@ -163,7 +163,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 文件选择器(用于材质) -->
|
<!-- 文件选择器(用于材质) -->
|
||||||
<template v-else-if="prop.type === 'file'">
|
<!-- <template v-else-if="prop.type === 'file'">
|
||||||
<div class="file-property">
|
<div class="file-property">
|
||||||
<div class="file-header">
|
<div class="file-header">
|
||||||
<span>{{ prop.name }}</span>
|
<span>{{ prop.name }}</span>
|
||||||
@@ -191,7 +191,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template> -->
|
||||||
|
|
||||||
<!-- 材质网格选择器 -->
|
<!-- 材质网格选择器 -->
|
||||||
<template v-else-if="prop.type === 'texture-grid'">
|
<template v-else-if="prop.type === 'texture-grid'">
|
||||||
@@ -200,6 +200,7 @@
|
|||||||
<span>{{ prop.name }}</span>
|
<span>{{ prop.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="texture-grid">
|
<div class="texture-grid">
|
||||||
|
<!-- 预设纹理 -->
|
||||||
<div
|
<div
|
||||||
v-for="texture in prop.options"
|
v-for="texture in prop.options"
|
||||||
:key="texture.value"
|
:key="texture.value"
|
||||||
@@ -212,8 +213,17 @@
|
|||||||
:alt="texture.label"
|
:alt="texture.label"
|
||||||
class="texture-thumbnail"
|
class="texture-thumbnail"
|
||||||
/>
|
/>
|
||||||
<span class="texture-label">{{ texture.label }}</span>
|
<!-- <span class="texture-label">{{ texture.label }}</span> -->
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="uploaded-textures-section">
|
||||||
|
<div class="uploaded-textures-divider">
|
||||||
|
<span>{{ $t("上传的纹理") }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="texture-grid">
|
||||||
|
<!-- 上传的纹理缓存区域 -->
|
||||||
|
|
||||||
<!-- 自定义纹理上传按钮 -->
|
<!-- 自定义纹理上传按钮 -->
|
||||||
<div
|
<div
|
||||||
class="texture-item upload-item"
|
class="texture-item upload-item"
|
||||||
@@ -222,8 +232,30 @@
|
|||||||
<div class="upload-icon">
|
<div class="upload-icon">
|
||||||
<span>+</span>
|
<span>+</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="texture-label">上传纹理</span>
|
<span class="texture-label">{{ $t("上传纹理") }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="textureId in brushStore.state.uploadedTextures"
|
||||||
|
:key="textureId"
|
||||||
|
class="texture-item upload-item"
|
||||||
|
:class="{ active: prop.value === textureId }"
|
||||||
|
@click="handleTextureSelect(textureId)"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="getUploadedTexturePreview(textureId)"
|
||||||
|
:alt="getUploadedTextureName(textureId)"
|
||||||
|
class="texture-thumbnail"
|
||||||
|
/>
|
||||||
|
<!-- 删除按钮 -->
|
||||||
|
<div
|
||||||
|
class="texture-remove-btn"
|
||||||
|
@click.stop="removeUploadedTexture(textureId)"
|
||||||
|
:title="$t('删除纹理')"
|
||||||
|
>
|
||||||
|
<span>×</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 隐藏的文件输入 -->
|
<!-- 隐藏的文件输入 -->
|
||||||
<input
|
<input
|
||||||
ref="textureFileInput"
|
ref="textureFileInput"
|
||||||
@@ -261,7 +293,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 材质库弹窗 -->
|
<!-- 材质库弹窗 -->
|
||||||
<div
|
<!-- <div
|
||||||
v-if="showLibrary"
|
v-if="showLibrary"
|
||||||
class="texture-library-overlay"
|
class="texture-library-overlay"
|
||||||
@click.self="showLibrary = false"
|
@click.self="showLibrary = false"
|
||||||
@@ -312,7 +344,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
|
|
||||||
<!-- 笔刷预设 -->
|
<!-- 笔刷预设 -->
|
||||||
<div class="brush-section">
|
<div class="brush-section">
|
||||||
@@ -373,6 +405,9 @@ const toolManager = inject("toolManager");
|
|||||||
// 命令管理器
|
// 命令管理器
|
||||||
const commandManager = inject("commandManager");
|
const commandManager = inject("commandManager");
|
||||||
|
|
||||||
|
// 注入纹理预设管理器
|
||||||
|
const texturePresetManager = inject("texturePresetManager");
|
||||||
|
|
||||||
// 计算属性:按分类获取当前笔刷可配置属性
|
// 计算属性:按分类获取当前笔刷可配置属性
|
||||||
const propertiesByCategory = computed(() => {
|
const propertiesByCategory = computed(() => {
|
||||||
return BrushStore.getPropertiesByCategory();
|
return BrushStore.getPropertiesByCategory();
|
||||||
@@ -460,7 +495,11 @@ function handleTextureSelect(textureId) {
|
|||||||
|
|
||||||
// 触发纹理上传文件选择
|
// 触发纹理上传文件选择
|
||||||
function triggerTextureUpload() {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取纹理预设管理器和笔刷管理器
|
// 获取笔刷管理器
|
||||||
const texturePresetManager = toolManager?.texturePresetManager;
|
|
||||||
const brushManager = toolManager?.brushManager;
|
const brushManager = toolManager?.brushManager;
|
||||||
|
|
||||||
if (!texturePresetManager || !brushManager) {
|
if (!texturePresetManager || !brushManager) {
|
||||||
@@ -501,7 +539,14 @@ async function handleTextureUpload(event) {
|
|||||||
brushManager: brushManager,
|
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 = "";
|
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) {
|
function formatPropertyValue(prop) {
|
||||||
if (prop.type === "slider") {
|
if (prop.type === "slider") {
|
||||||
@@ -560,30 +641,6 @@ function setBrushTypeWithCommand(type) {
|
|||||||
commandManager.execute(command, { nonUndoable: true }); // 不需要撤销
|
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) {
|
function getBrushPreviewStyle(brush) {
|
||||||
const baseStyle = {
|
const baseStyle = {
|
||||||
@@ -1360,7 +1417,7 @@ const brushStore = BrushStore;
|
|||||||
|
|
||||||
.texture-thumbnail {
|
.texture-thumbnail {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 45px;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
@@ -1611,10 +1668,97 @@ const brushStore = BrushStore;
|
|||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 移除内部动画,只保留整体动画 */
|
/* 上传纹理缓存区域样式 */
|
||||||
.brush-type-grid,
|
.uploaded-textures-section {
|
||||||
.property-list,
|
width: 100%;
|
||||||
.presets-container {
|
margin-top: 15px;
|
||||||
animation: none;
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -6,12 +6,10 @@
|
|||||||
:class="{ 'is-active': visible }"
|
:class="{ 'is-active': visible }"
|
||||||
>
|
>
|
||||||
<div class="text-editor-panel-header">
|
<div class="text-editor-panel-header">
|
||||||
<button class="header-btn import-btn">导入字体</button>
|
<div class="header-btn import-btn">编辑文本样式</div>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<button class="header-btn cancel-btn" @click="close">取消</button>
|
<div class="header-btn cancel-btn" @click="close">取消</div>
|
||||||
<button class="header-btn confirm-btn" @click="confirmEdit">
|
<div class="header-btn confirm-btn" @click="confirmEdit">完成</div>
|
||||||
完成
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -76,22 +74,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="param-item">
|
|
||||||
<div class="param-label">字符间距</div>
|
|
||||||
<div class="param-control">
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
v-model.number="textSpacing"
|
|
||||||
min="-50"
|
|
||||||
max="100"
|
|
||||||
step="0.1"
|
|
||||||
@input="updateTextSpacing"
|
|
||||||
class="slider-control"
|
|
||||||
/>
|
|
||||||
<div class="param-value">{{ textSpacing.toFixed(1) }}%</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="param-item">
|
<div class="param-item">
|
||||||
<div class="param-label">行距</div>
|
<div class="param-label">行距</div>
|
||||||
<div class="param-control">
|
<div class="param-control">
|
||||||
@@ -108,7 +90,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="param-item">
|
<!-- <div class="param-item">
|
||||||
<div class="param-label">基线</div>
|
<div class="param-label">基线</div>
|
||||||
<div class="param-control">
|
<div class="param-control">
|
||||||
<input
|
<input
|
||||||
@@ -121,7 +103,7 @@
|
|||||||
/>
|
/>
|
||||||
<div class="param-value">{{ baseline }}pt</div>
|
<div class="param-value">{{ baseline }}pt</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
|
|
||||||
<div class="param-item">
|
<div class="param-item">
|
||||||
<div class="param-label">不透明度</div>
|
<div class="param-label">不透明度</div>
|
||||||
@@ -145,71 +127,76 @@
|
|||||||
<div class="column-header">字体属性</div>
|
<div class="column-header">字体属性</div>
|
||||||
|
|
||||||
<div class="text-alignment">
|
<div class="text-alignment">
|
||||||
<button
|
<div
|
||||||
class="align-btn"
|
class="align-btn"
|
||||||
:class="{ active: textAlign === 'left' }"
|
:class="{ active: textAlign === 'left' }"
|
||||||
@click="setTextAlign('left')"
|
@click="setTextAlign('left')"
|
||||||
>
|
>
|
||||||
<div class="align-icon align-left"></div>
|
<svg-icon name="CFleft" size="20" />
|
||||||
</button>
|
</div>
|
||||||
<button
|
<div
|
||||||
class="align-btn"
|
class="align-btn"
|
||||||
:class="{ active: textAlign === 'center' }"
|
:class="{ active: textAlign === 'center' }"
|
||||||
@click="setTextAlign('center')"
|
@click="setTextAlign('center')"
|
||||||
>
|
>
|
||||||
<div class="align-icon align-center"></div>
|
<svg-icon name="CFcenter" size="20" />
|
||||||
</button>
|
</div>
|
||||||
<button
|
<div
|
||||||
class="align-btn"
|
class="align-btn"
|
||||||
:class="{ active: textAlign === 'right' }"
|
:class="{ active: textAlign === 'right' }"
|
||||||
@click="setTextAlign('right')"
|
@click="setTextAlign('right')"
|
||||||
>
|
>
|
||||||
<div class="align-icon align-right"></div>
|
<svg-icon name="CFright" size="20" />
|
||||||
</button>
|
</div>
|
||||||
<button
|
<div
|
||||||
class="align-btn"
|
class="align-btn"
|
||||||
:class="{ active: textAlign === 'justify' }"
|
:class="{ active: textAlign === 'justify' }"
|
||||||
@click="setTextAlign('justify')"
|
@click="setTextAlign('justify')"
|
||||||
>
|
>
|
||||||
<div class="align-icon align-justify"></div>
|
<svg-icon name="CFjustify" size="26" />
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-styles">
|
<div class="text-styles">
|
||||||
<button
|
<div
|
||||||
class="style-btn"
|
class="style-btn"
|
||||||
:class="{ active: underline }"
|
:class="{ active: underline }"
|
||||||
@click="toggleStyle('underline', !underline)"
|
@click="toggleStyle('underline', !underline)"
|
||||||
>
|
>
|
||||||
<div class="style-icon underline-icon">U</div>
|
<div class="style-icon underline-icon">U</div>
|
||||||
</button>
|
</div>
|
||||||
<button
|
<div
|
||||||
class="style-btn"
|
class="style-btn"
|
||||||
:class="{ active: overline }"
|
:class="{ active: overline }"
|
||||||
@click="toggleStyle('overline', !overline)"
|
@click="toggleStyle('overline', !overline)"
|
||||||
>
|
>
|
||||||
<div class="style-icon overline-icon">O</div>
|
<div class="style-icon overline-icon">O</div>
|
||||||
</button>
|
</div>
|
||||||
<button
|
<div
|
||||||
class="style-btn"
|
class="style-btn"
|
||||||
:class="{ active: linethrough }"
|
:class="{ active: linethrough }"
|
||||||
@click="toggleStyle('linethrough', !linethrough)"
|
@click="toggleStyle('linethrough', !linethrough)"
|
||||||
>
|
>
|
||||||
<div class="style-icon linethrough-icon">S</div>
|
<div class="style-icon linethrough-icon">S</div>
|
||||||
</button>
|
</div>
|
||||||
<button class="style-btn color-btn" @click="openColorPicker('text')">
|
</div>
|
||||||
<div
|
<!-- 添加字体色控制区域 -->
|
||||||
class="style-icon color-icon"
|
<div class="background-controls">
|
||||||
:style="{ backgroundColor: textColor }"
|
<div class="bg-header">字体色</div>
|
||||||
></div>
|
<div class="bg-options">
|
||||||
</button>
|
<div class="style-btn color-btn" @click="openColorPicker('text')">
|
||||||
|
<div
|
||||||
|
class="style-icon color-icon"
|
||||||
|
:style="{ backgroundColor: textColor }"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 添加背景色控制区域 -->
|
<!-- 添加背景色控制区域 -->
|
||||||
<div class="background-controls">
|
<div class="background-controls">
|
||||||
<div class="bg-header">背景色</div>
|
<div class="bg-header">背景色</div>
|
||||||
<div class="bg-options">
|
<div class="bg-options">
|
||||||
<button
|
<div
|
||||||
class="style-btn color-btn"
|
class="style-btn color-btn"
|
||||||
@click="openColorPicker('background')"
|
@click="openColorPicker('background')"
|
||||||
>
|
>
|
||||||
@@ -228,14 +215,14 @@
|
|||||||
backgroundPosition: hasTransparentBg ? '0 0, 4px 4px' : '0 0',
|
backgroundPosition: hasTransparentBg ? '0 0, 4px 4px' : '0 0',
|
||||||
}"
|
}"
|
||||||
></div>
|
></div>
|
||||||
</button>
|
</div>
|
||||||
<button
|
<div
|
||||||
class="transparent-btn"
|
class="transparent-btn"
|
||||||
:class="{ active: hasTransparentBg }"
|
:class="{ active: hasTransparentBg }"
|
||||||
@click="setTransparentBackground"
|
@click="setTransparentBackground"
|
||||||
>
|
>
|
||||||
透明
|
透明
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -248,9 +235,7 @@
|
|||||||
<span>{{
|
<span>{{
|
||||||
colorPickerMode === "text" ? "选择文字颜色" : "选择背景颜色"
|
colorPickerMode === "text" ? "选择文字颜色" : "选择背景颜色"
|
||||||
}}</span>
|
}}</span>
|
||||||
<button class="close-color-picker" @click="closeColorPicker">
|
<div class="close-color-picker" @click="closeColorPicker">×</div>
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="color-picker-content">
|
<div class="color-picker-content">
|
||||||
<input
|
<input
|
||||||
@@ -268,9 +253,9 @@
|
|||||||
@click="selectPresetColor(color)"
|
@click="selectPresetColor(color)"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<button class="confirm-color-btn" @click="confirmColorSelection">
|
<div class="confirm-color-btn" @click="confirmColorSelection">
|
||||||
确定
|
确定
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -991,6 +976,7 @@ export default {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
color: #333;
|
color: #333;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-options {
|
.bg-options {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import CanvasConfig from "./config/canvasConfig.js";
|
|||||||
import { LiquifyManager } from "./managers/liquify/LiquifyManager";
|
import { LiquifyManager } from "./managers/liquify/LiquifyManager";
|
||||||
import { SelectionManager } from "./managers/selection/SelectionManager";
|
import { SelectionManager } from "./managers/selection/SelectionManager";
|
||||||
import { RedGreenModeManager } from "./managers/RedGreenModeManager";
|
import { RedGreenModeManager } from "./managers/RedGreenModeManager";
|
||||||
|
import texturePresetManager from "./managers/brushes/TexturePresetManager";
|
||||||
|
|
||||||
// import { MinimapManager } from "./managers/minimap/MinimapManager";
|
// import { MinimapManager } from "./managers/minimap/MinimapManager";
|
||||||
|
|
||||||
@@ -271,6 +272,7 @@ onMounted(async () => {
|
|||||||
provide("keyboardManager", keyboardManager); // 提供给子组件使用
|
provide("keyboardManager", keyboardManager); // 提供给子组件使用
|
||||||
provide("activeTool", activeTool); // 提供给子组件使用
|
provide("activeTool", activeTool); // 提供给子组件使用
|
||||||
provide("liquifyManager", () => liquifyManager); // 提供液化管理器
|
provide("liquifyManager", () => liquifyManager); // 提供液化管理器
|
||||||
|
provide("texturePresetManager", texturePresetManager); // 提供纹理预设管理器
|
||||||
provide("layers", layers); // 提供图层数据
|
provide("layers", layers); // 提供图层数据
|
||||||
// 初始化网格设置
|
// 初始化网格设置
|
||||||
// toggleGridVisibility(gridEnabled.value);
|
// toggleGridVisibility(gridEnabled.value);
|
||||||
|
|||||||
@@ -478,80 +478,80 @@ export class TextureBrush extends BaseBrush {
|
|||||||
order: 100,
|
order: 100,
|
||||||
hidden: allTextures.length === 0,
|
hidden: allTextures.length === 0,
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
id: "textureRepeat",
|
// id: "textureRepeat",
|
||||||
name: "纹理重复模式",
|
// name: "纹理重复模式",
|
||||||
type: "select",
|
// type: "select",
|
||||||
defaultValue: this.textureRepeat,
|
// defaultValue: this.textureRepeat,
|
||||||
options: [
|
// options: [
|
||||||
{ value: "repeat", label: "双向重复" },
|
// { value: "repeat", label: "双向重复" },
|
||||||
{ value: "repeat-x", label: "水平重复" },
|
// { value: "repeat-x", label: "水平重复" },
|
||||||
{ value: "repeat-y", label: "垂直重复" },
|
// { value: "repeat-y", label: "垂直重复" },
|
||||||
{ value: "no-repeat", label: "不重复" },
|
// { value: "no-repeat", label: "不重复" },
|
||||||
],
|
// ],
|
||||||
description: "设置纹理的重复模式",
|
// description: "设置纹理的重复模式",
|
||||||
category: "纹理设置",
|
// category: "纹理设置",
|
||||||
order: 110,
|
// order: 110,
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
id: "textureScale",
|
// id: "textureScale",
|
||||||
name: "纹理缩放",
|
// name: "纹理缩放",
|
||||||
type: "slider",
|
// type: "slider",
|
||||||
defaultValue: this.textureScale,
|
// defaultValue: this.textureScale,
|
||||||
min: 0.1,
|
// min: 0.1,
|
||||||
max: 5,
|
// max: 5,
|
||||||
step: 0.1,
|
// step: 0.1,
|
||||||
description: "调整纹理的缩放比例",
|
// description: "调整纹理的缩放比例",
|
||||||
category: "纹理设置",
|
// category: "纹理设置",
|
||||||
order: 120,
|
// order: 120,
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
id: "textureAngle",
|
// id: "textureAngle",
|
||||||
name: "纹理旋转",
|
// name: "纹理旋转",
|
||||||
type: "slider",
|
// type: "slider",
|
||||||
defaultValue: this.textureAngle,
|
// defaultValue: this.textureAngle,
|
||||||
min: 0,
|
// min: 0,
|
||||||
max: 360,
|
// max: 360,
|
||||||
step: 5,
|
// step: 5,
|
||||||
description: "调整纹理的旋转角度",
|
// description: "调整纹理的旋转角度",
|
||||||
category: "纹理设置",
|
// category: "纹理设置",
|
||||||
order: 130,
|
// order: 130,
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
id: "textureOpacity",
|
// id: "textureOpacity",
|
||||||
name: "纹理透明度",
|
// name: "纹理透明度",
|
||||||
type: "slider",
|
// type: "slider",
|
||||||
defaultValue: this.textureOpacity,
|
// defaultValue: this.textureOpacity,
|
||||||
min: 0,
|
// min: 0,
|
||||||
max: 1,
|
// max: 1,
|
||||||
step: 0.05,
|
// step: 0.05,
|
||||||
description: "调整纹理的透明度",
|
// description: "调整纹理的透明度",
|
||||||
category: "纹理设置",
|
// category: "纹理设置",
|
||||||
order: 140,
|
// order: 140,
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
id: "uploadTexture",
|
// id: "uploadTexture",
|
||||||
name: "上传纹理",
|
// name: "上传纹理",
|
||||||
type: "button",
|
// type: "file",
|
||||||
action: "uploadTexture",
|
// action: "uploadTexture",
|
||||||
description: "上传自定义纹理",
|
// description: "上传自定义纹理",
|
||||||
category: "纹理设置",
|
// category: "纹理设置",
|
||||||
order: 150,
|
// order: 150,
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
id: "texturePreview",
|
// id: "texturePreview",
|
||||||
name: "纹理预览",
|
// name: "纹理预览",
|
||||||
type: "preview",
|
// type: "preview",
|
||||||
description: "当前纹理预览",
|
// description: "当前纹理预览",
|
||||||
category: "纹理设置",
|
// category: "纹理设置",
|
||||||
order: 160,
|
// order: 160,
|
||||||
getValue: () => {
|
// getValue: () => {
|
||||||
const currentTexture = this.getCurrentTexture();
|
// const currentTexture = this.getCurrentTexture();
|
||||||
return currentTexture
|
// return currentTexture
|
||||||
? texturePresetManager.getTexturePreviewUrl(currentTexture)
|
// ? texturePresetManager.getTexturePreviewUrl(currentTexture)
|
||||||
: null;
|
// : null;
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
];
|
];
|
||||||
|
|
||||||
// 合并并返回所有属性
|
// 合并并返回所有属性
|
||||||
|
|||||||
@@ -72,6 +72,9 @@ const state = reactive({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// 上传的纹理缓存列表
|
||||||
|
uploadedTextures: [],
|
||||||
|
|
||||||
// 最近使用的颜色
|
// 最近使用的颜色
|
||||||
recentColors: ["#000000", "#ffffff", "#ff0000", "#00ff00", "#0000ff"],
|
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对象
|
// 暴露给组件使用的Store对象
|
||||||
|
|||||||