CapabilitySystem/Packages/AutoUI/Editor/AutoUIWindow.cs
2026-04-15 19:47:09 +08:00

5218 lines
189 KiB
C#
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using UnityEditor.PackageManager;
using Newtonsoft.Json;
namespace AutoUI
{
/// <summary>
/// AutoUI 主窗口 - 对应原 panel.js
/// </summary>
public class AutoUIWindow : EditorWindow
{
[SerializeField] private VisualTreeAsset m_UXML;
// 服务实例
private AIService _aiService;
private AuthService _authService;
private PrefabBuilder _prefabBuilder;
private UpdateService _updateService;
// UI 元素引用 - 主界面
private TextField _imagePathField;
private TextField _texturePathField;
private Button _browseImageBtn;
private Button _browseTextureBtn;
private Button _generateBtn;
private Label _statusLabel;
private ScrollView _logScrollView;
// 图片拖放区域(需要在选图前后切换状态)
private VisualElement _imageDropZone;
private Image _dropZoneImage;
private Label _dropZoneText;
private Label _dropZoneInfo;
// 切图文件夹拖放区域(需要在选文件夹前后切换状态)
private VisualElement _textureDropZone;
private Label _textureDropText;
private Label _textureDropPath;
private Label _textureDropCount;
// 授权工具栏
private VisualElement _authToolbar;
private Label _toolbarBadge;
private Label _quotaText;
private Button _toolbarUpgradeBtn;
private Button _toolbarMenuBtn;
// 当前授权状态
private AuthResult _currentAuthState;
private bool _serverOnline;
// UI 元素引用 - 面板
private VisualElement _authPanel;
private VisualElement _mainPanel;
// UI 元素引用 - Tab导航
private Button _tabBtnGenerate;
private Button _tabBtnSettings;
private Button _tabBtnFeedback;
private VisualElement _tabGenerate;
private VisualElement _tabSettings;
private VisualElement _tabFeedback;
// UI 元素引用 - 设置Tab
private VisualElement _commonFoldersContainer;
private Button _addCommonFolderBtn;
private Label _commonFoldersHint;
private Label _folderCountBadge; // 文件夹数量徽章
// 公共文件夹列表
private List<string> _commonFolders = new List<string>();
private const int MAX_COMMON_FOLDERS = 5;
// UI 元素引用 - 登录
private VisualElement _loginPanel;
private TextField _loginEmailField;
private TextField _loginPasswordField;
private Button _loginBtn;
private Button _showRegisterBtn;
private Button _forgotPasswordBtn; // 【新增】忘记密码按钮
private Label _loginErrorLabel; // 【新增】登录错误提示
// UI 元素引用 - 注册
private VisualElement _registerPanel;
private TextField _regEmailField;
private TextField _regCodeField;
private TextField _regPasswordField;
private TextField _regConfirmPasswordField;
private Button _sendCodeBtn;
private Button _registerBtn;
private Button _showLoginBtn;
private Label _codeHintLabel;
private Label _registerErrorLabel; // 【新增】注册错误提示
// 【新增】UI 元素引用 - 重置密码
private VisualElement _resetPasswordPanel;
private TextField _resetEmailField;
private TextField _resetCodeField;
private TextField _resetNewPasswordField;
private TextField _resetConfirmPasswordField;
private Button _resetSendCodeBtn;
private Button _resetPasswordBtn;
private Button _resetBackToLoginBtn;
private Label _resetCodeHintLabel;
private Label _resetErrorLabel; // 【新增】重置密码错误提示
private bool _isSendingCode = false;
private bool _isGenerating = false;
private List<TextureFileInfo> _textureFiles = new List<TextureFileInfo>();
// 【新增】防连点机制
private Dictionary<string, DateTime> _lastClickTime = new Dictionary<string, DateTime>();
private const int COOLDOWN_SECONDS = 3; // 3秒冷却期
// 【新增】树形视图、JSON视图
private VisualElement _resultViewContainer;
private TextField _jsonViewField;
private ScrollView _jsonScrollView; // JSON视图的滚动容器
private VisualElement _treeViewContainer;
private ToolbarMenu _viewModeMenu;
private enum ViewMode { Json, Tree }
private ViewMode _currentViewMode = ViewMode.Json;
private UUIStructure _currentUIStructure;
private HashSet<string> _expandedTreeNodes = new HashSet<string>();
private string _selectedTreeNodePath;
// 【新增】反馈Tab
private TextField _feedbackTextField;
private Button _feedbackSubmitBtn;
private Label _feedbackHintLabel;
private VisualElement _feedbackSuccessContainer;
private Button _feedbackErrorBtn; // 状态栏错误反馈按钮
// 【新增】Tab状态
private enum TabType { Generate, Settings, Feedback }
private TabType _currentTab = TabType.Generate;
// 【新增】加载动画相关
private VisualElement _loadingOverlay;
private VisualElement _loadingSpinner;
private Label _loadingText;
private int _loadingDots = 0;
private long _loadingStartTime = 0;
private float _spinnerRotation = 0f;
// 【新增】错误反馈状态
private Exception _lastError;
private bool _errorFeedbackSubmitted;
// 【新增】DEBUG开关 - 控制生成结果显示
private const bool DEBUG_SHOW_RESULT = false;
// 【新增】分析模式选择
private AnalysisMode _currentAnalysisMode = AnalysisMode.UnityNative;
private UnityPrefabBuilder _unityPrefabBuilder;
private UnityPrefabNode _currentUnityPrefab; // 【新增】保存Unity原生模式结果
// EditorPrefs keys
private const string PREFS_TEXTURE_FOLDER_PATH = "AutoUI_TextureFolderPath";
private const string PREFS_LAST_IMAGE_DIR = "AutoUI_LastImageDir";
private const string PREFS_QUICK_MODE_IMAGE = "AutoUI_QuickMode_Image";
private const string PREFS_QUICK_MODE_TEXTURE = "AutoUI_QuickMode_Texture";
// 【新增】状态栏容器(根级别,始终显示)
private VisualElement _statusBarContainer;
private VisualElement _statusDot;
public static void ShowWindow()
{
var window = GetWindow<AutoUIWindow>();
window.titleContent = new GUIContent("AutoUI界面生成器");
window.minSize = new Vector2(450, 700);
}
private void CreateGUI()
{
// 加载UXML
var root = rootVisualElement;
// 如果没有指定UXML创建默认UI
if (m_UXML == null)
{
CreateDefaultUI(root);
}
else
{
m_UXML.CloneTree(root);
}
// 绑定UI事件
BindUIElements();
// 【关键】此时默认面板已显示,再开始同步初始化
// 初始化过程中状态栏会显示进度
InitializeSync();
}
/// <summary>
/// 【修复】同步初始化流程
/// 每一步都是同步调用,避免异步带来的复杂性
/// </summary>
private void InitializeSync()
{
try
{
Log("=== AutoUI 同步初始化开始 ===");
// 1. 同步初始化服务
Log("[1/4] 初始化服务...");
UpdateStatus("初始化服务中...", StatusColor.Info);
InitializeServicesSync();
// 2. 同步检查服务器状态
Log("[2/4] 检查服务器状态...");
UpdateStatus("检查服务器状态...", StatusColor.Info);
bool serverOk = CheckServerStatusSync();
if (!serverOk)
{
LogError("服务器连接失败,初始化终止");
UpdateStatus("初始化失败:服务器连接失败", StatusColor.Error);
ShowErrorFeedbackButton(new Exception("服务器连接失败"));
return;
}
// 3. 同步检查授权状态
Log("[3/4] 检查授权状态...");
UpdateStatus("检查授权状态...", StatusColor.Info);
RefreshAuthStateSync();
// 4. 更新UI
Log("[4/4] 更新界面状态...");
UpdateUIBasedOnState();
UpdateStatus("准备就绪", StatusColor.Success);
// 【首次体验优化】5. 检查是否首次使用
ShowFirstTimeGuide();
// 6. 延迟检查更新(异步,不阻塞)
_ = Task.Run(async () =>
{
await Task.Delay(2000);
try
{
await CheckForUpdatesAsync();
}
catch (Exception ex)
{
Debug.LogWarning($"[AutoUI] 自动检查更新失败: {ex.Message}");
}
});
Log("=== AutoUI 同步初始化完成 ===");
}
catch (Exception ex)
{
LogError($"初始化失败: {ex.Message}");
UpdateStatus($"初始化失败: {ex.Message}", StatusColor.Error);
ShowErrorFeedbackButton(ex);
Debug.LogException(ex);
}
}
/// <summary>
/// 同步初始化服务(后台线程网络请求,主线程处理结果)
/// </summary>
private void InitializeServicesSync()
{
try
{
// 【主线程】获取设备指纹和用户数据
Log("获取设备指纹...");
var deviceResult = DeviceFingerprint.Generate();
var deviceInfo = DeviceFingerprint.GetInfo();
// 【新增】加载公共文件夹设置
LoadCommonFolders();
string savedToken = EditorPrefs.GetString("autoui_token", "");
string savedUserInfo = EditorPrefs.GetString("autoui_user_info", "");
string savedUsername = "未知用户";
if (!string.IsNullOrEmpty(savedUserInfo))
{
try
{
var userInfo = JsonUtility.FromJson<UserInfo>(savedUserInfo);
savedUsername = userInfo?.email ?? "未知用户";
}
catch { }
}
// 【后台线程】执行网络初始化
_authService = new AuthService();
_authService.PreInitialize(deviceResult, deviceInfo, savedToken, savedUsername);
Task.Run(() => _authService.InitializeSync()).GetAwaiter().GetResult();
_aiService = new AIService(_authService);
_prefabBuilder = new PrefabBuilder();
_updateService = new UpdateService();
Task.Run(() => _updateService.InitializeAsync()).GetAwaiter().GetResult();
Log("服务初始化完成");
}
catch (Exception ex)
{
LogError($"服务初始化失败: {ex.Message}");
throw;
}
}
/// <summary>
/// 同步检查服务器状态(后台线程网络请求)
/// </summary>
private bool CheckServerStatusSync()
{
try
{
var status = Task.Run(() => _authService.CheckServerStatusSync()).GetAwaiter().GetResult();
_serverOnline = status.online;
if (status.online)
{
Log($"服务器状态: 在线 (版本: {status.version})");
return true;
}
else
{
LogError($"服务器状态: 离线 ({status.error})");
return false;
}
}
catch (Exception ex)
{
_serverOnline = false;
LogError($"服务器检查失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 同步刷新授权状态(后台线程网络请求,主线程处理结果)
/// </summary>
private void RefreshAuthStateSync(bool forceRefresh = false)
{
try
{
// 如果强制刷新,清除缓存
if (forceRefresh)
{
_authService?.ClearAuthCache();
}
// 使用 AuthService.Token已在主线程初始化时缓存
var authState = Task.Run(() => _authService.GetAuthSync(_authService.Token)).GetAwaiter().GetResult();
_currentAuthState = authState;
// 调试日志
Log($"授权状态刷新完成: Type={authState.Type}, Valid={authState.Valid}, Credits={authState.Credits}");
}
catch (Exception ex)
{
LogError($"刷新授权状态失败: {ex.Message}");
}
}
/// <summary>
/// 【新增】状态颜色枚举
/// </summary>
private enum StatusColor
{
Info, // 蓝色
Success, // 绿色
Warning, // 黄色
Error // 红色
}
/// <summary>
/// 【新增】更新状态栏(带颜色)
/// </summary>
private void UpdateStatus(string message, StatusColor color)
{
if (_statusLabel == null) return;
_statusLabel.text = message;
// 更新状态点颜色
if (_statusDot != null)
{
_statusDot.style.backgroundColor = color switch
{
StatusColor.Success => new Color(0.2f, 0.8f, 0.2f),
StatusColor.Warning => new Color(1f, 0.8f, 0f),
StatusColor.Error => new Color(1f, 0.3f, 0.3f),
_ => new Color(0.4f, 0.6f, 1f)
};
}
}
private void CreateDefaultUI(VisualElement root)
{
root.style.paddingTop = 0;
root.style.paddingLeft = 0;
root.style.paddingRight = 0;
root.style.paddingBottom = 0;
root.style.backgroundColor = new Color(0.12f, 0.12f, 0.14f);
// ===== 标题栏 =====
var headerContainer = new VisualElement()
{
style =
{
flexDirection = FlexDirection.Row,
alignItems = Align.Center,
paddingTop = 14,
paddingBottom = 10,
paddingLeft = 14,
paddingRight = 14,
backgroundColor = new Color(0.15f, 0.15f, 0.17f)
}
};
var title = new Label("AutoUI")
{
style = {
fontSize = 18,
unityFontStyleAndWeight = FontStyle.Bold,
color = new Color(0.95f, 0.95f, 0.95f)
}
};
headerContainer.Add(title);
var versionLabel = new Label($" v{Config.Version}")
{
style = {
fontSize = 12,
color = new Color(0.6f, 0.6f, 0.6f),
marginTop = 3
}
};
headerContainer.Add(versionLabel);
// 弹性空间
headerContainer.Add(new VisualElement() { style = { flexGrow = 1 } });
// 菜单按钮
_toolbarMenuBtn = new Button() { text = "⋮", style = { width = 28, height = 28, fontSize = 16 } };
_toolbarMenuBtn.style.backgroundColor = Color.clear;
_toolbarMenuBtn.style.borderLeftWidth = 0;
_toolbarMenuBtn.style.borderRightWidth = 0;
_toolbarMenuBtn.style.borderTopWidth = 0;
_toolbarMenuBtn.style.borderBottomWidth = 0;
_toolbarMenuBtn.style.borderTopLeftRadius = 4;
_toolbarMenuBtn.style.borderTopRightRadius = 4;
_toolbarMenuBtn.style.borderBottomLeftRadius = 4;
_toolbarMenuBtn.style.borderBottomRightRadius = 4;
_toolbarMenuBtn.style.color = new Color(0.75f, 0.75f, 0.75f);
headerContainer.Add(_toolbarMenuBtn);
root.Add(headerContainer);
// 认证面板容器
_authPanel = new VisualElement()
{
style =
{
paddingTop = 40,
paddingBottom = 20,
paddingLeft = 30,
paddingRight = 30
}
};
// ===== 登录面板 =====
_loginPanel = new VisualElement()
{
style =
{
display = DisplayStyle.None,
backgroundColor = new Color(0.12f, 0.12f, 0.14f),
borderTopLeftRadius = 8,
borderTopRightRadius = 8,
borderBottomLeftRadius = 8,
borderBottomRightRadius = 8,
paddingTop = 25,
paddingBottom = 25,
paddingLeft = 25,
paddingRight = 25,
marginBottom = 10
}
};
var loginTitle = new Label("用户登录")
{
style =
{
unityFontStyleAndWeight = FontStyle.Bold,
fontSize = 20,
color = new Color(0.95f, 0.95f, 0.95f),
marginBottom = 20,
unityTextAlign = TextAnchor.MiddleCenter
}
};
_loginPanel.Add(loginTitle);
_loginEmailField = new TextField("邮箱")
{
value = "",
style =
{
height = 36,
fontSize = 14,
marginBottom = 12,
marginTop = 8
}
};
_loginPanel.Add(_loginEmailField);
_loginPasswordField = new TextField("密码")
{
isPasswordField = true,
value = "",
style =
{
height = 36,
fontSize = 14,
marginBottom = 12,
marginTop = 8
}
};
_loginPanel.Add(_loginPasswordField);
var loginBtnRow = new VisualElement()
{
style = { flexDirection = FlexDirection.Row, marginTop = 15 }
};
_loginBtn = new Button()
{
text = "登录",
style =
{
flexGrow = 1,
height = 36,
fontSize = 14,
unityFontStyleAndWeight = FontStyle.Bold
}
};
_showRegisterBtn = new Button()
{
text = "注册账号",
style =
{
marginLeft = 10,
height = 36,
width = 90
}
};
loginBtnRow.Add(_loginBtn);
loginBtnRow.Add(_showRegisterBtn);
_loginPanel.Add(loginBtnRow);
// 【新增】登录错误提示
_loginErrorLabel = new Label("")
{
style =
{
color = new Color(1f, 0.3f, 0.3f),
fontSize = 12,
marginTop = 8,
display = DisplayStyle.None,
unityTextAlign = TextAnchor.MiddleCenter
}
};
_loginPanel.Add(_loginErrorLabel);
// 忘记密码链接
_forgotPasswordBtn = new Button()
{
text = "忘记密码?",
style =
{
marginTop = 12,
height = 24,
color = new Color(0.3f, 0.7f, 1f),
backgroundColor = Color.clear,
borderLeftColor = Color.clear,
borderRightColor = Color.clear,
borderTopColor = Color.clear,
borderBottomColor = Color.clear,
unityTextAlign = TextAnchor.MiddleCenter
}
};
_loginPanel.Add(_forgotPasswordBtn);
// 返回主界面按钮
var backToMainBtn = new Button()
{
text = "← 返回主界面",
style =
{
marginTop = 15,
height = 28,
color = new Color(0.6f, 0.6f, 0.6f),
backgroundColor = Color.clear,
borderLeftColor = Color.clear,
borderRightColor = Color.clear,
borderTopColor = Color.clear,
borderBottomColor = Color.clear,
unityTextAlign = TextAnchor.MiddleCenter,
fontSize = 12
}
};
backToMainBtn.clicked += () =>
{
_authPanel.style.display = DisplayStyle.None;
_mainPanel.style.display = DisplayStyle.Flex;
};
_loginPanel.Add(backToMainBtn);
_authPanel.Add(_loginPanel);
// ===== 注册面板 =====
_registerPanel = new VisualElement()
{
style =
{
display = DisplayStyle.None,
backgroundColor = new Color(0.12f, 0.12f, 0.14f),
borderTopLeftRadius = 8,
borderTopRightRadius = 8,
borderBottomLeftRadius = 8,
borderBottomRightRadius = 8,
paddingTop = 25,
paddingBottom = 25,
paddingLeft = 25,
paddingRight = 25,
marginBottom = 10
}
};
var regTitle = new Label("注册账号")
{
style =
{
unityFontStyleAndWeight = FontStyle.Bold,
fontSize = 20,
color = new Color(0.95f, 0.95f, 0.95f),
marginBottom = 20,
unityTextAlign = TextAnchor.MiddleCenter
}
};
_registerPanel.Add(regTitle);
_regEmailField = new TextField("邮箱")
{
style = { height = 36, fontSize = 14, marginBottom = 12, marginTop = 8 }
};
_registerPanel.Add(_regEmailField);
// 验证码行
var codeRow = new VisualElement()
{
style = { flexDirection = FlexDirection.Row, marginBottom = 12, alignItems = Align.FlexEnd }
};
_regCodeField = new TextField("验证码")
{
style = { flexGrow = 1, height = 36, fontSize = 14, marginTop = 8 }
};
_sendCodeBtn = new Button()
{
text = "发送",
style = { width = 80, marginLeft = 8, height = 36 }
};
codeRow.Add(_regCodeField);
codeRow.Add(_sendCodeBtn);
_registerPanel.Add(codeRow);
_codeHintLabel = new Label("")
{
style = { color = new Color(0.6f, 0.6f, 0.6f), fontSize = 11, marginTop = -5, marginBottom = 8 }
};
_registerPanel.Add(_codeHintLabel);
_regPasswordField = new TextField("密码")
{
isPasswordField = true,
style = { height = 36, fontSize = 14, marginBottom = 12, marginTop = 8 }
};
_registerPanel.Add(_regPasswordField);
_regConfirmPasswordField = new TextField("确认密码")
{
isPasswordField = true,
style = { height = 36, fontSize = 14, marginBottom = 12, marginTop = 8 }
};
_registerPanel.Add(_regConfirmPasswordField);
var regBtnRow = new VisualElement()
{
style = { flexDirection = FlexDirection.Row, marginTop = 15 }
};
_registerBtn = new Button()
{
text = "注册",
style = { flexGrow = 1, height = 36, fontSize = 14, unityFontStyleAndWeight = FontStyle.Bold }
};
_showLoginBtn = new Button()
{
text = "返回登录",
style = { marginLeft = 10, height = 36, width = 90 }
};
regBtnRow.Add(_registerBtn);
regBtnRow.Add(_showLoginBtn);
_registerPanel.Add(regBtnRow);
// 【新增】注册错误提示
_registerErrorLabel = new Label("")
{
style =
{
color = new Color(1f, 0.3f, 0.3f),
fontSize = 12,
marginTop = 8,
display = DisplayStyle.None,
unityTextAlign = TextAnchor.MiddleCenter
}
};
_registerPanel.Add(_registerErrorLabel);
// 返回主界面按钮
var regBackToMainBtn = new Button()
{
text = "← 返回主界面",
style =
{
marginTop = 15,
height = 28,
color = new Color(0.6f, 0.6f, 0.6f),
backgroundColor = Color.clear,
borderLeftColor = Color.clear,
borderRightColor = Color.clear,
borderTopColor = Color.clear,
borderBottomColor = Color.clear,
unityTextAlign = TextAnchor.MiddleCenter,
fontSize = 12
}
};
regBackToMainBtn.clicked += () =>
{
_authPanel.style.display = DisplayStyle.None;
_mainPanel.style.display = DisplayStyle.Flex;
};
_registerPanel.Add(regBackToMainBtn);
_authPanel.Add(_registerPanel);
// ===== 重置密码面板 =====
_resetPasswordPanel = new VisualElement()
{
style =
{
display = DisplayStyle.None,
backgroundColor = new Color(0.12f, 0.12f, 0.14f),
borderTopLeftRadius = 8,
borderTopRightRadius = 8,
borderBottomLeftRadius = 8,
borderBottomRightRadius = 8,
paddingTop = 25,
paddingBottom = 25,
paddingLeft = 25,
paddingRight = 25,
marginBottom = 10
}
};
var resetTitle = new Label("重置密码")
{
style =
{
unityFontStyleAndWeight = FontStyle.Bold,
fontSize = 20,
color = new Color(0.95f, 0.95f, 0.95f),
marginBottom = 20,
unityTextAlign = TextAnchor.MiddleCenter
}
};
_resetPasswordPanel.Add(resetTitle);
_resetEmailField = new TextField("邮箱")
{
style = { height = 36, fontSize = 14, marginBottom = 12, marginTop = 8 }
};
_resetPasswordPanel.Add(_resetEmailField);
// 验证码行
var resetCodeRow = new VisualElement()
{
style = { flexDirection = FlexDirection.Row, marginBottom = 12, alignItems = Align.FlexEnd }
};
_resetCodeField = new TextField("验证码")
{
style = { flexGrow = 1, height = 36, fontSize = 14, marginTop = 8 }
};
_resetSendCodeBtn = new Button()
{
text = "发送",
style = { width = 80, marginLeft = 8, height = 36 }
};
resetCodeRow.Add(_resetCodeField);
resetCodeRow.Add(_resetSendCodeBtn);
_resetPasswordPanel.Add(resetCodeRow);
_resetCodeHintLabel = new Label("")
{
style = { color = new Color(0.6f, 0.6f, 0.6f), fontSize = 11, marginTop = -5, marginBottom = 8 }
};
_resetPasswordPanel.Add(_resetCodeHintLabel);
_resetNewPasswordField = new TextField("新密码")
{
isPasswordField = true,
style = { height = 36, fontSize = 14, marginBottom = 12, marginTop = 8 }
};
_resetPasswordPanel.Add(_resetNewPasswordField);
_resetConfirmPasswordField = new TextField("确认新密码")
{
isPasswordField = true,
style = { height = 36, fontSize = 14, marginBottom = 12, marginTop = 8 }
};
_resetPasswordPanel.Add(_resetConfirmPasswordField);
var resetBtnRow = new VisualElement()
{
style = { flexDirection = FlexDirection.Row, marginTop = 15 }
};
_resetPasswordBtn = new Button()
{
text = "重置密码",
style = { flexGrow = 1, height = 36, fontSize = 14, unityFontStyleAndWeight = FontStyle.Bold }
};
_resetBackToLoginBtn = new Button()
{
text = "返回登录",
style = { marginLeft = 10, height = 36, width = 90 }
};
resetBtnRow.Add(_resetPasswordBtn);
resetBtnRow.Add(_resetBackToLoginBtn);
_resetPasswordPanel.Add(resetBtnRow);
// 【新增】重置密码错误提示
_resetErrorLabel = new Label("")
{
style =
{
color = new Color(1f, 0.3f, 0.3f),
fontSize = 12,
marginTop = 8,
display = DisplayStyle.None,
unityTextAlign = TextAnchor.MiddleCenter
}
};
_resetPasswordPanel.Add(_resetErrorLabel);
// 返回主界面按钮
var resetBackToMainBtn = new Button()
{
text = "← 返回主界面",
style =
{
marginTop = 15,
height = 28,
color = new Color(0.6f, 0.6f, 0.6f),
backgroundColor = Color.clear,
borderLeftColor = Color.clear,
borderRightColor = Color.clear,
borderTopColor = Color.clear,
borderBottomColor = Color.clear,
unityTextAlign = TextAnchor.MiddleCenter,
fontSize = 12
}
};
resetBackToMainBtn.clicked += () =>
{
_authPanel.style.display = DisplayStyle.None;
_mainPanel.style.display = DisplayStyle.Flex;
};
_resetPasswordPanel.Add(resetBackToMainBtn);
_authPanel.Add(_resetPasswordPanel);
root.Add(_authPanel);
// 主面板容器
_mainPanel = new VisualElement()
{
style =
{
paddingLeft = 14,
paddingRight = 14,
paddingTop = 12,
paddingBottom = 14
}
};
// ===== 授权工具栏(顶部,登录后始终显示)=====
_authToolbar = new VisualElement()
{
style =
{
flexDirection = FlexDirection.Row,
backgroundColor = new Color(0.18f, 0.18f, 0.2f),
paddingLeft = 12,
paddingRight = 12,
paddingTop = 8,
paddingBottom = 8,
marginBottom = 15,
borderBottomLeftRadius = 8,
borderBottomRightRadius = 8,
borderTopLeftRadius = 8,
borderTopRightRadius = 8,
alignItems = Align.Center,
display = DisplayStyle.None // 默认隐藏,登录后显示
}
};
// 套餐徽章
_toolbarBadge = new Label("畅享版")
{
style =
{
backgroundColor = new Color(0.25f, 0.55f, 0.95f),
color = Color.white,
paddingLeft = 10,
paddingRight = 10,
paddingTop = 4,
paddingBottom = 4,
borderTopLeftRadius = 4,
borderTopRightRadius = 4,
borderBottomLeftRadius = 4,
borderBottomRightRadius = 4,
fontSize = 12
}
};
_authToolbar.Add(_toolbarBadge);
// 额度显示
var quotaContainer = new VisualElement() { style = { flexDirection = FlexDirection.Row, alignItems = Align.Center } };
quotaContainer.Add(new Label("剩余 ") { style = { color = new Color(0.92f, 0.92f, 0.92f), fontSize = 13 } });
_quotaText = new Label("0")
{
style =
{
color = new Color(0.3f, 0.9f, 0.4f),
fontSize = 14,
unityFontStyleAndWeight = FontStyle.Bold
}
};
quotaContainer.Add(_quotaText);
_authToolbar.Add(quotaContainer);
// 弹性空间
_authToolbar.Add(new VisualElement() { style = { flexGrow = 1 } });
// 升级按钮
_toolbarUpgradeBtn = new Button() { text = "升级" };
_toolbarUpgradeBtn.style.backgroundColor = new Color(0.95f, 0.6f, 0.1f);
_toolbarUpgradeBtn.style.borderTopLeftRadius = 6;
_toolbarUpgradeBtn.style.borderBottomLeftRadius = 6;
_toolbarUpgradeBtn.style.borderTopRightRadius = 6;
_toolbarUpgradeBtn.style.borderBottomRightRadius = 6;
_toolbarUpgradeBtn.style.color = Color.white;
_toolbarUpgradeBtn.style.fontSize = 12;
_toolbarUpgradeBtn.clicked += ShowSubscribePanel;
_authToolbar.Add(_toolbarUpgradeBtn);
// 菜单按钮(工具栏内)
var toolbarMenuBtn = new Button() { text = "⋮", style = { marginLeft = 8, width = 24, height = 24, fontSize = 14 } };
toolbarMenuBtn.style.backgroundColor = Color.clear;
toolbarMenuBtn.style.borderLeftWidth = 0;
toolbarMenuBtn.style.borderRightWidth = 0;
toolbarMenuBtn.style.borderTopWidth = 0;
toolbarMenuBtn.style.borderBottomWidth = 0;
toolbarMenuBtn.style.color = new Color(0.8f, 0.8f, 0.8f);
toolbarMenuBtn.clicked += OnLogoutClicked;
_authToolbar.Add(toolbarMenuBtn);
_mainPanel.Add(_authToolbar);
// ===== Tab导航简洁下划线样式=====
var tabNav = new VisualElement()
{
style =
{
flexDirection = FlexDirection.Row,
marginBottom = 16
}
};
_tabBtnGenerate = new Button() { text = "生成界面" };
_tabBtnGenerate.style.backgroundColor = Color.clear;
_tabBtnGenerate.style.borderLeftWidth = 0;
_tabBtnGenerate.style.borderRightWidth = 0;
_tabBtnGenerate.style.borderTopWidth = 0;
_tabBtnGenerate.style.borderBottomWidth = 2;
_tabBtnGenerate.style.borderBottomColor = new Color(0.25f, 0.55f, 0.95f);
_tabBtnGenerate.style.borderTopLeftRadius = 0;
_tabBtnGenerate.style.borderTopRightRadius = 0;
_tabBtnGenerate.style.borderBottomLeftRadius = 0;
_tabBtnGenerate.style.borderBottomRightRadius = 0;
_tabBtnGenerate.style.color = new Color(0.9f, 0.9f, 0.9f);
_tabBtnGenerate.style.fontSize = 14;
_tabBtnGenerate.style.paddingLeft = 8;
_tabBtnGenerate.style.paddingRight = 8;
_tabBtnGenerate.style.paddingTop = 8;
_tabBtnGenerate.style.paddingBottom = 8;
_tabBtnGenerate.style.marginRight = 16;
_tabBtnSettings = new Button() { text = "系统设置" };
_tabBtnSettings.style.backgroundColor = Color.clear;
_tabBtnSettings.style.borderLeftWidth = 0;
_tabBtnSettings.style.borderRightWidth = 0;
_tabBtnSettings.style.borderTopWidth = 0;
_tabBtnSettings.style.borderBottomWidth = 2;
_tabBtnSettings.style.borderBottomColor = Color.clear;
_tabBtnSettings.style.borderTopLeftRadius = 0;
_tabBtnSettings.style.borderTopRightRadius = 0;
_tabBtnSettings.style.borderBottomLeftRadius = 0;
_tabBtnSettings.style.borderBottomRightRadius = 0;
_tabBtnSettings.style.color = new Color(0.7f, 0.7f, 0.7f);
_tabBtnSettings.style.fontSize = 14;
_tabBtnSettings.style.paddingLeft = 8;
_tabBtnSettings.style.paddingRight = 8;
_tabBtnSettings.style.paddingTop = 8;
_tabBtnSettings.style.paddingBottom = 8;
_tabBtnSettings.style.marginRight = 16;
_tabBtnFeedback = new Button() { text = "反馈意见" };
_tabBtnFeedback.style.backgroundColor = Color.clear;
_tabBtnFeedback.style.borderLeftWidth = 0;
_tabBtnFeedback.style.borderRightWidth = 0;
_tabBtnFeedback.style.borderTopWidth = 0;
_tabBtnFeedback.style.borderBottomWidth = 2;
_tabBtnFeedback.style.borderBottomColor = Color.clear;
_tabBtnFeedback.style.borderTopLeftRadius = 0;
_tabBtnFeedback.style.borderTopRightRadius = 0;
_tabBtnFeedback.style.borderBottomLeftRadius = 0;
_tabBtnFeedback.style.borderBottomRightRadius = 0;
_tabBtnFeedback.style.color = new Color(0.7f, 0.7f, 0.7f);
_tabBtnFeedback.style.fontSize = 14;
_tabBtnFeedback.style.paddingLeft = 8;
_tabBtnFeedback.style.paddingRight = 8;
_tabBtnFeedback.style.paddingTop = 8;
_tabBtnFeedback.style.paddingBottom = 8;
tabNav.Add(_tabBtnGenerate);
tabNav.Add(_tabBtnSettings);
tabNav.Add(_tabBtnFeedback);
_mainPanel.Add(tabNav);
// ===== Tab内容容器 =====
var tabContentContainer = new VisualElement()
{
style = { flexGrow = 1 }
};
// ===== 生成界面Tab =====
_tabGenerate = new VisualElement()
{
style = { flexGrow = 1 }
};
// ===== 效果图区域(拖放区域 - 支持选图前后切换)=====
_imageDropZone = new VisualElement()
{
style =
{
marginTop = 5,
height = 120,
borderLeftWidth = 1,
borderRightWidth = 1,
borderTopWidth = 1,
borderBottomWidth = 1,
borderLeftColor = new Color(0.35f, 0.35f, 0.38f),
borderRightColor = new Color(0.35f, 0.35f, 0.38f),
borderTopColor = new Color(0.35f, 0.35f, 0.38f),
borderBottomColor = new Color(0.35f, 0.35f, 0.38f),
borderTopLeftRadius = 8,
borderTopRightRadius = 8,
borderBottomLeftRadius = 8,
borderBottomRightRadius = 8,
flexDirection = FlexDirection.Row,
alignItems = Align.Center,
backgroundColor = new Color(0.14f, 0.14f, 0.16f),
overflow = Overflow.Hidden
}
};
_imagePathField = new TextField() { style = { display = DisplayStyle.None } };
_tabGenerate.Add(_imagePathField);
// 图片预览(左侧)
_dropZoneImage = new Image()
{
scaleMode = ScaleMode.ScaleToFit,
style =
{
width = 76,
height = 76,
marginLeft = 20,
marginTop = 2,
marginBottom = 2,
borderTopLeftRadius = 6,
borderTopRightRadius = 6,
borderBottomLeftRadius = 6,
borderBottomRightRadius = 6,
display = DisplayStyle.None // 默认隐藏
}
};
_imageDropZone.Add(_dropZoneImage);
// 文字和信息区域(右侧)
var textContainer = new VisualElement()
{
style =
{
flexGrow = 1,
alignItems = Align.Center,
justifyContent = Justify.Center,
paddingLeft = 10,
paddingRight = 10
}
};
_dropZoneText = new Label("点击选择界面效果图文件")
{
style =
{
color = new Color(0.95f, 0.95f, 0.95f),
fontSize = 14,
unityTextAlign = TextAnchor.MiddleCenter
}
};
textContainer.Add(_dropZoneText);
_dropZoneInfo = new Label("")
{
style =
{
color = new Color(0.7f, 0.7f, 0.7f),
fontSize = 11,
marginTop = 4,
display = DisplayStyle.None // 默认隐藏
}
};
textContainer.Add(_dropZoneInfo);
_imageDropZone.Add(textContainer);
// 清除按钮(右上角,默认隐藏)
var clearImageBtn = new Button() { text = "✕" };
clearImageBtn.style.position = Position.Absolute;
clearImageBtn.style.right = 4;
clearImageBtn.style.top = 4;
clearImageBtn.style.width = 20;
clearImageBtn.style.height = 20;
clearImageBtn.style.fontSize = 12;
clearImageBtn.style.backgroundColor = new Color(0.2f, 0.2f, 0.22f, 0.8f);
clearImageBtn.style.borderLeftWidth = 0;
clearImageBtn.style.borderRightWidth = 0;
clearImageBtn.style.borderTopWidth = 0;
clearImageBtn.style.borderBottomWidth = 0;
clearImageBtn.style.borderTopLeftRadius = 4;
clearImageBtn.style.borderTopRightRadius = 4;
clearImageBtn.style.borderBottomLeftRadius = 4;
clearImageBtn.style.borderBottomRightRadius = 4;
clearImageBtn.style.color = new Color(0.9f, 0.9f, 0.9f);
clearImageBtn.style.display = DisplayStyle.None; // 默认隐藏
clearImageBtn.name = "clearImageBtn";
clearImageBtn.clicked += ClearImagePreview;
_imageDropZone.Add(clearImageBtn);
// 点击选择图片
_imageDropZone.RegisterCallback<ClickEvent>(evt =>
{
// 如果点击的是清除按钮,不触发选择
if (evt.target is Button) return;
OnBrowseImageClicked();
});
_tabGenerate.Add(_imageDropZone);
// ===== 切图文件夹区域(拖放区域 - 支持选文件夹前后切换)=====
_textureDropZone = new VisualElement()
{
style =
{
marginTop = 15,
height = 80,
borderLeftWidth = 1,
borderRightWidth = 1,
borderTopWidth = 1,
borderBottomWidth = 1,
borderLeftColor = new Color(0.35f, 0.35f, 0.38f),
borderRightColor = new Color(0.35f, 0.35f, 0.38f),
borderTopColor = new Color(0.35f, 0.35f, 0.38f),
borderBottomColor = new Color(0.35f, 0.35f, 0.38f),
borderTopLeftRadius = 8,
borderTopRightRadius = 8,
borderBottomLeftRadius = 8,
borderBottomRightRadius = 8,
flexDirection = FlexDirection.Column,
justifyContent = Justify.Center,
alignItems = Align.Center,
backgroundColor = new Color(0.14f, 0.14f, 0.16f),
position = Position.Relative
}
};
_texturePathField = new TextField() { style = { display = DisplayStyle.None } };
_tabGenerate.Add(_texturePathField);
// 文件夹图标和名称
var textureHeaderRow = new VisualElement()
{
style = { flexDirection = FlexDirection.Row, alignItems = Align.Center }
};
_textureDropText = new Label("点击选择切图所在文件夹(可选)")
{
style = { color = new Color(0.95f, 0.95f, 0.95f), fontSize = 14 }
};
textureHeaderRow.Add(_textureDropText);
_textureDropZone.Add(textureHeaderRow);
// 路径显示(选中后显示)
_textureDropPath = new Label("")
{
style =
{
color = new Color(0.5f, 0.5f, 0.5f),
fontSize = 10,
marginTop = 4,
unityTextAlign = TextAnchor.MiddleCenter,
display = DisplayStyle.None
}
};
_textureDropZone.Add(_textureDropPath);
// 文件数量(选中后显示)
_textureDropCount = new Label("")
{
style =
{
color = new Color(0.2f, 0.7f, 0.3f),
fontSize = 11,
marginTop = 2,
display = DisplayStyle.None
}
};
_textureDropZone.Add(_textureDropCount);
// 清除按钮(右上角,默认隐藏)
var clearTextureBtn = new Button() { text = "✕" };
clearTextureBtn.style.position = Position.Absolute;
clearTextureBtn.style.right = 4;
clearTextureBtn.style.top = 4;
clearTextureBtn.style.width = 20;
clearTextureBtn.style.height = 20;
clearTextureBtn.style.fontSize = 12;
clearTextureBtn.style.backgroundColor = new Color(0.2f, 0.2f, 0.22f, 0.8f);
clearTextureBtn.style.borderLeftWidth = 0;
clearTextureBtn.style.borderRightWidth = 0;
clearTextureBtn.style.borderTopWidth = 0;
clearTextureBtn.style.borderBottomWidth = 0;
clearTextureBtn.style.borderTopLeftRadius = 4;
clearTextureBtn.style.borderTopRightRadius = 4;
clearTextureBtn.style.borderBottomLeftRadius = 4;
clearTextureBtn.style.borderBottomRightRadius = 4;
clearTextureBtn.style.color = new Color(0.9f, 0.9f, 0.9f);
clearTextureBtn.style.display = DisplayStyle.None;
clearTextureBtn.name = "clearTextureBtn";
clearTextureBtn.clicked += ClearTextureFolder;
_textureDropZone.Add(clearTextureBtn);
// 点击选择文件夹
_textureDropZone.RegisterCallback<ClickEvent>(evt =>
{
if (evt.target is Button) return;
OnBrowseTextureClicked();
});
_tabGenerate.Add(_textureDropZone);
// ===== 生成按钮(圆角样式)=====
_generateBtn = new Button()
{
text = "生成界面",
style = {
height = 44,
marginTop = 20,
backgroundColor = new Color(0.25f, 0.55f, 0.95f),
color = Color.white,
fontSize = 14,
unityFontStyleAndWeight = FontStyle.Bold,
borderTopLeftRadius = 8,
borderTopRightRadius = 8,
borderBottomLeftRadius = 8,
borderBottomRightRadius = 8
}
};
_tabGenerate.Add(_generateBtn);
// ===== 状态栏 =====
_statusBarContainer = new VisualElement()
{
style =
{
flexDirection = FlexDirection.Row,
alignItems = Align.Center,
marginTop = 15,
paddingTop = 8,
paddingBottom = 4
}
};
_statusDot = new VisualElement()
{
style =
{
width = 6,
height = 6,
borderTopLeftRadius = 3,
borderTopRightRadius = 3,
borderBottomLeftRadius = 3,
borderBottomRightRadius = 3,
backgroundColor = new Color(0.4f, 0.6f, 1f), // 默认蓝色(加载中)
marginRight = 6
}
};
_statusBarContainer.Add(_statusDot);
_statusLabel = new Label("正在初始化...") { style = { flexGrow = 1, color = new Color(0.9f, 0.9f, 0.9f), fontSize = 13 } };
_statusBarContainer.Add(_statusLabel);
_feedbackErrorBtn = new Button() { text = "反馈问题" };
_feedbackErrorBtn.style.display = DisplayStyle.None; // 默认隐藏
_feedbackErrorBtn.style.fontSize = 11;
_feedbackErrorBtn.style.backgroundColor = Color.clear;
_feedbackErrorBtn.style.borderLeftWidth = 0;
_feedbackErrorBtn.style.borderRightWidth = 0;
_feedbackErrorBtn.style.borderTopWidth = 0;
_feedbackErrorBtn.style.borderBottomWidth = 0;
_feedbackErrorBtn.style.color = new Color(0.4f, 0.6f, 1f);
_feedbackErrorBtn.style.paddingTop = 0;
_feedbackErrorBtn.style.paddingBottom = 0;
_feedbackErrorBtn.style.paddingLeft = 8;
_feedbackErrorBtn.style.paddingRight = 8;
_feedbackErrorBtn.clicked += OnFeedbackErrorClicked;
_statusBarContainer.Add(_feedbackErrorBtn);
_tabGenerate.Add(_statusBarContainer);
// ===== 生成结果区域 - 仅DEBUG模式显示放到最下面 =====
if (DEBUG_SHOW_RESULT)
{
_tabGenerate.Add(new Label("生成结果") { style = { unityFontStyleAndWeight = FontStyle.Bold, marginTop = 15 } });
// 视图切换工具栏
var viewToolbar = new VisualElement() { style = { flexDirection = FlexDirection.Row, marginBottom = 5 } };
_viewModeMenu = new ToolbarMenu();
_viewModeMenu.text = "JSON视图";
_viewModeMenu.menu.AppendAction("JSON视图", a => SwitchViewMode(ViewMode.Json), a => _currentViewMode == ViewMode.Json ? DropdownMenuAction.Status.Checked : DropdownMenuAction.Status.Normal);
_viewModeMenu.menu.AppendAction("树形视图", a => SwitchViewMode(ViewMode.Tree), a => _currentViewMode == ViewMode.Tree ? DropdownMenuAction.Status.Checked : DropdownMenuAction.Status.Normal);
viewToolbar.Add(_viewModeMenu);
viewToolbar.Add(new VisualElement() { style = { flexGrow = 1 } });
var expandAllBtn = new Button() { text = "全部展开" };
expandAllBtn.clicked += ExpandAllTreeNodes;
viewToolbar.Add(expandAllBtn);
var collapseAllBtn = new Button() { text = "全部折叠", style = { marginLeft = 5 } };
collapseAllBtn.clicked += CollapseAllTreeNodes;
viewToolbar.Add(collapseAllBtn);
var copyBtn = new Button() { text = "复制", style = { marginLeft = 5 } };
copyBtn.clicked += CopyResultToClipboard;
viewToolbar.Add(copyBtn);
_tabGenerate.Add(viewToolbar);
// 结果视图容器
_resultViewContainer = new VisualElement()
{
style =
{
height = 200,
display = DisplayStyle.Flex, // 默认显示方便测试时输入JSON
borderTopWidth = 1,
borderBottomWidth = 1,
borderLeftWidth = 1,
borderRightWidth = 1,
borderTopColor = new Color(0.3f, 0.3f, 0.3f),
borderBottomColor = new Color(0.3f, 0.3f, 0.3f),
borderLeftColor = new Color(0.3f, 0.3f, 0.3f),
borderRightColor = new Color(0.3f, 0.3f, 0.3f),
borderTopLeftRadius = 4,
borderTopRightRadius = 4,
borderBottomLeftRadius = 4,
borderBottomRightRadius = 4
}
};
// JSON视图 - 包装在ScrollView中以支持滚动
_jsonScrollView = new ScrollView()
{
style =
{
flexGrow = 1,
display = DisplayStyle.Flex
}
};
_jsonViewField = new TextField()
{
multiline = true,
style =
{
minWidth = 300, // 设置最小宽度确保内容不会被压缩
unityFont = Resources.Load<Font>("Fonts/Consolas")
}
};
// 【修复】TextField 可编辑方便测试时手动输入JSON
_jsonScrollView.Add(_jsonViewField);
// 树形视图
_treeViewContainer = new ScrollView()
{
style =
{
flexGrow = 1,
display = DisplayStyle.None // 默认隐藏
}
};
_resultViewContainer.Add(_jsonScrollView);
_resultViewContainer.Add(_treeViewContainer);
_tabGenerate.Add(_resultViewContainer);
}
// 状态栏不在 _tabGenerate 内,而是在根级别
tabContentContainer.Add(_tabGenerate);
// ===== 设置Tab =====
_tabSettings = new VisualElement()
{
style = { flexGrow = 1, display = DisplayStyle.None } // 默认隐藏
};
// ===== 公共切图文件夹卡片 =====
var settingsCard = new VisualElement()
{
style =
{
backgroundColor = new Color(0.14f, 0.14f, 0.16f),
borderLeftWidth = 1,
borderRightWidth = 1,
borderTopWidth = 1,
borderBottomWidth = 1,
borderLeftColor = new Color(0.35f, 0.35f, 0.38f),
borderRightColor = new Color(0.35f, 0.35f, 0.38f),
borderTopColor = new Color(0.35f, 0.35f, 0.38f),
borderBottomColor = new Color(0.35f, 0.35f, 0.38f),
borderTopLeftRadius = 8,
borderTopRightRadius = 8,
borderBottomLeftRadius = 8,
borderBottomRightRadius = 8,
paddingTop = 14,
paddingBottom = 14,
paddingLeft = 14,
paddingRight = 14
}
};
// 标题行(图标+标题+数量徽章)
var settingsHeaderRow = new VisualElement()
{
style = { flexDirection = FlexDirection.Row, alignItems = Align.Center, marginBottom = 12 }
};
var settingsTitle = new Label("公共切图文件夹")
{
style = { color = new Color(0.95f, 0.95f, 0.95f), fontSize = 14, unityFontStyleAndWeight = FontStyle.Bold }
};
settingsHeaderRow.Add(settingsTitle);
// 数量徽章
_folderCountBadge = new Label("0/5")
{
style =
{
backgroundColor = new Color(0.25f, 0.55f, 0.95f),
color = Color.white,
paddingLeft = 8,
paddingRight = 8,
paddingTop = 2,
paddingBottom = 2,
borderTopLeftRadius = 10,
borderTopRightRadius = 10,
borderBottomLeftRadius = 10,
borderBottomRightRadius = 10,
fontSize = 11,
marginLeft = 8
}
};
settingsHeaderRow.Add(_folderCountBadge);
settingsCard.Add(settingsHeaderRow);
// 公共文件夹容器
_commonFoldersContainer = new VisualElement()
{
style =
{
marginBottom = 12,
minHeight = 60
}
};
settingsCard.Add(_commonFoldersContainer);
// 添加文件夹按钮
_addCommonFolderBtn = new Button() { text = "+ 添加文件夹" };
_addCommonFolderBtn.style.height = 36;
_addCommonFolderBtn.style.fontSize = 13;
_addCommonFolderBtn.style.borderTopLeftRadius = 6;
_addCommonFolderBtn.style.borderTopRightRadius = 6;
_addCommonFolderBtn.style.borderBottomLeftRadius = 6;
_addCommonFolderBtn.style.borderBottomRightRadius = 6;
_addCommonFolderBtn.style.backgroundColor = new Color(0.25f, 0.55f, 0.95f);
_addCommonFolderBtn.style.color = Color.white;
settingsCard.Add(_addCommonFolderBtn);
// 提示文字
_commonFoldersHint = new Label("公共切图文件夹只需设置一次,下次自动加载。")
{
style =
{
color = new Color(0.6f, 0.6f, 0.65f),
fontSize = 11,
marginTop = 12,
whiteSpace = WhiteSpace.Normal
}
};
settingsCard.Add(_commonFoldersHint);
_tabSettings.Add(settingsCard);
tabContentContainer.Add(_tabSettings);
// ===== 反馈Tab =====
_tabFeedback = new VisualElement()
{
style = { flexGrow = 1, display = DisplayStyle.None } // 默认隐藏
};
_tabFeedback.Add(new Label("有问题或建议?请告诉我们:")
{
style = {
color = new Color(0.85f, 0.85f, 0.85f),
fontSize = 13,
marginTop = 5,
marginBottom = 12
}
});
_feedbackTextField = new TextField()
{
multiline = true,
value = "", // 设置初始值,避免 null
tooltip = "请描述您遇到的问题或建议...",
style =
{
height = 150,
marginBottom = 12,
borderTopLeftRadius = 6,
borderTopRightRadius = 6,
borderBottomLeftRadius = 6,
borderBottomRightRadius = 6,
backgroundColor = new Color(0.15f, 0.15f, 0.17f)
}
};
_tabFeedback.Add(_feedbackTextField);
var feedbackSubmitRow = new VisualElement()
{
style = { flexDirection = FlexDirection.Row, alignItems = Align.Center }
};
_feedbackHintLabel = new Label("")
{
style =
{
flexGrow = 1,
color = new Color(0.9f, 0.3f, 0.3f),
fontSize = 12
}
};
feedbackSubmitRow.Add(_feedbackHintLabel);
_feedbackSubmitBtn = new Button() { text = "提交" };
_feedbackSubmitBtn.style.width = 100;
_feedbackSubmitBtn.style.height = 36;
_feedbackSubmitBtn.style.fontSize = 14;
_feedbackSubmitBtn.style.borderTopLeftRadius = 6;
_feedbackSubmitBtn.style.borderTopRightRadius = 6;
_feedbackSubmitBtn.style.borderBottomLeftRadius = 6;
_feedbackSubmitBtn.style.borderBottomRightRadius = 6;
_feedbackSubmitBtn.style.backgroundColor = new Color(0.25f, 0.55f, 0.95f);
_feedbackSubmitBtn.style.color = Color.white;
_feedbackSubmitBtn.clicked += OnFeedbackSubmitClicked;
feedbackSubmitRow.Add(_feedbackSubmitBtn);
_tabFeedback.Add(feedbackSubmitRow);
// 反馈成功提示
_feedbackSuccessContainer = new VisualElement()
{
style =
{
display = DisplayStyle.None,
flexDirection = FlexDirection.Row,
alignItems = Align.Center,
justifyContent = Justify.Center,
marginTop = 30,
paddingTop = 20,
paddingBottom = 20,
backgroundColor = new Color(0.15f, 0.3f, 0.15f),
borderTopLeftRadius = 8,
borderTopRightRadius = 8,
borderBottomLeftRadius = 8,
borderBottomRightRadius = 8
}
};
var successIcon = new Label("✓")
{
style =
{
fontSize = 32,
color = new Color(0.2f, 0.9f, 0.2f),
marginRight = 15
}
};
_feedbackSuccessContainer.Add(successIcon);
var successText = new Label("感谢您的反馈!")
{
style =
{
fontSize = 16,
unityFontStyleAndWeight = FontStyle.Bold,
color = new Color(0.2f, 0.9f, 0.2f)
}
};
_feedbackSuccessContainer.Add(successText);
_tabFeedback.Add(_feedbackSuccessContainer);
tabContentContainer.Add(_tabFeedback);
_mainPanel.Add(tabContentContainer);
root.Add(_mainPanel);
// 【修复】默认面板显示逻辑:
// 1. 认证面板默认隐藏,只有用户点击登录相关操作时才显示
// 2. 主面板默认显示,用户可以直接看到功能界面
_authPanel.style.display = DisplayStyle.None;
_mainPanel.style.display = DisplayStyle.Flex;
// ===== 加载动画遮罩层 =====
CreateLoadingOverlay(root);
}
/// <summary>
/// 创建加载动画遮罩层
/// </summary>
private void CreateLoadingOverlay(VisualElement root)
{
_loadingOverlay = new VisualElement()
{
name = "LoadingOverlay",
pickingMode = PickingMode.Position,
style =
{
position = Position.Absolute,
left = 0, right = 0, top = 0, bottom = 0,
backgroundColor = new Color(0.08f, 0.08f, 0.1f, 0.95f),
justifyContent = Justify.Center,
alignItems = Align.Center,
flexDirection = FlexDirection.Column,
display = DisplayStyle.None
}
};
// 内容容器 - 固定宽度
var contentContainer = new VisualElement()
{
style =
{
width = 220,
height = 80,
alignItems = Align.Center,
flexDirection = FlexDirection.Column
}
};
// 进度条背景 - 固定宽度
var progressBg = new VisualElement()
{
style =
{
width = 180,
height = 4,
backgroundColor = new Color(0.2f, 0.2f, 0.22f),
borderTopLeftRadius = 2,
borderTopRightRadius = 2,
borderBottomLeftRadius = 2,
borderBottomRightRadius = 2,
marginBottom = 20,
overflow = Overflow.Hidden
}
};
// 进度条填充(动画条)- 固定宽度
_loadingSpinner = new VisualElement()
{
name = "LoadingBar",
style =
{
width = 60,
height = 4,
backgroundColor = new Color(0.25f, 0.55f, 0.95f),
borderTopLeftRadius = 2,
borderTopRightRadius = 2,
borderBottomLeftRadius = 2,
borderBottomRightRadius = 2
}
};
progressBg.Add(_loadingSpinner);
contentContainer.Add(progressBg);
// 加载提示文字 - 固定宽度,左对齐文字但整体居中
_loadingText = new Label("正在分析设计图...")
{
style =
{
width = 180,
color = new Color(0.95f, 0.95f, 0.95f),
fontSize = 14,
unityTextAlign = TextAnchor.MiddleCenter
}
};
contentContainer.Add(_loadingText);
// 时间提示 - 固定宽度
var timeHint = new Label("分析可能需要 1-3 分钟")
{
style =
{
width = 180,
color = new Color(0.5f, 0.5f, 0.55f),
fontSize = 11,
marginTop = 8,
unityTextAlign = TextAnchor.MiddleCenter
}
};
contentContainer.Add(timeHint);
_loadingOverlay.Add(contentContainer);
root.Add(_loadingOverlay);
}
/// <summary>
/// 显示加载动画
/// </summary>
private void ShowLoadingOverlay(string message = "正在分析设计图...")
{
if (_loadingOverlay == null) return;
_loadingText.text = message;
_loadingOverlay.style.display = DisplayStyle.Flex;
_loadingStartTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
_loadingDots = 0;
_spinnerRotation = 0f;
// 强制重绘
_loadingOverlay.MarkDirtyRepaint();
// 启动动画 - 使用 EditorApplication.update
EditorApplication.update -= UpdateLoadingAnimation;
EditorApplication.update += UpdateLoadingAnimation;
}
/// <summary>
/// 隐藏加载动画
/// </summary>
private void HideLoadingOverlay()
{
if (_loadingOverlay == null) return;
_loadingOverlay.style.display = DisplayStyle.None;
// 停止动画
EditorApplication.update -= UpdateLoadingAnimation;
}
/// <summary>
/// 更新加载动画(进度条来回移动)
/// </summary>
private void UpdateLoadingAnimation()
{
if (_loadingOverlay == null || _loadingOverlay.style.display == DisplayStyle.None)
return;
_spinnerRotation += 1f; // 再降低速度
// 进度条来回移动动画
float progress = Mathf.PingPong(_spinnerRotation / 2000f, 1f);
float barWidth = 60f;
float containerWidth = 180f;
float maxMargin = containerWidth - barWidth;
float marginLeft = progress * maxMargin;
if (_loadingSpinner != null)
{
_loadingSpinner.style.marginLeft = marginLeft;
}
// 动态点点点每500ms更新一次
int dotCount = ((int)(_spinnerRotation / 600f)) % 4;
string dots = new string('.', dotCount);
string spaces = new string(' ', 3 - dotCount);
// 更新耗时显示 - 始终显示时间格式,固定宽度
if (_loadingStartTime > 0)
{
long elapsed = (DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - _loadingStartTime) / 1000;
// 始终显示时间,保持格式一致
_loadingText.text = $"正在分析设计图{dots}{spaces} ({elapsed,2}秒)";
}
}
private void BindUIElements()
{
// 主界面按钮
if (_browseImageBtn != null)
_browseImageBtn.clicked += OnBrowseImageClicked;
if (_browseTextureBtn != null)
_browseTextureBtn.clicked += OnBrowseTextureClicked;
if (_generateBtn != null)
_generateBtn.clicked += OnGenerateClicked;
// 登录/注册切换
if (_loginBtn != null)
_loginBtn.clicked += OnLoginClicked;
if (_showRegisterBtn != null)
_showRegisterBtn.clicked += OnShowRegisterClicked;
if (_showLoginBtn != null)
_showLoginBtn.clicked += OnShowLoginClicked;
if (_registerBtn != null)
_registerBtn.clicked += OnRegisterClicked;
if (_sendCodeBtn != null)
_sendCodeBtn.clicked += OnSendCodeClicked;
// 【新增】重置密码相关事件
if (_forgotPasswordBtn != null)
_forgotPasswordBtn.clicked += OnForgotPasswordClicked;
if (_resetSendCodeBtn != null)
_resetSendCodeBtn.clicked += OnResetSendCodeClicked;
if (_resetPasswordBtn != null)
_resetPasswordBtn.clicked += OnResetPasswordClicked;
if (_resetBackToLoginBtn != null)
_resetBackToLoginBtn.clicked += OnShowLoginClicked;
// Tab切换
if (_tabBtnGenerate != null)
_tabBtnGenerate.clicked += () => SwitchTab(TabType.Generate);
if (_tabBtnSettings != null)
_tabBtnSettings.clicked += () => SwitchTab(TabType.Settings);
if (_tabBtnFeedback != null)
_tabBtnFeedback.clicked += () => SwitchTab(TabType.Feedback);
// 设置Tab - 添加公共文件夹按钮
if (_addCommonFolderBtn != null)
_addCommonFolderBtn.clicked += OnAddCommonFolderClicked;
}
/// <summary>
/// 检查更新(自动)
/// </summary>
private async Task CheckForUpdatesAsync()
{
try
{
if (_updateService == null) return;
Log("检查更新中...");
var updateInfo = await _updateService.CheckUpdateAsync();
if (updateInfo != null)
{
Log($"发现新版本: {updateInfo.version}");
// 在主线程显示更新提示
EditorApplication.delayCall += () =>
{
bool confirm;
if (updateInfo.mandatory)
{
// 强制更新:不提供取消选项
confirm = EditorUtility.DisplayDialog(
"发现新版本(必须更新)",
$"当前版本: {Config.Version}\n新版本: {updateInfo.version}\n\n{updateInfo.releaseNotes}\n\n⚠ 此更新为强制更新,必须安装后才能继续使用。",
"立即更新",
""
);
}
else
{
// 可选更新:提供稍后选项
confirm = EditorUtility.DisplayDialog(
"发现新版本",
$"当前版本: {Config.Version}\n新版本: {updateInfo.version}\n\n{updateInfo.releaseNotes}",
"立即更新",
"稍后"
);
}
if (confirm)
{
_ = DownloadAndInstallUpdateAsync(updateInfo);
}
};
}
else
{
Log("当前已是最新版本");
}
}
catch (Exception ex)
{
Log($"检查更新失败: {ex.Message}");
}
}
/// <summary>
/// 下载并安装更新
/// </summary>
private async Task DownloadAndInstallUpdateAsync(UpdateInfo updateInfo)
{
try
{
Log("下载更新中...");
bool success = await _updateService.DownloadAndInstallAsync(updateInfo);
if (success)
{
Log("更新安装成功");
// 刷新 Unity 资产数据库
EditorApplication.delayCall += () =>
{
AssetDatabase.Refresh();
EditorUtility.DisplayDialog(
"更新完成",
$"已成功更新到版本 {updateInfo.version}\n\n请重启 Unity 编辑器以应用更新。",
"确定"
);
};
}
else
{
LogError("更新安装失败,请查看 Console 了解详细错误信息");
EditorUtility.DisplayDialog(
"更新失败",
"更新安装失败。\n\n请查看 Console (Window > General > Console) 了解详细信息。",
"确定"
);
}
}
catch (Exception ex)
{
LogError($"更新失败: {ex.Message}");
EditorUtility.DisplayDialog("更新错误", $"更新过程中发生错误:\n\n{ex.Message}", "确定");
}
}
/// <summary>
/// 刷新授权状态(支持强制刷新)
/// 从服务端获取最新的授权和额度信息
/// </summary>
private async Task RefreshAuthStateAsync(bool forceRefresh = false)
{
if (_authService == null)
{
LogError("授权服务未初始化");
return;
}
try
{
Log(forceRefresh ? "正在从服务器获取最新授权状态..." : "正在读取授权状态...");
_currentAuthState = await Task.Run(() => _authService.GetAuthSync(_authService.Token));
// 更新UI显示包含额度更新
EditorApplication.delayCall += () =>
{
UpdateUIBasedOnState();
};
// 记录状态日志 - 【修复】使用 UserState 判断
var userState = GetUserState();
switch (userState)
{
case UserState.LoggedIn:
Log($"授权状态: 已登录 | 套餐: {GetPlanDisplayName(_currentAuthState.Plan)} | 额度: {_currentAuthState.Credits}/{_currentAuthState.MaxCredits}");
break;
case UserState.Trial:
Log($"授权状态: 试用模式 | 剩余 {_currentAuthState.RemainingTrials} 次");
break;
case UserState.TrialExhausted:
Log($"授权状态: 试用耗尽 | {_currentAuthState.Message}");
break;
}
}
catch (Exception ex)
{
LogError($"刷新授权状态失败: {ex.Message}");
// 出错时回退到本地检查
try
{
_currentAuthState = await Task.Run(() => _authService.GetAuthSync(_authService.Token));
EditorApplication.delayCall += () =>
{
UpdateUIBasedOnState();
};
}
catch
{
// 完全失败,显示登录界面
// 【修复】试用耗尽时 Type 仍是 Trial通过 RemainingTrials=0 表示
_currentAuthState = new AuthResult { Type = AuthType.Trial, Valid = false, RemainingTrials = 0 };
EditorApplication.delayCall += () =>
{
_authPanel.style.display = DisplayStyle.Flex;
_mainPanel.style.display = DisplayStyle.None;
ShowLoginPanel();
};
}
}
}
/// <summary>
/// 【关键修复】更新认证面板可见性 - 使用 UserState 判断
/// </summary>
private void UpdateAuthPanelVisibility()
{
if (_authPanel == null || _mainPanel == null) return;
var userState = GetUserState();
switch (userState)
{
case UserState.LoggedIn:
// 已登录:显示主面板
_authPanel.style.display = DisplayStyle.None;
_mainPanel.style.display = DisplayStyle.Flex;
break;
case UserState.Trial:
// 试用中:显示主面板
_authPanel.style.display = DisplayStyle.None;
_mainPanel.style.display = DisplayStyle.Flex;
break;
case UserState.TrialExhausted:
// 试用耗尽:显示主面板
_authPanel.style.display = DisplayStyle.None;
_mainPanel.style.display = DisplayStyle.Flex;
break;
default:
// 未授权:显示认证面板
_authPanel.style.display = DisplayStyle.Flex;
_mainPanel.style.display = DisplayStyle.None;
ShowLoginPanel();
break;
}
}
/// <summary>
/// 获取套餐显示名称
/// </summary>
private string GetPlanDisplayName(string plan)
{
return plan?.ToLower() switch
{
"trial" => "试用版",
"free" => "免费版",
"starter" => "畅享版",
"professional" => "专业版",
"enterprise" => "企业版",
_ => "免费版"
};
}
/// <summary>
/// 根据用户状态更新UI - 对应 plugin 的 updateUIBasedOnState()
/// </summary>
private void UpdateUIBasedOnState()
{
if (_currentAuthState == null || _generateBtn == null) return;
var userState = GetUserState();
Log($"当前用户状态: {userState}");
// 先更新面板可见性
UpdateAuthPanelVisibility();
switch (userState)
{
case UserState.Trial:
// 试用中:隐藏授权工具栏,显示正常生成按钮
if (_authToolbar != null) _authToolbar.style.display = DisplayStyle.None;
_generateBtn.text = "生成界面";
_generateBtn.SetEnabled(true);
// 清除旧的事件处理
_generateBtn.clicked -= OnGenerateClicked;
_generateBtn.clicked -= ShowLoginPanel;
_generateBtn.clicked -= ShowSubscribePanel;
_generateBtn.clicked += OnGenerateClicked;
break;
case UserState.TrialExhausted:
// 试用耗尽:隐藏授权工具栏,显示登录引导按钮
if (_authToolbar != null) _authToolbar.style.display = DisplayStyle.None;
_generateBtn.text = "试用次数已用完,登录获取更多";
_generateBtn.SetEnabled(true);
_generateBtn.clicked -= OnGenerateClicked;
_generateBtn.clicked -= ShowLoginPanel;
_generateBtn.clicked -= ShowSubscribePanel;
_generateBtn.clicked += ShowLoginPanel;
break;
case UserState.LoggedIn:
// 已登录:显示授权工具栏并更新内容
UpdateAuthToolbar();
// 检查是否有额度
bool hasQuota = _currentAuthState.Credits > 0 || _currentAuthState.MaxCredits == -1 || _currentAuthState.MaxCredits >= 999999;
if (hasQuota)
{
_generateBtn.text = "生成界面";
_generateBtn.SetEnabled(true);
_generateBtn.clicked -= OnGenerateClicked;
_generateBtn.clicked -= ShowLoginPanel;
_generateBtn.clicked -= ShowSubscribePanel;
_generateBtn.clicked += OnGenerateClicked;
}
else
{
_generateBtn.text = "额度已用完,订阅解锁更多";
_generateBtn.SetEnabled(true);
_generateBtn.clicked -= OnGenerateClicked;
_generateBtn.clicked -= ShowLoginPanel;
_generateBtn.clicked -= ShowSubscribePanel;
_generateBtn.clicked += ShowSubscribePanel;
}
break;
}
}
/// <summary>
/// 【关键修复】获取当前用户状态 - 对齐 Plugin 的 getUserState() 逻辑
/// 用户状态由 AuthType 和 RemainingTrials 计算得出,不是直接存储的
/// </summary>
private UserState GetUserState()
{
if (_currentAuthState == null) return UserState.Trial;
// 已登录(非试用)- Valid 为 true 且 Type 不是 Trial
if (_currentAuthState.Valid && _currentAuthState.Type != AuthType.Trial)
{
return UserState.LoggedIn;
}
// 试用耗尽 - Type 是 Trial但剩余次数 <= 0 或 Valid 为 false
if (_currentAuthState.Type == AuthType.Trial &&
(_currentAuthState.RemainingTrials <= 0 || !_currentAuthState.Valid))
{
return UserState.TrialExhausted;
}
// 试用中
return UserState.Trial;
}
/// <summary>
/// 【修复】更新授权工具栏显示 - 使用 UserState 判断
/// </summary>
private void UpdateAuthToolbar()
{
if (_authToolbar == null || _currentAuthState == null) return;
var userState = GetUserState();
// 已登录(非试用)时显示工具栏
if (userState == UserState.LoggedIn)
{
_authToolbar.style.display = DisplayStyle.Flex;
// 更新徽章
if (_toolbarBadge != null)
{
_toolbarBadge.text = GetPlanDisplayName(_currentAuthState.Plan);
_toolbarBadge.AddToClassList($"badge-{_currentAuthState.Plan?.ToLower() ?? "free"}");
}
// 更新额度显示
if (_quotaText != null)
{
if (_currentAuthState.MaxCredits == -1 || _currentAuthState.MaxCredits >= 999999)
{
_quotaText.text = "无限";
}
else
{
_quotaText.text = $"{_currentAuthState.Credits}/{_currentAuthState.MaxCredits}";
// 额度低时变红色
if (_currentAuthState.Credits <= 3)
{
_quotaText.style.color = Color.red;
}
}
}
// 显示/隐藏升级按钮
if (_toolbarUpgradeBtn != null)
{
bool showUpgrade = _currentAuthState.Plan?.ToLower() is "trial" or "free" or "starter";
_toolbarUpgradeBtn.style.display = showUpgrade ? DisplayStyle.Flex : DisplayStyle.None;
}
}
else
{
_authToolbar.style.display = DisplayStyle.None;
}
}
/// <summary>
/// 显示订阅面板
/// </summary>
private void ShowSubscribePanel()
{
SubscribeWindow.ShowWindow();
}
private void ShowLoginPanel()
{
_authPanel.style.display = DisplayStyle.Flex;
_mainPanel.style.display = DisplayStyle.None;
_loginPanel.style.display = DisplayStyle.Flex;
_registerPanel.style.display = DisplayStyle.None;
_resetPasswordPanel.style.display = DisplayStyle.None;
ShowLoginError(""); // 清空错误提示
}
private void ShowRegisterPanel()
{
_loginPanel.style.display = DisplayStyle.None;
_registerPanel.style.display = DisplayStyle.Flex;
_resetPasswordPanel.style.display = DisplayStyle.None;
ShowRegisterError(""); // 清空错误提示
}
private void ShowResetPasswordPanel()
{
_loginPanel.style.display = DisplayStyle.None;
_registerPanel.style.display = DisplayStyle.None;
_resetPasswordPanel.style.display = DisplayStyle.Flex;
ShowResetError(""); // 清空错误提示
}
/// <summary>
/// 显示登录错误提示
/// </summary>
private void ShowLoginError(string message)
{
if (_loginErrorLabel == null) return;
if (string.IsNullOrEmpty(message))
{
_loginErrorLabel.style.display = DisplayStyle.None;
}
else
{
_loginErrorLabel.text = message;
_loginErrorLabel.style.display = DisplayStyle.Flex;
}
}
/// <summary>
/// 显示注册错误提示
/// </summary>
private void ShowRegisterError(string message)
{
if (_registerErrorLabel == null) return;
if (string.IsNullOrEmpty(message))
{
_registerErrorLabel.style.display = DisplayStyle.None;
}
else
{
_registerErrorLabel.text = message;
_registerErrorLabel.style.display = DisplayStyle.Flex;
}
}
/// <summary>
/// 显示重置密码错误提示
/// </summary>
private void ShowResetError(string message)
{
if (_resetErrorLabel == null) return;
if (string.IsNullOrEmpty(message))
{
_resetErrorLabel.style.display = DisplayStyle.None;
}
else
{
_resetErrorLabel.text = message;
_resetErrorLabel.style.display = DisplayStyle.Flex;
}
}
#region Event Handlers
private void OnBrowseImageClicked()
{
// 判断是否处于快速体验模式(效果图)
bool quickMode = EditorPrefs.GetBool(PREFS_QUICK_MODE_IMAGE, false);
string defaultPath;
if (quickMode)
{
// 快速体验模式:使用 Samples 路径
string samplePath = GetSampleImagesPath();
defaultPath = samplePath ?? "";
}
else
{
// 非快速体验模式:使用上次保存的路径,没有则为空
defaultPath = EditorPrefs.GetString(PREFS_LAST_IMAGE_DIR, "");
}
string path = EditorUtility.OpenFilePanelWithFilters(
"选择设计图",
defaultPath,
new[] { "图片文件", "png,jpg,jpeg,webp", "所有文件", "*" }
);
if (!string.IsNullOrEmpty(path))
{
_imagePathField.value = path;
ShowImagePreview(path);
// 保存用户选择的路径,清除快速体验模式
EditorPrefs.SetString(PREFS_LAST_IMAGE_DIR, Path.GetDirectoryName(path));
EditorPrefs.SetBool(PREFS_QUICK_MODE_IMAGE, false);
}
}
private void OnBrowseTextureClicked()
{
// 判断是否处于快速体验模式(切图文件夹)
bool quickMode = EditorPrefs.GetBool(PREFS_QUICK_MODE_TEXTURE, false);
string defaultPath;
if (quickMode)
{
// 快速体验模式:使用 Samples 路径
string samplePath = GetSampleSlicesPath();
defaultPath = samplePath ?? "";
}
else
{
// 非快速体验模式:使用上次保存的路径,没有则为空
defaultPath = EditorPrefs.GetString(PREFS_TEXTURE_FOLDER_PATH, "");
}
string path = EditorUtility.OpenFolderPanel("选择切图文件夹", defaultPath, "");
if (!string.IsNullOrEmpty(path))
{
_texturePathField.value = path;
// 保存路径,清除快速体验模式
EditorPrefs.SetString(PREFS_TEXTURE_FOLDER_PATH, path);
EditorPrefs.SetBool(PREFS_QUICK_MODE_TEXTURE, false);
// 自动扫描
OnScanTextureClicked();
}
}
private void OnScanTextureClicked()
{
string path = _texturePathField.value;
if (string.IsNullOrEmpty(path) || !Directory.Exists(path))
{
ClearTextureFolder();
return;
}
_textureFiles.Clear();
var files = TextureScanner.ScanDirectory(path);
_textureFiles.AddRange(files);
// 全部默认选中
foreach (var f in _textureFiles)
{
f.selected = true;
}
// 更新拖放区域显示
UpdateTextureDropZone(path, _textureFiles.Count);
Log($"扫描完成,找到 {_textureFiles.Count} 个切图文件");
}
/// <summary>
/// 更新切图文件夹拖放区域显示
/// </summary>
private void UpdateTextureDropZone(string path, int fileCount)
{
string folderName = Path.GetFileName(path);
if (string.IsNullOrEmpty(folderName))
{
folderName = path;
}
// 显示文件夹图标和名称
var folderIcon = _textureDropZone.Q<Label>("folderIcon");
if (folderIcon != null)
{
folderIcon.style.display = DisplayStyle.Flex;
}
_textureDropText.text = folderName;
_textureDropText.style.color = new Color(0.95f, 0.95f, 0.95f);
_textureDropText.style.fontSize = 14;
// 显示路径
_textureDropPath.text = path;
_textureDropPath.style.display = DisplayStyle.Flex;
// 显示文件数量
if (fileCount > 0)
{
_textureDropCount.text = $"{fileCount} 个切图文件";
_textureDropCount.style.display = DisplayStyle.Flex;
}
else
{
_textureDropCount.text = "未找到切图文件";
_textureDropCount.style.color = new Color(0.9f, 0.5f, 0.2f);
_textureDropCount.style.display = DisplayStyle.Flex;
}
// 显示清除按钮
var clearBtn = _textureDropZone.Q<Button>("clearTextureBtn");
if (clearBtn != null)
{
clearBtn.style.display = DisplayStyle.Flex;
}
// 更新边框颜色为绿色
_textureDropZone.style.borderLeftColor = new Color(0.2f, 0.7f, 0.3f);
_textureDropZone.style.borderRightColor = new Color(0.2f, 0.7f, 0.3f);
_textureDropZone.style.borderTopColor = new Color(0.2f, 0.7f, 0.3f);
_textureDropZone.style.borderBottomColor = new Color(0.2f, 0.7f, 0.3f);
}
/// <summary>
/// 清除切图文件夹选择
/// </summary>
private void ClearTextureFolder()
{
// 隐藏文件夹图标
var folderIcon = _textureDropZone.Q<Label>("folderIcon");
if (folderIcon != null)
{
folderIcon.style.display = DisplayStyle.None;
}
// 恢复初始文字
_textureDropText.text = "点击选择切图所在文件夹(可选)";
_textureDropText.style.color = new Color(0.95f, 0.95f, 0.95f);
// 隐藏路径和数量
_textureDropPath.text = "";
_textureDropPath.style.display = DisplayStyle.None;
_textureDropCount.text = "";
_textureDropCount.style.display = DisplayStyle.None;
_textureDropCount.style.color = new Color(0.2f, 0.7f, 0.3f); // 恢复默认颜色
// 隐藏清除按钮
var clearBtn = _textureDropZone.Q<Button>("clearTextureBtn");
if (clearBtn != null)
{
clearBtn.style.display = DisplayStyle.None;
}
// 恢复边框颜色
_textureDropZone.style.borderLeftColor = new Color(0.35f, 0.35f, 0.38f);
_textureDropZone.style.borderRightColor = new Color(0.35f, 0.35f, 0.38f);
_textureDropZone.style.borderTopColor = new Color(0.35f, 0.35f, 0.38f);
_textureDropZone.style.borderBottomColor = new Color(0.35f, 0.35f, 0.38f);
// 清除路径和数据
_texturePathField.value = "";
_textureFiles.Clear();
}
private async void OnGenerateClicked()
{
if (_isGenerating) return;
// 【新增】防连点检查
if (IsInCooldown("generate")) return;
_isGenerating = true;
_generateBtn.SetEnabled(false);
_generateBtn.text = "生成中...";
// 【新增】初始化UnityPrefabBuilder
if (_unityPrefabBuilder == null)
{
_unityPrefabBuilder = new UnityPrefabBuilder();
}
try
{
// 【新增】根据分析模式选择不同的处理流程
if (_currentAnalysisMode == AnalysisMode.UnityNative)
{
await GenerateUnityNative();
}
else
{
await GenerateUniversal();
}
}
catch (AppException ex)
{
// 【新增】隐藏加载动画
HideLoadingOverlay();
// 【新增】使用错误码处理错误
HandleError(ex, "生成UI");
}
catch (Exception ex)
{
// 【新增】隐藏加载动画
HideLoadingOverlay();
// 【新增】使用错误码处理错误
HandleError(ex, "生成UI");
}
finally
{
// 【新增】确保隐藏加载动画
HideLoadingOverlay();
_isGenerating = false;
_generateBtn.SetEnabled(true);
// 【修复】根据当前授权状态更新按钮文字,而不是硬编码
UpdateUIBasedOnState();
}
}
/// <summary>
/// 【新增】Unity原生模式生成流程
/// </summary>
private async Task GenerateUnityNative()
{
// 【新增】清空通用模式数据,避免混淆
_currentUIStructure = null;
UnityAnalyzeData result = null;
// 【新增】检查JSON视图是否有内容有则直接使用测试模式
string jsonContent = _jsonViewField?.value?.Trim();
if (!string.IsNullOrEmpty(jsonContent))
{
Log("检测到JSON内容尝试解析测试模式...");
try
{
// Unity Native 模式使用 Newtonsoft.Json
var prefabNode = JsonConvert.DeserializeObject<UnityPrefabNode>(jsonContent);
if (prefabNode == null)
{
LogWarning("JSON内容无效将调用服务器分析...");
}
else
{
Log("JSON解析成功直接使用测试模式");
result = new UnityAnalyzeData
{
prefab = prefabNode,
usage = null, // 测试模式下没有 usage 信息
credits = -1, // 测试模式下没有额度信息
remainingTrials = -1
};
}
}
catch (Exception ex)
{
LogWarning($"JSON解析失败: {ex.Message},将调用服务器分析...");
result = null;
}
}
// 如果没有有效的JSON内容调用服务器分析
if (result == null)
{
string imagePath = _imagePathField.value;
if (string.IsNullOrEmpty(imagePath) || !File.Exists(imagePath))
{
LogError("请先选择有效的设计图");
UpdateStatus("请先选择有效的设计图", StatusColor.Error);
return;
}
// 显示加载动画
ShowLoadingOverlay("正在分析设计图(Unity模式)...");
Log("开始Unity原生模式分析...");
// 准备切图信息
var selectedTextures = _textureFiles.Where(t => t.selected).ToList();
// 扫描公共文件夹
var commonTextures = ScanCommonFolders();
// 调用Unity专用API
result = await Task.Run(() => _aiService.AnalyzeUnitySync(imagePath, selectedTextures, commonTextures));
// 隐藏加载动画
HideLoadingOverlay();
}
Log($"Unity分析完成,检测到预制体节点");
// 【新增】更新额度信息(从服务器返回)
// 注意:-1 表示无值JsonUtility 不支持 int?
if (result.credits >= 0 && _currentAuthState != null)
{
int oldCredits = _currentAuthState.Credits;
_currentAuthState.Credits = result.credits;
// 立即更新 UI主线程
EditorApplication.delayCall += () =>
{
UpdateUIBasedOnState();
};
}
else if (result.remainingTrials >= 0 && _currentAuthState != null)
{
int oldTrials = _currentAuthState.RemainingTrials;
_currentAuthState.RemainingTrials = result.remainingTrials;
// 立即更新 UI主线程
EditorApplication.delayCall += () =>
{
UpdateUIBasedOnState();
};
}
// 成功时隐藏错误反馈按钮
HideErrorFeedbackButton();
// 【新增】保存Unity预制体数据用于复制按钮
_currentUnityPrefab = result.prefab;
// 【新增】显示Unity分析结果调试模式
if (DEBUG_SHOW_RESULT)
{
DisplayUnityResult(result.prefab);
}
// 准备输出目录
string outputPath = "Assets/AutoUI_Generated";
string texturesPath = Path.Combine(outputPath, "Textures");
string prefabName = $"GeneratedUI_{DateTime.Now:yyyyMMdd_HHmmss}";
string fullOutputPath = Path.Combine(outputPath, prefabName + ".prefab");
// 拷贝专用切图
string textureFolderPath = _texturePathField?.value;
var textureMappings = CopyAndRefreshTextures(textureFolderPath, texturesPath);
// 处理公共切图文件夹
var commonMappings = ProcessCommonTextures(texturesPath);
// 合并映射表(公共切图映射添加到专用切图映射中)
foreach (var kvp in commonMappings)
{
textureMappings[kvp.Key] = kvp.Value;
}
// 使用UnityPrefabBuilder构建预制体
var buildResult = _unityPrefabBuilder.BuildFromUnityPrefab(result.prefab, new BuildOptions
{
Name = prefabName,
OutputPath = fullOutputPath,
TextureMappings = textureMappings
});
if (buildResult.success)
{
Log($"预制体生成成功: {fullOutputPath}");
UpdateStatus($"预制体生成成功: {fullOutputPath}", StatusColor.Success);
EditorGUIUtility.PingObject(AssetDatabase.LoadAssetAtPath<GameObject>(fullOutputPath));
}
else
{
LogError($"预制体生成失败: {string.Join(", ", buildResult.stats.errors.Select(e => e.message))}");
UpdateStatus($"预制体生成失败: {string.Join(", ", buildResult.stats.errors.Select(e => e.message))}", StatusColor.Error);
}
}
/// <summary>
/// 【新增】通用模式生成流程
/// </summary>
private async Task GenerateUniversal()
{
UUIStructure uiStructure = null;
// 【新增】清空Unity数据避免混淆
_currentUnityPrefab = null;
// 【新增】检查JSON视图是否有内容有则直接使用测试模式
string jsonContent = _jsonViewField?.value?.Trim();
if (!string.IsNullOrEmpty(jsonContent))
{
Log("检测到JSON内容直接使用测试模式...");
try
{
uiStructure = JsonUtility.FromJson<UUIStructure>(jsonContent);
if (uiStructure?.root == null)
{
LogWarning("JSON内容无效将调用服务器分析...");
uiStructure = null;
}
}
catch (Exception ex)
{
LogWarning($"JSON解析失败: {ex.Message},将调用服务器分析...");
uiStructure = null;
}
}
// 如果没有有效的JSON内容调用服务器分析
if (uiStructure == null)
{
string imagePath = _imagePathField.value;
if (string.IsNullOrEmpty(imagePath) || !File.Exists(imagePath))
{
LogError("请先选择有效的设计图");
UpdateStatus("请先选择有效的设计图", StatusColor.Error);
return;
}
// 【新增】显示加载动画
ShowLoadingOverlay("正在分析设计图...");
Log("开始分析设计图...");
// 准备切图信息
var selectedTextures = _textureFiles.Where(t => t.selected).ToList();
// 扫描公共文件夹
var commonTextures = ScanCommonFolders();
// 调用AI服务后台线程
var result = await Task.Run(() => _aiService.AnalyzeSync(imagePath, selectedTextures, commonTextures));
// 【新增】隐藏加载动画
HideLoadingOverlay();
Log($"分析完成,检测到 {result.NodeCount} 个UI元素");
// 【新增】更新额度信息(从服务器返回)
// 注意:-1 表示无值JsonUtility 不支持 int?
if (result.credits >= 0 && _currentAuthState != null)
{
_currentAuthState.Credits = result.credits;
// 立即更新 UI主线程
EditorApplication.delayCall += () => UpdateUIBasedOnState();
}
else if (result.remainingTrials >= 0 && _currentAuthState != null)
{
_currentAuthState.RemainingTrials = result.remainingTrials;
// 立即更新 UI主线程
EditorApplication.delayCall += () => UpdateUIBasedOnState();
}
// 【新增】成功时隐藏错误反馈按钮
HideErrorFeedbackButton();
uiStructure = result.UIStructure;
}
// 【修复】仅在 DEBUG 模式下显示生成结果
if (DEBUG_SHOW_RESULT)
{
DisplayResult(uiStructure);
}
else
{
// 非 DEBUG 模式,仅保存结果用于后续处理
_currentUIStructure = uiStructure;
}
// 准备输出目录
string outputPath = "Assets/AutoUI_Generated";
string texturesPath = Path.Combine(outputPath, "Textures");
string prefabName = $"GeneratedUI_{DateTime.Now:yyyyMMdd_HHmmss}";
string fullOutputPath = Path.Combine(outputPath, prefabName + ".prefab");
// 拷贝专用切图
string textureFolderPath = _texturePathField?.value;
var textureMappings = CopyAndRefreshTextures(textureFolderPath, texturesPath);
// 处理公共切图文件夹
var commonMappings = ProcessCommonTextures(texturesPath);
// 合并映射表(公共切图映射添加到专用切图映射中)
foreach (var kvp in commonMappings)
{
textureMappings[kvp.Key] = kvp.Value;
}
// 构建预制体
var buildResult = _prefabBuilder.BuildFromUUI(uiStructure, new BuildOptions
{
Name = prefabName,
OutputPath = fullOutputPath,
TextureMappings = textureMappings
});
if (buildResult.success)
{
Log($"预制体生成成功: {fullOutputPath}");
UpdateStatus($"预制体生成成功: {fullOutputPath}", StatusColor.Success);
EditorGUIUtility.PingObject(AssetDatabase.LoadAssetAtPath<GameObject>(fullOutputPath));
}
else
{
LogError($"预制体生成失败: {string.Join(", ", buildResult.stats.errors.Select(e => e.message))}");
UpdateStatus($"预制体生成失败: {string.Join(", ", buildResult.stats.errors.Select(e => e.message))}", StatusColor.Error);
}
}
private async void OnLoginClicked()
{
try
{
string email = _loginEmailField.value;
string password = _loginPasswordField.value;
// 清空错误提示
ShowLoginError("");
// 客户端校验
if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(password))
{
ShowLoginError("请填写邮箱和密码");
return;
}
// 验证邮箱格式
if (!System.Text.RegularExpressions.Regex.IsMatch(email, @"^[^\s@]+@[^\s@]+\.[^\s@]+$"))
{
ShowLoginError("请输入有效的邮箱地址");
return;
}
if (password.Length < 8)
{
ShowLoginError("密码长度至少为8位");
return;
}
Log("登录中...");
// 后台线程执行登录
var result = await Task.Run(() => _authService.LoginSync(email, password));
if (result.success)
{
// 【主线程】保存登录信息
EditorPrefs.SetString("autoui_token", result.token);
EditorPrefs.SetString("autoui_user_info", JsonUtility.ToJson(result.user));
// 使用 AuthService 统一更新 token
_authService.UpdateToken(result.token);
_authService.UpdateUsername(result.user.email);
Log("登录成功");
ShowLoginError(""); // 清空错误
RefreshAuthStateSync(true); // 强制刷新
UpdateUIBasedOnState();
}
else
{
ShowLoginError(result.message);
LogError($"登录失败: {result.message}");
}
}
catch (Exception ex)
{
ShowLoginError(ex.Message);
LogError($"登录异常: {ex.Message}");
}
}
private void OnShowRegisterClicked()
{
ShowRegisterPanel();
}
private void OnShowLoginClicked()
{
ShowLoginPanel();
}
/// <summary>
/// 【新增】检查是否在冷却期内
/// </summary>
private bool IsInCooldown(string actionKey)
{
if (_lastClickTime.TryGetValue(actionKey, out DateTime lastTime))
{
double secondsElapsed = DateTime.Now.Subtract(lastTime).TotalSeconds;
if (secondsElapsed < COOLDOWN_SECONDS)
{
int remaining = COOLDOWN_SECONDS - (int)secondsElapsed;
LogWarning($"请等待 {remaining} 秒后重试");
return true;
}
}
_lastClickTime[actionKey] = DateTime.Now;
return false;
}
private async void OnSendCodeClicked()
{
if (_isSendingCode) return;
// 防连点检查
if (IsInCooldown("sendCode")) return;
string email = _regEmailField.value;
if (string.IsNullOrEmpty(email))
{
LogError("请输入邮箱地址");
return;
}
_isSendingCode = true;
_sendCodeBtn.SetEnabled(false);
_sendCodeBtn.text = "发送中...";
try
{
// 后台线程执行
var result = await Task.Run(() => _authService.SendCodeSync(email, "register"));
if (result.success)
{
Log("验证码已发送");
_codeHintLabel.text = $"验证码已发送至 {email}";
// 开始倒计时
int cooldown = result.cooldownSeconds > 0 ? result.cooldownSeconds : 60;
StartCodeCountdown(cooldown);
}
else
{
LogError($"发送失败: {result.message}");
_sendCodeBtn.SetEnabled(true);
_sendCodeBtn.text = "发送";
}
}
catch (Exception ex)
{
LogError($"发送异常: {ex.Message}");
_sendCodeBtn.SetEnabled(true);
_sendCodeBtn.text = "发送";
}
finally
{
_isSendingCode = false;
}
}
private async void StartCodeCountdown(int seconds)
{
for (int i = seconds; i > 0; i--)
{
_sendCodeBtn.text = $"{i}s";
await Task.Delay(1000);
}
_sendCodeBtn.text = "发送";
_sendCodeBtn.SetEnabled(true);
}
private async void OnRegisterClicked()
{
try
{
string email = _regEmailField.value;
string code = _regCodeField.value;
string password = _regPasswordField.value;
string confirmPassword = _regConfirmPasswordField.value;
// 清空错误提示
ShowRegisterError("");
// 客户端校验
if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(code) ||
string.IsNullOrEmpty(password) || string.IsNullOrEmpty(confirmPassword))
{
ShowRegisterError("请填写所有字段");
return;
}
if (password != confirmPassword)
{
ShowRegisterError("两次输入的密码不一致");
return;
}
if (password.Length < 8)
{
ShowRegisterError("密码长度至少为8位");
return;
}
// 检查密码是否包含字母和数字
bool hasLetter = false;
bool hasDigit = false;
foreach (char c in password)
{
if (char.IsLetter(c)) hasLetter = true;
if (char.IsDigit(c)) hasDigit = true;
}
if (!hasLetter || !hasDigit)
{
ShowRegisterError("密码需包含字母和数字");
return;
}
Log("注册中...");
// 后台线程执行注册
var result = await Task.Run(() => _authService.RegisterSync(email, password, code));
if (result.success)
{
// 【主线程】保存登录信息
EditorPrefs.SetString("autoui_token", result.token);
EditorPrefs.SetString("autoui_user_info", JsonUtility.ToJson(result.user));
// 使用 AuthService 统一更新 token
_authService.UpdateToken(result.token);
_authService.UpdateUsername(result.user.email);
Log("注册成功,已自动登录");
ShowRegisterError(""); // 清空错误
RefreshAuthStateSync(true); // 强制刷新
UpdateUIBasedOnState();
}
else
{
ShowRegisterError(result.message);
LogError($"注册失败: {result.message}");
}
}
catch (Exception ex)
{
ShowRegisterError(ex.Message);
LogError($"注册异常: {ex.Message}");
}
}
private void OnLogoutClicked()
{
// 显示下拉菜单
var menu = new GenericMenu();
// 显示账号信息(不可点击)
string email = _currentAuthState?.Email ?? _authService?.Username ?? "未知用户";
string plan = GetPlanDisplayName(_currentAuthState?.Plan);
menu.AddDisabledItem(new GUIContent($"👤 {email}"));
menu.AddDisabledItem(new GUIContent($"📦 {plan}"));
menu.AddSeparator("");
// 退出登录选项
menu.AddItem(new GUIContent("退出登录"), false, PerformLogout);
// 显示菜单
menu.ShowAsContext();
}
/// <summary>
/// 执行退出登录
/// </summary>
private async void PerformLogout()
{
_authService.LogoutSync();
await RefreshAuthStateAsync(true);
// 注意RefreshAuthStateAsync 已经会调用 UpdateUIBasedOnState()
Log("已退出登录");
}
/// <summary>
/// 【新增】忘记密码按钮点击
/// </summary>
private void OnForgotPasswordClicked()
{
ShowResetPasswordPanel();
}
/// <summary>
/// 【新增】重置密码 - 发送验证码
/// </summary>
private async void OnResetSendCodeClicked()
{
if (_isSendingCode) return;
if (IsInCooldown("resetSendCode")) return;
string email = _resetEmailField.value;
if (string.IsNullOrEmpty(email))
{
LogError("请输入邮箱地址");
return;
}
_isSendingCode = true;
_resetSendCodeBtn.SetEnabled(false);
_resetSendCodeBtn.text = "发送中...";
try
{
// 后台线程执行type = "reset" 表示重置密码的验证码)
var result = await Task.Run(() => _authService.SendCodeSync(email, "reset"));
if (result.success)
{
Log("验证码已发送");
_resetCodeHintLabel.text = $"验证码已发送至 {email}";
int cooldown = result.cooldownSeconds > 0 ? result.cooldownSeconds : 60;
StartResetCodeCountdown(cooldown);
}
else
{
LogError($"发送失败: {result.message}");
_resetSendCodeBtn.SetEnabled(true);
_resetSendCodeBtn.text = "发送";
}
}
catch (Exception ex)
{
LogError($"发送异常: {ex.Message}");
_resetSendCodeBtn.SetEnabled(true);
_resetSendCodeBtn.text = "发送";
}
finally
{
_isSendingCode = false;
}
}
/// <summary>
/// 【新增】重置密码倒计时
/// </summary>
private async void StartResetCodeCountdown(int seconds)
{
for (int i = seconds; i > 0; i--)
{
_resetSendCodeBtn.text = $"{i}s";
await Task.Delay(1000);
}
_resetSendCodeBtn.text = "发送";
_resetSendCodeBtn.SetEnabled(true);
}
/// <summary>
/// 重置密码按钮点击
/// </summary>
private async void OnResetPasswordClicked()
{
try
{
string email = _resetEmailField.value;
string code = _resetCodeField.value;
string newPassword = _resetNewPasswordField.value;
string confirmPassword = _resetConfirmPasswordField.value;
// 清空错误提示
ShowResetError("");
// 客户端校验
if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(code) ||
string.IsNullOrEmpty(newPassword) || string.IsNullOrEmpty(confirmPassword))
{
ShowResetError("请填写所有字段");
return;
}
if (newPassword != confirmPassword)
{
ShowResetError("两次输入的密码不一致");
return;
}
if (newPassword.Length < 8)
{
ShowResetError("密码长度至少为8位");
return;
}
// 检查密码是否包含字母和数字
bool hasLetter = false;
bool hasDigit = false;
foreach (char c in newPassword)
{
if (char.IsLetter(c)) hasLetter = true;
if (char.IsDigit(c)) hasDigit = true;
}
if (!hasLetter || !hasDigit)
{
ShowResetError("密码需包含字母和数字");
return;
}
Log("正在重置密码...");
// 后台线程执行
var result = await Task.Run(() => _authService.ResetPasswordSync(email, newPassword, code));
if (result.success)
{
Log("密码重置成功,请登录");
ShowResetError(""); // 清空错误
// 清空表单
_resetEmailField.value = "";
_resetCodeField.value = "";
_resetNewPasswordField.value = "";
_resetConfirmPasswordField.value = "";
_resetCodeHintLabel.text = "";
// 返回登录面板
ShowLoginPanel();
}
else
{
ShowResetError(result.message);
LogError($"重置失败: {result.message}");
}
}
catch (Exception ex)
{
ShowResetError(ex.Message);
LogError($"重置异常: {ex.Message}");
}
}
#endregion
#region Logging
private void Log(string message)
{
if (DEBUG_SHOW_RESULT)
{
Debug.Log($"[AutoUI] {message}");
}
}
private void LogError(string message)
{
Debug.LogError($"[AutoUI] {message}");
}
private void LogWarning(string message)
{
if (DEBUG_SHOW_RESULT)
{
Debug.LogWarning($"[AutoUI] {message}");
}
}
#endregion
#region
/// <summary>
/// 拷贝切图资源到项目Assets目录并刷新
/// </summary>
/// <param name="sourceFolderPath">切图文件夹路径</param>
/// <param name="texturesPath">目标路径相对于Assets</param>
/// <returns>资源映射表spriteName -> assetPath</returns>
private Dictionary<string, string> CopyAndRefreshTextures(string sourceFolderPath, string texturesPath)
{
var mappings = new Dictionary<string, string>();
if (string.IsNullOrEmpty(sourceFolderPath) || !Directory.Exists(sourceFolderPath))
{
LogWarning($"切图文件夹不存在: {sourceFolderPath}");
return mappings;
}
// 确保目标目录存在
string fullPath = Path.Combine(Directory.GetCurrentDirectory(), texturesPath);
if (!Directory.Exists(fullPath))
{
Directory.CreateDirectory(fullPath);
}
else
{
// 清理目标目录中的旧文件和子目录
ClearDirectory(fullPath);
}
Log($"拷贝切图资源到: {texturesPath}");
// 支持的图片扩展名
string[] imageExtensions = { ".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp" };
// 递归收集所有图片文件
var imageFiles = Directory.GetFiles(sourceFolderPath, "*.*", SearchOption.AllDirectories)
.Where(f => imageExtensions.Contains(Path.GetExtension(f).ToLower()))
.ToList();
foreach (string srcFile in imageFiles)
{
try
{
// 获取相对路径(保持子目录结构)
string relativePath = srcFile.Substring(sourceFolderPath.Length).TrimStart(Path.DirectorySeparatorChar, '/');
string fileName = Path.GetFileName(relativePath);
string spriteName = Path.GetFileNameWithoutExtension(relativePath);
string destDir = Path.GetDirectoryName(Path.Combine(fullPath, relativePath));
string destPath = Path.Combine(fullPath, relativePath);
string assetPath = Path.Combine(texturesPath, relativePath).Replace("\\", "/");
// 确保目标目录存在
if (!Directory.Exists(destDir))
{
Directory.CreateDirectory(destDir);
}
// 拷贝文件
File.Copy(srcFile, destPath, true);
// 使用不带扩展名的文件名作为key
mappings[spriteName] = assetPath;
Log($" 拷贝: {spriteName} -> {assetPath}");
}
catch (Exception ex)
{
LogWarning($"拷贝文件失败: {srcFile} - {ex.Message}");
}
}
// 刷新资源数据库
if (mappings.Count > 0)
{
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
// 强制设置所有导入的纹理为 Sprite 类型
// Unity 默认导入的图片不是 Sprite 类型,需要手动设置才能被 Image 组件使用
foreach (var kvp in mappings)
{
string assetPath = kvp.Value;
TextureImporter importer = AssetImporter.GetAtPath(assetPath) as TextureImporter;
if (importer != null)
{
importer.textureType = TextureImporterType.Sprite;
importer.spriteImportMode = SpriteImportMode.Single;
importer.mipmapEnabled = false;
importer.filterMode = FilterMode.Bilinear;
importer.SaveAndReimport();
Log($" 设置Sprite类型: {assetPath}");
}
}
Log($"资源刷新完成,共 {mappings.Count} 个文件");
}
return mappings;
}
/// <summary>
/// 清理目录中的所有文件和子目录
/// </summary>
private void ClearDirectory(string directoryPath)
{
try
{
if (!Directory.Exists(directoryPath)) return;
// 先删除所有文件
foreach (string file in Directory.GetFiles(directoryPath))
{
File.Delete(file);
}
// 再递归删除所有子目录
foreach (string dir in Directory.GetDirectories(directoryPath))
{
Directory.Delete(dir, true);
}
Log($"已清理目录: {directoryPath}");
}
catch (Exception ex)
{
LogWarning($"清理目录失败: {directoryPath} - {ex.Message}");
}
}
/// <summary>
/// 处理公共切图文件夹
/// 实现智能检测和拷贝,避免重复拷贝和文件冲突
/// </summary>
/// <param name="texturesPath">Textures目录路径相对路径</param>
/// <returns>公共切图映射表</returns>
private Dictionary<string, string> ProcessCommonTextures(string texturesPath)
{
var allMappings = new Dictionary<string, string>();
if (_commonFolders == null || _commonFolders.Count == 0)
{
return allMappings;
}
try
{
var processor = new CommonTextureProcessor();
foreach (string folderPath in _commonFolders)
{
if (string.IsNullOrEmpty(folderPath) || !Directory.Exists(folderPath))
{
LogWarning($"公共切图文件夹不存在: {folderPath}");
continue;
}
Log($"处理公共切图文件夹: {folderPath}");
var result = processor.ProcessCommonTextures(folderPath, texturesPath, Log);
// 合并映射表
foreach (var kvp in result.TextureMappings)
{
allMappings[kvp.Key] = kvp.Value;
}
// 记录统计信息
if (result.ConflictFiles.Count > 0)
{
LogWarning($"发现 {result.ConflictFiles.Count} 个同名但内容不同的文件:");
foreach (var conflict in result.ConflictFiles)
{
LogWarning($" - {conflict}");
}
}
}
}
catch (Exception ex)
{
LogError($"处理公共切图失败: {ex.Message}");
}
return allMappings;
}
#endregion
#region
/// <summary>
/// 显示图片预览(在拖放区域内切换状态)
/// </summary>
private void ShowImagePreview(string imagePath)
{
try
{
if (string.IsNullOrEmpty(imagePath) || !File.Exists(imagePath))
{
ClearImagePreview();
return;
}
// 加载图片
byte[] imageData = File.ReadAllBytes(imagePath);
Texture2D texture = new Texture2D(2, 2);
if (!texture.LoadImage(imageData))
{
LogError("无法加载图片");
return;
}
// 更新拖放区域为预览状态
_dropZoneImage.image = texture;
_dropZoneImage.style.display = DisplayStyle.Flex;
// 更新文字显示文件名
string fileName = Path.GetFileName(imagePath);
_dropZoneText.text = fileName;
_dropZoneText.style.color = new Color(0.95f, 0.95f, 0.95f);
_dropZoneText.style.fontSize = 13;
// 显示尺寸和大小信息
_dropZoneInfo.text = $"{texture.width}x{texture.height} | {FormatFileSize(imageData.Length)}";
_dropZoneInfo.style.display = DisplayStyle.Flex;
// 显示清除按钮
var clearBtn = _imageDropZone.Q<Button>("clearImageBtn");
if (clearBtn != null)
{
clearBtn.style.display = DisplayStyle.Flex;
}
// 更新边框颜色为绿色(选中状态)
_imageDropZone.style.borderLeftColor = new Color(0.2f, 0.7f, 0.3f);
_imageDropZone.style.borderRightColor = new Color(0.2f, 0.7f, 0.3f);
_imageDropZone.style.borderTopColor = new Color(0.2f, 0.7f, 0.3f);
_imageDropZone.style.borderBottomColor = new Color(0.2f, 0.7f, 0.3f);
Log($"已加载图片预览: {fileName}");
}
catch (Exception ex)
{
LogError($"加载图片预览失败: {ex.Message}");
}
}
/// <summary>
/// 清除图片预览(恢复拖放区域到初始状态)
/// </summary>
private void ClearImagePreview()
{
// 清除图片
_dropZoneImage.image = null;
_dropZoneImage.style.display = DisplayStyle.None;
// 恢复初始文字
_dropZoneText.text = "点击选择界面效果图文件";
_dropZoneText.style.color = new Color(0.95f, 0.95f, 0.95f);
_dropZoneText.style.fontSize = 14;
// 隐藏信息
_dropZoneInfo.text = "";
_dropZoneInfo.style.display = DisplayStyle.None;
// 隐藏清除按钮
var clearBtn = _imageDropZone.Q<Button>("clearImageBtn");
if (clearBtn != null)
{
clearBtn.style.display = DisplayStyle.None;
}
// 恢复边框颜色
_imageDropZone.style.borderLeftColor = new Color(0.35f, 0.35f, 0.38f);
_imageDropZone.style.borderRightColor = new Color(0.35f, 0.35f, 0.38f);
_imageDropZone.style.borderTopColor = new Color(0.35f, 0.35f, 0.38f);
_imageDropZone.style.borderBottomColor = new Color(0.35f, 0.35f, 0.38f);
// 清除路径
_imagePathField.value = "";
}
/// <summary>
/// 【新增】格式化文件大小
/// </summary>
private string FormatFileSize(long bytes)
{
string[] sizes = { "B", "KB", "MB", "GB" };
int order = 0;
double size = bytes;
while (size >= 1024 && order < sizes.Length - 1)
{
order++;
size /= 1024;
}
return $"{size:0.##} {sizes[order]}";
}
#endregion
#region (JSON视图 / )
/// <summary>
/// 【新增】切换视图模式
/// </summary>
private void SwitchViewMode(ViewMode mode)
{
// 【修复】非 DEBUG 模式下可能为 null
if (!DEBUG_SHOW_RESULT) return;
_currentViewMode = mode;
if (_viewModeMenu != null)
_viewModeMenu.text = mode == ViewMode.Json ? "JSON视图" : "树形视图";
if (mode == ViewMode.Json)
{
if (_jsonScrollView != null)
_jsonScrollView.style.display = DisplayStyle.Flex;
if (_treeViewContainer != null)
_treeViewContainer.style.display = DisplayStyle.None;
}
else
{
if (_jsonScrollView != null)
_jsonScrollView.style.display = DisplayStyle.None;
if (_treeViewContainer != null)
_treeViewContainer.style.display = DisplayStyle.Flex;
RenderTreeView();
}
}
/// <summary>
/// 【新增】显示生成结果
/// </summary>
private void DisplayResult(UUIStructure uiStructure)
{
_currentUIStructure = uiStructure;
// 【修复】非 DEBUG 模式下 UI 元素可能为 null
if (!DEBUG_SHOW_RESULT || _resultViewContainer == null)
{
return;
}
if (uiStructure == null)
{
// 清空内容,但保持容器可见(方便测试输入)
if (_jsonViewField != null)
_jsonViewField.value = "";
if (_treeViewContainer != null)
_treeViewContainer.Clear();
// 不再隐藏容器
return;
}
// 显示结果容器
_resultViewContainer.style.display = DisplayStyle.Flex;
// 更新JSON视图 - 限制长度避免顶点超限
if (_jsonViewField != null)
{
string json = JsonUtility.ToJson(uiStructure, true);
// Unity UIToolkit 有 65535 顶点限制,长文本会超限
const int MAX_JSON_LENGTH = 60000; // 安全长度
if (json.Length > MAX_JSON_LENGTH)
{
json = json.Substring(0, MAX_JSON_LENGTH) + "\n\n...(内容过长,已截断,可使用复制按钮获取完整内容)";
}
_jsonViewField.value = json;
}
// 如果当前是树形视图,重新渲染
if (_currentViewMode == ViewMode.Tree)
{
RenderTreeView();
}
}
/// <summary>
/// 【新增】显示Unity分析结果用于调试
/// </summary>
private void DisplayUnityResult(UnityPrefabNode prefabRoot)
{
// 【修复】非 DEBUG 模式下 UI 元素可能为 null
if (!DEBUG_SHOW_RESULT || _resultViewContainer == null)
{
return;
}
if (prefabRoot == null)
{
// 清空内容
if (_jsonViewField != null)
_jsonViewField.value = "";
if (_treeViewContainer != null)
_treeViewContainer.Clear();
return;
}
// 显示结果容器
_resultViewContainer.style.display = DisplayStyle.Flex;
// 更新JSON视图 - 显示Unity格式结果
if (_jsonViewField != null)
{
string json = JsonConvert.SerializeObject(prefabRoot, Formatting.Indented);
// Unity UIToolkit 有 65535 顶点限制,长文本会超限
const int MAX_JSON_LENGTH = 60000; // 安全长度
if (json.Length > MAX_JSON_LENGTH)
{
json = json.Substring(0, MAX_JSON_LENGTH) + "\n\n...(内容过长,已截断,可使用复制按钮获取完整内容)";
}
_jsonViewField.value = json;
}
// 树形视图暂时不支持Unity格式仅在JSON视图显示
// 如果需要树形视图支持可以后续实现RenderUnityTreeView方法
}
/// <summary>
/// 【新增】渲染树形视图
/// </summary>
private void RenderTreeView()
{
// 【修复】非 DEBUG 模式下可能为 null
if (_treeViewContainer == null) return;
_treeViewContainer.Clear();
if (_currentUIStructure?.root == null)
{
_treeViewContainer.Add(new Label("暂无UI结构") { style = { color = Color.gray, marginTop = 10 } });
return;
}
// 添加根节点UUIRoot类型
var rootElement = CreateRootTreeNode(_currentUIStructure.root, "root", 0);
_treeViewContainer.Add(rootElement);
}
/// <summary>
/// 【新增】创建根树节点UUIRoot专用
/// </summary>
private VisualElement CreateRootTreeNode(UUIRoot root, string nodePath, int depth)
{
var container = new VisualElement();
container.AddToClassList("tree-node-container");
bool hasChildren = root.children != null && root.children.Count > 0;
bool isExpanded = _expandedTreeNodes.Contains(nodePath);
bool isSelected = _selectedTreeNodePath == nodePath;
// 节点行
var nodeRow = new VisualElement()
{
style =
{
flexDirection = FlexDirection.Row,
alignItems = Align.Center,
paddingLeft = depth * 16 + 8,
paddingTop = 2,
paddingBottom = 2,
backgroundColor = isSelected ? new Color(0.2f, 0.4f, 0.6f) : Color.clear,
borderTopLeftRadius = 3,
borderTopRightRadius = 3,
borderBottomLeftRadius = 3,
borderBottomRightRadius = 3
}
};
// 展开/折叠按钮
if (hasChildren)
{
var toggleBtn = new Button()
{
text = isExpanded ? "▼" : "▶",
style =
{
width = 20,
height = 20,
paddingLeft = 0,
paddingRight = 0,
paddingTop = 0,
paddingBottom = 0,
marginLeft = 0,
marginRight = 2,
backgroundColor = Color.clear,
borderLeftColor = Color.clear,
borderRightColor = Color.clear,
borderTopColor = Color.clear,
borderBottomColor = Color.clear
}
};
string currentPath = nodePath;
toggleBtn.clicked += () => ToggleTreeNode(currentPath);
nodeRow.Add(toggleBtn);
}
else
{
nodeRow.Add(new VisualElement() { style = { width = 22 } });
}
// 节点图标
var icon = new Label("🖼")
{
style = { marginRight = 5, fontSize = 14 }
};
nodeRow.Add(icon);
// 节点名称
var nameLabel = new Label(root.name ?? "Canvas")
{
style =
{
flexGrow = 1,
unityFontStyleAndWeight = FontStyle.Bold
}
};
nodeRow.Add(nameLabel);
// 节点类型标签
var typeLabel = new Label(root.type ?? "Canvas")
{
style =
{
fontSize = 11,
color = new Color(0.5f, 0.5f, 0.5f),
marginLeft = 10
}
};
nodeRow.Add(typeLabel);
// 点击选中
nodeRow.RegisterCallback<ClickEvent>(evt =>
{
_selectedTreeNodePath = nodePath;
RenderTreeView();
});
container.Add(nodeRow);
// 子节点
if (hasChildren && isExpanded)
{
var childrenContainer = new VisualElement()
{
style = { marginLeft = 16 }
};
for (int i = 0; i < root.children.Count; i++)
{
string childPath = $"{nodePath}/{i}";
var childElement = CreateTreeNode(root.children[i], childPath, depth + 1);
childrenContainer.Add(childElement);
}
container.Add(childrenContainer);
}
return container;
}
/// <summary>
/// 【新增】创建树节点
/// </summary>
private VisualElement CreateTreeNode(UUINode node, string nodePath, int depth)
{
var container = new VisualElement();
container.AddToClassList("tree-node-container");
bool hasChildren = node.children != null && node.children.Count > 0;
bool isExpanded = _expandedTreeNodes.Contains(nodePath);
bool isSelected = _selectedTreeNodePath == nodePath;
// 节点行
var nodeRow = new VisualElement()
{
style =
{
flexDirection = FlexDirection.Row,
alignItems = Align.Center,
paddingLeft = depth * 16 + 8,
paddingTop = 2,
paddingBottom = 2,
backgroundColor = isSelected ? new Color(0.2f, 0.4f, 0.6f) : Color.clear,
borderTopLeftRadius = 3,
borderTopRightRadius = 3,
borderBottomLeftRadius = 3,
borderBottomRightRadius = 3
}
};
// 展开/折叠按钮
if (hasChildren)
{
var toggleBtn = new Button()
{
text = isExpanded ? "▼" : "▶",
style =
{
width = 20,
height = 20,
paddingLeft = 0,
paddingRight = 0,
paddingTop = 0,
paddingBottom = 0,
marginLeft = 0,
marginRight = 2,
backgroundColor = Color.clear,
borderTopColor = Color.clear,
borderBottomColor = Color.clear,
borderLeftColor = Color.clear,
borderRightColor = Color.clear
}
};
string currentPath = nodePath; // 捕获变量
toggleBtn.clicked += () => ToggleTreeNode(currentPath);
nodeRow.Add(toggleBtn);
}
else
{
nodeRow.Add(new VisualElement() { style = { width = 22 } });
}
// 节点图标
var icon = new Label(GetNodeIcon(node.type))
{
style = { marginRight = 5, fontSize = 14 }
};
nodeRow.Add(icon);
// 节点名称
var nameLabel = new Label(node.name ?? node.type ?? "Node")
{
style =
{
flexGrow = 1,
unityFontStyleAndWeight = FontStyle.Bold
}
};
nodeRow.Add(nameLabel);
// 节点类型标签
var typeLabel = new Label(node.type ?? "Unknown")
{
style =
{
fontSize = 11,
color = new Color(0.5f, 0.5f, 0.5f),
marginLeft = 10
}
};
nodeRow.Add(typeLabel);
// 点击选中
nodeRow.RegisterCallback<ClickEvent>(evt =>
{
_selectedTreeNodePath = nodePath;
RenderTreeView(); // 重新渲染以更新选中状态
});
container.Add(nodeRow);
// 子节点
if (hasChildren && isExpanded)
{
var childrenContainer = new VisualElement()
{
style = { marginLeft = 16 }
};
for (int i = 0; i < node.children.Count; i++)
{
string childPath = $"{nodePath}/{i}";
var childElement = CreateTreeNode(node.children[i], childPath, depth + 1);
childrenContainer.Add(childElement);
}
container.Add(childrenContainer);
}
return container;
}
/// <summary>
/// 【新增】切换树节点展开/折叠
/// </summary>
private void ToggleTreeNode(string nodePath)
{
if (_expandedTreeNodes.Contains(nodePath))
{
_expandedTreeNodes.Remove(nodePath);
}
else
{
_expandedTreeNodes.Add(nodePath);
}
RenderTreeView();
}
/// <summary>
/// 【新增】全部展开
/// </summary>
private void ExpandAllTreeNodes()
{
if (!DEBUG_SHOW_RESULT) return;
CollectAllNodePaths(_currentUIStructure?.root, "root");
if (_currentViewMode == ViewMode.Tree)
{
RenderTreeView();
}
}
/// <summary>
/// 【新增】全部折叠
/// </summary>
private void CollapseAllTreeNodes()
{
if (!DEBUG_SHOW_RESULT) return;
_expandedTreeNodes.Clear();
_expandedTreeNodes.Add("root"); // 保持根节点展开
if (_currentViewMode == ViewMode.Tree)
{
RenderTreeView();
}
}
/// <summary>
/// 【新增】收集所有节点路径(从根节点开始)
/// </summary>
private void CollectAllNodePaths(UUIRoot root, string nodePath)
{
if (root == null) return;
_expandedTreeNodes.Add(nodePath);
if (root.children != null)
{
for (int i = 0; i < root.children.Count; i++)
{
CollectAllNodePaths(root.children[i], $"{nodePath}/{i}");
}
}
}
/// <summary>
/// 【新增】收集所有节点路径
/// </summary>
private void CollectAllNodePaths(UUINode node, string nodePath)
{
if (node == null) return;
_expandedTreeNodes.Add(nodePath);
if (node.children != null)
{
for (int i = 0; i < node.children.Count; i++)
{
CollectAllNodePaths(node.children[i], $"{nodePath}/{i}");
}
}
}
/// <summary>
/// 【新增】获取节点图标
/// </summary>
private string GetNodeIcon(string nodeType)
{
return nodeType?.ToLower() switch
{
"sprite" => "🖼",
"label" => "📝",
"button" => "🔘",
"editbox" => "⌨",
"toggle" => "☑",
"slider" => "🎚",
"progressbar" => "📊",
"scrollview" => "📜",
"pageview" => "📑",
"layout" => "📐",
"container" => "📦",
_ => "📄"
};
}
/// <summary>
/// 【新增】复制结果到剪贴板
/// </summary>
private void CopyResultToClipboard()
{
// 【修复】根据当前分析模式复制正确的数据
if (_currentAnalysisMode == AnalysisMode.UnityNative)
{
// Unity原生模式
if (_currentUnityPrefab == null)
{
LogWarning("没有可复制的Unity预制体数据");
return;
}
string json = JsonConvert.SerializeObject(_currentUnityPrefab, Formatting.Indented);
GUIUtility.systemCopyBuffer = json;
Log("已复制Unity预制体数据到剪贴板");
}
else
{
// 通用模式
if (_currentUIStructure == null)
{
LogWarning("没有可复制的界面结构");
return;
}
string json = JsonUtility.ToJson(_currentUIStructure, true);
GUIUtility.systemCopyBuffer = json;
Log("已复制到剪贴板");
}
}
#endregion
#region
/// <summary>
/// 【新增】处理错误(带错误码)
/// </summary>
private void HandleError(Exception error, string context = null)
{
string errorCode = "UNKNOWN";
string message = error.Message;
// 解析错误码 - 优先使用 AppException 的 ErrorCode
if (error is AppException appEx)
{
errorCode = appEx.ErrorCode;
}
else if (error.Message.Contains("TRIAL_EXHAUSTED") ||
error.Message.Contains("Trial credits exhausted") ||
error.Message.Contains("试用额度已用完"))
{
errorCode = ErrorCodes.TRIAL_EXHAUSTED;
}
else if (error.Message.Contains("额度") ||
error.Message.Contains("credits") ||
error.Message.Contains("quota"))
{
errorCode = ErrorCodes.INSUFFICIENT_CREDITS;
}
else if (error.Message.Contains("服务器") ||
error.Message.Contains("离线"))
{
errorCode = ErrorCodes.SERVER_OFFLINE;
}
else if (error.Message.Contains("网络") ||
error.Message.Contains("Network") ||
error.Message.Contains("sending the request") ||
error.Message.Contains("unable to connect") ||
error.Message.Contains("connection refused") ||
error.Message.Contains("No such host"))
{
errorCode = ErrorCodes.NETWORK_ERROR;
}
else if (error.Message.Contains("超时") ||
error.Message.Contains("Timeout"))
{
errorCode = ErrorCodes.TIMEOUT;
}
else if (error.Message.Contains("授权") ||
error.Message.Contains("Unauthorized"))
{
errorCode = ErrorCodes.UNAUTHORIZED;
}
// 记录错误
LogError($"[{errorCode}] {context}: {message}");
// 【新增】显示错误反馈按钮
ShowErrorFeedbackButton(error);
// 处理特定错误码
switch (errorCode)
{
case ErrorCodes.TRIAL_EXHAUSTED:
if (_currentAuthState != null)
{
// 【修复】试用耗尽时 Type 保持 Trial通过 RemainingTrials 和 Valid 表示状态
_currentAuthState.Valid = false;
_currentAuthState.RemainingTrials = 0;
UpdateUIBasedOnState();
}
break;
case ErrorCodes.INSUFFICIENT_CREDITS:
// 【修复】使用 UserState 判断或检查 Type 不是 Trial
if (_currentAuthState != null && _currentAuthState.Type != AuthType.Trial)
{
_currentAuthState.Credits = 0;
UpdateUIBasedOnState();
}
break;
case ErrorCodes.SERVER_OFFLINE:
case ErrorCodes.NETWORK_ERROR:
_serverOnline = false;
break;
case ErrorCodes.UNAUTHORIZED:
// 尝试刷新token或引导重新登录
_ = HandleUnauthorizedAsync();
break;
}
}
/// <summary>
/// 【新增】处理未授权错误
/// </summary>
private async Task HandleUnauthorizedAsync()
{
try
{
Log("Token已过期尝试刷新...");
string newToken = await Task.Run(() => _authService.TryRefreshTokenSync());
if (!string.IsNullOrEmpty(newToken))
{
Log("Token刷新成功");
// 在主线程同步到 EditorPrefs
EditorApplication.delayCall += () =>
{
_authService.UpdateToken(newToken);
};
await RefreshAuthStateAsync(true);
}
else
{
Log("Token刷新失败需要重新登录");
_authService.LogoutSync();
UpdateUIBasedOnState();
}
}
catch (Exception ex)
{
LogError($"处理未授权错误失败: {ex.Message}");
}
}
#endregion
#region Tab切换与反馈功能
/// <summary>
/// 【新增】切换Tab
/// </summary>
private void SwitchTab(TabType tabType)
{
_currentTab = tabType;
// 重置所有按钮样式
_tabBtnGenerate.style.color = new Color(0.7f, 0.7f, 0.7f);
_tabBtnGenerate.style.borderBottomColor = Color.clear;
_tabBtnSettings.style.color = new Color(0.7f, 0.7f, 0.7f);
_tabBtnSettings.style.borderBottomColor = Color.clear;
_tabBtnFeedback.style.color = new Color(0.7f, 0.7f, 0.7f);
_tabBtnFeedback.style.borderBottomColor = Color.clear;
// 隐藏所有Tab内容
_tabGenerate.style.display = DisplayStyle.None;
_tabSettings.style.display = DisplayStyle.None;
_tabFeedback.style.display = DisplayStyle.None;
// 显示选中的Tab
switch (tabType)
{
case TabType.Generate:
_tabBtnGenerate.style.color = new Color(0.9f, 0.9f, 0.9f);
_tabBtnGenerate.style.borderBottomColor = new Color(0.25f, 0.55f, 0.95f);
_tabGenerate.style.display = DisplayStyle.Flex;
break;
case TabType.Settings:
_tabBtnSettings.style.color = new Color(0.9f, 0.9f, 0.9f);
_tabBtnSettings.style.borderBottomColor = new Color(0.25f, 0.55f, 0.95f);
_tabSettings.style.display = DisplayStyle.Flex;
break;
case TabType.Feedback:
_tabBtnFeedback.style.color = new Color(0.9f, 0.9f, 0.9f);
_tabBtnFeedback.style.borderBottomColor = new Color(0.25f, 0.55f, 0.95f);
_tabFeedback.style.display = DisplayStyle.Flex;
break;
}
}
/// <summary>
/// 【新增】处理反馈提交按钮点击
/// </summary>
private async void OnFeedbackSubmitClicked()
{
string message = _feedbackTextField.value?.Trim();
if (string.IsNullOrEmpty(message))
{
_feedbackHintLabel.text = "请输入反馈内容";
return;
}
_feedbackSubmitBtn.SetEnabled(false);
_feedbackSubmitBtn.text = "提交中...";
_feedbackHintLabel.text = "";
try
{
var result = await SubmitFeedbackAsync(message, false);
if (result.success)
{
// 显示成功提示
_feedbackSuccessContainer.style.display = DisplayStyle.Flex;
_feedbackTextField.value = "";
// 恢复按钮状态
_feedbackSubmitBtn.SetEnabled(true);
_feedbackSubmitBtn.text = "提交";
// 5秒后隐藏成功提示
_ = HideFeedbackSuccessAsync(3000);
Log("反馈提交成功");
}
else
{
_feedbackHintLabel.text = result.error ?? "提交失败,请稍后重试";
_feedbackSubmitBtn.SetEnabled(true);
_feedbackSubmitBtn.text = "提交";
}
}
catch (Exception ex)
{
_feedbackHintLabel.text = $"提交失败: {ex.Message}";
_feedbackSubmitBtn.SetEnabled(true);
_feedbackSubmitBtn.text = "提交";
}
}
/// <summary>
/// 【新增】异步隐藏反馈成功提示
/// </summary>
private async Task HideFeedbackSuccessAsync(int delayMs)
{
await Task.Delay(delayMs);
EditorApplication.delayCall += () =>
{
if (_feedbackSuccessContainer != null)
{
_feedbackSuccessContainer.style.display = DisplayStyle.None;
}
};
}
/// <summary>
/// 【新增】处理错误反馈按钮点击
/// </summary>
private async void OnFeedbackErrorClicked()
{
if (_feedbackErrorBtn == null || !_feedbackErrorBtn.enabledSelf) return;
_feedbackErrorBtn.SetEnabled(false);
_feedbackErrorBtn.text = "提交中...";
// 构建自动反馈消息
string autoMessage = $"[自动反馈] {_lastError?.Message ?? ""}";
var result = await SubmitFeedbackAsync(autoMessage, true);
if (result.success)
{
_feedbackErrorBtn.text = "已提交 ✓";
_errorFeedbackSubmitted = true;
Log("错误反馈已提交");
}
else
{
_feedbackErrorBtn.text = "反馈问题";
_feedbackErrorBtn.SetEnabled(true);
LogError($"反馈提交失败: {result.error}");
}
}
/// <summary>
/// 【新增】显示错误反馈按钮
/// </summary>
private void ShowErrorFeedbackButton(Exception error)
{
_lastError = error;
_errorFeedbackSubmitted = false;
if (_feedbackErrorBtn != null)
{
_feedbackErrorBtn.style.display = DisplayStyle.Flex;
_feedbackErrorBtn.text = "反馈问题";
_feedbackErrorBtn.SetEnabled(true);
}
}
/// <summary>
/// 【新增】隐藏错误反馈按钮
/// </summary>
private void HideErrorFeedbackButton()
{
if (_feedbackErrorBtn != null)
{
_feedbackErrorBtn.style.display = DisplayStyle.None;
}
}
/// <summary>
/// 【新增】提交反馈到服务器
/// </summary>
private async Task<FeedbackResult> SubmitFeedbackAsync(string message, bool isAutoFeedback)
{
try
{
var feedbackData = new FeedbackData
{
message = message,
context = new FeedbackContext
{
pluginVersion = Config.Version,
unityVersion = Application.unityVersion,
timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
errorLog = isAutoFeedback && _lastError != null ? _lastError.ToString() : null
}
};
// 使用AuthService的ApiClient发送请求后台线程
// 直接传入对象,让 APIClient 内部序列化
string responseStr = await Task.Run(() => _authService.ApiClient.PostSync("/api/v1/feedback", feedbackData));
var response = JsonUtility.FromJson<FeedbackResponse>(responseStr);
if (response != null && response.success)
{
return new FeedbackResult { success = true, feedbackId = response.feedbackId };
}
else
{
return new FeedbackResult { success = false, error = response?.message ?? "提交失败" };
}
}
catch (Exception ex)
{
Debug.LogError($"[AutoUI] 反馈提交异常: {ex.Message}");
return new FeedbackResult { success = false, error = ex.Message };
}
}
#endregion
#region
/// <summary>
/// 【首次体验优化】首次使用引导对话框
/// </summary>
private void ShowFirstTimeGuide()
{
// 检查是否已经显示过引导
if (EditorPrefs.GetBool("AutoUI_FirstTimeShown", false))
{
return;
}
// 标记为已显示
EditorPrefs.SetBool("AutoUI_FirstTimeShown", true);
// 延迟显示对话框,确保窗口已经完全初始化
EditorApplication.delayCall += () =>
{
int result = EditorUtility.DisplayDialogComplex(
"欢迎使用 AutoUI",
"检测到您是首次使用。建议:\n\n" +
"1. 【快速体验】加载内置示例(含效果图+切图)\n" +
"2. 【查看教程】了解如何准备资源\n\n" +
"内置示例可直接生成完整UI无需准备任何资源。",
"快速体验",
"查看教程",
"我知道了"
);
switch (result)
{
case 0: // 快速体验
LoadBuiltinSample();
break;
case 1: // 查看教程
UpdateStatus("教程正在抓紧制作中,敬请期待", StatusColor.Success);
//Application.OpenURL("https://github.com/your-repo/autoui-docs");
break;
case 2: // 我知道了
// 不做任何操作
UpdateStatus("等待选择效果图文件", StatusColor.Success);
break;
}
};
}
/// <summary>
/// 【首次体验优化】加载内置示例(效果图 + 切图)
/// 直接指向包内的示例资源路径
/// </summary>
private void LoadBuiltinSample()
{
try
{
// 获取包路径
string packagePath = GetPackagePath();
if (string.IsNullOrEmpty(packagePath))
{
Debug.LogWarning("[AutoUI] 无法获取插件路径,使用程序化示例");
CreateProceduralSample();
return;
}
// 包内示例资源路径
string sampleImageDir = $"{packagePath}/Samples/Images";
string sampleSliceDir = $"{packagePath}/Samples/Slices";
Debug.Log($"[AutoUI] 检查示例路径: Images={sampleImageDir}, Slices={sampleSliceDir}");
// 检查示例资源是否存在
bool imagesExist = Directory.Exists(sampleImageDir) && Directory.GetFiles(sampleImageDir).Length > 0;
bool slicesExist = Directory.Exists(sampleSliceDir) && Directory.GetFiles(sampleSliceDir).Length > 0;
Debug.Log($"[AutoUI] Images存在={imagesExist}, Slices存在={slicesExist}");
if (!imagesExist || !slicesExist)
{
// 如果包内没有示例,创建程序化示例到 Assets 目录
Debug.LogWarning("[AutoUI] 包内示例不完整,使用程序化示例");
CreateProceduralSample();
return;
}
// 设置快速体验模式标记(效果图和切图文件夹分开)
EditorPrefs.SetBool(PREFS_QUICK_MODE_IMAGE, true);
EditorPrefs.SetBool(PREFS_QUICK_MODE_TEXTURE, true);
Log("[快速体验] 内置示例已准备就绪");
UpdateStatus("示例文件已准备就绪,请点击选择效果图/切图文件夹", StatusColor.Success);
}
catch (Exception ex)
{
Debug.LogError($"[AutoUI] 加载示例失败: {ex.Message}");
UpdateStatus($"加载示例失败: {ex.Message}", StatusColor.Error);
}
}
/// <summary>
/// 获取插件包的路径
/// </summary>
private static string GetPackagePath()
{
// 方式1: 通过 PackageInfo 获取
var packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssembly(typeof(AutoUIWindow).Assembly);
if (packageInfo != null)
{
Debug.Log($"[AutoUI] PackageInfo resolvedPath: {packageInfo.resolvedPath}");
return packageInfo.resolvedPath;
}
// 方式2: 直接使用包名(作为备选)
string packagePath = "Packages/com.autoui.generator";
// 检查虚拟路径是否存在Unity 的 Packages 目录是虚拟映射)
if (AssetDatabase.IsValidFolder("Packages/com.autoui.generator"))
{
// 获取真实路径
string realPath = Path.GetFullPath(packagePath);
Debug.Log($"[AutoUI] Virtual package path resolved: {realPath}");
return realPath;
}
// 方式3: 尝试常见的包安装位置
string[] possiblePaths = new string[]
{
"Packages/com.autoui.generator",
"Assets/Packages/com.autoui.generator",
"../Packages/com.autoui.generator"
};
foreach (var path in possiblePaths)
{
string fullPath = Path.GetFullPath(path);
if (Directory.Exists(fullPath))
{
Debug.Log($"[AutoUI] Found package at: {fullPath}");
return fullPath;
}
}
Debug.LogWarning("[AutoUI] Could not find package path");
return null;
}
/// <summary>
/// 获取 Samples/Images 目录路径(优先包内,其次 Assets 下的程序化示例)
/// </summary>
private static string GetSampleImagesPath()
{
// 优先检查包内示例
string packagePath = GetPackagePath();
if (!string.IsNullOrEmpty(packagePath))
{
string imagesPath = $"{packagePath}/Samples/Images";
if (Directory.Exists(imagesPath) && Directory.GetFiles(imagesPath).Length > 0)
{
return imagesPath;
}
}
// 其次检查 Assets 下的程序化示例
string assetsImagesPath = "Assets/AutoUI_Samples/Images";
if (Directory.Exists(assetsImagesPath) && Directory.GetFiles(assetsImagesPath).Length > 0)
{
return Path.GetFullPath(assetsImagesPath);
}
return null;
}
/// <summary>
/// 获取 Samples/Slices 目录路径(优先包内,其次 Assets 下的程序化示例)
/// </summary>
private static string GetSampleSlicesPath()
{
// 优先检查包内示例
string packagePath = GetPackagePath();
if (!string.IsNullOrEmpty(packagePath))
{
string slicesPath = $"{packagePath}/Samples/Slices";
if (Directory.Exists(slicesPath) && Directory.GetFiles(slicesPath).Length > 0)
{
return slicesPath;
}
}
// 其次检查 Assets 下的程序化示例
string assetsSlicesPath = "Assets/AutoUI_Samples/Slices";
if (Directory.Exists(assetsSlicesPath) && Directory.GetFiles(assetsSlicesPath).Length > 0)
{
return Path.GetFullPath(assetsSlicesPath);
}
return null;
}
/// <summary>
/// 创建程序化示例(当包内没有示例资源时使用)
/// </summary>
private void CreateProceduralSample()
{
// 创建示例目录
string sampleDir = "Assets/AutoUI_Samples";
string imageDir = $"{sampleDir}/Images";
string sliceDir = $"{sampleDir}/Slices";
if (!Directory.Exists(imageDir))
{
Directory.CreateDirectory(imageDir);
}
if (!Directory.Exists(sliceDir))
{
Directory.CreateDirectory(sliceDir);
}
// 创建一个简单的示例效果图
Texture2D sampleTexture = CreateSampleUITexture();
string imagePath = $"{imageDir}/sample_login_ui.png";
File.WriteAllBytes(Path.GetFullPath(imagePath), sampleTexture.EncodeToPNG());
AssetDatabase.ImportAsset(imagePath);
// 创建简单的示例切图
CreateSampleSlices(sliceDir);
// 设置快速体验模式标记(效果图和切图文件夹分开)
EditorPrefs.SetBool(PREFS_QUICK_MODE_IMAGE, true);
EditorPrefs.SetBool(PREFS_QUICK_MODE_TEXTURE, true);
AssetDatabase.Refresh();
Log("[快速体验] 创建程序化示例完成");
UpdateStatus("示例文件已准备就绪,请点击选择效果图/切图文件夹", StatusColor.Success);
}
/// <summary>
/// 创建示例UI纹理程序生成
/// </summary>
private Texture2D CreateSampleUITexture()
{
int width = 800;
int height = 600;
Texture2D texture = new Texture2D(width, height, TextureFormat.RGBA32, false);
// 填充背景
Color bgColor = new Color(0.15f, 0.15f, 0.18f);
Color[] bgPixels = Enumerable.Repeat(bgColor, width * height).ToArray();
texture.SetPixels(bgPixels);
// 绘制登录框背景(中心区域)
int boxX = 200, boxY = 100, boxW = 400, boxH = 400;
Color boxColor = new Color(0.2f, 0.2f, 0.25f);
for (int y = boxY; y < boxY + boxH && y < height; y++)
{
for (int x = boxX; x < boxX + boxW && x < width; x++)
{
texture.SetPixel(x, y, boxColor);
}
}
// 绘制标题区域
int titleY = 380, titleH = 50;
Color titleColor = new Color(0.25f, 0.25f, 0.3f);
for (int y = titleY; y < titleY + titleH && y < height; y++)
{
for (int x = boxX; x < boxX + boxW && x < width; x++)
{
texture.SetPixel(x, y, titleColor);
}
}
// 绘制输入框区域
Color inputColor = new Color(0.12f, 0.12f, 0.15f);
// 用户名输入框
DrawRect(texture, boxX + 50, 280, 300, 40, inputColor);
// 密码输入框
DrawRect(texture, boxX + 50, 220, 300, 40, inputColor);
// 绘制登录按钮
Color btnColor = new Color(0.2f, 0.5f, 0.8f);
DrawRect(texture, boxX + 50, 150, 300, 45, btnColor);
// 绘制关闭按钮
Color closeBtnColor = new Color(0.7f, 0.2f, 0.2f);
DrawRect(texture, boxX + boxW - 50, boxY + boxH - 40, 30, 30, closeBtnColor);
texture.Apply();
return texture;
}
/// <summary>
/// 绘制矩形
/// </summary>
private void DrawRect(Texture2D texture, int x, int y, int w, int h, Color color)
{
for (int py = y; py < y + h && py < texture.height; py++)
{
for (int px = x; px < x + w && px < texture.width; px++)
{
texture.SetPixel(px, py, color);
}
}
}
/// <summary>
/// 创建示例切图
/// </summary>
private void CreateSampleSlices(string sliceDir)
{
// 创建登录按钮切图
CreateButtonSlice(sliceDir, "btn_login", new Color(0.2f, 0.5f, 0.8f));
// 创建关闭按钮切图
CreateButtonSlice(sliceDir, "btn_close", new Color(0.7f, 0.2f, 0.2f));
// 创建输入框背景切图
CreateInputSlice(sliceDir, "input_bg", new Color(0.12f, 0.12f, 0.15f));
// 创建面板背景切图
CreatePanelSlice(sliceDir, "panel_bg", new Color(0.2f, 0.2f, 0.25f));
}
/// <summary>
/// 创建按钮切图
/// </summary>
private void CreateButtonSlice(string dir, string name, Color color)
{
int size = 100;
Texture2D texture = new Texture2D(size, size, TextureFormat.RGBA32, false);
// 填充颜色
Color[] pixels = Enumerable.Repeat(color, size * size).ToArray();
texture.SetPixels(pixels);
// 添加圆角效果(简化版)
int corner = 10;
Color transparent = Color.clear;
for (int y = 0; y < corner; y++)
{
for (int x = 0; x < corner; x++)
{
if (x * x + y * y < corner * corner)
{
texture.SetPixel(x, y, transparent);
texture.SetPixel(size - 1 - x, y, transparent);
texture.SetPixel(x, size - 1 - y, transparent);
texture.SetPixel(size - 1 - x, size - 1 - y, transparent);
}
}
}
texture.Apply();
File.WriteAllBytes(Path.GetFullPath($"{dir}/{name}.png"), texture.EncodeToPNG());
}
/// <summary>
/// 创建输入框切图
/// </summary>
private void CreateInputSlice(string dir, string name, Color color)
{
int width = 200, height = 50;
Texture2D texture = new Texture2D(width, height, TextureFormat.RGBA32, false);
Color[] pixels = Enumerable.Repeat(color, width * height).ToArray();
texture.SetPixels(pixels);
texture.Apply();
File.WriteAllBytes(Path.GetFullPath($"{dir}/{name}.png"), texture.EncodeToPNG());
}
/// <summary>
/// 创建面板切图
/// </summary>
private void CreatePanelSlice(string dir, string name, Color color)
{
int size = 200;
Texture2D texture = new Texture2D(size, size, TextureFormat.RGBA32, false);
Color[] pixels = Enumerable.Repeat(color, size * size).ToArray();
texture.SetPixels(pixels);
// 添加边框
Color borderColor = new Color(color.r + 0.1f, color.g + 0.1f, color.b + 0.1f);
for (int i = 0; i < size; i++)
{
texture.SetPixel(i, 0, borderColor);
texture.SetPixel(i, size - 1, borderColor);
texture.SetPixel(0, i, borderColor);
texture.SetPixel(size - 1, i, borderColor);
}
texture.Apply();
File.WriteAllBytes(Path.GetFullPath($"{dir}/{name}.png"), texture.EncodeToPNG());
}
#endregion
#region
/// <summary>
/// 【新增】加载公共文件夹设置
/// </summary>
private void LoadCommonFolders()
{
try
{
string savedFolders = EditorPrefs.GetString("AutoUI_CommonFolders", "");
if (!string.IsNullOrEmpty(savedFolders))
{
var folders = JsonUtility.FromJson<CommonFoldersData>(savedFolders);
if (folders != null && folders.folders != null)
{
// 过滤掉不存在的路径
_commonFolders = folders.folders.FindAll(path => Directory.Exists(path));
}
}
}
catch (Exception ex)
{
Debug.LogWarning($"[AutoUI] 加载公共文件夹设置失败: {ex.Message}");
_commonFolders = new List<string>();
}
RenderCommonFolders();
}
/// <summary>
/// 【新增】保存公共文件夹设置
/// </summary>
private void SaveCommonFolders()
{
try
{
var data = new CommonFoldersData { folders = _commonFolders };
string json = JsonUtility.ToJson(data);
EditorPrefs.SetString("AutoUI_CommonFolders", json);
}
catch (Exception ex)
{
Debug.LogError($"[AutoUI] 保存公共文件夹设置失败: {ex.Message}");
}
}
/// <summary>
/// 【新增】渲染公共文件夹列表
/// </summary>
private void RenderCommonFolders()
{
if (_commonFoldersContainer == null) return;
// 更新徽章数量
if (_folderCountBadge != null)
{
_folderCountBadge.text = $"{_commonFolders.Count}/{MAX_COMMON_FOLDERS}";
}
_commonFoldersContainer.Clear();
if (_commonFolders.Count == 0)
{
// 空状态提示
var emptyContainer = new VisualElement()
{
style =
{
flexDirection = FlexDirection.Column,
alignItems = Align.Center,
justifyContent = Justify.Center,
paddingTop = 20,
paddingBottom = 20
}
};
var emptyLabel = new Label("暂无公共文件夹")
{
style =
{
color = new Color(0.6f, 0.6f, 0.65f),
fontSize = 13
}
};
emptyContainer.Add(emptyLabel);
var emptyHint = new Label("点击下方按钮添加公共切图文件夹")
{
style =
{
color = new Color(0.5f, 0.5f, 0.55f),
fontSize = 11,
marginTop = 4
}
};
emptyContainer.Add(emptyHint);
_commonFoldersContainer.Add(emptyContainer);
return;
}
for (int i = 0; i < _commonFolders.Count; i++)
{
int index = i; // 闭包捕获
string folderPath = _commonFolders[i];
string folderName = Path.GetFileName(folderPath);
var item = new VisualElement()
{
style =
{
flexDirection = FlexDirection.Row,
alignItems = Align.Center,
marginTop = 6,
marginBottom = 6,
paddingTop = 10,
paddingBottom = 10,
paddingLeft = 12,
paddingRight = 12,
backgroundColor = new Color(0.18f, 0.18f, 0.2f),
borderTopLeftRadius = 6,
borderTopRightRadius = 6,
borderBottomLeftRadius = 6,
borderBottomRightRadius = 6,
borderLeftWidth = 1,
borderRightWidth = 1,
borderTopWidth = 1,
borderBottomWidth = 1,
borderLeftColor = new Color(0.3f, 0.3f, 0.32f),
borderRightColor = new Color(0.3f, 0.3f, 0.32f),
borderTopColor = new Color(0.3f, 0.3f, 0.32f),
borderBottomColor = new Color(0.3f, 0.3f, 0.32f)
}
};
// 文件夹图标
var folderIcon = new Label("📁")
{
style = { fontSize = 14, marginRight = 10 }
};
item.Add(folderIcon);
// 路径信息容器
var pathContainer = new VisualElement()
{
style = { flexGrow = 1 }
};
// 文件夹名称
var nameLabel = new Label(folderName)
{
style =
{
color = new Color(0.95f, 0.95f, 0.95f),
fontSize = 12,
unityFontStyleAndWeight = FontStyle.Bold,
marginBottom = 2
}
};
pathContainer.Add(nameLabel);
// 完整路径
var pathLabel = new Label(TruncatePath(folderPath, 50))
{
style =
{
color = new Color(0.5f, 0.5f, 0.55f),
fontSize = 10
}
};
pathLabel.tooltip = folderPath;
pathContainer.Add(pathLabel);
item.Add(pathContainer);
// 删除按钮
var removeBtn = new Button() { text = "✕" };
removeBtn.style.width = 28;
removeBtn.style.height = 28;
removeBtn.style.fontSize = 12;
removeBtn.style.borderTopLeftRadius = 4;
removeBtn.style.borderTopRightRadius = 4;
removeBtn.style.borderBottomLeftRadius = 4;
removeBtn.style.borderBottomRightRadius = 4;
removeBtn.style.backgroundColor = new Color(0.25f, 0.25f, 0.28f);
removeBtn.style.color = new Color(0.7f, 0.7f, 0.7f);
removeBtn.style.borderLeftWidth = 0;
removeBtn.style.borderRightWidth = 0;
removeBtn.style.borderTopWidth = 0;
removeBtn.style.borderBottomWidth = 0;
removeBtn.style.marginLeft = 8;
removeBtn.clicked += () => OnRemoveCommonFolderClicked(index);
item.Add(removeBtn);
_commonFoldersContainer.Add(item);
}
}
/// <summary>
/// 截断路径显示
/// </summary>
private string TruncatePath(string path, int maxLength)
{
if (string.IsNullOrEmpty(path) || path.Length <= maxLength)
return path;
return "..." + path.Substring(path.Length - maxLength + 3);
}
/// <summary>
/// 【新增】添加公共文件夹按钮点击事件
/// </summary>
private void OnAddCommonFolderClicked()
{
// 检查数量上限
if (_commonFolders.Count >= MAX_COMMON_FOLDERS)
{
EditorUtility.DisplayDialog("提示", $"最多只能添加 {MAX_COMMON_FOLDERS} 个公共文件夹", "确定");
return;
}
// 打开文件夹选择对话框
string folderPath = EditorUtility.OpenFolderPanel("选择公共切图文件夹", "", "");
if (string.IsNullOrEmpty(folderPath))
return;
// 检查是否已存在
if (_commonFolders.Contains(folderPath))
{
EditorUtility.DisplayDialog("提示", "该文件夹已添加", "确定");
return;
}
// 添加到列表
_commonFolders.Add(folderPath);
// 保存设置
SaveCommonFolders();
// 更新UI
RenderCommonFolders();
UpdateStatus("公共文件夹添加成功", StatusColor.Success);
}
/// <summary>
/// 【新增】删除公共文件夹按钮点击事件
/// </summary>
private void OnRemoveCommonFolderClicked(int index)
{
if (index < 0 || index >= _commonFolders.Count)
return;
// 从列表中移除
_commonFolders.RemoveAt(index);
// 保存设置
SaveCommonFolders();
// 更新UI
RenderCommonFolders();
UpdateStatus("公共文件夹已删除", StatusColor.Success);
}
/// <summary>
/// 【新增】扫描所有公共文件夹中的图片
/// </summary>
private List<TextureFileInfo> ScanCommonFolders()
{
var allTextures = new List<TextureFileInfo>();
int maxTotalFiles = 500;
foreach (string folderPath in _commonFolders)
{
if (allTextures.Count >= maxTotalFiles)
{
Debug.Log($"[AutoUI] 已达到最大文件数限制 ({maxTotalFiles}),跳过剩余文件夹");
break;
}
try
{
var files = TextureScanner.ScanDirectory(folderPath, Config.MAX_SCAN_DEPTH, Config.MAX_TEXTURE_FILES);
foreach (var file in files)
{
if (allTextures.Count >= maxTotalFiles) break;
allTextures.Add(file);
}
}
catch (Exception ex)
{
Debug.LogWarning($"[AutoUI] 扫描公共文件夹失败 {folderPath}: {ex.Message}");
}
}
Debug.Log($"[AutoUI] 扫描公共文件夹完成,共 {allTextures.Count} 个文件");
return allTextures;
}
#endregion
}
// 【新增】反馈相关数据模型
[Serializable]
public class FeedbackData
{
public string message;
public FeedbackContext context;
}
[Serializable]
public class FeedbackContext
{
public string pluginVersion;
public string unityVersion;
public long timestamp;
public string errorLog;
}
[Serializable]
public class FeedbackResponse
{
public bool success;
public string feedbackId;
public string message;
public int retryAfter;
}
public class FeedbackResult
{
public bool success;
public string feedbackId;
public string error;
}
// 【新增】公共文件夹数据模型
[Serializable]
public class CommonFoldersData
{
public List<string> folders;
}
}