|
@@ -0,0 +1,359 @@
|
|
|
|
|
+import 'package:flutter/material.dart';
|
|
|
|
|
+import 'package:intl/intl.dart';
|
|
|
|
|
+import 'package:japp_flutter/core/constants/options.dart';
|
|
|
|
|
+import 'package:japp_flutter/features/widgets/app_dropdown.dart';
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class MockPage extends StatelessWidget {
|
|
|
|
|
+ const MockPage({super.key});
|
|
|
|
|
+
|
|
|
|
|
+ @override
|
|
|
|
|
+ Widget build(BuildContext context) {
|
|
|
|
|
+ return MaterialApp(
|
|
|
|
|
+ title: 'Flutter 表单示例',
|
|
|
|
|
+ theme: ThemeData(
|
|
|
|
|
+ primarySwatch: Colors.blue,
|
|
|
|
|
+ ),
|
|
|
|
|
+ home: const MyFormPage(),
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 表单数据模型
|
|
|
|
|
+class FormData {
|
|
|
|
|
+ String name;
|
|
|
|
|
+ String email;
|
|
|
|
|
+ String phone;
|
|
|
|
|
+ String password;
|
|
|
|
|
+ bool agreeTerms;
|
|
|
|
|
+ DateTime? startDate;
|
|
|
|
|
+ DateTime? endDate;
|
|
|
|
|
+ int? difficult;
|
|
|
|
|
+ FormData({
|
|
|
|
|
+ this.name = '',
|
|
|
|
|
+ this.email = 'fin@qq.com',
|
|
|
|
|
+ this.phone = '13770656091',
|
|
|
|
|
+ this.password = '',
|
|
|
|
|
+ this.agreeTerms = false,
|
|
|
|
|
+ this.startDate,
|
|
|
|
|
+ this.endDate,
|
|
|
|
|
+ this.difficult = 2,
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 转换为Map方便打印
|
|
|
|
|
+ Map<String, dynamic> toMap() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ 'name': name,
|
|
|
|
|
+ 'email': email,
|
|
|
|
|
+ 'phone': phone,
|
|
|
|
|
+ 'password': password.replaceAll(RegExp(r'.'), '*'), // 密码用*号代替
|
|
|
|
|
+ 'agreeTerms': agreeTerms,
|
|
|
|
|
+ 'startDate': startDate != null ? DateFormat('yyyy-MM-dd').format(startDate!) : null,
|
|
|
|
|
+ 'endDate': endDate != null ? DateFormat('yyyy-MM-dd').format(endDate!) : null,
|
|
|
|
|
+ 'difficult':difficult
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+// 难度等级模型
|
|
|
|
|
+class DifficultyLevel {
|
|
|
|
|
+ final int value;
|
|
|
|
|
+ final String label;
|
|
|
|
|
+ final IconData? icon;
|
|
|
|
|
+
|
|
|
|
|
+ const DifficultyLevel({
|
|
|
|
|
+ required this.value,
|
|
|
|
|
+ required this.label,
|
|
|
|
|
+ this.icon,
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 预定义的难度等级
|
|
|
|
|
+const List<DifficultyLevel> difficultyLevels = [
|
|
|
|
|
+ DifficultyLevel(value: 1, label: '简单', icon: Icons.sentiment_very_satisfied),
|
|
|
|
|
+ DifficultyLevel(value: 2, label: '中等', icon: Icons.sentiment_satisfied),
|
|
|
|
|
+ DifficultyLevel(value: 3, label: '困难', icon: Icons.sentiment_dissatisfied),
|
|
|
|
|
+ DifficultyLevel(value: 4, label: '专家', icon: Icons.sentiment_very_dissatisfied),
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class MyFormPage extends StatefulWidget {
|
|
|
|
|
+ const MyFormPage({super.key});
|
|
|
|
|
+
|
|
|
|
|
+ @override
|
|
|
|
|
+ State<MyFormPage> createState() => _MyFormPageState();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+class _MyFormPageState extends State<MyFormPage> {
|
|
|
|
|
+ final _formKey = GlobalKey<FormState>();
|
|
|
|
|
+ final FormData _formData = FormData(
|
|
|
|
|
+ startDate: DateTime.now(), // 默认开始日期为今天
|
|
|
|
|
+ endDate: DateTime.now().add(const Duration(days: 7)), // 默认结束日期为7天后
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // 选择开始日期
|
|
|
|
|
+ Future<void> _selectStartDate() async {
|
|
|
|
|
+ final DateTime? picked = await showDatePicker(
|
|
|
|
|
+ context: context,
|
|
|
|
|
+ initialDate: _formData.startDate ?? DateTime.now(),
|
|
|
|
|
+ firstDate: DateTime(2000),
|
|
|
|
|
+ lastDate: DateTime(2100),
|
|
|
|
|
+ );
|
|
|
|
|
+ if (picked != null && picked != _formData.startDate) {
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _formData.startDate = picked;
|
|
|
|
|
+ // 如果结束日期早于开始日期,重置结束日期
|
|
|
|
|
+ if (_formData.endDate != null && _formData.endDate!.isBefore(picked)) {
|
|
|
|
|
+ _formData.endDate = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 选择结束日期
|
|
|
|
|
+ Future<void> _selectEndDate() async {
|
|
|
|
|
+ final DateTime? picked = await showDatePicker(
|
|
|
|
|
+ context: context,
|
|
|
|
|
+ initialDate: _formData.endDate ?? (_formData.startDate ?? DateTime.now()),
|
|
|
|
|
+ firstDate: _formData.startDate ?? DateTime.now(),
|
|
|
|
|
+ lastDate: DateTime(2100),
|
|
|
|
|
+ );
|
|
|
|
|
+ if (picked != null && picked != _formData.endDate) {
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _formData.endDate = picked;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ // 提交表单
|
|
|
|
|
+ void _submitForm() {
|
|
|
|
|
+ if (_formKey.currentState!.validate()) {
|
|
|
|
|
+ _formKey.currentState!.save();
|
|
|
|
|
+
|
|
|
|
|
+ // 获取完整的难度信息(不仅仅是value)
|
|
|
|
|
+ final selectedDifficulty = difficultyLevels.firstWhere(
|
|
|
|
|
+ (level) => level.value == _formData.difficult,
|
|
|
|
|
+ orElse: () => const DifficultyLevel(value: 0, label: '未知'),
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ print('表单提交数据: ${_formData.toMap()}');
|
|
|
|
|
+
|
|
|
|
|
+ // 显示提交成功的提示
|
|
|
|
|
+ ScaffoldMessenger.of(context).showSnackBar(
|
|
|
|
|
+ const SnackBar(content: Text('表单提交成功! 数据已打印到控制台')),
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ @override
|
|
|
|
|
+ Widget build(BuildContext context) {
|
|
|
|
|
+ return Scaffold(
|
|
|
|
|
+ appBar: AppBar(
|
|
|
|
|
+ title: const Text('Flutter 表单示例'),
|
|
|
|
|
+ ),
|
|
|
|
|
+ body: Padding(
|
|
|
|
|
+ padding: const EdgeInsets.all(16.0),
|
|
|
|
|
+ child: Form(
|
|
|
|
|
+ key: _formKey,
|
|
|
|
|
+ child: ListView(
|
|
|
|
|
+ children: [
|
|
|
|
|
+ TextFormField(
|
|
|
|
|
+ decoration: const InputDecoration(
|
|
|
|
|
+ labelText: '姓名',
|
|
|
|
|
+ hintText: '请输入您的姓名',
|
|
|
|
|
+ border: OutlineInputBorder(),
|
|
|
|
|
+ ),
|
|
|
|
|
+ validator: (value) {
|
|
|
|
|
+ if (value == null || value.isEmpty) {
|
|
|
|
|
+ return '请输入姓名';
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ },
|
|
|
|
|
+ onSaved: (value) => _formData.name = value!,
|
|
|
|
|
+ ),
|
|
|
|
|
+ const SizedBox(height: 16),
|
|
|
|
|
+ TextFormField(
|
|
|
|
|
+ initialValue: _formData.email,
|
|
|
|
|
+ decoration: const InputDecoration(
|
|
|
|
|
+ labelText: '电子邮件',
|
|
|
|
|
+ hintText: '请输入您的电子邮件',
|
|
|
|
|
+ border: OutlineInputBorder(),
|
|
|
|
|
+ ),
|
|
|
|
|
+ keyboardType: TextInputType.emailAddress,
|
|
|
|
|
+ validator: (value) {
|
|
|
|
|
+ if (value == null || value.isEmpty) {
|
|
|
|
|
+ return '请输入电子邮件';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
|
|
|
|
|
+ return '请输入有效的电子邮件地址';
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ },
|
|
|
|
|
+ onSaved: (value) => _formData.email = value!,
|
|
|
|
|
+ ),
|
|
|
|
|
+ const SizedBox(height: 16),
|
|
|
|
|
+ TextFormField(
|
|
|
|
|
+ initialValue: _formData.phone,
|
|
|
|
|
+ decoration: const InputDecoration(
|
|
|
|
|
+ labelText: '手机号码',
|
|
|
|
|
+ hintText: '请输入您的手机号码',
|
|
|
|
|
+ border: OutlineInputBorder(),
|
|
|
|
|
+ ),
|
|
|
|
|
+ keyboardType: TextInputType.phone,
|
|
|
|
|
+ validator: (value) {
|
|
|
|
|
+ if (value == null || value.isEmpty) {
|
|
|
|
|
+ return '请输入手机号码';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!RegExp(r'^[0-9]{11}$').hasMatch(value)) {
|
|
|
|
|
+ return '请输入有效的11位手机号码';
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ },
|
|
|
|
|
+ onSaved: (value) => _formData.phone = value!,
|
|
|
|
|
+ ),
|
|
|
|
|
+ const SizedBox(height: 16),
|
|
|
|
|
+ TextFormField(
|
|
|
|
|
+ decoration: const InputDecoration(
|
|
|
|
|
+ labelText: '密码',
|
|
|
|
|
+ hintText: '请输入您的密码',
|
|
|
|
|
+ border: OutlineInputBorder(),
|
|
|
|
|
+ ),
|
|
|
|
|
+ obscureText: true,
|
|
|
|
|
+ validator: (value) {
|
|
|
|
|
+ if (value == null || value.isEmpty) {
|
|
|
|
|
+ return '请输入密码';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (value.length < 6) {
|
|
|
|
|
+ return '密码长度不能少于6位';
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ },
|
|
|
|
|
+ onSaved: (value) => _formData.password = value!,
|
|
|
|
|
+ ),
|
|
|
|
|
+ const SizedBox(height: 16),
|
|
|
|
|
+ // 开始日期选择
|
|
|
|
|
+ InkWell(
|
|
|
|
|
+ onTap: _selectStartDate,
|
|
|
|
|
+ child: InputDecorator(
|
|
|
|
|
+ decoration: const InputDecoration(
|
|
|
|
|
+ labelText: '开始日期',
|
|
|
|
|
+ border: OutlineInputBorder(),
|
|
|
|
|
+ ),
|
|
|
|
|
+ child: Row(
|
|
|
|
|
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
|
+ children: [
|
|
|
|
|
+ Text(
|
|
|
|
|
+ _formData.startDate != null
|
|
|
|
|
+ ? DateFormat('yyyy-MM-dd').format(_formData.startDate!)
|
|
|
|
|
+ : '请选择开始日期',
|
|
|
|
|
+ ),
|
|
|
|
|
+ const Icon(Icons.calendar_today, size: 20),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ const SizedBox(height: 16),
|
|
|
|
|
+ // 结束日期选择
|
|
|
|
|
+ InkWell(
|
|
|
|
|
+ onTap: _formData.startDate == null
|
|
|
|
|
+ ? null
|
|
|
|
|
+ : _selectEndDate,
|
|
|
|
|
+ child: InputDecorator(
|
|
|
|
|
+ decoration: const InputDecoration(
|
|
|
|
|
+ labelText: '结束日期',
|
|
|
|
|
+ border: OutlineInputBorder(),
|
|
|
|
|
+ ),
|
|
|
|
|
+ child: Row(
|
|
|
|
|
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
|
+ children: [
|
|
|
|
|
+ Text(
|
|
|
|
|
+ _formData.endDate != null
|
|
|
|
|
+ ? DateFormat('yyyy-MM-dd').format(_formData.endDate!)
|
|
|
|
|
+ : '请选择结束日期',
|
|
|
|
|
+ ),
|
|
|
|
|
+ const Icon(Icons.calendar_today, size: 20),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ const SizedBox(height: 16),
|
|
|
|
|
+ // 难度选择下拉框
|
|
|
|
|
+ // DropdownButtonFormField<int>(
|
|
|
|
|
+ // decoration: const InputDecoration(
|
|
|
|
|
+ // labelText: '挑战难度',
|
|
|
|
|
+ // border: OutlineInputBorder(),
|
|
|
|
|
+ // ),
|
|
|
|
|
+ // value: _formData.difficult,
|
|
|
|
|
+ // items: difficultyLevels.map((level) {
|
|
|
|
|
+ // return DropdownMenuItem<int>(
|
|
|
|
|
+ // value: level.value,
|
|
|
|
|
+ // child: Row(
|
|
|
|
|
+ // children: [
|
|
|
|
|
+ // if (level.icon != null)
|
|
|
|
|
+ // Icon(level.icon, size: 20),
|
|
|
|
|
+ // const SizedBox(width: 8),
|
|
|
|
|
+ // Text(level.label),
|
|
|
|
|
+ // ],
|
|
|
|
|
+ // ),
|
|
|
|
|
+ // );
|
|
|
|
|
+ // }).toList(),
|
|
|
|
|
+ // onChanged: (value) {
|
|
|
|
|
+ // setState(() {
|
|
|
|
|
+ // _formData.difficult = value;
|
|
|
|
|
+ // });
|
|
|
|
|
+ // },
|
|
|
|
|
+ // validator: (value) {
|
|
|
|
|
+ // if (value == null) {
|
|
|
|
|
+ // return '请选择难度等级';
|
|
|
|
|
+ // }
|
|
|
|
|
+ // return null;
|
|
|
|
|
+ // },
|
|
|
|
|
+ // onSaved: (value) => _formData.difficult = value,
|
|
|
|
|
+ // ),
|
|
|
|
|
+
|
|
|
|
|
+ AppDropdown(
|
|
|
|
|
+ label: '挑战难度',
|
|
|
|
|
+ value: _formData.difficult,
|
|
|
|
|
+ items: challengeDifficultyOpts,
|
|
|
|
|
+ onChanged: (value) {
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _formData.difficult = value;
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ ),
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ const SizedBox(height: 16),
|
|
|
|
|
+
|
|
|
|
|
+ Row(
|
|
|
|
|
+ children: [
|
|
|
|
|
+ Checkbox(
|
|
|
|
|
+ value: _formData.agreeTerms,
|
|
|
|
|
+ onChanged: (value) {
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _formData.agreeTerms = value!;
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ ),
|
|
|
|
|
+ const Text('我同意用户协议'),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ const SizedBox(height: 24),
|
|
|
|
|
+ ElevatedButton(
|
|
|
|
|
+ onPressed: _submitForm,
|
|
|
|
|
+ style: ElevatedButton.styleFrom(
|
|
|
|
|
+ padding: const EdgeInsets.symmetric(vertical: 16),
|
|
|
|
|
+ ),
|
|
|
|
|
+ child: const Text('提交表单'),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+}
|