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": "同步中",
"en": "Syncing"
},
{
{
"key": "100310",
"zh": "已同步 {0} 条记录",
"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_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!224 &7655272676486550921
RectTransform:
m_ObjectHideFlags: 0
@ -451,10 +451,10 @@ RectTransform:
- {fileID: 8098680310359255571}
m_Father: {fileID: 4482340305028100583}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 100}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 345, y: -454}
m_SizeDelta: {x: 630, y: 100}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &459455692708586888
CanvasRenderer:
@ -1209,7 +1209,7 @@ MonoBehaviour:
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: Simple Password
m_Text: Reset Password
--- !u!114 &6798849037590746149
MonoBehaviour:
m_ObjectHideFlags: 0
@ -1222,7 +1222,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 89f1d49b50a31514787b4e268b1c73bb, type: 3}
m_Name:
m_EditorClassIdentifier:
textKey: 100020
textKey: 100318
--- !u!1 &2636706380647424798
GameObject:
m_ObjectHideFlags: 0
@ -1593,7 +1593,7 @@ RectTransform:
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 1, y: 1}
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}
--- !u!222 &3376027943477643505
CanvasRenderer:
@ -2292,7 +2292,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!224 &3480274442878060420
RectTransform:
m_ObjectHideFlags: 0
@ -2307,10 +2307,10 @@ RectTransform:
m_Children: []
m_Father: {fileID: 4482340305028100583}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 1}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 345, y: -403.5}
m_SizeDelta: {x: 630, y: 1}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &9222182864204810855
CanvasRenderer:
@ -2363,7 +2363,7 @@ GameObject:
- component: {fileID: 4334117203526359151}
- component: {fileID: 173285266742594171}
m_Layer: 5
m_Name: "\u7B80\u5355\u5BC6\u7801"
m_Name: "\u91CD\u7F6E\u5BC6\u7801"
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
@ -3384,12 +3384,7 @@ MonoBehaviour:
countryText: {fileID: 869492382333240842}
selectCountryButton: {fileID: 4981612120447436577}
signoutButton: {fileID: 8124639496175450611}
setPasswordButtons:
- {fileID: 173285266742594171}
- {fileID: 6647111382430836735}
setPasswordButtonTexts:
- {fileID: 8913907960913215519}
- {fileID: 6070206944283714795}
resetPassword: {fileID: 173285266742594171}
signoutTip: {fileID: 1786064314622589889}
--- !u!1 &8678496074108022521
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 LaserError; // 激光器错误 (0=无, 1=温度, 2=电压低, 3=电压高)
public byte VisionError; // 视觉错误 (0=无, 1=通信)
public byte AccelerometerError; // 加速度错误 (0=无, 1=通信)
public byte AccelerometerError; // 加速度错误 (0=无, 1=通信,2=设备不稳定)
public byte MillimeterWaveError; // 毫米波雷达错误 (0=无, 1=通信)
public byte LidarError; // 激光雷达错误 (0=无, 1=通信)

View File

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

View File

