Bläddra i källkod

增加修改密码功能; 增加弱密码登录后跳转修改密码;

maqiang 6 månader sedan
förälder
incheckning
88e34a04c4

+ 3 - 0
lib/model/rsp/login_rsp.dart

@@ -18,12 +18,15 @@ class LoginRsp {
18 18
   final String? accessToken;
19 19
   final String? refreshToken;
20 20
   final num? expiresTime;
21
+  /// 是否需要修改密码
22
+  final bool? defaultPassword;
21 23
 
22 24
   const LoginRsp({
23 25
     this.userId,
24 26
     this.accessToken,
25 27
     this.refreshToken,
26 28
     this.expiresTime,
29
+    this.defaultPassword,
27 30
   });
28 31
 
29 32
   factory LoginRsp.fromJson(Map<String, dynamic> json) => _$LoginRspFromJson(json);

+ 2 - 0
lib/model/rsp/login_rsp.g.dart

@@ -11,6 +11,7 @@ LoginRsp _$LoginRspFromJson(Map<String, dynamic> json) => LoginRsp(
11 11
       accessToken: const StringConverter().fromJson(json['accessToken']),
12 12
       refreshToken: const StringConverter().fromJson(json['refreshToken']),
13 13
       expiresTime: const NumConverter().fromJson(json['expiresTime']),
14
+      defaultPassword: json['defaultPassword'] as bool?,
14 15
     );
15 16
 
16 17
 Map<String, dynamic> _$LoginRspToJson(LoginRsp instance) => <String, dynamic>{
@@ -18,4 +19,5 @@ Map<String, dynamic> _$LoginRspToJson(LoginRsp instance) => <String, dynamic>{
18 19
       'accessToken': const StringConverter().toJson(instance.accessToken),
19 20
       'refreshToken': const StringConverter().toJson(instance.refreshToken),
20 21
       'expiresTime': const NumConverter().toJson(instance.expiresTime),
22
+      'defaultPassword': instance.defaultPassword,
21 23
     };

+ 6 - 1
lib/network/api.dart

@@ -10,7 +10,6 @@ import 'package:lszlgl/model/rsp/user_rsp.dart';
10 10
 import 'package:lszlgl/network/base_dio.dart';
11 11
 import 'package:retrofit/retrofit.dart';
12 12
 
13
-import '../drfit/database.dart';
14 13
 import '../model/req/device_req.dart';
15 14
 import '../model/rsp/district_rsp.dart';
16 15
 
@@ -31,6 +30,12 @@ abstract class Api {
31 30
   @GET('/admin-api/system/user/profile/get')
32 31
   Future<ApiRsp<UserRsp>> userProfile();
33 32
 
33
+  /// 修改用户个人密码
34
+  /// * [oldPassword] 旧密码
35
+  /// * [newPassword] 新密码
36
+  @PUT('/admin-api/system/user/profile/update-password')
37
+  Future<ApiRsp<bool>> updatePassword(@Body() Map<String, dynamic> map);
38
+
34 39
   /// 获取所有字典
35 40
   @GET('/admin-api/system/dict-data/simple-list')
36 41
   Future<ApiRsp<List<DictRsp>>> getAllDict();

+ 31 - 0
lib/network/api.g.dart

@@ -80,6 +80,37 @@ class _Api implements Api {
80 80
   }
81 81
 
82 82
   @override
83
+  Future<ApiRsp<bool>> updatePassword(Map<String, dynamic> map) async {
84
+    const _extra = <String, dynamic>{};
85
+    final queryParameters = <String, dynamic>{};
86
+    final _headers = <String, dynamic>{};
87
+    final _data = <String, dynamic>{};
88
+    _data.addAll(map);
89
+    final _result = await _dio
90
+        .fetch<Map<String, dynamic>>(_setStreamType<ApiRsp<bool>>(Options(
91
+      method: 'PUT',
92
+      headers: _headers,
93
+      extra: _extra,
94
+    )
95
+            .compose(
96
+              _dio.options,
97
+              '/admin-api/system/user/profile/update-password',
98
+              queryParameters: queryParameters,
99
+              data: _data,
100
+            )
101
+            .copyWith(
102
+                baseUrl: _combineBaseUrls(
103
+              _dio.options.baseUrl,
104
+              baseUrl,
105
+            ))));
106
+    final value = ApiRsp<bool>.fromJson(
107
+      _result.data!,
108
+      (json) => json as bool,
109
+    );
110
+    return value;
111
+  }
112
+
113
+  @override
83 114
   Future<ApiRsp<List<DictRsp>>> getAllDict() async {
84 115
     const _extra = <String, dynamic>{};
85 116
     final queryParameters = <String, dynamic>{};

+ 16 - 1
lib/page/login/login_page.dart

@@ -40,6 +40,11 @@ class _LoginPageState extends BaseLifecycleState<LoginPage> {
40 40
     try {
41 41
       // 登录
42 42
       var login = await MyApi.get().login(LoginReq(username: account, password: pwd));
43
+      if (login.data == null) {
44
+        MyNavigator.showToast('获取数据失败');
45
+        MyNavigator.dismissLoading();
46
+        return;
47
+      }
43 48
       await UserService.get().saveLogin(login.data);
44 49
       getSystemData();
45 50
     } on DioException catch (_) {
@@ -74,7 +79,15 @@ class _LoginPageState extends BaseLifecycleState<LoginPage> {
74 79
 
75 80
   /// 进入主页
76 81
   void startHome() {
77
-    MyRouter.startMain(popAll: true);
82
+    var login = UserService.get().getLogin()!;
83
+    if (login.defaultPassword ?? false) {
84
+      MyNavigator.showToast('密码强度过低,请修改密码');
85
+      // 修改密码
86
+      MyRouter.startChangePwd(startHome: true);
87
+    } else {
88
+      // 进入主页
89
+      MyRouter.startMain(popAll: true);
90
+    }
78 91
   }
79 92
 
80 93
   @override
@@ -94,8 +107,10 @@ class _LoginPageState extends BaseLifecycleState<LoginPage> {
94 107
     LocationUtils.updatePrivacyShow(true, true);
95 108
     LocationUtils.updatePrivacyAgree(true);
96 109
     LocationUtils.setApiKey(kReleaseMode ? '2c783509376e267b24d63b21681686fa' : '7d0c033909f84adc14a0e60a835f044f', '');
110
+
97 111
     /// 获取手机设备信息
98 112
     PrintService.getDeviceInfo();
113
+
99 114
     /// 同步数据库里的蓝牙设备信息到服务器
100 115
     database.savaBleDataToServer();
101 116
 

+ 180 - 64
lib/page/user_center/change_pwd_page.dart

@@ -4,39 +4,127 @@ import 'package:flutter/material.dart';
4 4
 import 'package:flutter/services.dart';
5 5
 import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
6 6
 import 'package:lszlgl/base/base_state.dart';
7
+import 'package:lszlgl/main.dart';
8
+import 'package:lszlgl/network/my_api.dart';
7 9
 import 'package:lszlgl/widget/button.dart';
8
-import 'package:lszlgl/widget/card_item.dart';
9 10
 
10 11
 /// 修改密码
11 12
 class ChangePwdPage extends StatefulWidget {
12
-  const ChangePwdPage({Key? key}) : super(key: key);
13
+  final bool startHome;
14
+
15
+  const ChangePwdPage({
16
+    Key? key,
17
+    bool? startHome,
18
+  })  : startHome = startHome ?? false,
19
+        super(key: key);
13 20
 
14 21
   @override
15 22
   State<ChangePwdPage> createState() => _ChangePwdPageState();
16 23
 }
17 24
 
18 25
 class _ChangePwdPageState extends BaseState<ChangePwdPage> {
19
-  int countDown = 0;
26
+  late TextEditingController oldPwdCtrl;
27
+  late TextEditingController newPwdCtrl;
28
+  late TextEditingController confirmPwdCtrl;
29
+  final ValueNotifier<String?> oldError = null.notifier();
30
+  final ValueNotifier<String?> newError = null.notifier();
31
+  final ValueNotifier<String?> confirmError = null.notifier();
32
+
33
+  void onChange() async {
34
+    List<bool> verifyList = await Future.wait([verifyOldText(), verifyNewText(), verifyConfirmText()]);
35
+    if (!verifyList.firstWhere((element) => !element, orElse: () => true)) {
36
+      return;
37
+    }
38
+    MyNavigator.showLoading(msg: '密码修改中...');
39
+    try {
40
+      var rsp = await MyApi.get().updatePassword({
41
+        'oldPassword': oldPwdCtrl.text,
42
+        'newPassword': newPwdCtrl.text,
43
+      });
44
+      if (rsp.data ?? false) {
45
+        MyNavigator.showToast('修改成功');
46
+        if (widget.startHome) {
47
+          // 进入主页
48
+          MyRouter.startMain(popAll: true);
49
+        } else {
50
+          MyNavigator.pop();
51
+        }
52
+      }
53
+    } catch (e) {
54
+      logger.e(e);
55
+    }
56
+    MyNavigator.dismissLoading();
57
+  }
20 58
 
21
-  Timer? timer;
59
+  Future<bool> verifyOldText() async {
60
+    var text = oldPwdCtrl.text;
61
+    if (text.isEmpty) {
62
+      oldError.value = '请输入旧密码';
63
+      return false;
64
+    }
65
+    int length = text.length;
66
+    if (length < 8) {
67
+      oldError.value = '密码长度要大于8位';
68
+      return false;
69
+    }
70
+    oldError.value = null;
71
+    return true;
72
+  }
73
+
74
+  Future<bool> verifyNewText() async {
75
+    var text = newPwdCtrl.text;
76
+    if (text.isEmpty) {
77
+      newError.value = '请输入新密码';
78
+      return false;
79
+    }
80
+    int length = text.length;
81
+    if (length < 8) {
82
+      newError.value = '密码长度要大于8位';
83
+      return false;
84
+    }
85
+    var regex = RegExp(r'\d');
86
+    if (!regex.hasMatch(text)) {
87
+      newError.value = '密码要包含数字';
88
+      return false;
89
+    }
90
+    regex = RegExp(r'[a-zA-Z]');
91
+    if (!regex.hasMatch(text)) {
92
+      newError.value = '密码要包含大写字母或小写字母';
93
+      return false;
94
+    }
95
+    regex = RegExp(r'[!\"#$%&()*+,-./:;<=>?@\]\[^_`{|}~]');
96
+    if (!regex.hasMatch(text)) {
97
+      newError.value = '密码要包含特殊字符';
98
+      return false;
99
+    }
100
+    newError.value = null;
101
+    return true;
102
+  }
22 103
 
23
-  void startTimer() {
24
-    timer?.cancel();
25
-    setState(() => countDown = 60);
26
-    timer = Timer.periodic(const Duration(seconds: 1), (timer) {
27
-      setState(() => countDown--);
28
-      if (countDown == 0) timer.cancel();
29
-    });
104
+  Future<bool> verifyConfirmText() async {
105
+    var text = confirmPwdCtrl.text;
106
+    if (text.isEmpty) {
107
+      confirmError.value = '请输入确认密码';
108
+      return false;
109
+    }
110
+    if (text != newPwdCtrl.text) {
111
+      confirmError.value = '两次输入密码不一致';
112
+      return false;
113
+    }
114
+    confirmError.value = null;
115
+    return true;
30 116
   }
31 117
 
32
-  void onChange() {
33
-    MyNavigator.showToast('修改成功');
34
-    MyNavigator.pop();
118
+  @override
119
+  void initState() {
120
+    super.initState();
121
+    oldPwdCtrl = TextEditingController();
122
+    newPwdCtrl = TextEditingController();
123
+    confirmPwdCtrl = TextEditingController();
35 124
   }
36 125
 
37 126
   @override
38 127
   void dispose() {
39
-    timer?.cancel();
40 128
     super.dispose();
41 129
   }
42 130
 
@@ -69,70 +157,98 @@ class _ChangePwdPageState extends BaseState<ChangePwdPage> {
69 157
   }
70 158
 
71 159
   Widget buildList() {
160
+    var formatters = [FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9!\"#$%&()*+,-./:;<=>?@\]\[^_`{|}~]'))];
72 161
     return Container(
73 162
       margin: const EdgeInsets.symmetric(horizontal: 12),
163
+      padding: const EdgeInsets.fromLTRB(16, 24, 16, 8),
74 164
       clipBehavior: Clip.hardEdge,
75
-      decoration: const BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(10))),
165
+      decoration: const BoxDecoration(
166
+        borderRadius: BorderRadius.all(Radius.circular(10)),
167
+        color: Colors.white,
168
+      ),
76 169
       child: Column(
77 170
         children: [
78
-          CardItemWidget(
79
-            '短信验证码',
80
-            rightChild: buildSmsCode(),
81
-            bottomLine: true,
82
-          ),
83
-          CardItemWidget(
84
-            '新密码',
85
-            rightChild: buildNewPwd(),
171
+          oldError.builder(
172
+            (error) => buildEdit(
173
+              ctrl: oldPwdCtrl,
174
+              labelText: '旧密码',
175
+              hint: '请输入',
176
+              formatters: formatters,
177
+              inputType: TextInputType.visiblePassword,
178
+              obscureText: true,
179
+              errorText: error,
180
+              onChanged: (value) => verifyOldText(),
181
+            ),
86 182
           ),
87
-        ],
88
-      ),
89
-    );
90
-  }
91
-
92
-  Widget buildSmsCode() {
93
-    return Row(
94
-      children: [
95
-        Expanded(
96
-          child: TextField(
97
-            keyboardType: TextInputType.number,
98
-            textAlign: TextAlign.right,
99
-            decoration: const InputDecoration(
100
-              hintText: '请输入',
101
-              border: InputBorder.none,
102
-              contentPadding: EdgeInsets.zero,
103
-              isDense: true,
104
-              counterText: '',
183
+          const SizedBox(height: 12),
184
+          newError.builder(
185
+            (error) => buildEdit(
186
+              ctrl: newPwdCtrl,
187
+              labelText: '新密码',
188
+              hint: '密码长度8-20位,需包含英文、数字及字符',
189
+              formatters: formatters,
190
+              inputType: TextInputType.visiblePassword,
191
+              obscureText: true,
192
+              maxLength: 20,
193
+              onChanged: (value) => verifyNewText(),
194
+              errorText: error,
105 195
             ),
106
-            style: const TextStyle(fontSize: 14),
107
-            maxLength: 6,
108
-            inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'\d'))],
109 196
           ),
110
-        ),
111
-        const SizedBox(width: 16),
112
-        SizedBox(
113
-          height: 32,
114
-          child: FilledButton(
115
-            onPressed: countDown > 0 ? null : startTimer,
116
-            style: FilledButton.styleFrom(backgroundColor: const Color(0xFF25A6EE)),
117
-            child: Text(countDown > 0 ? '${countDown}s' : '发送'),
197
+          const SizedBox(height: 12),
198
+          confirmError.builder(
199
+            (error) => buildEdit(
200
+              ctrl: confirmPwdCtrl,
201
+              labelText: '确认密码',
202
+              hint: '密码长度8-20位,需包含英文、数字及字符',
203
+              formatters: formatters,
204
+              inputType: TextInputType.visiblePassword,
205
+              obscureText: true,
206
+              maxLength: 20,
207
+              onChanged: (value) => verifyConfirmText(),
208
+              errorText: error,
209
+            ),
118 210
           ),
119
-        ),
120
-      ],
211
+        ],
212
+      ),
121 213
     );
122 214
   }
123 215
 
124
-  Widget buildNewPwd() {
125
-    return const TextField(
126
-      keyboardType: TextInputType.visiblePassword,
127
-      textAlign: TextAlign.right,
216
+  Widget buildEdit({
217
+    required TextEditingController ctrl,
218
+    String? hint,
219
+    String? labelText,
220
+    TextInputAction action = TextInputAction.next,
221
+    bool obscureText = false,
222
+    ValueChanged? onSubmit,
223
+    ValueChanged<String>? onChanged,
224
+    List<TextInputFormatter>? formatters,
225
+    int? maxLength,
226
+    String? errorText,
227
+    TextInputType? inputType,
228
+  }) {
229
+    return TextField(
230
+      controller: ctrl,
128 231
       decoration: InputDecoration(
129
-        hintText: '至少6位字符或者数字',
130
-        border: InputBorder.none,
131
-        contentPadding: EdgeInsets.zero,
232
+        prefixIconConstraints: const BoxConstraints(),
233
+        border: const OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(100))),
234
+        enabledBorder: const OutlineInputBorder(
235
+          borderSide: BorderSide(color: Color(0xFFE6E6E6), width: 1),
236
+          borderRadius: BorderRadius.all(Radius.circular(100)),
237
+        ),
238
+        labelText: labelText,
239
+        hintText: hint,
240
+        hintStyle: const TextStyle(color: Color(0xFFBBBBBB)),
132 241
         isDense: true,
242
+        contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
243
+        errorText: errorText,
133 244
       ),
134
-      style: TextStyle(fontSize: 14),
135
-      inputFormatters: [],
245
+      maxLength: maxLength,
246
+      style: const TextStyle(fontSize: 14),
247
+      textInputAction: action,
248
+      obscureText: obscureText,
249
+      onSubmitted: onSubmit,
250
+      onChanged: onChanged,
251
+      inputFormatters: formatters,
136 252
     );
137 253
   }
138 254
 }

+ 5 - 6
lib/router/my_router.dart

@@ -45,7 +45,7 @@ final Map<String, MyNavigatorBuilder> rRouteMap = {
45 45
   rMainTabPage: (context, args) => const MainTabPage(),
46 46
   rAccountManagePage: (context, args) => const AccountManagePage(),
47 47
   rSettingPage: (context, args) => const SettingPage(),
48
-  rChangePwdPage: (context, args) => const ChangePwdPage(),
48
+  rChangePwdPage: (context, args) => ChangePwdPage(startHome: args as bool?),
49 49
   rSampleTaskListTabPage: (context, args) => SampleTaskListTabPage(args: args as SampleTaskListTabPageArgs?),
50 50
   rReapSampleTaskPage: (context, args) => ReapSampleTaskPage(args: args as ReapSampleTaskPageArgs),
51 51
   rStockSampleTaskPage: (context, args) => StockSampleTaskPage(args: args as StockSampleTaskPageArgs),
@@ -87,8 +87,8 @@ class MyRouter {
87 87
   }
88 88
 
89 89
   /// 修改密码
90
-  static void startChangePwd() {
91
-    MyNavigator.push(rChangePwdPage);
90
+  static void startChangePwd({bool? startHome}) {
91
+    MyNavigator.push(rChangePwdPage, args: startHome);
92 92
   }
93 93
 
94 94
   /// 收获环节列表
@@ -123,10 +123,9 @@ class MyRouter {
123 123
 
124 124
   /// 打印任务
125 125
   static Future<dynamic> startConnectPrint({ConnectPrintPageArgs? args}) {
126
-    return MyNavigator.push(
127
-        rConnectPrintPage, args: args ?? ConnectPrintPageArgs());
126
+    return MyNavigator.push(rConnectPrintPage, args: args ?? ConnectPrintPageArgs());
128 127
   }
129
-  
128
+
130 129
   /// 扫一扫
131 130
   static Future<dynamic> startQrCodeScan() {
132 131
     return MyNavigator.push(rQrCodeScanPage);

+ 6 - 0
lib/widget/card_item.dart

@@ -294,7 +294,10 @@ class CardWidgets {
294 294
     List<TextInputFormatter>? formatters,
295 295
     Color? backgroundColor = Colors.white,
296 296
     bool bottomLine = true,
297
+    bool obscureText = false,
297 298
     TextEditingController? ctrl,
299
+    int? maxLength,
300
+    String? errorText,
298 301
   }) {
299 302
     if (isDetail) {
300 303
       return CardItemWidget(
@@ -317,11 +320,14 @@ class CardWidgets {
317 320
           border: InputBorder.none,
318 321
           contentPadding: EdgeInsets.zero,
319 322
           isDense: true,
323
+          errorText: errorText,
320 324
         ),
321 325
         style: const TextStyle(color: Color(0xFF01B2C8), fontSize: 14, fontWeight: FontWeight.w500),
322 326
         textInputAction: TextInputAction.next,
323 327
         inputFormatters: formatters,
324 328
         onChanged: onChanged,
329
+        obscureText: obscureText,
330
+        maxLength: maxLength,
325 331
       ),
326 332
       backgroundColor: backgroundColor,
327 333
       bottomLine: bottomLine,