| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543 |
- using AppUI.Localization;
- using AppUI.Manager;
- using AppUI.Manager.Role;
- using AppUI.Util.Avatar;
- using JCUnityLib;
- using JCUnityLib.UI;
- using System.Collections;
- using System.Text.RegularExpressions;
- using TMPro;
- using UnityEngine;
- namespace AppUI.View
- {
- /// <summary>
- /// AppUI 注册向导:账号密码 → 短信/邮箱验证码并注册 → 昵称与预设头像。
- /// 全部输入使用 <see cref="TMP_InputField"/>;提示走 <see cref="ModuleViewMgr"/>,网络复用旧版 Controller。
- /// </summary>
- public class RegisterView : MonoBehaviour
- {
- enum RegisterPhase
- {
- AccountPassword,
- SmsAndRegister,
- Profile
- }
- RegisterPhase _phase = RegisterPhase.AccountPassword;
- [Header("阶段面板(可选,未绑定则仅逻辑推进)")]
- [SerializeField]
- GameObject panelAccountPassword;
- [SerializeField]
- GameObject panelSms;
- [SerializeField]
- GameObject panelProfile;
- [Header("阶段一:账号 / 密码(可 Inspector 绑定,或运行时按路径查找)")]
- [SerializeField]
- TMP_InputField accountInput;
- [SerializeField]
- TMP_InputField passwordInput;
- [Header("阶段二:短信验证码")]
- [SerializeField]
- TMP_InputField smsCodeInput;
- [SerializeField]
- TMP_Text sendSmsCountdownLabel;
- [Header("阶段三:昵称 / 预设头像")]
- [SerializeField]
- TMP_InputField nicknameInput;
- [SerializeField]
- UnityEngine.UI.Image avatarPreviewImage;
- [Tooltip("头像列表与 Sprite 数据源;Sprite 仅在此组件的数组中配置一份即可")]
- [SerializeField]
- AvatarGroup avatarGroup;
- [Header("拼图验证(与旧版 RegisterView 一致;未赋值则直接请求发送验证码)")]
- [SerializeField]
- GameObject prefabValidateJigsawView;
- [Header("行为")]
- [SerializeField]
- bool resetFlowOnEnable = true;
- // TODO(AppUI.RegisterView): 若注册前必须勾选用户协议,取消注释并在 OnClick_ConfirmRegister 开头调用。
- // [SerializeField] bool requireAgreementBeforeRegister = true;
- string _storedAccount = "";
- string _storedPassword = "";
- string _registrationTicket = "";
- int _selectedAvatarIndex;
- static long _lastSendSmsMs;
- Coroutine _sendCooldownRoutine;
- bool _sendCooldownActive;
- readonly Throttler _throttlerRegister = new Throttler(3000);
- readonly Throttler _throttlerProfileSave = new Throttler(2000);
- void OnEnable()
- {
- if (resetFlowOnEnable)
- ResetFlowToAccountPassword();
- }
- void Awake()
- {
- InitPasswordAlphanumericFilter();
- }
- void Start()
- {
- WireAvatarGroup();
- }
- void OnDestroy()
- {
- if (avatarGroup != null)
- avatarGroup.OnAvatarSelected -= OnAvatarGroupSelection;
- }
- /// <summary>
- /// 将 <see cref="AvatarGroup"/> 的写入目标设为 <see cref="avatarPreviewImage"/>,并订阅选中事件以同步 <see cref="_selectedAvatarIndex"/>。
- /// </summary>
- void WireAvatarGroup()
- {
- if (avatarGroup == null)
- return;
- if (avatarPreviewImage != null)
- avatarGroup.targetImage = avatarPreviewImage;
- avatarGroup.OnAvatarSelected -= OnAvatarGroupSelection;
- avatarGroup.OnAvatarSelected += OnAvatarGroupSelection;
- avatarGroup.SelectByIndex(0, notify: false);
- _selectedAvatarIndex = avatarGroup.SelectedIndex;
- }
- void OnAvatarGroupSelection(int index, Sprite _)
- {
- _selectedAvatarIndex = index;
- }
- void InitPasswordAlphanumericFilter()
- {
- if (passwordInput == null)
- return;
- passwordInput.onValueChanged.AddListener(text =>
- {
- var match = new Regex("[^A-Za-z0-9]").Match(text);
- if (match.Success)
- passwordInput.text = text.Replace(match.Value, "");
- });
- }
- /// <summary>外部(例如从登录页)切回本页时可调用,清空状态与 UI。</summary>
- public void ResetFlowToAccountPassword()
- {
- _phase = RegisterPhase.AccountPassword;
- _storedAccount = "";
- _storedPassword = "";
- _registrationTicket = "";
- _selectedAvatarIndex = 0;
- if (avatarGroup != null)
- avatarGroup.SelectByIndex(0, notify: false);
- if (smsCodeInput != null)
- smsCodeInput.text = "";
- if (nicknameInput != null)
- nicknameInput.text = "";
- ApplyPhasePanels();
- }
- void ApplyPhasePanels()
- {
- if (panelAccountPassword != null)
- panelAccountPassword.SetActive(_phase == RegisterPhase.AccountPassword);
- if (panelSms != null)
- panelSms.SetActive(_phase == RegisterPhase.SmsAndRegister);
- if (panelProfile != null)
- panelProfile.SetActive(_phase == RegisterPhase.Profile);
- }
- bool IsDomesticPhoneMode() => CommonConfig.AppArea == 0;
- void ShowTip(string message)
- {
- ModuleViewMgr.ins.Show(message);
- }
- /// <summary>阶段一「下一步」:校验账号密码后进入验证码阶段。</summary>
- public void OnClick_ContinueToSmsStep()
- {
- if (accountInput == null || passwordInput == null)
- {
- Debug.LogError("[AppUI.RegisterView] 未绑定账号或密码 TMP_InputField。");
- return;
- }
- string account = accountInput.text.Trim();
- if (account.Length == 0)
- {
- ShowTip(AppUILocalization.GetTextByCNKey("请输入账号"));
- return;
- }
- if (IsDomesticPhoneMode())
- {
- if (!ValidateHelper.IsMobilePhone(account))
- {
- ShowTip(AppUILocalization.GetTextByKey("RelateValidateView-a1"));
- return;
- }
- }
- else
- {
- if (!ValidateHelper.IsEmail(account))
- {
- ShowTip(AppUILocalization.GetTextByKey("RelateValidateView-a0"));
- return;
- }
- }
- string pwd = passwordInput.text.Trim();
- if (pwd.Length == 0)
- {
- ShowTip(AppUILocalization.GetTextByCNKey("请输入密码"));
- return;
- }
- if (pwd.Length < 6)
- {
- ShowTip(AppUILocalization.GetTextByCNKey("密码长度至少6位"));
- return;
- }
- _storedAccount = account;
- _storedPassword = pwd;
- _phase = RegisterPhase.SmsAndRegister;
- ApplyPhasePanels();
- //发送验证码
- OnClick_SendSmsCode();
- }
- /// <summary>发送短信或邮箱验证码(与旧版 <see cref="RegisterView.OnClick_SendCode"/> 对齐;限流 60s)。</summary>
- public void OnClick_SendSmsCode()
- {
- long gap = TimeUtils.GetTimestamp() - _lastSendSmsMs;
- const long maxMs = 60 * 1000;
- if (gap < maxMs)
- {
- long second = (maxMs - gap) / 1000;
- if (second <= 0)
- second = 1;
- ShowTip(string.Format(AppUILocalization.GetTextByKey("RelateValidateView-a2"), second));
- return;
- }
- if (_sendCooldownActive)
- {
- return;
- }
- if (IsDomesticPhoneMode())
- {
- if (!ValidateHelper.IsMobilePhone(_storedAccount))
- {
- ShowTip(AppUILocalization.GetTextByKey("RelateValidateView-a1"));
- return;
- }
- }
- else
- {
- if (!ValidateHelper.IsEmail(_storedAccount))
- {
- ShowTip(AppUILocalization.GetTextByKey("RelateValidateView-a0"));
- return;
- }
- }
- if (prefabValidateJigsawView != null)
- {
- var go = Instantiate(prefabValidateJigsawView);
- var jigsaw = go.GetComponent<ValidateJigsawView>();
- if (jigsaw != null)
- {
- jigsaw.onComplete = RequestSendValidateCode;
- jigsaw.SetTextLabel(AppUILocalization.GetTextByKey("ValidateJigsawView_label"));
- jigsaw.SetTextTip(AppUILocalization.GetTextByKey("ValidateJigsawView_tip"));
- jigsaw.SetTextOK(AppUILocalization.GetTextByKey("ValidateJigsawView_ok"));
- }
- else
- {
- Destroy(go);
- RequestSendValidateCode();
- }
- }
- else
- {
- RequestSendValidateCode();
- }
- }
- void RequestSendValidateCode()
- {
- _lastSendSmsMs = JCUnityLib.TimeUtils.GetTimestamp();
- if (_sendCooldownRoutine != null)
- StopCoroutine(_sendCooldownRoutine);
- _sendCooldownRoutine = StartCoroutine(SendSmsCooldownUiRoutine());
- if (IsDomesticPhoneMode())
- {
- StartCoroutine(PhoneValidateController.Instance.SendPhoneValidateCode(_storedAccount, res =>
- {
- if (res.code == 0)
- ShowTip(AppUILocalization.GetTextByKey("RelateValidateView-b2"));
- else
- ShowTip(AppUILocalization.GetTextByKey("RelateValidateView-b3"));
- }));
- }
- else
- {
- StartCoroutine(EmailValidateController.Instance.SendEmailValidateCode(_storedAccount, res =>
- {
- if (res.code == 0)
- ShowTip(AppUILocalization.GetTextByKey("RelateValidateView-b0"));
- else
- ShowTip(AppUILocalization.GetTextByKey("RelateValidateView-b1"));
- }));
- }
- }
- IEnumerator SendSmsCooldownUiRoutine()
- {
- _sendCooldownActive = true;
- const float maxSec = 60f;
- float left = maxSec;
- while (left > 0f)
- {
- if (sendSmsCountdownLabel != null)
- sendSmsCountdownLabel.text = string.Format(AppUILocalization.GetTextByKey("appui-resend"),"("+ Mathf.CeilToInt(left) + " S)");
- left -= Time.deltaTime;
- yield return null;
- }
- _sendCooldownActive = false;
- if (sendSmsCountdownLabel != null)
- sendSmsCountdownLabel.text = sendSmsCountdownLabel.text = string.Format(AppUILocalization.GetTextByKey("appui-resend"), "");
- _sendCooldownRoutine = null;
- }
- /// <summary>提交验证码并调用注册接口;失败提示,成功进入资料步骤。</summary>
- public void OnClick_ConfirmRegister()
- {
- // TODO(AppUI.RegisterView): 需要协议时在此校验 AgreenmentOption.ins.IsAgreementChecked()。
- if (smsCodeInput == null)
- {
- Debug.LogError("[AppUI.RegisterView] 未绑定验证码 TMP_InputField。");
- return;
- }
- string codeText = smsCodeInput.text.Trim();
- if (codeText.Length != 6)
- {
- ShowTip(AppUILocalization.GetTextByKey("RelateValidateView-a3"));
- return;
- }
- if (!int.TryParse(codeText, out int codeValue))
- {
- ShowTip(AppUILocalization.GetTextByKey("RelateValidateView-a3"));
- return;
- }
- if (!_throttlerRegister.CanPass())
- {
- ShowTip(AppUILocalization.GetTextByCNKey("操作过于频繁"));
- return;
- }
- string emailField = IsDomesticPhoneMode() ? "" : _storedAccount;
- string phoneField = IsDomesticPhoneMode() ? _storedAccount : "";
- StartCoroutine(LoginController.Instance.RegisterByCode(
- _storedAccount,
- _storedPassword,
- emailField,
- phoneField,
- codeValue,
- res =>
- {
- ShowTip(AppUILocalization.GetTextByCNKey(res.msg));
- if (res.code != 0)
- return;
- _registrationTicket = LoginController.TryGetRegistrationTicketFromData(res.data);
- if (string.IsNullOrEmpty(_registrationTicket))
- {
- ShowTip(AppUILocalization.GetTextByCNKey("注册成功但缺少资料票据,请重新登录后完善资料"));
- return;
- }
- _phase = RegisterPhase.Profile;
- if (nicknameInput != null)
- {
- nicknameInput.text = _storedAccount;
- }
- ApplyPhasePanels();
- TryRefreshAvatarPreview();
- var loginView = FindObjectOfType<LoginView>();
- loginView?.FillLoginInput(_storedAccount, _storedPassword);
- }));
- }
- /// <summary>预设头像列表项按钮可绑定:索引与 <see cref="AvatarGroup"/> 内 Sprite 数组一致。</summary>
- public void OnSelectPresetAvatar(int index)
- {
- if (avatarGroup == null)
- return;
- var max = Mathf.Max(0, avatarGroup.AvatarCount - 1);
- avatarGroup.SelectByIndex(Mathf.Clamp(index, 0, max), notify: true);
- }
- /// <summary>Inspector 无法直接传 int 参数时,可用多个按钮分别绑定下列方法。</summary>
- public void OnSelectPresetAvatar0() => OnSelectPresetAvatar(0);
- public void OnSelectPresetAvatar1() => OnSelectPresetAvatar(1);
- public void OnSelectPresetAvatar2() => OnSelectPresetAvatar(2);
- public void OnSelectPresetAvatar3() => OnSelectPresetAvatar(3);
- public void OnSelectPresetAvatar4() => OnSelectPresetAvatar(4);
- public void OnSelectPresetAvatar5() => OnSelectPresetAvatar(5);
- void TryRefreshAvatarPreview()
- {
- if (avatarPreviewImage == null || avatarGroup == null)
- return;
- var sp = avatarGroup.GetSelectedSprite();
- if (sp != null)
- avatarPreviewImage.sprite = sp;
- }
- /// <summary>返回上一阶段(供 UI 返回键绑定)。</summary>
- public void OnClick_Back()
- {
- switch (_phase)
- {
- case RegisterPhase.SmsAndRegister:
- _phase = RegisterPhase.AccountPassword;
- ApplyPhasePanels();
- break;
- case RegisterPhase.Profile:
- // TODO(AppUI.RegisterView): 注册已成功落库时是否允许退回验证码阶段需产品确认;当前仅回到短信页并保留已填昵称。
- _phase = RegisterPhase.SmsAndRegister;
- ApplyPhasePanels();
- break;
- default:
- // TODO(AppUI.RegisterView): 若需关闭注册页回到登录,在此调用 LoginMgr.showLoginView() 或关闭当前弹窗。
- break;
- }
- }
- /// <summary>
- /// TODO(AppUI.RegisterView): 接入 CommonConfig.banBindRelateAccount 为 true 时的 Register2(邮箱+手机双绑)分支时在此封装专用提交流程。
- /// </summary>
- public void OnClick_RegisterWithBoundContacts_Todo()
- {
- }
- /// <summary>
- /// TODO(AppUI.RegisterView): 从相册/拍照选取自定义头像后得到 Texture2D,再调用 LoginController.UploadAvatar。
- /// </summary>
- public void OnClick_PickCustomAvatarFromGallery_Todo()
- {
- }
- /// <summary>资料提交:完善用户信息;头像上传在登录拿 token 后尝试。</summary>
- public void OnClick_CompleteProfile()
- {
- if (nicknameInput == null)
- {
- Debug.LogError("[AppUI.RegisterView] 未绑定昵称 TMP_InputField。");
- return;
- }
- string nickname = nicknameInput.text.Trim();
- if (nickname.Length == 0)
- {
- ShowTip(AppUILocalization.GetTextByCNKey("请输入游戏昵称"));
- return;
- }
- if (!_throttlerProfileSave.CanPass())
- {
- ShowTip(AppUILocalization.GetTextByCNKey("操作过于频繁"));
- return;
- }
- const int defaultGender = 1;
- const string defaultBirthday = "2000-01-01";
- if (string.IsNullOrEmpty(_registrationTicket))
- {
- ShowTip(AppUILocalization.GetTextByCNKey("资料票据已失效,请从验证码步骤重新注册"));
- return;
- }
- int avatarId = avatarGroup != null
- ? avatarGroup.GetSelectedAvatarId()
- : RoleManager.PresetGridIndexToAvatarId(_selectedAvatarIndex);
- if (!RoleManager.IsAppUIPresetAvatarId(avatarId))
- {
- ShowTip(AppUILocalization.GetTextByCNKey("请选择头像"));
- return;
- }
- StartCoroutine(LoginController.Instance.CompleteUserInfoByTicket(
- _registrationTicket,
- avatarId,
- nickname,
- defaultGender,
- defaultBirthday,
- "",
- "",
- "",
- res =>
- {
- ShowTip(AppUILocalization.GetTextByCNKey(res.msg));
- if (res.code != 0)
- return;
- StartCoroutine(AfterProfileSavedRoutine());
- }));
- }
- IEnumerator AfterProfileSavedRoutine()
- {
- var loginOk = false;
- yield return LoginController.Instance.LoginNormal(_storedAccount, _storedPassword, res =>
- {
- if (res.code != 0 || !(res.data is string loginToken))
- return;
- var parts = loginToken.Split('&');
- if (parts.Length > 2)
- CommonConfig.businessServerWsURL = parts[2];
- PlayerPrefs.SetString(LoginMgr.LoginTokenKey, loginToken);
- loginOk = true;
- });
- if (!loginOk)
- ShowTip(AppUILocalization.GetTextByKey("home_loginAuthFail"));
- var mgr = FindObjectOfType<LoginMgr>();
- if (mgr != null)
- mgr.showLoginView();
- else
- {
- // TODO(AppUI.RegisterView): LoginMgr 不在当前场景时,改为跳转到登录场景或打开 AppUI 登录页。
- Debug.LogWarning("[AppUI.RegisterView] 未找到 LoginMgr,无法自动返回登录页。");
- }
- }
- }
- }
|