@ -4,6 +4,9 @@ using Kill.Utils;
using Kill.Managers;
using System.Threading.Tasks;
using System;
using System.Text.RegularExpressions;
using UnityEngine.WSA;
using Kill.UI.Components;
namespace Kill.UI.Pages
{
@ -13,22 +16,29 @@ namespace Kill.UI.Pages
/// </summary>
public class SetPasswordPanel : LoginSubPageBase
{
[Header("输入组件")]
public InputField newPasswordInput;
public InputField confirmPasswordInput;
[Header("显示组件")]
public Text errorText;
/// <summary>
/// 密码类型提示文本0简单密码1复杂密码
/// </summary>
public Text[] passwordTypeTips;
[Header("按钮")]
public Button confirmBtn;
public GameObject confirmBtnDisabled;
/// <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 bool isConfirmEnabled = false;
@ -43,6 +53,11 @@ namespace Kill.UI.Pages
Register, // 注册设置密码
ResetPassword // 忘记密码重置密码
}
void Start()
{
OnInitialize();
OnShow();
}
protected override void OnInitialize()
{
if (confirmBtn != null)
@ -52,25 +67,29 @@ namespace Kill.UI.Pages
changeTypeBtns[1].GetComponent<Button>().onClick.AddListener(() => ChangePasswordType(0));
changeTypeBtns[3].GetComponent<Button>().onClick.AddListener(() => ChangePasswordType(1));
}
newPasswordInput.onEndEdit.AddListener(CheckPasswordFormat);
confirmPasswordInput.onEndEdit.AddListener(CheckPasswordFormat);
errorText.text = "";
newPasswordInput.onValueChanged.AddListener(OnPasswordValueChanged);
confirmPasswordInput.onValueChanged.AddListener(OnPasswordValueChanged);
}
protected override void OnShow()
{
// 页面显示时初始化密码输入框
newPasswordInput.text = "";
confirmPasswordInput.text = "";
errorText.text = "";
ChangePasswordType(0);
}
private void OnPasswordValueChanged(string value)
{
CheckPasswordRules();
UpdateSubmitButton();
}
/// <summary>
/// 切换密码类型
/// </summary>
/// <param name="type">密码类型0简单密码1复杂密码</param>
private void ChangePasswordType(int type)
{
errorText.text = "";
passwordType = type;
newPasswordInput.text = "";
confirmPasswordInput.text = "";
@ -98,37 +117,114 @@ namespace Kill.UI.Pages
}
passwordTypeTips[passwordType].gameObject.SetActive(true);
passwordTypeTips[1 - passwordType].gameObject.SetActive(false);
// 切换规则容器并重置显示
UpdateRulesContainerVisibility();
ResetRulesDisplay();
UpdateSubmitButton();
}
public void CheckPasswordFormat(string password)
/// <summary>
/// 更新规则容器可见性
/// </summary>
private void UpdateRulesContainerVisibility()
{
isConfirmEnabled = true;
errorText.text = "";
if (simpleRulesContainer != null)
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 (!ValidationUtils.IsValidSimplePassword(newPasswordInput.text))
{
errorText.text = LanguageManager.Instance.GetLanguage("100025");
isConfirmEnabled = false;
return;
}
// 简单密码 - 1条规则6位数字
bool isValid = ValidationUtils.IsValidSimplePassword(newPwd);
pwdRulesValid = isValid;
if (simplePasswordRules != null && simplePasswordRules.Length > 0 && simplePasswordRules[0] != null)
simplePasswordRules[0].sprite = isValid ? correctIconSprite : incorrectIconSprite;
Debug.Log(isValid);
}
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");
isConfirmEnabled = false;
return;
if (complexPasswordRules[0] != null) complexPasswordRules[0].sprite = rule1Valid ? correctIconSprite : incorrectIconSprite;
if (complexPasswordRules[1] != null) complexPasswordRules[1].sprite = rule2Valid ? correctIconSprite : incorrectIconSprite;
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)
{
errorText.text = LanguageManager.Instance.GetLanguage("100028");
isConfirmEnabled = false;
}
// 两条密码一致
bool matchValid = !string.IsNullOrEmpty(newPwd) && newPwd == confirmPwd;
var matchRule = passwordType == 0 ? simpleConfirmMatchRule : complexConfirmMatchRule;
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>
@ -158,7 +254,7 @@ namespace Kill.UI.Pages
},
onError: (code, msg) =>
{
errorText.text =code.ToString();
ToastUI.ShowText(code.ToString());
}
);
}
@ -173,7 +269,7 @@ namespace Kill.UI.Pages
},
onError: (code, msg) =>
{
errorText.text = code.ToString();
ToastUI.ShowText(code.ToString());
}
);
}

View File

