接入画布
This commit is contained in:
582
src/component/Canvas/CanvasEditor/store/BrushStore.js
Normal file
582
src/component/Canvas/CanvasEditor/store/BrushStore.js
Normal file
@@ -0,0 +1,582 @@
|
||||
import { reactive, readonly } from "vue";
|
||||
import texturePresetManager from "../managers/brushes/TexturePresetManager";
|
||||
|
||||
/**
|
||||
* 笔刷数据存储
|
||||
* 使用Vue 3的响应式API实现笔刷相关数据的全局状态管理
|
||||
*/
|
||||
const state = reactive({
|
||||
// 笔刷基础属性
|
||||
size: 5, // 笔刷大小
|
||||
color: "#000000", // 笔刷颜色
|
||||
opacity: 1, // 笔刷透明度
|
||||
type: "pencil", // 当前笔刷类型
|
||||
|
||||
// 笔刷材质相关
|
||||
textureScale: 1, // 材质缩放
|
||||
textureEnabled: false, // 是否启用材质
|
||||
texturePath: "", // 材质图片路径
|
||||
textureOpacity: 1, // 材质透明度
|
||||
textureRepeat: "repeat", // 材质重复模式
|
||||
textureAngle: 0, // 材质旋转角度
|
||||
selectedTextureId: null, // 当前选中的材质ID
|
||||
|
||||
// 可用笔刷类型列表 (由BrushManager初始化)
|
||||
availableBrushes: [],
|
||||
|
||||
// 自定义笔刷列表
|
||||
customBrushes: [],
|
||||
|
||||
// 笔刷预设
|
||||
presets: [
|
||||
{ name: "细线", size: 2, opacity: 1, color: "#000000", type: "pencil" },
|
||||
{ name: "中粗", size: 5, opacity: 1, color: "#000000", type: "pencil" },
|
||||
{ name: "粗线", size: 10, opacity: 1, color: "#000000", type: "pencil" },
|
||||
{ name: "水彩", size: 15, opacity: 0.7, color: "#3366ff", type: "marker" },
|
||||
{ name: "喷枪", size: 20, opacity: 0.5, color: "#ff6633", type: "spray" },
|
||||
],
|
||||
|
||||
// 材质预设
|
||||
texturePresets: [
|
||||
{
|
||||
name: "默认纹理",
|
||||
textureId: "preset_texture_0",
|
||||
scale: 1,
|
||||
opacity: 1,
|
||||
repeat: "repeat",
|
||||
angle: 0,
|
||||
},
|
||||
{
|
||||
name: "细纹理",
|
||||
textureId: "preset_texture_1",
|
||||
scale: 0.5,
|
||||
opacity: 0.8,
|
||||
repeat: "repeat",
|
||||
angle: 0,
|
||||
},
|
||||
{
|
||||
name: "粗纹理",
|
||||
textureId: "preset_texture_2",
|
||||
scale: 2,
|
||||
opacity: 1,
|
||||
repeat: "repeat",
|
||||
angle: 45,
|
||||
},
|
||||
{
|
||||
name: "水彩纹理",
|
||||
textureId: "preset_texture_5",
|
||||
scale: 1.5,
|
||||
opacity: 0.6,
|
||||
repeat: "no-repeat",
|
||||
angle: 0,
|
||||
},
|
||||
],
|
||||
|
||||
// 最近使用的颜色
|
||||
recentColors: ["#000000", "#ffffff", "#ff0000", "#00ff00", "#0000ff"],
|
||||
|
||||
// 最近使用的材质
|
||||
recentTextures: [],
|
||||
|
||||
// 当前笔刷可配置属性(由当前选中笔刷动态设置)
|
||||
currentBrushProperties: [],
|
||||
|
||||
// 当前笔刷实例的引用
|
||||
currentBrushInstance: null,
|
||||
|
||||
// 笔刷属性值的映射,存储可由UI修改的属性值
|
||||
propertyValues: {},
|
||||
});
|
||||
|
||||
// Actions - 修改状态的函数
|
||||
const actions = {
|
||||
setBrushSize(size) {
|
||||
state.size = Math.max(0.5, Math.min(100, size));
|
||||
},
|
||||
|
||||
setBrushColor(color) {
|
||||
state.color = color;
|
||||
// 添加到最近使用的颜色
|
||||
if (!state.recentColors.includes(color)) {
|
||||
state.recentColors.unshift(color);
|
||||
if (state.recentColors.length > 10) {
|
||||
state.recentColors.pop();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setBrushOpacity(opacity) {
|
||||
state.opacity = Math.max(0.05, Math.min(1, opacity));
|
||||
},
|
||||
|
||||
setBrushType(type) {
|
||||
if (state.availableBrushes.some((brush) => brush.id === type)) {
|
||||
state.type = type;
|
||||
}
|
||||
},
|
||||
|
||||
setTextureScale(scale) {
|
||||
state.textureScale = Math.max(0.1, Math.min(10, scale));
|
||||
},
|
||||
|
||||
setTextureEnabled(enabled) {
|
||||
state.textureEnabled = enabled;
|
||||
},
|
||||
|
||||
setTexturePath(path) {
|
||||
state.texturePath = path;
|
||||
},
|
||||
|
||||
setAvailableBrushes(brushes) {
|
||||
state.availableBrushes = brushes;
|
||||
},
|
||||
|
||||
addCustomBrush(brush) {
|
||||
if (!brush.id) {
|
||||
brush.id = `custom_${Date.now()}`;
|
||||
}
|
||||
|
||||
state.customBrushes.push(brush);
|
||||
return brush.id;
|
||||
},
|
||||
|
||||
removeCustomBrush(brushId) {
|
||||
const index = state.customBrushes.findIndex((b) => b.id === brushId);
|
||||
if (index !== -1) {
|
||||
state.customBrushes.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// 应用预设
|
||||
applyPreset(presetIndex) {
|
||||
const preset = state.presets[presetIndex];
|
||||
if (preset) {
|
||||
state.size = preset.size;
|
||||
state.opacity = preset.opacity;
|
||||
state.color = preset.color;
|
||||
state.type = preset.type;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// 将当前设置保存为新预设
|
||||
saveCurrentAsPreset(name) {
|
||||
const newPreset = {
|
||||
name: name || `预设 ${state.presets.length + 1}`,
|
||||
size: state.size,
|
||||
opacity: state.opacity,
|
||||
color: state.color,
|
||||
type: state.type,
|
||||
textureEnabled: state.textureEnabled,
|
||||
textureScale: state.textureScale,
|
||||
texturePath: state.texturePath,
|
||||
};
|
||||
|
||||
state.presets.push(newPreset);
|
||||
return state.presets.length - 1; // 返回新预设的索引
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置当前笔刷实例
|
||||
* @param {Object} brushInstance BaseBrush实例
|
||||
*/
|
||||
setCurrentBrushInstance(brushInstance) {
|
||||
state.currentBrushInstance = brushInstance;
|
||||
|
||||
// 获取并设置当前笔刷的可配置属性
|
||||
if (brushInstance && brushInstance.getConfigurableProperties) {
|
||||
const properties = brushInstance.getConfigurableProperties();
|
||||
state.currentBrushProperties = properties;
|
||||
|
||||
// 初始化属性值
|
||||
properties.forEach((prop) => {
|
||||
// 如果是基础属性,使用已有值
|
||||
if (prop.id === "size") {
|
||||
state.propertyValues[prop.id] = state.size;
|
||||
} else if (prop.id === "color") {
|
||||
state.propertyValues[prop.id] = state.color;
|
||||
} else if (prop.id === "opacity") {
|
||||
state.propertyValues[prop.id] = state.opacity;
|
||||
} else {
|
||||
// 对于特殊属性,使用默认值
|
||||
state.propertyValues[prop.id] = prop.defaultValue;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 如果没有实例或方法,清空属性列表
|
||||
state.currentBrushProperties = [];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新笔刷属性值
|
||||
* @param {String} propId 属性ID
|
||||
* @param {any} value 属性值
|
||||
*/
|
||||
updatePropertyValue(propId, value) {
|
||||
// 更新Store中的值
|
||||
state.propertyValues[propId] = value;
|
||||
|
||||
// 同步更新基础属性
|
||||
if (propId === "size") {
|
||||
state.size = value;
|
||||
} else if (propId === "color") {
|
||||
state.color = value;
|
||||
} else if (propId === "opacity") {
|
||||
state.opacity = value;
|
||||
}
|
||||
|
||||
// 如果有当前笔刷实例且有更新方法,则调用
|
||||
if (
|
||||
state.currentBrushInstance &&
|
||||
state.currentBrushInstance.updateProperty
|
||||
) {
|
||||
state.currentBrushInstance.updateProperty(propId, value);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取属性值
|
||||
* @param {String} propId 属性ID
|
||||
* @param {any} defaultValue 默认值
|
||||
* @returns {any} 属性值
|
||||
*/
|
||||
getPropertyValue(propId, defaultValue) {
|
||||
// 检查属性值是否存在
|
||||
if (state.propertyValues.hasOwnProperty(propId)) {
|
||||
return state.propertyValues[propId];
|
||||
}
|
||||
|
||||
// 对于基础属性,返回store中的值
|
||||
if (propId === "size") {
|
||||
return state.size;
|
||||
} else if (propId === "color") {
|
||||
return state.color;
|
||||
} else if (propId === "opacity") {
|
||||
return state.opacity;
|
||||
}
|
||||
|
||||
// 否则返回默认值
|
||||
return defaultValue;
|
||||
},
|
||||
|
||||
/**
|
||||
* 按分类获取当前笔刷可配置属性
|
||||
* @returns {Object} 按分类分组的属性对象
|
||||
*/
|
||||
getPropertiesByCategory() {
|
||||
const result = {};
|
||||
|
||||
state.currentBrushProperties.forEach((prop) => {
|
||||
const category = prop.category || "默认";
|
||||
if (!result[category]) {
|
||||
result[category] = [];
|
||||
}
|
||||
result[category].push({
|
||||
...prop,
|
||||
value: this.getPropertyValue(prop.id, prop.defaultValue),
|
||||
});
|
||||
});
|
||||
|
||||
// 按order排序每个分类中的属性
|
||||
Object.keys(result).forEach((category) => {
|
||||
result[category].sort((a, b) => (a.order || 0) - (b.order || 0));
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 材质相关方法
|
||||
*/
|
||||
setTextureOpacity(opacity) {
|
||||
state.textureOpacity = Math.max(0, Math.min(1, opacity));
|
||||
},
|
||||
|
||||
setTextureRepeat(repeat) {
|
||||
const validModes = ["repeat", "repeat-x", "repeat-y", "no-repeat"];
|
||||
if (validModes.includes(repeat)) {
|
||||
state.textureRepeat = repeat;
|
||||
}
|
||||
},
|
||||
|
||||
setTextureAngle(angle) {
|
||||
state.textureAngle = angle % 360;
|
||||
},
|
||||
|
||||
setSelectedTextureId(textureId) {
|
||||
state.selectedTextureId = textureId;
|
||||
|
||||
// 添加到最近使用的材质
|
||||
if (textureId && !state.recentTextures.includes(textureId)) {
|
||||
state.recentTextures.unshift(textureId);
|
||||
if (state.recentTextures.length > 8) {
|
||||
state.recentTextures.pop();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 应用材质预设
|
||||
* @param {Number} presetIndex 预设索引
|
||||
* @returns {Boolean} 是否应用成功
|
||||
*/
|
||||
applyTexturePreset(presetIndex) {
|
||||
const preset = state.texturePresets[presetIndex];
|
||||
if (preset) {
|
||||
state.selectedTextureId = preset.textureId;
|
||||
state.textureScale = preset.scale;
|
||||
state.textureOpacity = preset.opacity;
|
||||
state.textureRepeat = preset.repeat;
|
||||
state.textureAngle = preset.angle;
|
||||
|
||||
// 添加到最近使用
|
||||
this.setSelectedTextureId(preset.textureId);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* 将当前材质设置保存为新预设
|
||||
* @param {String} name 预设名称
|
||||
* @returns {Number} 新预设的索引
|
||||
*/
|
||||
saveCurrentTextureAsPreset(name) {
|
||||
const newPreset = {
|
||||
name: name || `材质预设 ${state.texturePresets.length + 1}`,
|
||||
textureId: state.selectedTextureId,
|
||||
scale: state.textureScale,
|
||||
opacity: state.textureOpacity,
|
||||
repeat: state.textureRepeat,
|
||||
angle: state.textureAngle,
|
||||
};
|
||||
|
||||
state.texturePresets.push(newPreset);
|
||||
return state.texturePresets.length - 1;
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除材质预设
|
||||
* @param {Number} presetIndex 预设索引
|
||||
* @returns {Boolean} 是否删除成功
|
||||
*/
|
||||
removeTexturePreset(presetIndex) {
|
||||
if (presetIndex >= 0 && presetIndex < state.texturePresets.length) {
|
||||
state.texturePresets.splice(presetIndex, 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取所有可用材质(预设+自定义)
|
||||
* @returns {Array} 材质列表
|
||||
*/
|
||||
getAllTextures() {
|
||||
return texturePresetManager.getAllTextures();
|
||||
},
|
||||
|
||||
/**
|
||||
* 根据ID获取材质信息
|
||||
* @param {String} textureId 材质ID
|
||||
* @returns {Object|null} 材质对象
|
||||
*/
|
||||
getTextureById(textureId) {
|
||||
return texturePresetManager.getTextureById(textureId);
|
||||
},
|
||||
|
||||
/**
|
||||
* 按分类获取材质
|
||||
* @param {String} category 分类名称
|
||||
* @returns {Array} 材质列表
|
||||
*/
|
||||
getTexturesByCategory(category) {
|
||||
return texturePresetManager.getTexturesByCategory(category);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取材质分类列表
|
||||
* @returns {Array} 分类名称数组
|
||||
*/
|
||||
getTextureCategories() {
|
||||
return texturePresetManager.getCategories();
|
||||
},
|
||||
|
||||
/**
|
||||
* 搜索材质
|
||||
* @param {String} keyword 搜索关键词
|
||||
* @returns {Array} 匹配的材质列表
|
||||
*/
|
||||
searchTextures(keyword) {
|
||||
return texturePresetManager.searchTextures(keyword);
|
||||
},
|
||||
|
||||
/**
|
||||
* 添加自定义材质
|
||||
* @param {Object} textureData 材质数据
|
||||
* @returns {String} 材质ID
|
||||
*/
|
||||
addCustomTexture(textureData) {
|
||||
const textureId = texturePresetManager.addCustomTexture(textureData);
|
||||
|
||||
// 保存到本地存储
|
||||
texturePresetManager.saveCustomTexturesToStorage();
|
||||
|
||||
return textureId;
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除自定义材质
|
||||
* @param {String} textureId 材质ID
|
||||
* @returns {Boolean} 是否删除成功
|
||||
*/
|
||||
removeCustomTexture(textureId) {
|
||||
const success = texturePresetManager.removeCustomTexture(textureId);
|
||||
|
||||
if (success) {
|
||||
// 如果删除的是当前选中的材质,清空选择
|
||||
if (state.selectedTextureId === textureId) {
|
||||
state.selectedTextureId = null;
|
||||
}
|
||||
|
||||
// 从最近使用中移除
|
||||
const recentIndex = state.recentTextures.indexOf(textureId);
|
||||
if (recentIndex !== -1) {
|
||||
state.recentTextures.splice(recentIndex, 1);
|
||||
}
|
||||
|
||||
// 保存到本地存储
|
||||
texturePresetManager.saveCustomTexturesToStorage();
|
||||
}
|
||||
|
||||
return success;
|
||||
},
|
||||
|
||||
/**
|
||||
* 从文件上传自定义材质
|
||||
* @param {File} file 图片文件
|
||||
* @param {String} name 材质名称(可选)
|
||||
* @returns {Promise<String>} 材质ID
|
||||
*/
|
||||
uploadCustomTexture(file, name) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 验证文件
|
||||
if (!texturePresetManager.validateTextureFile(file)) {
|
||||
reject(new Error("无效的材质文件"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 读取文件
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const textureData = {
|
||||
name: name || file.name.replace(/\.[^/.]+$/, ""),
|
||||
path: e.target.result,
|
||||
preview: e.target.result,
|
||||
description: `用户上传的材质: ${file.name}`,
|
||||
};
|
||||
|
||||
const textureId = this.addCustomTexture(textureData);
|
||||
resolve(textureId);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
reader.onerror = () => {
|
||||
reject(new Error("文件读取失败"));
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 导出材质预设配置
|
||||
* @returns {String} JSON格式的配置
|
||||
*/
|
||||
exportTexturePresets() {
|
||||
const config = {
|
||||
texturePresets: state.texturePresets,
|
||||
customTextures: texturePresetManager.exportCustomTextures(),
|
||||
};
|
||||
return JSON.stringify(config, null, 2);
|
||||
},
|
||||
|
||||
/**
|
||||
* 导入材质预设配置
|
||||
* @param {String} configJson JSON格式的配置
|
||||
* @returns {Boolean} 是否导入成功
|
||||
*/
|
||||
importTexturePresets(configJson) {
|
||||
try {
|
||||
const config = JSON.parse(configJson);
|
||||
|
||||
// 导入材质预设
|
||||
if (config.texturePresets && Array.isArray(config.texturePresets)) {
|
||||
state.texturePresets = [
|
||||
...state.texturePresets,
|
||||
...config.texturePresets,
|
||||
];
|
||||
}
|
||||
|
||||
// 导入自定义材质
|
||||
if (config.customTextures) {
|
||||
texturePresetManager.importCustomTextures(config.customTextures);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("导入材质预设失败:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始化材质预设管理器
|
||||
*/
|
||||
initializeTexturePresets() {
|
||||
// 从本地存储加载自定义材质
|
||||
texturePresetManager.loadCustomTexturesFromStorage();
|
||||
|
||||
// 确保预设材质引用的是有效的材质ID
|
||||
state.texturePresets.forEach((preset, index) => {
|
||||
const texture = texturePresetManager.getTextureById(preset.textureId);
|
||||
if (!texture) {
|
||||
console.warn(
|
||||
`材质预设 "${preset.name}" 引用的材质 ${preset.textureId} 不存在`
|
||||
);
|
||||
// 可以选择使用默认材质替换或删除该预设
|
||||
if (texturePresetManager.getAllTextures().length > 0) {
|
||||
preset.textureId = texturePresetManager.getAllTextures()[0].id;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
// 暴露给组件使用的Store对象
|
||||
export const BrushStore = {
|
||||
// 只读状态,防止直接修改
|
||||
state: readonly(state),
|
||||
|
||||
// 可调用的Actions
|
||||
...actions,
|
||||
|
||||
// 辅助方法
|
||||
getRGBAColor() {
|
||||
// 解析十六进制颜色并添加透明度
|
||||
const hex = state.color.replace("#", "");
|
||||
const r = parseInt(hex.substring(0, 2), 16);
|
||||
const g = parseInt(hex.substring(2, 4), 16);
|
||||
const b = parseInt(hex.substring(4, 6), 16);
|
||||
|
||||
return `rgba(${r}, ${g}, ${b}, ${state.opacity})`;
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user