Template
chore: 初始化脚手架 — sunny_mochi 企业级 Flutter 模板
Core 基础设施:Flavor 三包体系、Drift+SQLCipher 加密数据库 7 拦截器 Dio 网络栈、CrashReporter 崩溃日志、Sealed Failure 错误体系 5 色板主题系统、Auth 认证骨架(Clean Architecture)、Dev Panel、Mock Adapter 通过验证:flutter analyze(0 errors)、flutter test(全绿)、staging APK 74.8MB
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
/// 密码策略校验(12 位 + 大小写 + 数字 + 特殊符号)
|
||||
///
|
||||
/// 对齐设计稿 #3/#5 要求:"密码不能少于12位,必须包含大小写字母、数字、特殊符号。"
|
||||
String? validatePasswordPolicy(String pwd) {
|
||||
if (pwd.length < 12) return '密码不能少于12位';
|
||||
if (!RegExp('[A-Z]').hasMatch(pwd)) return '需包含大写字母';
|
||||
if (!RegExp('[a-z]').hasMatch(pwd)) return '需包含小写字母';
|
||||
if (!RegExp('[0-9]').hasMatch(pwd)) return '需包含数字';
|
||||
if (!RegExp(r'[!@#$%^&*()\-_=+\[\]{};:,.<>?/|\\]').hasMatch(pwd)) {
|
||||
return '需包含特殊符号';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 密码强度评分(用于 StrengthIndicator 重映射)
|
||||
///
|
||||
/// 返回 0-3:0=不合格 / 1=弱 / 2=中 / 3=强(满足全部要求)
|
||||
int passwordStrengthLevel(String pwd) {
|
||||
if (pwd.length < 12) return 0;
|
||||
var score = 0;
|
||||
if (RegExp('[A-Z]').hasMatch(pwd)) score++;
|
||||
if (RegExp('[a-z]').hasMatch(pwd)) score++;
|
||||
if (RegExp('[0-9]').hasMatch(pwd)) score++;
|
||||
if (RegExp(r'[!@#$%^&*()\-_=+\[\]{};:,.<>?/|\\]').hasMatch(pwd)) score++;
|
||||
return switch (score) {
|
||||
0 || 1 => 1,
|
||||
2 || 3 => 2,
|
||||
_ => 3,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
/// 短信验证码倒计时 mixin — 抽自 phone_add / old_phone_verify 重复模板。
|
||||
///
|
||||
/// 用法(任意 [State] 子类,包含 ConsumerState):
|
||||
/// ```dart
|
||||
/// class _MyPageState extends ConsumerState<MyPage>
|
||||
/// with SmsCountdownMixin<MyPage> {
|
||||
///
|
||||
/// Future<void> _sendCode() async {
|
||||
/// await repo.sendSms(phone);
|
||||
/// startSmsCountdown(); // 默认 60s
|
||||
/// }
|
||||
///
|
||||
/// @override
|
||||
/// Widget build(BuildContext context) {
|
||||
/// final text = isSmsCountingDown ? '${smsCountdown}s 后重发' : '发送验证码';
|
||||
/// ...
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// dispose 时自动 cancel Timer,子类 `super.dispose()` 即可。
|
||||
mixin SmsCountdownMixin<T extends StatefulWidget> on State<T> {
|
||||
int _countdown = 0;
|
||||
Timer? _timer;
|
||||
|
||||
/// 当前剩余秒数(0 = 未运行)
|
||||
int get smsCountdown => _countdown;
|
||||
|
||||
/// 是否正在倒计时
|
||||
bool get isSmsCountingDown => _countdown > 0;
|
||||
|
||||
/// 启动倒计时(重复调用会取消上一个 Timer 重新开始)。
|
||||
void startSmsCountdown({int seconds = 60}) {
|
||||
_timer?.cancel();
|
||||
setState(() => _countdown = seconds);
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (t) {
|
||||
if (!mounted) {
|
||||
t.cancel();
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_countdown -= 1;
|
||||
if (_countdown <= 0) t.cancel();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// 对应 iOS BasicModule/Util/Validator.swift
|
||||
// 所有校验统一在此,禁止在各 Page 内重复定义
|
||||
abstract class Validator {
|
||||
static final _phoneReg = RegExp(r'^1[3-9]\d{9}$');
|
||||
static final _idCardReg = RegExp(r'^\d{17}[\dXx]$');
|
||||
// 密码:≥8位,含大小写字母和数字(iOS 原规则 ≥12 位,按需调整)
|
||||
static final _passwordReg = RegExp(
|
||||
r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d!@#$%^&*]{8,}$',
|
||||
);
|
||||
|
||||
static bool isValidPhone(String phone) => _phoneReg.hasMatch(phone);
|
||||
|
||||
static bool isValidIdCard(String id) => _idCardReg.hasMatch(id);
|
||||
|
||||
static bool isValidPassword(String pwd) => _passwordReg.hasMatch(pwd);
|
||||
|
||||
static bool isValidEmail(String email) => email.contains('@');
|
||||
|
||||
static bool isNotEmpty(String? value) =>
|
||||
value != null && value.trim().isNotEmpty;
|
||||
}
|
||||
Reference in New Issue
Block a user