初始化

This commit is contained in:
zhutao
2025-11-19 17:56:39 +08:00
commit 1b28239352
115 changed files with 5440 additions and 0 deletions

View File

@@ -0,0 +1,154 @@
import 'dart:async';
import 'package:app/router/route_paths.dart';
import 'package:app/widgets/base/button/index.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:go_router/go_router.dart';
import 'package:remixicon/remixicon.dart';
import 'widgets/login_agree.dart';
import 'widgets/login_input.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
///协议
bool _agree = false;
///输入框
final TextEditingController _telController = TextEditingController();
final TextEditingController _codeController = TextEditingController();
///验证码倒计时
var _countDown = 0;
Timer? _timer;
@override
void initState() {
super.initState();
}
///点击发送验证码
void _sendCode() async {
RegExp regExp = RegExp(r'^1\d{10}$');
if (!regExp.hasMatch(_telController.text)) {
EasyLoading.showToast("请填写正确的手机号");
return;
}
setState(() {
_countDown = 60;
});
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
setState(() {
_countDown--;
});
if (_countDown <= 0) {
_timer?.cancel();
_timer = null;
}
});
}
///提交登录
void _handSubmit() async {
RegExp regExp = RegExp(r'^1\d{10}$');
if (!regExp.hasMatch(_telController.text) || _codeController.text.isEmpty) {
EasyLoading.showToast("请填写完整手机号或验证码");
return;
}
context.go(RoutePaths.sHome);
}
@override
void dispose() {
super.dispose();
_timer?.cancel();
_timer = null;
}
@override
Widget build(BuildContext context) {
//是否已发送验证码
bool codeOk = _countDown == 0;
return Scaffold(
resizeToAvoidBottomInset: false,
body: SafeArea(
child: Container(
padding: EdgeInsets.only(left: 20, right: 20, top: 0.08.sh, bottom: 40),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: EdgeInsets.only(bottom: 40),
child: Text("登陆学光自习室", style: Theme.of(context).textTheme.titleLarge),
),
LoginInput(
hintText: "请输入手机号",
controller: _telController,
suffix: Visibility(
visible: _telController.text.isNotEmpty,
child: InkWell(
onTap: () {
_telController.clear();
},
child: Icon(RemixIcons.close_circle_fill, size: 20),
),
),
),
Container(
margin: EdgeInsets.only(top: 20),
child: Row(
spacing: 20,
children: [
Expanded(
child: LoginInput(
hintText: "输入验证码",
controller: _codeController,
maxLength: 4,
),
),
SizedBox(
height: 45,
child: Button(
text: codeOk ? "发送验证码" : "$_countDown 秒后发送",
radius: BorderRadius.circular(10),
disabled: !codeOk,
onPressed: _sendCode,
),
),
],
),
),
Container(
margin: EdgeInsets.only(top: 40),
height: 50,
child: Button(text: "登 录", onPressed: _handSubmit),
),
Container(
width: double.infinity,
margin: EdgeInsets.only(top: 20),
alignment: Alignment.center,
child: LoginAgree(
value: _agree,
onChanged: (value) {
setState(() {
_agree = value!;
});
},
),
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,71 @@
import 'package:app/router/route_paths.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class LoginAgree extends StatelessWidget {
final ValueChanged<bool?>? onChanged;
final bool value;
const LoginAgree({
super.key,
this.onChanged,
required this.value,
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 25,
child: Transform.scale(
scale: 0.8,
child: Checkbox(
value: value,
shape: CircleBorder(),
onChanged: onChanged,
),
),
),
RichText(
text: TextSpan(
style: Theme.of(context).textTheme.labelMedium,
children: [
TextSpan(
text: "注册或登录即表示您了解并同意",
),
TextSpan(
text: "服务条款",
style: TextStyle(color: Theme.of(context).primaryColor),
recognizer: TapGestureRecognizer()
..onTap = () => context.push(
RoutePaths.agreement,
extra: {
"title": "Terms of Service",
"url": "https://support.curain.ai/privacy/foodcura/terms_service.html",
},
),
),
TextSpan(text: ""),
TextSpan(
text: "隐私协议",
style: TextStyle(color: Theme.of(context).primaryColor),
recognizer: TapGestureRecognizer()
..onTap = () => context.push(
RoutePaths.agreement,
extra: {
"title": "Privacy",
"url": "https://support.curain.ai/privacy/foodcura/privacy_policy.html",
},
),
),
],
),
),
],
);
}
}

View File

@@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class LoginInput extends StatelessWidget {
final String hintText;
final int maxLength;
final TextEditingController controller;
final Widget? suffix;
const LoginInput({
super.key,
required this.hintText,
this.maxLength = 11,
required this.controller,
this.suffix,
});
@override
Widget build(BuildContext context) {
return TextField(
controller: controller,
maxLength: maxLength,
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly, // 只允许 0-9
],
decoration: InputDecoration(
hintText: hintText,
hintStyle: Theme.of(context).textTheme.labelLarge?.copyWith(fontSize: 16),
counterText: '',
filled: true,
fillColor: Theme.of(context).colorScheme.surfaceContainer,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(60),
borderSide: BorderSide.none, // 去掉边框
),
isCollapsed: true,
contentPadding: EdgeInsets.symmetric(vertical: 14, horizontal: 20),
suffixIconConstraints: BoxConstraints(minWidth: 0, minHeight: 0),
suffixIcon: suffix != null
? Container(
padding: EdgeInsets.only(right: 20),
child: suffix,
)
: null,
),
);
}
}

View File

@@ -0,0 +1,30 @@
import 'package:app/router/route_paths.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class SplashPage extends StatefulWidget {
const SplashPage({super.key});
@override
State<SplashPage> createState() => _SplashPageState();
}
class _SplashPageState extends State<SplashPage> {
@override
void initState() {
super.initState();
initPermission();
}
///权限效验初始化
void initPermission() async {
WidgetsBinding.instance.addPostFrameCallback((_) async {
context.go(RoutePaths.login);
});
}
@override
Widget build(BuildContext context) {
return Scaffold();
}
}