@ -32,14 +32,15 @@ namespace Kill.UI.Pages
/// <summary>
/// 0简单密码 1复杂密码
/// </summary>
public Button[] setPasswordButtons;
public Text[] setPasswordButtonTexts;
public Button resetPassword;
public async Task Init(UserInfo userInfo)
{
this.userInfo = userInfo;
UIManager.Instance.RegisterBackAction(OnBack);
backButton.onClick.RemoveAllListeners();
backButton.onClick.AddListener(OnBack);
resetPassword.onClick.RemoveAllListeners();
resetPassword.onClick.AddListener(OnSetPassword);
renameButton.onClick.RemoveAllListeners();
renameButton.onClick.AddListener(() =>
{
@ -73,8 +74,6 @@ namespace Kill.UI.Pages
selectCountryButton.onClick.RemoveAllListeners();
selectCountryButton.onClick.AddListener(OnSelectCountry);
}
InitLanguageText();
}
/// <summary>
@ -113,30 +112,6 @@ namespace Kill.UI.Pages
public void InitLanguageText()
{
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)
@ -148,11 +123,9 @@ namespace Kill.UI.Pages
DataManager.Instance.userInfo.username = newNickname;
UIManager.Instance.RegisterBackAction(OnBack);
}
int passwordType = -1;
SelfVerificationCodePanel verificationCodePanel;
public void OnSetPassword(int passwordType)
public void OnSetPassword()
{
this.passwordType = passwordType;
verificationCodePanel = Instantiate(verificationCodePanelPrefab, transform);
verificationCodePanel.Init(OnSureVerificationCode);
}
@ -161,14 +134,13 @@ namespace Kill.UI.Pages
{
Destroy(verificationCodePanel.gameObject);
setPasswordPanel = Instantiate(setPasswordPanelPrefab, transform);
setPasswordPanel.Init(passwordType, OnSetPasswordSuccess);
setPasswordPanel.Init(OnSetPasswordSuccess);
}
public void OnSetPasswordSuccess()
{
Destroy(setPasswordPanel.gameObject);
UIManager.Instance.RegisterBackAction(OnBack);
Init(DataManager.Instance.userInfo);
OnSignOut();
}
public void OnBack()
{
@ -187,7 +159,7 @@ namespace Kill.UI.Pages
// 清除用户数据
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 System.Threading.Tasks;
using System;
using System.Text.RegularExpressions;
using Kill.UI.Components;
using Kill.Network;
@ -18,23 +19,33 @@ namespace Kill.UI.Pages
[Header("输入组件")]
public InputField newPasswordInput;
public InputField confirmPasswordInput;
[Header("显示组件")]
public Text errorText;
/// <summary>
/// 密码类型提示文本0简单密码1复杂密码
/// </summary>
public Text[] passwordTypeTips;
[Header("按钮")]
public Button confirmBtn;
public GameObject confirmBtnDisabled;
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 bool isConfirmEnabled = false;
Action callback;
public void Init(int type, Action callback)
public void Init( Action callback)
{
passwordType = type;
passwordType = 0;
this.callback = callback;
if (confirmBtn != null)
{
@ -46,23 +57,29 @@ namespace Kill.UI.Pages
backBtn.onClick.RemoveAllListeners();
backBtn.onClick.AddListener(OnBack);
}
newPasswordInput.onEndEdit.AddListener(CheckPasswordFormat);
confirmPasswordInput.onEndEdit.AddListener(CheckPasswordFormat);
errorText.text = "";
if (changeTypeBtns != null && changeTypeBtns.Length == 4)
{
changeTypeBtns[1].GetComponent<Button>().onClick.AddListener(() => ChangePasswordType(0));
changeTypeBtns[3].GetComponent<Button>().onClick.AddListener(() => ChangePasswordType(1));
}
newPasswordInput.onValueChanged.AddListener(OnPasswordValueChanged);
confirmPasswordInput.onValueChanged.AddListener(OnPasswordValueChanged);
// 页面显示时初始化密码输入框
newPasswordInput.text = "";
confirmPasswordInput.text = "";
errorText.text = "";
ChangePasswordType(type);
ChangePasswordType(0);
}
private void OnPasswordValueChanged(string value)
{
CheckPasswordRules();
UpdateSubmitButton();
}
/// <summary>
/// 切换密码类型
/// </summary>
/// <param name="type">密码类型0简单密码1复杂密码</param>
private void ChangePasswordType(int type)
{
errorText.text = "";
passwordType = type;
newPasswordInput.text = "";
confirmPasswordInput.text = "";
@ -72,6 +89,10 @@ namespace Kill.UI.Pages
confirmPasswordInput.characterLimit = 6;
newPasswordInput.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
@ -80,40 +101,111 @@ namespace Kill.UI.Pages
confirmPasswordInput.characterLimit = 16;
newPasswordInput.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;
errorText.text = "";
if (simpleRulesContainer != null)
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 (!ValidationUtils.IsValidSimplePassword(newPasswordInput.text))
{
errorText.text = LanguageManager.Instance.GetLanguage("100025");
isConfirmEnabled = false;
return;
}
// 简单密码 - 1条规则6位数字
bool isValid = ValidationUtils.IsValidSimplePassword(newPwd);
pwdRulesValid = isValid;
if (simplePasswordRules != null && simplePasswordRules.Length > 0 && simplePasswordRules[0] != null)
simplePasswordRules[0].sprite = isValid ? correctIconSprite : incorrectIconSprite;
}
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");
isConfirmEnabled = false;
return;
if (complexPasswordRules[0] != null) complexPasswordRules[0].sprite = rule1Valid ? correctIconSprite : incorrectIconSprite;
if (complexPasswordRules[1] != null) complexPasswordRules[1].sprite = rule2Valid ? correctIconSprite : incorrectIconSprite;
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)
{
errorText.text = LanguageManager.Instance.GetLanguage("100028");
isConfirmEnabled = false;
}
// 两条密码一致
bool matchValid = !string.IsNullOrEmpty(newPwd) && newPwd == confirmPwd;
var matchRule = passwordType == 0 ? simpleConfirmMatchRule : complexConfirmMatchRule;
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>
@ -143,7 +235,7 @@ namespace Kill.UI.Pages
},
onError: (code, msg) =>
{
errorText.text = code.ToString();
ToastUI.ShowText(code.ToString());
}
);
@ -174,6 +266,7 @@ namespace Kill.UI.Pages
onSuccess: (data) =>
{
Debug.Log("重置密码成功");
ToastUI.Show("100319");
onSuccess?.Invoke();
},
onError: onError

View File

@ -63,7 +63,62 @@ namespace Kill.Utils
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
}
}