feat: 新增重置密码功能并完善密码校验逻辑

1.  新增加速度传感器设备不稳定错误提示
2.  新增多套密码校验规则与多语言文案
3.  重构个人页面与密码设置面板的校验逻辑
4.  优化设备状态错误展示与UI布局
5.  新增重置密码相关的UI资源与逻辑
This commit is contained in:
“虞渠成” 2026-07-28 14:54:36 +08:00
parent 4c253a419e
commit 4d0448c80d
14 changed files with 4963 additions and 675 deletions

View File

@ -1544,10 +1544,55 @@
"zh": "同步中", "zh": "同步中",
"en": "Syncing" "en": "Syncing"
}, },
{ {
"key": "100310", "key": "100310",
"zh": "已同步 {0} 条记录", "zh": "已同步 {0} 条记录",
"en": "{0} records synced" "en": "{0} records synced"
},
{
"key": "100311",
"zh": "设备不稳定",
"en": "Device unstable"
},
{
"key": "100312",
"zh": "密码长度6位仅允许使用数字字符",
"en": "Fixed 6-digit numeric password only."
},
{
"key": "100313",
"zh": "密码长度8-16位不能有空格",
"en": "816 characters, no spaces. "
},
{
"key": "100314",
"zh": "字符格式应至少包括大写字母、小写字母、数字、特殊字符的三种;",
"en": "Include at least 3 types: uppercase, lowercase, digits, special chars. No 3+ consecutive or repeated digits/letters."
},
{
"key": "100315",
"zh": "不能包含连续或重复的三个及三个以上的数字或字母;",
"en": "No 3+ consecutive or repeated digits/letters. "
},
{
"key": "100316",
"zh": "密码不能是登录名的一部分。",
"en": "Cannot be part of login name."
},
{
"key": "100317",
"zh": "两次密码一致",
"en": "Passwords match"
},
{
"key": "100318",
"zh": "重置密码",
"en": "Reset Password"
},
{
"key": "100319",
"zh": "重置密码成功",
"en": "Reset password successfully"
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 B

View File

@ -0,0 +1,127 @@
fileFormatVersion: 2
guid: e4fedc13a812fd94ea43e1a648b671cb
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 B

View File

@ -0,0 +1,127 @@
fileFormatVersion: 2
guid: 3cfdbe04679b2324d961926824c97222
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@ -433,7 +433,7 @@ GameObject:
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
m_NavMeshLayer: 0 m_NavMeshLayer: 0
m_StaticEditorFlags: 0 m_StaticEditorFlags: 0
m_IsActive: 1 m_IsActive: 0
--- !u!224 &7655272676486550921 --- !u!224 &7655272676486550921
RectTransform: RectTransform:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -451,10 +451,10 @@ RectTransform:
- {fileID: 8098680310359255571} - {fileID: 8098680310359255571}
m_Father: {fileID: 4482340305028100583} m_Father: {fileID: 4482340305028100583}
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: 1}
m_AnchorMax: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 0, y: 0} m_AnchoredPosition: {x: 345, y: -454}
m_SizeDelta: {x: 0, y: 100} m_SizeDelta: {x: 630, y: 100}
m_Pivot: {x: 0.5, y: 0.5} m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &459455692708586888 --- !u!222 &459455692708586888
CanvasRenderer: CanvasRenderer:
@ -1209,7 +1209,7 @@ MonoBehaviour:
m_HorizontalOverflow: 0 m_HorizontalOverflow: 0
m_VerticalOverflow: 0 m_VerticalOverflow: 0
m_LineSpacing: 1 m_LineSpacing: 1
m_Text: Simple Password m_Text: Reset Password
--- !u!114 &6798849037590746149 --- !u!114 &6798849037590746149
MonoBehaviour: MonoBehaviour:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -1222,7 +1222,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 89f1d49b50a31514787b4e268b1c73bb, type: 3} m_Script: {fileID: 11500000, guid: 89f1d49b50a31514787b4e268b1c73bb, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
textKey: 100020 textKey: 100318
--- !u!1 &2636706380647424798 --- !u!1 &2636706380647424798
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -1593,7 +1593,7 @@ RectTransform:
m_AnchorMin: {x: 0, y: 1} m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 1, y: 1} m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: -200} m_AnchoredPosition: {x: 0, y: -200}
m_SizeDelta: {x: -60, y: 500} m_SizeDelta: {x: -60, y: 400}
m_Pivot: {x: 0.5, y: 1} m_Pivot: {x: 0.5, y: 1}
--- !u!222 &3376027943477643505 --- !u!222 &3376027943477643505
CanvasRenderer: CanvasRenderer:
@ -2292,7 +2292,7 @@ GameObject:
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
m_NavMeshLayer: 0 m_NavMeshLayer: 0
m_StaticEditorFlags: 0 m_StaticEditorFlags: 0
m_IsActive: 1 m_IsActive: 0
--- !u!224 &3480274442878060420 --- !u!224 &3480274442878060420
RectTransform: RectTransform:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -2307,10 +2307,10 @@ RectTransform:
m_Children: [] m_Children: []
m_Father: {fileID: 4482340305028100583} m_Father: {fileID: 4482340305028100583}
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: 1}
m_AnchorMax: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 0, y: 0} m_AnchoredPosition: {x: 345, y: -403.5}
m_SizeDelta: {x: 0, y: 1} m_SizeDelta: {x: 630, y: 1}
m_Pivot: {x: 0.5, y: 0.5} m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &9222182864204810855 --- !u!222 &9222182864204810855
CanvasRenderer: CanvasRenderer:
@ -2363,7 +2363,7 @@ GameObject:
- component: {fileID: 4334117203526359151} - component: {fileID: 4334117203526359151}
- component: {fileID: 173285266742594171} - component: {fileID: 173285266742594171}
m_Layer: 5 m_Layer: 5
m_Name: "\u7B80\u5355\u5BC6\u7801" m_Name: "\u91CD\u7F6E\u5BC6\u7801"
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
m_NavMeshLayer: 0 m_NavMeshLayer: 0
@ -3384,12 +3384,7 @@ MonoBehaviour:
countryText: {fileID: 869492382333240842} countryText: {fileID: 869492382333240842}
selectCountryButton: {fileID: 4981612120447436577} selectCountryButton: {fileID: 4981612120447436577}
signoutButton: {fileID: 8124639496175450611} signoutButton: {fileID: 8124639496175450611}
setPasswordButtons: resetPassword: {fileID: 173285266742594171}
- {fileID: 173285266742594171}
- {fileID: 6647111382430836735}
setPasswordButtonTexts:
- {fileID: 8913907960913215519}
- {fileID: 6070206944283714795}
signoutTip: {fileID: 1786064314622589889} signoutTip: {fileID: 1786064314622589889}
--- !u!1 &8678496074108022521 --- !u!1 &8678496074108022521
GameObject: GameObject:

