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 { /// /// AutoUI 主窗口 - 对应原 panel.js /// 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 _commonFolders = new List(); 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 _textureFiles = new List(); // 【新增】防连点机制 private Dictionary _lastClickTime = new Dictionary(); 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 _expandedTreeNodes = new HashSet(); 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(); 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(); } /// /// 【修复】同步初始化流程 /// 每一步都是同步调用,避免异步带来的复杂性 /// 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); } } /// /// 同步初始化服务(后台线程网络请求,主线程处理结果) /// 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(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; } } /// /// 同步检查服务器状态(后台线程网络请求) /// 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; } } /// /// 同步刷新授权状态(后台线程网络请求,主线程处理结果) /// 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}"); } } /// /// 【新增】状态颜色枚举 /// private enum StatusColor { Info, // 蓝色 Success, // 绿色 Warning, // 黄色 Error // 红色 } /// /// 【新增】更新状态栏(带颜色) /// 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(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(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("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); } /// /// 创建加载动画遮罩层 /// 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); } /// /// 显示加载动画 /// 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; } /// /// 隐藏加载动画 /// private void HideLoadingOverlay() { if (_loadingOverlay == null) return; _loadingOverlay.style.display = DisplayStyle.None; // 停止动画 EditorApplication.update -= UpdateLoadingAnimation; } /// /// 更新加载动画(进度条来回移动) /// 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; } /// /// 检查更新(自动) /// 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}"); } } /// /// 下载并安装更新 /// 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}", "确定"); } } /// /// 刷新授权状态(支持强制刷新) /// 从服务端获取最新的授权和额度信息 /// 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(); }; } } } /// /// 【关键修复】更新认证面板可见性 - 使用 UserState 判断 /// 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; } } /// /// 获取套餐显示名称 /// private string GetPlanDisplayName(string plan) { return plan?.ToLower() switch { "trial" => "试用版", "free" => "免费版", "starter" => "畅享版", "professional" => "专业版", "enterprise" => "企业版", _ => "免费版" }; } /// /// 根据用户状态更新UI - 对应 plugin 的 updateUIBasedOnState() /// 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; } } /// /// 【关键修复】获取当前用户状态 - 对齐 Plugin 的 getUserState() 逻辑 /// 用户状态由 AuthType 和 RemainingTrials 计算得出,不是直接存储的 /// 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; } /// /// 【修复】更新授权工具栏显示 - 使用 UserState 判断 /// 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; } } /// /// 显示订阅面板 /// 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(""); // 清空错误提示 } /// /// 显示登录错误提示 /// 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; } } /// /// 显示注册错误提示 /// 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; } } /// /// 显示重置密码错误提示 /// 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} 个切图文件"); } /// /// 更新切图文件夹拖放区域显示 /// private void UpdateTextureDropZone(string path, int fileCount) { string folderName = Path.GetFileName(path); if (string.IsNullOrEmpty(folderName)) { folderName = path; } // 显示文件夹图标和名称 var folderIcon = _textureDropZone.Q