CapabilitySystem/Packages/AutoUI/Editor/AutoUIWindow.cs

5218 lines
189 KiB
C#
Raw Permalink Normal View History

2026-04-15 19:47:09 +08:00
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;
}
}