File diff suppressed because it is too large Load Diff

View File

@ -1832,7 +1832,7 @@ namespace Kill.Bluetooth.Protocol
public byte MotorError; // 电机错误 (0=无, 1=温度, 2=反馈) public byte MotorError; // 电机错误 (0=无, 1=温度, 2=反馈)
public byte LaserError; // 激光器错误 (0=无, 1=温度, 2=电压低, 3=电压高) public byte LaserError; // 激光器错误 (0=无, 1=温度, 2=电压低, 3=电压高)
public byte VisionError; // 视觉错误 (0=无, 1=通信) public byte VisionError; // 视觉错误 (0=无, 1=通信)
public byte AccelerometerError; // 加速度错误 (0=无, 1=通信) public byte AccelerometerError; // 加速度错误 (0=无, 1=通信,2=设备不稳定)
public byte MillimeterWaveError; // 毫米波雷达错误 (0=无, 1=通信) public byte MillimeterWaveError; // 毫米波雷达错误 (0=无, 1=通信)
public byte LidarError; // 激光雷达错误 (0=无, 1=通信) public byte LidarError; // 激光雷达错误 (0=无, 1=通信)

View File

@ -404,7 +404,8 @@ namespace Kill.UI.Pages
isQueryingStatus = false; isQueryingStatus = false;
Debug.Log("[HomePageCtrl] 串行查询设备状态完成"); Debug.Log("[HomePageCtrl] 串行查询设备状态完成");
OnGetAllMosquitoDataClick(); if(!hasWifi)
OnGetAllMosquitoDataClick();
} }
/// <summary> /// <summary>
@ -1280,10 +1281,11 @@ namespace Kill.UI.Pages
public void GetDeviceStatus() public void GetDeviceStatus()
{ {
List<string> errorlist = new List<string>(); List<string> errorlist = new List<string>();
BLECommunicationManager.Instance.ReadHardwareStatus((status) => BLECommunicationManager.Instance.ReadHardwareStatus((status) =>
{ {
Loom.QueueOnMainThread(() => Loom.QueueOnMainThread(() =>
{ {
bool hasError = false; bool hasError = false;
if (status.TemperatureError != 0) if (status.TemperatureError != 0)
{ {
@ -1297,6 +1299,7 @@ namespace Kill.UI.Pages
{ {
error += LanguageManager.Instance.GetLanguage("100230"); error += LanguageManager.Instance.GetLanguage("100230");
} }
errorlist.Add(error);
} }
if (status.MotorError != 0) if (status.MotorError != 0)
{ {
@ -1334,42 +1337,51 @@ namespace Kill.UI.Pages
{ {
hasError = true; hasError = true;
string error = LanguageManager.Instance.GetLanguage("100237"); string error = LanguageManager.Instance.GetLanguage("100237");
error += LanguageManager.Instance.GetLanguage("100232"); error += LanguageManager.Instance.GetLanguage("100238");
errorlist.Add(error); errorlist.Add(error);
} }
if (status.AccelerometerError != 0) if (status.AccelerometerError != 0)
{ {
hasError = true; hasError = true;
string error = LanguageManager.Instance.GetLanguage("100239"); string error = LanguageManager.Instance.GetLanguage("100239");
error += LanguageManager.Instance.GetLanguage("100232"); if (status.AccelerometerError == 1)
{
error += LanguageManager.Instance.GetLanguage("100238");
}
else
{
error += LanguageManager.Instance.GetLanguage("100311");
}
errorlist.Add(error); errorlist.Add(error);
} }
if (status.MillimeterWaveError != 0) if (status.MillimeterWaveError != 0)
{ {
hasError = true; hasError = true;
string error = LanguageManager.Instance.GetLanguage("100240"); string error = LanguageManager.Instance.GetLanguage("100240");
error += LanguageManager.Instance.GetLanguage("100232"); error += LanguageManager.Instance.GetLanguage("100238");
errorlist.Add(error); errorlist.Add(error);
} }
if (status.LidarError != 0) if (status.LidarError != 0)
{ {
hasError = true; hasError = true;
string error = LanguageManager.Instance.GetLanguage("100241"); string error = LanguageManager.Instance.GetLanguage("100241");
error += LanguageManager.Instance.GetLanguage("100232"); error += LanguageManager.Instance.GetLanguage("100238");
errorlist.Add(error); errorlist.Add(error);
} }
errorTipButton.gameObject.SetActive(hasError); errorTipButton.gameObject.SetActive(hasError);
string fullerror = ""; string fullerror = "";
int index = 1; int index = 1;
foreach (string e in errorlist) foreach (string e in errorlist)
{ {
fullerror += $"{index}.{e}/n"; fullerror += $"{index}.{e}\n";
} index++;
deiviceErrorTip.GetComponentInChildren<Text>().text=fullerror; }
Debug.Log("错误提示:" + fullerror);
deiviceErrorTip.transform.Find("框").Find("提示").GetComponent<Text>().text = fullerror;
}); });
}); });
} }
public Text lastRecordTimeTexts; public Text lastRecordTimeTexts;

View File

@ -4,6 +4,9 @@ using Kill.Utils;
using Kill.Managers; using Kill.Managers;
using System.Threading.Tasks; using System.Threading.Tasks;
using System; using System;
using System.Text.RegularExpressions;
using UnityEngine.WSA;
using Kill.UI.Components;
namespace Kill.UI.Pages namespace Kill.UI.Pages
{ {
@ -13,22 +16,29 @@ namespace Kill.UI.Pages
/// </summary> /// </summary>
public class SetPasswordPanel : LoginSubPageBase public class SetPasswordPanel : LoginSubPageBase
{ {
[Header("输入组件")]
public InputField newPasswordInput; public InputField newPasswordInput;
public InputField confirmPasswordInput; public InputField confirmPasswordInput;
[Header("显示组件")]
public Text errorText;
/// <summary> /// <summary>
/// 密码类型提示文本0简单密码1复杂密码 /// 密码类型提示文本0简单密码1复杂密码
/// </summary> /// </summary>
public Text[] passwordTypeTips; public Text[] passwordTypeTips;
[Header("按钮")]
public Button confirmBtn; public Button confirmBtn;
public GameObject confirmBtnDisabled;
/// <summary> /// <summary>
/// 切换简单/复杂密码按钮 0简单密码选中 1简单密码未选中 2复杂密码选中 3复杂密码未选中 未选中可点击 /// 切换简单/复杂密码按钮 0简单密码选中 1简单密码未选中 2复杂密码选中 3复杂密码未选中 未选中可点击
/// </summary> /// </summary>
public GameObject[] changeTypeBtns; public GameObject[] changeTypeBtns;
[Header("密码规则验证")]
public Sprite correctIconSprite;
public Sprite incorrectIconSprite;
public Image[] simplePasswordRules;
public Image[] complexPasswordRules;
public Image simpleConfirmMatchRule;
public Image complexConfirmMatchRule;
public GameObject simpleRulesContainer;
public GameObject complexRulesContainer;
private int passwordType = 0; private int passwordType = 0;
private bool isConfirmEnabled = false; private bool isConfirmEnabled = false;
@ -43,6 +53,11 @@ namespace Kill.UI.Pages
Register, // 注册设置密码 Register, // 注册设置密码
ResetPassword // 忘记密码重置密码 ResetPassword // 忘记密码重置密码
} }
void Start()
{
OnInitialize();
OnShow();
}
protected override void OnInitialize() protected override void OnInitialize()
{ {
if (confirmBtn != null) if (confirmBtn != null)
@ -52,25 +67,29 @@ namespace Kill.UI.Pages
changeTypeBtns[1].GetComponent<Button>().onClick.AddListener(() => ChangePasswordType(0)); changeTypeBtns[1].GetComponent<Button>().onClick.AddListener(() => ChangePasswordType(0));
changeTypeBtns[3].GetComponent<Button>().onClick.AddListener(() => ChangePasswordType(1)); changeTypeBtns[3].GetComponent<Button>().onClick.AddListener(() => ChangePasswordType(1));
} }
newPasswordInput.onEndEdit.AddListener(CheckPasswordFormat); newPasswordInput.onValueChanged.AddListener(OnPasswordValueChanged);
confirmPasswordInput.onEndEdit.AddListener(CheckPasswordFormat); confirmPasswordInput.onValueChanged.AddListener(OnPasswordValueChanged);
errorText.text = "";
} }
protected override void OnShow() protected override void OnShow()
{ {
// 页面显示时初始化密码输入框 // 页面显示时初始化密码输入框
newPasswordInput.text = ""; newPasswordInput.text = "";
confirmPasswordInput.text = ""; confirmPasswordInput.text = "";
errorText.text = "";
ChangePasswordType(0); ChangePasswordType(0);
} }
private void OnPasswordValueChanged(string value)
{
CheckPasswordRules();
UpdateSubmitButton();
}
/// <summary> /// <summary>
/// 切换密码类型 /// 切换密码类型
/// </summary> /// </summary>
/// <param name="type">密码类型0简单密码1复杂密码</param> /// <param name="type">密码类型0简单密码1复杂密码</param>
private void ChangePasswordType(int type) private void ChangePasswordType(int type)
{ {
errorText.text = "";
passwordType = type; passwordType = type;
newPasswordInput.text = ""; newPasswordInput.text = "";
confirmPasswordInput.text = ""; confirmPasswordInput.text = "";
@ -98,37 +117,114 @@ namespace Kill.UI.Pages
} }
passwordTypeTips[passwordType].gameObject.SetActive(true); passwordTypeTips[passwordType].gameObject.SetActive(true);
passwordTypeTips[1 - passwordType].gameObject.SetActive(false); passwordTypeTips[1 - passwordType].gameObject.SetActive(false);
// 切换规则容器并重置显示
UpdateRulesContainerVisibility();
ResetRulesDisplay();
UpdateSubmitButton();
} }
public void CheckPasswordFormat(string password)
/// <summary>
/// 更新规则容器可见性
/// </summary>
private void UpdateRulesContainerVisibility()
{ {
isConfirmEnabled = true; if (simpleRulesContainer != null)
errorText.text = ""; simpleRulesContainer.SetActive(passwordType == 0);
if (complexRulesContainer != null)
complexRulesContainer.SetActive(passwordType == 1);
}
/// <summary>
/// 重置规则显示为未通过状态
/// </summary>
private void ResetRulesDisplay()
{
var rules = passwordType == 0 ? simplePasswordRules : complexPasswordRules;
if (rules != null)
{
foreach (var image in rules)
{
if (image != null)
image.sprite = incorrectIconSprite;
}
}
var matchRule = passwordType == 0 ? simpleConfirmMatchRule : complexConfirmMatchRule;
if (matchRule != null)
matchRule.sprite = incorrectIconSprite;
}
/// <summary>
/// 逐条检查密码规则并更新UI图标
/// </summary>
private void CheckPasswordRules()
{
string newPwd = newPasswordInput != null ? newPasswordInput.text : "";
string confirmPwd = confirmPasswordInput != null ? confirmPasswordInput.text : "";
bool pwdRulesValid = false;
if (passwordType == 0) if (passwordType == 0)
{ {
if (!ValidationUtils.IsValidSimplePassword(newPasswordInput.text)) // 简单密码 - 1条规则6位数字
{ bool isValid = ValidationUtils.IsValidSimplePassword(newPwd);
errorText.text = LanguageManager.Instance.GetLanguage("100025"); pwdRulesValid = isValid;
isConfirmEnabled = false; if (simplePasswordRules != null && simplePasswordRules.Length > 0 && simplePasswordRules[0] != null)
return; simplePasswordRules[0].sprite = isValid ? correctIconSprite : incorrectIconSprite;
} Debug.Log(isValid);
} }
else else
{ {
if (!ValidationUtils.IsValidComplexPassword(newPasswordInput.text)) // 复杂密码 - 4条规则
// 规则1长度8-16位不能有空格
bool rule1Valid = newPwd.Length >= 8 && newPwd.Length <= 16 && !newPwd.Contains(" ");
// 规则2至少包含大写字母、小写字母、数字、特殊字符中的三种
bool hasUpper = Regex.IsMatch(newPwd, @"[A-Z]");
bool hasLower = Regex.IsMatch(newPwd, @"[a-z]");
bool hasDigit = Regex.IsMatch(newPwd, @"\d");
bool hasSpecial = !string.IsNullOrEmpty(newPwd) && Regex.IsMatch(newPwd, @"[!@#$%^&*()_+\-=\[\]{};':""\\|,.<>\/?]|\p{P}");
int typeCount = (hasUpper ? 1 : 0) + (hasLower ? 1 : 0) + (hasDigit ? 1 : 0) + (hasSpecial ? 1 : 0);
bool rule2Valid = typeCount >= 3;
// 规则3不能包含连续或重复的三个及以上的数字或字母大小写不敏感
bool rule3Valid = ValidationUtils.IsValidNoRepeatOrConsecutive(newPwd);
// 规则4密码不能包含邮箱中任意连续两个字符
string email = LoginPageCtrl.Instance.GetCurrentEmail();
bool rule4Valid = ValidationUtils.IsValidNotEmailPart(newPwd, email);
pwdRulesValid = rule1Valid && rule2Valid && rule3Valid && rule4Valid;
if (complexPasswordRules != null && complexPasswordRules.Length >= 4)
{ {
errorText.text = LanguageManager.Instance.GetLanguage("100029"); if (complexPasswordRules[0] != null) complexPasswordRules[0].sprite = rule1Valid ? correctIconSprite : incorrectIconSprite;
isConfirmEnabled = false; if (complexPasswordRules[1] != null) complexPasswordRules[1].sprite = rule2Valid ? correctIconSprite : incorrectIconSprite;
return; if (complexPasswordRules[2] != null) complexPasswordRules[2].sprite = rule3Valid ? correctIconSprite : incorrectIconSprite;
if (complexPasswordRules[3] != null) complexPasswordRules[3].sprite = rule4Valid ? correctIconSprite : incorrectIconSprite;
} }
} }
if (newPasswordInput.text != confirmPasswordInput.text) // 两条密码一致
{ bool matchValid = !string.IsNullOrEmpty(newPwd) && newPwd == confirmPwd;
errorText.text = LanguageManager.Instance.GetLanguage("100028"); var matchRule = passwordType == 0 ? simpleConfirmMatchRule : complexConfirmMatchRule;
isConfirmEnabled = false; if (matchRule != null)
} matchRule.sprite = matchValid ? correctIconSprite : incorrectIconSprite;
isConfirmEnabled = pwdRulesValid && matchValid;
} }
/// <summary>
/// 更新提交按钮显示状态
/// </summary>
private void UpdateSubmitButton()
{
if (confirmBtn != null)
confirmBtn.gameObject.SetActive(isConfirmEnabled);
if (confirmBtnDisabled != null)
confirmBtnDisabled.SetActive(!isConfirmEnabled);
}
/// <summary> /// <summary>
/// 设置页面模式(注册或重置密码) /// 设置页面模式(注册或重置密码)
/// </summary> /// </summary>
@ -158,7 +254,7 @@ namespace Kill.UI.Pages
}, },
onError: (code, msg) => onError: (code, msg) =>
{ {
errorText.text =code.ToString(); ToastUI.ShowText(code.ToString());
} }
); );
} }
@ -173,7 +269,7 @@ namespace Kill.UI.Pages
}, },
onError: (code, msg) => onError: (code, msg) =>
{ {
errorText.text = code.ToString(); ToastUI.ShowText(code.ToString());
} }
); );
} }

