refactor(homepage): 新增主页下拉刷新功能,优化密码校验逻辑

1.  新增HomePagePullRefreshTrigger组件实现全局下拉刷新
2.  调整Android最低SDK版本到31
3.  新增密码校验失败的多语言文案
4.  重构密码校验逻辑,添加异常捕获和统一的错误提示
5.  优化首页离线时的页面重置逻辑
This commit is contained in:
“虞渠成” 2026-09-01 09:25:48 +08:00
parent 8c40683026
commit e84d72dbf7
7 changed files with 183 additions and 30 deletions

View File

@ -199,6 +199,11 @@
"zh": "账号或密码错误,请重试", "zh": "账号或密码错误,请重试",
"en": "Incorrect account or password. Please try again." "en": "Incorrect account or password. Please try again."
}, },
{
"key": "home_password_check_failed",
"zh": "密码错误或校验失败,请重试",
"en": "Password incorrect or verification failed. Please try again."
},
{ {
"key": "100041", "key": "100041",
"zh": "该邮箱已绑定至其他账号,请更换邮箱", "zh": "该邮箱已绑定至其他账号,请更换邮箱",

View File

@ -7955,6 +7955,7 @@ GameObject:
- component: {fileID: 6551506190466531803} - component: {fileID: 6551506190466531803}
- component: {fileID: 3881773501436982883} - component: {fileID: 3881773501436982883}
- component: {fileID: 6647875750186480783} - component: {fileID: 6647875750186480783}
- component: {fileID: 2568189713370643574}
m_Layer: 5 m_Layer: 5
m_Name: "\u65E0\u8BBE\u5907" m_Name: "\u65E0\u8BBE\u5907"
m_TagString: Untagged m_TagString: Untagged
@ -8004,7 +8005,7 @@ MonoBehaviour:
m_EditorClassIdentifier: m_EditorClassIdentifier:
m_Material: {fileID: 0} m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1} m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 0 m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1 m_Maskable: 1
m_OnCullStateChanged: m_OnCullStateChanged:
@ -8020,6 +8021,18 @@ MonoBehaviour:
m_FillOrigin: 0 m_FillOrigin: 0
m_UseSpriteMesh: 0 m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1 m_PixelsPerUnitMultiplier: 1
--- !u!114 &2568189713370643574
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3582556473535289502}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 432e8b6f5f7dd7a4f94d9c3b85c8b262, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1 &3686991546777705807 --- !u!1 &3686991546777705807
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -15843,7 +15856,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0} m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 0} m_AnchorMax: {x: 1, y: 0}
m_AnchoredPosition: {x: -9.000002, y: 10} m_AnchoredPosition: {x: -9, y: 10}
m_SizeDelta: {x: -57.999992, y: 55} m_SizeDelta: {x: -57.999992, y: 55}
m_Pivot: {x: 0.5, y: 0} m_Pivot: {x: 0.5, y: 0}
--- !u!222 &3186938245855593830 --- !u!222 &3186938245855593830
@ -16935,6 +16948,8 @@ MonoBehaviour:
m_EditorClassIdentifier: m_EditorClassIdentifier:
noDevicePage: {fileID: 3582556473535289502} noDevicePage: {fileID: 3582556473535289502}
normalPage: {fileID: 8115864904787809625} normalPage: {fileID: 8115864904787809625}
normalScrollRect: {fileID: 7398385099859628837}
pullTriggerDistance: 120
selectDeviceButton: {fileID: 1920078014523356616} selectDeviceButton: {fileID: 1920078014523356616}
selectDevicePage: {fileID: 2340476075600265632} selectDevicePage: {fileID: 2340476075600265632}
selectCountryPage: {fileID: 1196293823344848224, guid: 90501a08c2cf82c4187b5f13c391291f, selectCountryPage: {fileID: 1196293823344848224, guid: 90501a08c2cf82c4187b5f13c391291f,
@ -17117,6 +17132,7 @@ GameObject:
- component: {fileID: 2697609740981075072} - component: {fileID: 2697609740981075072}
- component: {fileID: 928882153762487195} - component: {fileID: 928882153762487195}
- component: {fileID: 7398385099859628837} - component: {fileID: 7398385099859628837}
- component: {fileID: 5876146908749457361}
m_Layer: 5 m_Layer: 5
m_Name: "\u6B63\u5E38" m_Name: "\u6B63\u5E38"
m_TagString: Untagged m_TagString: Untagged
@ -17225,6 +17241,18 @@ MonoBehaviour:
m_OnValueChanged: m_OnValueChanged:
m_PersistentCalls: m_PersistentCalls:
m_Calls: [] m_Calls: []
--- !u!114 &5876146908749457361
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8115864904787809625}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 432e8b6f5f7dd7a4f94d9c3b85c8b262, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1 &8135258645254993493 --- !u!1 &8135258645254993493
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@ -0,0 +1,31 @@
using UnityEngine;
using UnityEngine.EventSystems;
using Kill.UI.Pages;
namespace Kill.UI.Components
{
/// <summary>
/// 下拉刷新触发器:挂到主页 ScrollRect带 Image 的 Graphic接收 EventSystem
/// 拖拽事件并转发给 HomePageCtrl。这样在滚动区域内任意位置都可触发下拉刷新。
/// </summary>
public class HomePagePullRefreshTrigger : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
public void OnBeginDrag(PointerEventData eventData)
{
var ctrl = HomePageCtrl.Instance;
if (ctrl != null) ctrl.OnPullRefreshBeginDrag();
}
public void OnDrag(PointerEventData eventData)
{
var ctrl = HomePageCtrl.Instance;
if (ctrl != null) ctrl.OnPullRefreshDrag(eventData.delta.y);
}
public void OnEndDrag(PointerEventData eventData)
{
var ctrl = HomePageCtrl.Instance;
if (ctrl != null) ctrl.OnPullRefreshEndDrag();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 432e8b6f5f7dd7a4f94d9c3b85c8b262
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -24,34 +24,45 @@ namespace Kill.UI.Pages
{ {
errorText.text=""; errorText.text="";
LoadingUI.Show(); LoadingUI.Show();
string password = passwordInput != null ? passwordInput.text : ""; try
if (string.IsNullOrEmpty(password))
{ {
errorText.text = LanguageManager.Instance.GetLanguage("100040"); string password = passwordInput != null ? passwordInput.text : "";
if (string.IsNullOrEmpty(password))
{
errorText.text = LanguageManager.Instance.GetLanguage("home_password_check_failed");
return;
}
PasswordRequest requestData=new PasswordRequest
{
password=passwordInput.text
};
var response = await NetworkCtrl.Instance.Post<LoginResponse>("/api/v1/auth/password/verify", requestData);
if (response != null && response.Data != null && response.Data.code == 200)
{
plane.SetActive(false);
gameObject.SetActive(false);
errorText.text="";
passwordInput.text="";
UIManager.Instance.isFirstTime=false;
}
else
{
errorText.text = LanguageManager.Instance.GetLanguage("home_password_check_failed");
passwordInput.text="";
}
}
catch (System.Exception ex)
{
Debug.LogError($"[CheckPasswordPage] 校验密码异常: {ex.Message}");
errorText.text = LanguageManager.Instance.GetLanguage("home_password_check_failed");
if (passwordInput != null) passwordInput.text = "";
}
finally
{
// 无论成功/失败/异常都关闭 Loading避免一直挂着
LoadingUI.Hide(); LoadingUI.Hide();
return;
}
PasswordRequest requestData=new PasswordRequest
{
password=passwordInput.text
};
var response = await NetworkCtrl.Instance.Post<LoginResponse>("/api/v1/auth/password/verify", requestData);
LoadingUI.Hide();
if (response.Data.code == 200)
{
plane.SetActive(false);
gameObject.SetActive(false);
errorText.text="";
passwordInput.text="";
UIManager.Instance.isFirstTime=false;
}
else
{
errorText.text = LanguageManager.Instance.GetLanguage("100040");
passwordInput.text="";
} }
} }

View File

@ -21,6 +21,13 @@ namespace Kill.UI.Pages
public GameObject noDevicePage; public GameObject noDevicePage;
public GameObject normalPage; public GameObject normalPage;
// 下拉刷新:滚动区域 ScrollRect用于判断内容是否在最顶部无设备页可不填
public ScrollRect normalScrollRect;
// 下拉刷新参数
[SerializeField] private float pullTriggerDistance = 120f; // 触发刷新的拖动距离(像素)
private float pullDistance; // 当前累积下拉距离
private bool isPullingAtTop; // 拖动开始时是否在内容顶部
private bool isRefreshing;
// 存储待执行的UI更新操作 // 存储待执行的UI更新操作
private List<Action> pendingUIUpdates = new List<Action>(); private List<Action> pendingUIUpdates = new List<Action>();
public Button selectDeviceButton; public Button selectDeviceButton;
@ -477,6 +484,7 @@ namespace Kill.UI.Pages
/// <summary> /// <summary>
/// 网络断开:立刻把 WiFi 设备标离线并刷新 UI /// 网络断开:立刻把 WiFi 设备标离线并刷新 UI
/// 设备离线导致的 UI 重置由后台轮询触发,避免此处重复 Init
/// </summary> /// </summary>
private void OnNetworkDisconnectedHandler(object sender, EventArgs e) private void OnNetworkDisconnectedHandler(object sender, EventArgs e)
{ {
@ -1878,11 +1886,14 @@ namespace Kill.UI.Pages
// WiFi和蓝牙都断开 // WiFi和蓝牙都断开
if (wasOnline) if (wasOnline)
{ {
Debug.Log("[HomePageCtrl] 设备WiFi离线"); Debug.Log("[HomePageCtrl] 设备WiFi离线,重新加载首页");
pendingUIUpdates.Add(() => pendingUIUpdates.Add(() =>
{ {
deviceState?.UpdateDeviceState(hasBluetooth, hasWifi); deviceState?.UpdateDeviceState(hasBluetooth, hasWifi);
}); });
// 设备由在线变为离线且无蓝牙连接 → 直接重新加载首页
UIManager.Instance.OpenMainPage(UIManager.PageName.homePage);
return; // 旧页面即将销毁,结束轮询
} }
else else
{ {
@ -1938,6 +1949,62 @@ namespace Kill.UI.Pages
pendingUIUpdates.Add(action); pendingUIUpdates.Add(action);
} }
#region HomePagePullRefreshTrigger
public void OnPullRefreshBeginDrag()
{
if (isRefreshing) return;
isPullingAtTop = IsContentAtTop();
pullDistance = 0f;
}
public void OnPullRefreshDrag(float deltaY)
{
if (isRefreshing || !isPullingAtTop) return;
if (deltaY < 0)
{
pullDistance += -deltaY;
}
}
public void OnPullRefreshEndDrag()
{
if (isRefreshing) return;
if (pullDistance >= pullTriggerDistance)
{
TriggerPullRefresh();
}
else
{
ResetPullRefreshUI();
}
isPullingAtTop = false;
}
/// <summary>
/// 判断 ScrollRect 内容是否已在顶部(无 ScrollRect 时视为可下拉)
/// </summary>
private bool IsContentAtTop()
{
if (normalScrollRect == null) return true;
return normalScrollRect.verticalNormalizedPosition >= 0.999f;
}
private void TriggerPullRefresh()
{
if (isRefreshing) return;
isRefreshing = true;
// 重新加载首页(销毁旧实例并实例化新实例,触发 Start/Init 重新跑)
UIManager.Instance.OpenMainPage(UIManager.PageName.homePage);
}
private void ResetPullRefreshUI()
{
pullDistance = 0f;
}
#endregion
/// <summary> /// <summary>
/// 从WiFi刷新主页定时任务显示 /// 从WiFi刷新主页定时任务显示
/// </summary> /// </summary>

View File

@ -173,7 +173,7 @@ PlayerSettings:
tvOS: 0 tvOS: 0
overrideDefaultApplicationIdentifier: 1 overrideDefaultApplicationIdentifier: 1
AndroidBundleVersionCode: 4 AndroidBundleVersionCode: 4
AndroidMinSdkVersion: 33 AndroidMinSdkVersion: 31
AndroidTargetSdkVersion: 36 AndroidTargetSdkVersion: 36
AndroidPreferredInstallLocation: 0 AndroidPreferredInstallLocation: 0
aotOptions: aotOptions: