5218 lines
189 KiB
C#
5218 lines
189 KiB
C#
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;
|
||
}
|
||
}
|