View File

@ -32,14 +32,15 @@ namespace Kill.UI.Pages
/// <summary> /// <summary>
/// 0简单密码 1复杂密码 /// 0简单密码 1复杂密码
/// </summary> /// </summary>
public Button[] setPasswordButtons; public Button resetPassword;
public Text[] setPasswordButtonTexts;
public async Task Init(UserInfo userInfo) public async Task Init(UserInfo userInfo)
{ {
this.userInfo = userInfo; this.userInfo = userInfo;
UIManager.Instance.RegisterBackAction(OnBack); UIManager.Instance.RegisterBackAction(OnBack);
backButton.onClick.RemoveAllListeners(); backButton.onClick.RemoveAllListeners();
backButton.onClick.AddListener(OnBack); backButton.onClick.AddListener(OnBack);
resetPassword.onClick.RemoveAllListeners();
resetPassword.onClick.AddListener(OnSetPassword);
renameButton.onClick.RemoveAllListeners(); renameButton.onClick.RemoveAllListeners();
renameButton.onClick.AddListener(() => renameButton.onClick.AddListener(() =>
{ {
@ -73,8 +74,6 @@ namespace Kill.UI.Pages
selectCountryButton.onClick.RemoveAllListeners(); selectCountryButton.onClick.RemoveAllListeners();
selectCountryButton.onClick.AddListener(OnSelectCountry); selectCountryButton.onClick.AddListener(OnSelectCountry);
} }
InitLanguageText();
} }
/// <summary> /// <summary>
@ -113,30 +112,6 @@ namespace Kill.UI.Pages
public void InitLanguageText() public void InitLanguageText()
{ {
passwordSet = new List<string>(); passwordSet = new List<string>();
foreach (var key in passwordSetKeys)
{
passwordSet.Add(LanguageManager.Instance.GetLanguage(key));
}
if (userInfo.has_simple_password)
{
setPasswordButtonTexts[0].text = passwordSet[1];
}
else
{
setPasswordButtonTexts[0].text = passwordSet[0];
}
if (userInfo.has_complex_password)
{
setPasswordButtonTexts[1].text = passwordSet[1];
}
else
{
setPasswordButtonTexts[1].text = passwordSet[0];
}
setPasswordButtons[0].onClick.RemoveAllListeners();
setPasswordButtons[0].onClick.AddListener(() => OnSetPassword(0));
setPasswordButtons[1].onClick.RemoveAllListeners();
setPasswordButtons[1].onClick.AddListener(() => OnSetPassword(1));
} }
public void CloseRenamePage(string newNickname) public void CloseRenamePage(string newNickname)
@ -148,11 +123,9 @@ namespace Kill.UI.Pages
DataManager.Instance.userInfo.username = newNickname; DataManager.Instance.userInfo.username = newNickname;
UIManager.Instance.RegisterBackAction(OnBack); UIManager.Instance.RegisterBackAction(OnBack);
} }
int passwordType = -1;
SelfVerificationCodePanel verificationCodePanel; SelfVerificationCodePanel verificationCodePanel;
public void OnSetPassword(int passwordType) public void OnSetPassword()
{ {
this.passwordType = passwordType;
verificationCodePanel = Instantiate(verificationCodePanelPrefab, transform); verificationCodePanel = Instantiate(verificationCodePanelPrefab, transform);
verificationCodePanel.Init(OnSureVerificationCode); verificationCodePanel.Init(OnSureVerificationCode);
} }
@ -161,14 +134,13 @@ namespace Kill.UI.Pages
{ {
Destroy(verificationCodePanel.gameObject); Destroy(verificationCodePanel.gameObject);
setPasswordPanel = Instantiate(setPasswordPanelPrefab, transform); setPasswordPanel = Instantiate(setPasswordPanelPrefab, transform);
setPasswordPanel.Init(passwordType, OnSetPasswordSuccess); setPasswordPanel.Init(OnSetPasswordSuccess);
} }
public void OnSetPasswordSuccess() public void OnSetPasswordSuccess()
{ {
Destroy(setPasswordPanel.gameObject); Destroy(setPasswordPanel.gameObject);
OnSignOut();
UIManager.Instance.RegisterBackAction(OnBack);
Init(DataManager.Instance.userInfo);
} }
public void OnBack() public void OnBack()
{ {
@ -187,7 +159,7 @@ namespace Kill.UI.Pages
// 清除用户数据 // 清除用户数据
DataManager.Instance.ClearInfo(); DataManager.Instance.ClearInfo();
// 返回登录页面 // 返回登录页面
UIManager.Instance.OpenPage(UIManager.PageName.loginPage); UIManager.Instance.OpenMainPage(UIManager.PageName.loginPage);
} }

View File

@ -4,6 +4,7 @@ using Kill.Utils;
using Kill.Managers; using Kill.Managers;
using System.Threading.Tasks; using System.Threading.Tasks;
using System; using System;
using System.Text.RegularExpressions;
using Kill.UI.Components; using Kill.UI.Components;
using Kill.Network; using Kill.Network;
@ -18,23 +19,33 @@ namespace Kill.UI.Pages
[Header("输入组件")] [Header("输入组件")]
public InputField newPasswordInput; public InputField newPasswordInput;
public InputField confirmPasswordInput; public InputField confirmPasswordInput;
[Header("显示组件")]
public Text errorText;
/// <summary>
/// 密码类型提示文本0简单密码1复杂密码
/// </summary>
public Text[] passwordTypeTips;
[Header("按钮")] [Header("按钮")]
public Button confirmBtn; public Button confirmBtn;
public GameObject confirmBtnDisabled;
public Button backBtn; public Button backBtn;
/// <summary>
/// 切换简单/复杂密码按钮 0简单密码选中 1简单密码未选中 2复杂密码选中 3复杂密码未选中 未选中可点击
/// </summary>
public GameObject[] changeTypeBtns;
[Header("密码规则验证")]
public Sprite correctIconSprite;
public Sprite incorrectIconSprite;
public Image[] simplePasswordRules;
public Image[] complexPasswordRules;
public Image simpleConfirmMatchRule;
public Image complexConfirmMatchRule;
public GameObject simpleRulesContainer;
public GameObject complexRulesContainer;
private int passwordType = 0; private int passwordType = 0;
private bool isConfirmEnabled = false; private bool isConfirmEnabled = false;
Action callback; Action callback;
public void Init(int type, Action callback) public void Init( Action callback)
{ {
passwordType = type; passwordType = 0;
this.callback = callback; this.callback = callback;
if (confirmBtn != null) if (confirmBtn != null)
{ {
@ -46,23 +57,29 @@ namespace Kill.UI.Pages
backBtn.onClick.RemoveAllListeners(); backBtn.onClick.RemoveAllListeners();
backBtn.onClick.AddListener(OnBack); backBtn.onClick.AddListener(OnBack);
} }
if (changeTypeBtns != null && changeTypeBtns.Length == 4)
newPasswordInput.onEndEdit.AddListener(CheckPasswordFormat); {
confirmPasswordInput.onEndEdit.AddListener(CheckPasswordFormat); changeTypeBtns[1].GetComponent<Button>().onClick.AddListener(() => ChangePasswordType(0));
errorText.text = ""; changeTypeBtns[3].GetComponent<Button>().onClick.AddListener(() => ChangePasswordType(1));
}
newPasswordInput.onValueChanged.AddListener(OnPasswordValueChanged);
confirmPasswordInput.onValueChanged.AddListener(OnPasswordValueChanged);
// 页面显示时初始化密码输入框 // 页面显示时初始化密码输入框
newPasswordInput.text = ""; ChangePasswordType(0);
confirmPasswordInput.text = "";
errorText.text = "";
ChangePasswordType(type);
} }
private void OnPasswordValueChanged(string value)
{
CheckPasswordRules();
UpdateSubmitButton();
}
/// <summary> /// <summary>
/// 切换密码类型 /// 切换密码类型
/// </summary> /// </summary>
/// <param name="type">密码类型0简单密码1复杂密码</param> /// <param name="type">密码类型0简单密码1复杂密码</param>
private void ChangePasswordType(int type) private void ChangePasswordType(int type)
{ {
errorText.text = "";
passwordType = type; passwordType = type;
newPasswordInput.text = ""; newPasswordInput.text = "";
confirmPasswordInput.text = ""; confirmPasswordInput.text = "";
@ -72,6 +89,10 @@ namespace Kill.UI.Pages
confirmPasswordInput.characterLimit = 6; confirmPasswordInput.characterLimit = 6;
newPasswordInput.contentType = InputField.ContentType.Pin; newPasswordInput.contentType = InputField.ContentType.Pin;
confirmPasswordInput.contentType = InputField.ContentType.Pin; confirmPasswordInput.contentType = InputField.ContentType.Pin;
changeTypeBtns[0].SetActive(true);
changeTypeBtns[1].SetActive(false);
changeTypeBtns[2].SetActive(false);
changeTypeBtns[3].SetActive(true);
} }
else else
@ -80,40 +101,111 @@ namespace Kill.UI.Pages
confirmPasswordInput.characterLimit = 16; confirmPasswordInput.characterLimit = 16;
newPasswordInput.contentType = InputField.ContentType.Password; newPasswordInput.contentType = InputField.ContentType.Password;
confirmPasswordInput.contentType = InputField.ContentType.Password; confirmPasswordInput.contentType = InputField.ContentType.Password;
changeTypeBtns[0].SetActive(false);
changeTypeBtns[1].SetActive(true);
changeTypeBtns[2].SetActive(true);
changeTypeBtns[3].SetActive(false);
} }
passwordTypeTips[passwordType].gameObject.SetActive(true);
passwordTypeTips[1 - passwordType].gameObject.SetActive(false); // 切换规则容器并重置显示
UpdateRulesContainerVisibility();
ResetRulesDisplay();
UpdateSubmitButton();
} }
public void CheckPasswordFormat(string password)
private void UpdateRulesContainerVisibility()
{ {
isConfirmEnabled = true; if (simpleRulesContainer != null)
errorText.text = ""; simpleRulesContainer.SetActive(passwordType == 0);
if (complexRulesContainer != null)
complexRulesContainer.SetActive(passwordType == 1);
}
private void ResetRulesDisplay()
{
var rules = passwordType == 0 ? simplePasswordRules : complexPasswordRules;
if (rules != null)
{
foreach (var image in rules)
{
if (image != null)
image.sprite = incorrectIconSprite;
}
}
var matchRule = passwordType == 0 ? simpleConfirmMatchRule : complexConfirmMatchRule;
if (matchRule != null)
matchRule.sprite = incorrectIconSprite;
}
/// <summary>
/// 逐条检查密码规则并更新UI图标
/// </summary>
private void CheckPasswordRules()
{
string newPwd = newPasswordInput != null ? newPasswordInput.text : "";
string confirmPwd = confirmPasswordInput != null ? confirmPasswordInput.text : "";
bool pwdRulesValid = false;
if (passwordType == 0) if (passwordType == 0)
{ {
if (!ValidationUtils.IsValidSimplePassword(newPasswordInput.text)) // 简单密码 - 1条规则6位数字
{ bool isValid = ValidationUtils.IsValidSimplePassword(newPwd);
errorText.text = LanguageManager.Instance.GetLanguage("100025"); pwdRulesValid = isValid;
isConfirmEnabled = false; if (simplePasswordRules != null && simplePasswordRules.Length > 0 && simplePasswordRules[0] != null)
return; simplePasswordRules[0].sprite = isValid ? correctIconSprite : incorrectIconSprite;
}
} }
else else
{ {
if (!ValidationUtils.IsValidComplexPassword(newPasswordInput.text)) // 复杂密码 - 4条规则
// 规则1长度8-16位不能有空格
bool rule1Valid = newPwd.Length >= 8 && newPwd.Length <= 16 && !newPwd.Contains(" ");
// 规则2至少包含大写字母、小写字母、数字、特殊字符中的三种
bool hasUpper = Regex.IsMatch(newPwd, @"[A-Z]");
bool hasLower = Regex.IsMatch(newPwd, @"[a-z]");
bool hasDigit = Regex.IsMatch(newPwd, @"\d");
bool hasSpecial = !string.IsNullOrEmpty(newPwd) && Regex.IsMatch(newPwd, @"[!@#$%^&*()_+\-=\[\]{};':""\\|,.<>\/?]|\p{P}");
int typeCount = (hasUpper ? 1 : 0) + (hasLower ? 1 : 0) + (hasDigit ? 1 : 0) + (hasSpecial ? 1 : 0);
bool rule2Valid = typeCount >= 3;
// 规则3不能包含连续或重复的三个及以上的数字或字母大小写不敏感
bool rule3Valid = ValidationUtils.IsValidNoRepeatOrConsecutive(newPwd);
// 规则4密码不能包含邮箱中任意连续两个字符
string email = DataManager.Instance.userInfo.email;
bool rule4Valid = ValidationUtils.IsValidNotEmailPart(newPwd, email);
pwdRulesValid = rule1Valid && rule2Valid && rule3Valid && rule4Valid;
if (complexPasswordRules != null && complexPasswordRules.Length >= 4)
{ {
errorText.text = LanguageManager.Instance.GetLanguage("100029"); if (complexPasswordRules[0] != null) complexPasswordRules[0].sprite = rule1Valid ? correctIconSprite : incorrectIconSprite;
isConfirmEnabled = false; if (complexPasswordRules[1] != null) complexPasswordRules[1].sprite = rule2Valid ? correctIconSprite : incorrectIconSprite;
return; if (complexPasswordRules[2] != null) complexPasswordRules[2].sprite = rule3Valid ? correctIconSprite : incorrectIconSprite;
if (complexPasswordRules[3] != null) complexPasswordRules[3].sprite = rule4Valid ? correctIconSprite : incorrectIconSprite;
} }
} }
if (newPasswordInput.text != confirmPasswordInput.text) // 两条密码一致
{ bool matchValid = !string.IsNullOrEmpty(newPwd) && newPwd == confirmPwd;
errorText.text = LanguageManager.Instance.GetLanguage("100028"); var matchRule = passwordType == 0 ? simpleConfirmMatchRule : complexConfirmMatchRule;
isConfirmEnabled = false; if (matchRule != null)
} matchRule.sprite = matchValid ? correctIconSprite : incorrectIconSprite;
isConfirmEnabled = pwdRulesValid && matchValid;
}
/// <summary>
/// 更新提交按钮显示状态
/// </summary>
private void UpdateSubmitButton()
{
if (confirmBtn != null)
confirmBtn.gameObject.SetActive(isConfirmEnabled);
if (confirmBtnDisabled != null)
confirmBtnDisabled.SetActive(!isConfirmEnabled);
} }
/// <summary> /// <summary>
@ -143,7 +235,7 @@ namespace Kill.UI.Pages
}, },
onError: (code, msg) => onError: (code, msg) =>
{ {
errorText.text = code.ToString(); ToastUI.ShowText(code.ToString());
} }
); );
@ -174,6 +266,7 @@ namespace Kill.UI.Pages
onSuccess: (data) => onSuccess: (data) =>
{ {
Debug.Log("重置密码成功"); Debug.Log("重置密码成功");
ToastUI.Show("100319");
onSuccess?.Invoke(); onSuccess?.Invoke();
}, },
onError: onError onError: onError

View File

@ -63,6 +63,61 @@ namespace Kill.Utils
return hasDigit && hasLetter && hasSpecial; return hasDigit && hasLetter && hasSpecial;
} }
/// <summary>
/// 检查是否包含连续或重复的三个及以上数字或字母(大小写不敏感)
/// </summary>
public static bool IsValidNoRepeatOrConsecutive(string password)
{
for (int i = 0; i <= password.Length - 3; i++)
{
char c0 = password[i];
char c1 = password[i + 1];
char c2 = password[i + 2];
if (!char.IsLetterOrDigit(c0) || !char.IsLetterOrDigit(c1) || !char.IsLetterOrDigit(c2))
continue;
bool isRepeated;
if (char.IsLetter(c0))
isRepeated = char.ToLower(c0) == char.ToLower(c1) && char.ToLower(c1) == char.ToLower(c2);
else
isRepeated = c0 == c1 && c1 == c2;
if (isRepeated) return false;
if (char.IsDigit(c0) && char.IsDigit(c1) && char.IsDigit(c2))
{
if ((c1 - c0 == 1 && c2 - c1 == 1) || (c0 - c1 == 1 && c1 - c2 == 1))
return false;
}
else if (char.IsLetter(c0))
{
char l0 = char.ToLower(c0), l1 = char.ToLower(c1), l2 = char.ToLower(c2);
if ((l1 - l0 == 1 && l2 - l1 == 1) || (l0 - l1 == 1 && l1 - l2 == 1))
return false;
}
}
return true;
}
/// <summary>
/// 检查密码是否包含邮箱中任意连续两个字符
/// </summary>
public static bool IsValidNotEmailPart(string password, string email)
{
if (string.IsNullOrEmpty(email)) return true;
string emailName = email.Contains('@') ? email.Split('@')[0] : email;
string pwdLower = password.ToLower();
for (int i = 0; i <= emailName.Length - 2; i++)
{
string pair = emailName.Substring(i, 2).ToLower();
if (pwdLower.Contains(pair))
return false;
}
return true;
}
#endregion #endregion
} }