Browse Source

Add 增加配置和字典查询,字典label组件

Yue 2 weeks ago
parent
commit
03d2353cbb

+ 28 - 0
UI/CF.APP/chicken_farm/lib/apis/system/_config.dart

@@ -0,0 +1,28 @@
+import 'package:chicken_farm/core/api/api_service.dart';
+import 'package:chicken_farm/modes/system/config.dart';
+
+class ConfigApi {
+  static final ConfigApi _instance = ConfigApi._internal();
+
+  factory ConfigApi() => _instance;
+
+  ConfigApi._internal();
+
+  Future<dynamic> getConfigs() async {
+    return await ApiService().get(
+      '/system/config/list',
+      queryParameters: {'pageSize': 10000},
+    );
+  }
+
+  Future<ConfigModel?> getConfigKey(String configKey) async {
+    final response = await ApiService().get(
+      '/system/config/configKey/$configKey',
+    );
+    return response != null ? ConfigModel.fromJson(response) : null;
+  }
+
+  Future<dynamic> refreshCache() async {
+    return await ApiService().delete('/system/config/refreshCache');
+  }
+}

+ 90 - 0
UI/CF.APP/chicken_farm/lib/apis/system/_dict.dart

@@ -0,0 +1,90 @@
+import 'package:chicken_farm/core/api/api_service.dart';
+import 'package:chicken_farm/modes/system/dict.dart';
+
+class DictApi {
+  static final DictApi _instance = DictApi._internal();
+
+  factory DictApi() => _instance;
+
+  DictApi._internal();
+
+  Future<dynamic> getTypes() async {
+    return await ApiService().get(
+      '/system/dict/type/list',
+      queryParameters: {'pageSize': 10000},
+    );
+  }
+
+  Future<dynamic> getType(String id) async {
+    return await ApiService().get('/system/dict/type/$id');
+  }
+
+  // 修改此方法以正确处理返回的字典数据列表
+  Future<List<DictDataModel>?> getDicts(String type) async {
+    try {
+      final response = await ApiService().get('/system/dict/data/type/$type');
+      if (response != null) {
+        // 根据RuoYi的标准API格式处理响应
+        if (response is List) {
+          // 直接是列表的情况
+          return response
+              .map(
+                (item) => item is Map<String, dynamic>
+                    ? DictDataModel.fromJson(item)
+                    : DictDataModel(
+                        dictCode: 0,
+                        dictSort: 0,
+                        dictLabel: '',
+                        dictValue: '',
+                        dictType: type,
+                      ),
+              )
+              .toList();
+        } else if (response is Map<String, dynamic>) {
+          // 包含在data字段中的情况
+          if (response['data'] is List) {
+            return (response['data'] as List)
+                .map(
+                  (item) => item is Map<String, dynamic>
+                      ? DictDataModel.fromJson(item)
+                      : DictDataModel(
+                          dictCode: 0,
+                          dictSort: 0,
+                          dictLabel: '',
+                          dictValue: '',
+                          dictType: type,
+                        ),
+                )
+                .toList();
+          } else if (response['rows'] is List) {
+            // 包含在rows字段中的情况
+            return (response['rows'] as List)
+                .map(
+                  (item) => item is Map<String, dynamic>
+                      ? DictDataModel.fromJson(item)
+                      : DictDataModel(
+                          dictCode: 0,
+                          dictSort: 0,
+                          dictLabel: '',
+                          dictValue: '',
+                          dictType: type,
+                        ),
+                )
+                .toList();
+          } else {
+            // 单个对象的情况
+            return [DictDataModel.fromJson(response)];
+          }
+        }
+      }
+      return null;
+    } catch (e) {
+      // 出现异常时返回null
+      return null;
+    }
+  }
+
+  Future<dynamic> refreshCache() async {
+    return await ApiService().delete('/system/dict/type/refreshCache');
+  }
+}

+ 5 - 1
UI/CF.APP/chicken_farm/lib/apis/system/index.dart

@@ -1,4 +1,6 @@
 import '_user.dart';
+import '_config.dart';
+import '_dict.dart';
 
 class SystemApis {
   static final SystemApis _instance = SystemApis._internal();
@@ -8,4 +10,6 @@ class SystemApis {
   SystemApis._internal();
 
   late final UserApi userApi = UserApi();
-}
+  late final ConfigApi configApi = ConfigApi();
+  late final DictApi dictApi = DictApi();
+}

+ 102 - 0
UI/CF.APP/chicken_farm/lib/components/dict_label.dart

@@ -0,0 +1,102 @@
+import 'package:flutter/material.dart';
+import 'package:chicken_farm/stores/dict_stroe.dart';
+import 'package:chicken_farm/modes/system/dict.dart';
+
+class DictLabel extends StatelessWidget {
+  final String dictType;
+  final String value;
+  
+  const DictLabel({
+    Key? key,
+    required this.dictType,
+    required this.value,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return FutureBuilder<DictDataModel?>(
+      future: DictStore().getDictByTypeAndValue(dictType, value),
+      builder: (context, snapshot) {
+        if (snapshot.connectionState == ConnectionState.waiting) {
+          return const SizedBox(
+            width: 16,
+            height: 16,
+            child: CircularProgressIndicator(strokeWidth: 2),
+          );
+        }
+        
+        if (snapshot.hasData && snapshot.data != null) {
+          DictDataModel dict = snapshot.data!;
+          Color backgroundColor = _getColorByListClass(dict.listClass);
+          Color textColor = _getTextColorByListClass(dict.listClass);
+          
+          return Container(
+            padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
+            decoration: BoxDecoration(
+              color: backgroundColor,
+              borderRadius: BorderRadius.circular(4),
+            ),
+            child: Text(
+              dict.dictLabel,
+              style: TextStyle(
+                color: textColor,
+                fontSize: 12,
+                fontWeight: FontWeight.w400,
+              ),
+            ),
+          );
+        }
+        
+        return Container(
+          padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
+          decoration: BoxDecoration(
+            color: Colors.grey[200],
+            borderRadius: BorderRadius.circular(4),
+          ),
+          child: Text(
+            value,
+            style: const TextStyle(
+              color: Colors.grey,
+              fontSize: 12,
+              fontWeight: FontWeight.w400,
+            ),
+          ),
+        );
+      },
+    );
+  }
+  
+  Color _getColorByListClass(String? listClass) {
+    switch (listClass) {
+      case 'primary':
+        return Colors.blue.withOpacity(0.1);
+      case 'success':
+        return Colors.green.withOpacity(0.1);
+      case 'danger':
+        return Colors.red.withOpacity(0.1);
+      case 'warning':
+        return Colors.orange.withOpacity(0.1);
+      case 'info':
+        return Colors.cyan.withOpacity(0.1);
+      default:
+        return Colors.grey.withOpacity(0.1);
+    }
+  }
+  
+  Color _getTextColorByListClass(String? listClass) {
+    switch (listClass) {
+      case 'primary':
+        return Colors.blue;
+      case 'success':
+        return Colors.green;
+      case 'danger':
+        return Colors.red;
+      case 'warning':
+        return Colors.orange;
+      case 'info':
+        return Colors.cyan;
+      default:
+        return Colors.grey;
+    }
+  }
+}

+ 3 - 0
UI/CF.APP/chicken_farm/lib/core/config/config_key.dart

@@ -0,0 +1,3 @@
+class ConfigKey {
+  static const String SysRepairUserIds = "sys.repair.userIds";
+}

+ 0 - 1
UI/CF.APP/chicken_farm/lib/core/config/env_config.dart

@@ -1 +0,0 @@
-  

+ 12 - 1
UI/CF.APP/chicken_farm/lib/core/utils/storage.dart

@@ -41,8 +41,19 @@ class StorageUtils {
     await prefs.remove(key);
   }
 
+  // 添加根据前缀删除多个键的方法
+  static Future<void> removeWithPrefix(String prefix) async {
+    final prefs = await _getInstance();
+    final keys = prefs.getKeys();
+    for (final key in keys) {
+      if (key.startsWith(prefix)) {
+        await prefs.remove(key);
+      }
+    }
+  }
+
   static Future<void> clear() async {
     final prefs = await _getInstance();
     await prefs.clear();
   }
-}
+}

+ 115 - 82
UI/CF.APP/chicken_farm/lib/core/utils/toast.dart

@@ -29,6 +29,7 @@ class ToastUtil {
 
   static Future<void> errorAlert(
     String message, {
+    String? title,
     String confirmText = '确定',
     BuildContext? context,
   }) async {
@@ -42,15 +43,24 @@ class ToastUtil {
       context: dialogContext,
       builder: (BuildContext context) {
         return AlertDialog(
-          title: const Text(
-            '错误',
-            style: TextStyle(
-              fontSize: 20,
-              fontWeight: FontWeight.bold,
-              color: Colors.red,
-            ),
+          title: Row(
+            children: [
+              Icon(Icons.error_outline, color: Colors.red, size: 24),
+              SizedBox(width: 10),
+              Text(
+                title ?? '错误',
+                style: TextStyle(
+                  fontSize: 20,
+                  fontWeight: FontWeight.bold,
+                  color: Colors.red,
+                ),
+              ),
+            ],
+          ),
+          content: ConstrainedBox(
+            constraints: BoxConstraints(minWidth: 350),
+            child: Text(message, style: const TextStyle(fontSize: 16)),
           ),
-          content: Text(message, style: const TextStyle(fontSize: 16)),
           backgroundColor: Colors.white,
           shape: RoundedRectangleBorder(
             borderRadius: BorderRadius.circular(15),
@@ -58,28 +68,32 @@ class ToastUtil {
           elevation: 24,
           actions: <Widget>[
             Container(
+              width: double.infinity,
               padding: const EdgeInsets.only(right: 0, bottom: 0),
               alignment: Alignment.centerRight,
-              child: ElevatedButton(
-                style: ElevatedButton.styleFrom(
-                  backgroundColor: Colors.red,
-                  shape: RoundedRectangleBorder(
-                    borderRadius: BorderRadius.circular(8),
-                  ),
-                  padding: const EdgeInsets.symmetric(
-                    horizontal: 24,
-                    vertical: 12,
+              child: SizedBox(
+                width: 120,
+                child: ElevatedButton(
+                  style: ElevatedButton.styleFrom(
+                    backgroundColor: Colors.red,
+                    shape: RoundedRectangleBorder(
+                      borderRadius: BorderRadius.circular(8),
+                    ),
+                    padding: const EdgeInsets.symmetric(
+                      horizontal: 16,
+                      vertical: 10,
+                    ),
                   ),
-                ),
-                onPressed: () {
-                  Navigator.of(context).pop(true); // 返回true表示确认
-                },
-                child: Text(
-                  confirmText,
-                  style: const TextStyle(
-                    fontSize: 16,
-                    fontWeight: FontWeight.w500,
-                    color: Colors.white,
+                  onPressed: () {
+                    Navigator.of(context).pop(true); // 返回true表示确认
+                  },
+                  child: Text(
+                    confirmText,
+                    style: const TextStyle(
+                      fontSize: 16,
+                      fontWeight: FontWeight.w500,
+                      color: Colors.white,
+                    ),
                   ),
                 ),
               ),
@@ -93,6 +107,7 @@ class ToastUtil {
   static Future<void> confirm(
     String message,
     VoidCallback onConfirm, {
+    String? title,
     String confirmText = '确定',
     String cancelText = '取消',
     BuildContext? context,
@@ -106,15 +121,24 @@ class ToastUtil {
       context: dialogContext,
       builder: (BuildContext context) {
         return AlertDialog(
-          title: const Text(
-            '确认',
-            style: TextStyle(
-              fontSize: 20,
-              fontWeight: FontWeight.bold,
-              color: Colors.blue,
-            ),
+          title: Row(
+            children: [
+              const Icon(Icons.help_outline, color: Colors.blue, size: 24),
+              const SizedBox(width: 10),
+              Text(
+                title ?? '提示',
+                style: const TextStyle(
+                  fontSize: 20,
+                  fontWeight: FontWeight.bold,
+                  color: Colors.blue,
+                ),
+              ),
+            ],
+          ),
+          content: ConstrainedBox(
+            constraints: BoxConstraints(minWidth: 350),
+            child: Text(message, style: const TextStyle(fontSize: 16)),
           ),
-          content: Text(message, style: const TextStyle(fontSize: 16)),
           backgroundColor: Colors.white,
           shape: RoundedRectangleBorder(
             borderRadius: BorderRadius.circular(15),
@@ -122,59 +146,68 @@ class ToastUtil {
           elevation: 24,
           actions: <Widget>[
             Container(
+              width: double.infinity,
               padding: const EdgeInsets.only(right: 0, bottom: 0),
               alignment: Alignment.centerRight,
-              child: Row(
-                mainAxisSize: MainAxisSize.min,
-                children: [
-                  OutlinedButton(
-                    style: OutlinedButton.styleFrom(
-                      side: const BorderSide(color: Colors.grey),
-                      shape: RoundedRectangleBorder(
-                        borderRadius: BorderRadius.circular(8),
-                      ),
-                      padding: const EdgeInsets.symmetric(
-                        horizontal: 20,
-                        vertical: 12,
+              child: SizedBox(
+                width: 200,
+                child: Row(
+                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+                  children: [
+                    SizedBox(
+                      width: 80,
+                      child: OutlinedButton(
+                        style: OutlinedButton.styleFrom(
+                          side: const BorderSide(color: Colors.grey),
+                          shape: RoundedRectangleBorder(
+                            borderRadius: BorderRadius.circular(8),
+                          ),
+                          padding: const EdgeInsets.symmetric(
+                            horizontal: 12,
+                            vertical: 8,
+                          ),
+                        ),
+                        onPressed: () {
+                          Navigator.of(context).pop(false); // 取消,关闭确认框
+                        },
+                        child: Text(
+                          cancelText,
+                          style: const TextStyle(
+                            fontSize: 16,
+                            fontWeight: FontWeight.w500,
+                            color: Colors.grey,
+                          ),
+                        ),
                       ),
                     ),
-                    onPressed: () {
-                      Navigator.of(context).pop(false); // 取消,关闭确认框
-                    },
-                    child: Text(
-                      cancelText,
-                      style: const TextStyle(
-                        fontSize: 16,
-                        fontWeight: FontWeight.w500,
-                        color: Colors.grey,
+                    SizedBox(
+                      width: 80,
+                      child: ElevatedButton(
+                        style: ElevatedButton.styleFrom(
+                          backgroundColor: Colors.blue,
+                          shape: RoundedRectangleBorder(
+                            borderRadius: BorderRadius.circular(8),
+                          ),
+                          padding: const EdgeInsets.symmetric(
+                            horizontal: 12,
+                            vertical: 8,
+                          ),
+                        ),
+                        onPressed: () {
+                          Navigator.of(context).pop(true); // 确定,关闭确认框
+                        },
+                        child: Text(
+                          confirmText,
+                          style: const TextStyle(
+                            fontSize: 16,
+                            fontWeight: FontWeight.w500,
+                            color: Colors.white,
+                          ),
+                        ),
                       ),
                     ),
-                  ),
-                  const SizedBox(width: 12),
-                  ElevatedButton(
-                    style: ElevatedButton.styleFrom(
-                      backgroundColor: Colors.blue,
-                      shape: RoundedRectangleBorder(
-                        borderRadius: BorderRadius.circular(8),
-                      ),
-                      padding: const EdgeInsets.symmetric(
-                        horizontal: 20,
-                        vertical: 12,
-                      ),
-                    ),
-                    onPressed: () {
-                      Navigator.of(context).pop(true); // 确定,关闭确认框
-                    },
-                    child: Text(
-                      confirmText,
-                      style: const TextStyle(
-                        fontSize: 16,
-                        fontWeight: FontWeight.w500,
-                        color: Colors.white,
-                      ),
-                    ),
-                  ),
-                ],
+                  ],
+                ),
               ),
             ),
           ],

+ 25 - 0
UI/CF.APP/chicken_farm/lib/modes/system/config.dart

@@ -0,0 +1,25 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'config.g.dart';
+
+@JsonSerializable()
+class ConfigModel {
+  int configId;
+  String configName;
+  String configKey;
+  String configValue;
+  String? configType;
+
+  ConfigModel({
+    required this.configId,
+    required this.configName,
+    required this.configKey,
+    required this.configValue,
+    this.configType,
+  });
+
+  factory ConfigModel.fromJson(Map<String, dynamic> json) =>
+      _$ConfigModelFromJson(json);
+
+  Map<String, dynamic> toJson() => _$ConfigModelToJson(this);
+}

+ 24 - 0
UI/CF.APP/chicken_farm/lib/modes/system/config.g.dart

@@ -0,0 +1,24 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'config.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+ConfigModel _$ConfigModelFromJson(Map<String, dynamic> json) => ConfigModel(
+  configId: (json['configId'] as num).toInt(),
+  configName: json['configName'] as String,
+  configKey: json['configKey'] as String,
+  configValue: json['configValue'] as String,
+  configType: json['configType'] as String?,
+);
+
+Map<String, dynamic> _$ConfigModelToJson(ConfigModel instance) =>
+    <String, dynamic>{
+      'configId': instance.configId,
+      'configName': instance.configName,
+      'configKey': instance.configKey,
+      'configValue': instance.configValue,
+      'configType': instance.configType,
+    };

+ 29 - 0
UI/CF.APP/chicken_farm/lib/modes/system/dict.dart

@@ -0,0 +1,29 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'dict.g.dart';
+
+@JsonSerializable()
+class DictDataModel {
+  int dictCode;
+  int dictSort;
+  String dictLabel;
+  String dictValue;
+  String dictType;
+  String? cssClass;
+  String? listClass;
+
+  DictDataModel({
+    required this.dictCode,
+    required this.dictSort,
+    required this.dictLabel,
+    required this.dictValue,
+    required this.dictType,
+    this.cssClass,
+    this.listClass,
+  });
+
+  factory DictDataModel.fromJson(Map<String, dynamic> json) =>
+      _$DictDataModelFromJson(json);
+
+  Map<String, dynamic> toJson() => _$DictDataModelToJson(this);
+}

+ 29 - 0
UI/CF.APP/chicken_farm/lib/modes/system/dict.g.dart

@@ -0,0 +1,29 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'dict.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+DictDataModel _$DictDataModelFromJson(Map<String, dynamic> json) =>
+    DictDataModel(
+      dictCode: (json['dictCode'] as num).toInt(),
+      dictSort: (json['dictSort'] as num).toInt(),
+      dictLabel: json['dictLabel'] as String,
+      dictValue: json['dictValue'] as String,
+      dictType: json['dictType'] as String,
+      cssClass: json['cssClass'] as String?,
+      listClass: json['listClass'] as String?,
+    );
+
+Map<String, dynamic> _$DictDataModelToJson(DictDataModel instance) =>
+    <String, dynamic>{
+      'dictCode': instance.dictCode,
+      'dictSort': instance.dictSort,
+      'dictLabel': instance.dictLabel,
+      'dictValue': instance.dictValue,
+      'dictType': instance.dictType,
+      'cssClass': instance.cssClass,
+      'listClass': instance.listClass,
+    };

+ 1 - 1
UI/CF.APP/chicken_farm/lib/pages/checkin/checkin_page.dart

@@ -47,7 +47,7 @@ class _CheckinPageState extends State<CheckinPage> {
       body: QRScannerComponent(
         startWithString: _qrCodePrefix,
         onScanCallback: _performCheckin,
-        invalidQRMessage: '非法二维码',
+        invalidQRMessage: '签到二维码无效!',
         qrCodeParser: _parseCheckinQRCode,
       ),
     );

+ 83 - 34
UI/CF.APP/chicken_farm/lib/pages/home/profile.dart

@@ -1,4 +1,8 @@
+import 'package:chicken_farm/core/utils/storage.dart';
+import 'package:chicken_farm/core/utils/toast.dart';
 import 'package:chicken_farm/stores/auth_store.dart';
+import 'package:chicken_farm/stores/config_store.dart';
+import 'package:chicken_farm/stores/dict_stroe.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 
@@ -19,57 +23,102 @@ class ProfilePage extends ConsumerWidget {
             const SizedBox(height: 20),
             if (authState.state == AuthState.authenticated &&
                 authState.user != null) ...[
-              Card(
-                child: Padding(
-                  padding: const EdgeInsets.all(16.0),
+              Expanded(
+                child: SingleChildScrollView(
                   child: Column(
                     crossAxisAlignment: CrossAxisAlignment.start,
                     children: [
-                      ListTile(
-                        leading: const CircleAvatar(
-                          radius: 30,
-                          child: Icon(Icons.person, size: 30),
-                        ),
-                        title: Text(
-                          authState.user!.nickName ?? '未设置昵称',
-                          style: const TextStyle(
-                            fontSize: 20,
-                            fontWeight: FontWeight.bold,
+                      Card(
+                        child: Padding(
+                          padding: const EdgeInsets.all(16.0),
+                          child: Column(
+                            crossAxisAlignment: CrossAxisAlignment.start,
+                            children: [
+                              ListTile(
+                                leading: const CircleAvatar(
+                                  radius: 30,
+                                  child: Icon(Icons.person, size: 30),
+                                ),
+                                title: Text(
+                                  authState.user!.nickName ?? '未设置昵称',
+                                  style: const TextStyle(
+                                    fontSize: 20,
+                                    fontWeight: FontWeight.bold,
+                                  ),
+                                ),
+                                subtitle: Text(
+                                  '用户名: ${authState.user!.userName}',
+                                ),
+                              ),
+                              const Divider(),
+                              const Text(
+                                '基本信息',
+                                style: TextStyle(fontWeight: FontWeight.bold),
+                              ),
+                              const SizedBox(height: 10),
+                              _buildInfoRow(
+                                '手机号码',
+                                authState.user!.phonenumber ?? '未填写',
+                              ),
+                              _buildInfoRow(
+                                '邮箱地址',
+                                authState.user!.email ?? '未填写',
+                              ),
+                              _buildInfoRow(
+                                '部门',
+                                authState.user!.orgName ?? '未分配',
+                              ),
+                              _buildInfoRow(
+                                '角色',
+                                (authState.user!.roles
+                                        ?.map((r) => r.roleName)
+                                        .join(', ')) ??
+                                    '未分配',
+                              ),
+                            ],
                           ),
                         ),
-                        subtitle: Text('用户名: ${authState.user!.userName}'),
-                      ),
-                      const Divider(),
-                      const Text(
-                        '基本信息',
-                        style: TextStyle(fontWeight: FontWeight.bold),
-                      ),
-                      const SizedBox(height: 10),
-                      _buildInfoRow(
-                        '手机号码',
-                        authState.user!.phonenumber ?? '未填写',
                       ),
-                      _buildInfoRow('邮箱地址', authState.user!.email ?? '未填写'),
-                      _buildInfoRow('部门', authState.user!.orgName ?? '未分配'),
-                      _buildInfoRow(
-                        '角色',
-                        (authState.user!.roles
-                                ?.map((r) => r.roleName)
-                                .join(', ')) ??
-                            '未分配',
+                      const SizedBox(height: 20),
+                      SizedBox(
+                        width: double.infinity,
+                        child: ElevatedButton.icon(
+                          onPressed: () {
+                            ToastUtil.confirm('确定要清除所有缓存吗?', () async {
+                              await StorageUtils.removeWithPrefix("vb_");
+                              ConfigStore().clearAll();
+                              DictStore().clearAll();
+                              if (context.mounted) {
+                                ToastUtil.success('缓存已清除');
+                              }
+                            });
+                          },
+                          icon: const Icon(Icons.cleaning_services),
+                          label: const Text('清除缓存'),
+                          style: ElevatedButton.styleFrom(
+                            padding: const EdgeInsets.symmetric(
+                              horizontal: 30,
+                              vertical: 15,
+                            ),
+                            textStyle: const TextStyle(fontSize: 16),
+                          ),
+                        ),
                       ),
                     ],
                   ),
                 ),
               ),
-              const SizedBox(height: 20),
               SizedBox(
                 width: double.infinity,
                 child: ElevatedButton.icon(
-                  onPressed: () => authStore.logout(),
+                  onPressed: () {
+                    ToastUtil.confirm('确定要退出登录吗?', () => authStore.logout());
+                  },
                   icon: const Icon(Icons.logout),
                   label: const Text('退出登录'),
                   style: ElevatedButton.styleFrom(
+                    // 保持背景色不变,只改变文字颜色为红色
+                    foregroundColor: Colors.red,
                     padding: const EdgeInsets.symmetric(
                       horizontal: 30,
                       vertical: 15,

+ 4 - 7
UI/CF.APP/chicken_farm/lib/pages/sample/sample_detail_page.dart

@@ -1,3 +1,4 @@
+import 'package:chicken_farm/components/dict_label.dart';
 import 'package:flutter/material.dart';
 import 'package:chicken_farm/components/vb_app_bar.dart';
 import 'package:chicken_farm/modes/experiment/sample/sample.dart';
@@ -101,13 +102,9 @@ class _SampleDetailPageState extends State<SampleDetailPage> {
                     _sample!.sampleName,
                     style: Theme.of(context).textTheme.titleLarge,
                   ),
-                  Chip(
-                    label: Text(
-                      _getSampleStatusText(_sample!.sampleStatus),
-                      style: TextStyle(
-                        color: _getSampleStatusColor(_sample!.sampleStatus),
-                      ),
-                    ),
+                  DictLabel(
+                    dictType: "experiment_sample_status",
+                    value: _sample!.sampleStatus.toString(),
                   ),
                 ],
               ),

+ 1 - 1
UI/CF.APP/chicken_farm/lib/pages/sample/sample_query_page.dart

@@ -45,7 +45,7 @@ class _SampleQueryPageState extends State<SampleQueryPage> {
       body: QRScannerComponent(
         startWithString: _qrCodePrefix,
         onScanCallback: _handleSampleScan,
-        invalidQRMessage: '这不是一个有效的样品二维码',
+        invalidQRMessage: '样品二维码无效!',
         qrCodeParser: _parseSampleQRCode,
       ),
     );

+ 61 - 0
UI/CF.APP/chicken_farm/lib/stores/config_store.dart

@@ -0,0 +1,61 @@
+import 'package:chicken_farm/apis/system/_config.dart';
+import 'package:chicken_farm/modes/system/config.dart';
+
+class ConfigStore {
+  static final ConfigStore _instance = ConfigStore._internal();
+  // static const String _localStorgeKey = 'vb_config_';
+  final Map<String, ConfigModel> _cache = {};
+
+  factory ConfigStore() => _instance;
+
+  ConfigStore._internal();
+
+  Future<String?> getConfigValue(String configKey) async {
+    final config = await getConfigByKey(configKey);
+    return config?.configValue;
+  }
+
+  /// 根据配置key获取配置信息
+  /// 先从缓存中查找,如果缓存中没有则调用接口查询
+  Future<ConfigModel?> getConfigByKey(String configKey) async {
+    // 先从内存缓存中查找
+    if (_cache.containsKey(configKey)) {
+      return _cache[configKey];
+    }
+
+    // 再从本地存储中查找
+    // final cachedConfig = await StorageUtils.getObject<Map<String, dynamic>>(
+    //   '$_localStorgeKey$configKey',
+    // );
+    // if (cachedConfig != null) {
+    //   final config = ConfigModel.fromJson(cachedConfig);
+    //   _cache[configKey] = config;
+    //   return config;
+    // }
+
+    // 如果本地都没有,则调用接口查询
+    final config = await ConfigApi().getConfigKey(configKey);
+    if (config != null) {
+      // 存入缓存
+      _cache[configKey] = config;
+      // await StorageUtils.setObject(
+      //   '$_localStorgeKey$configKey',
+      //   config.toJson(),
+      // );
+    }
+
+    return config;
+  }
+
+  /// 清除指定配置的缓存
+  void clearConfigCache(String configKey) {
+    _cache.remove(configKey);
+    //StorageUtils.remove('$_localStorgeKey$configKey');
+  }
+
+  /// 清除所有配置缓存
+  void clearAll() {
+    _cache.clear();
+    //StorageUtils.removeWithPrefix(_localStorgeKey);
+  }
+}

+ 104 - 0
UI/CF.APP/chicken_farm/lib/stores/dict_stroe.dart

@@ -0,0 +1,104 @@
+import 'package:chicken_farm/apis/system/_dict.dart';
+import 'package:chicken_farm/core/utils/storage.dart';
+import 'package:chicken_farm/modes/system/dict.dart';
+
+class DictStore {
+  static final DictStore _instance = DictStore._internal();
+  static const String _localStorgeKey = 'vb_dict_';
+  final Map<String, List<DictDataModel>> _cache = {};
+
+  factory DictStore() => _instance;
+
+  DictStore._internal();
+
+  /// 根据字典类型获取字典数据列表
+  /// 先从缓存中查找,如果缓存中没有则调用接口查询
+  Future<List<DictDataModel>?> getDictByType(String dictType) async {
+    // 先从内存缓存中查找
+    if (_cache.containsKey(dictType)) {
+      return _cache[dictType];
+    }
+
+    // 再从本地存储中查找
+    // final cachedDict = await StorageUtils.getObject<List<dynamic>>(
+    //   '$_localStorgeKey$dictType',
+    // );
+    // if (cachedDict != null) {
+    //   try {
+    //     final dictList = cachedDict
+    //         .map(
+    //           (item) => item is Map<String, dynamic>
+    //               ? DictDataModel.fromJson(item)
+    //               : null,
+    //         )
+    //         .where((item) => item != null)
+    //         .cast<DictDataModel>()
+    //         .toList();
+    //     _cache[dictType] = dictList;
+    //     return dictList;
+    //   } catch (e) {
+    //     // 解析失败,继续从网络获取
+    //   }
+    // }
+
+    // 如果本地都没有,则调用接口查询
+    final dictData = await DictApi().getDicts(dictType);
+    if (dictData != null) {
+      // 存入缓存
+      _cache[dictType] = dictData;
+      await StorageUtils.setObject(
+        '$_localStorgeKey$dictType',
+        dictData.map((e) => e.toJson()).toList(),
+      );
+
+      return dictData;
+    }
+
+    return null;
+  }
+
+  /// 根据字典类型和值查询标签
+  Future<String?> getLabelByTypeAndValue(String dictType, String value) async {
+    final dict = await getDictByTypeAndValue(dictType, value);
+    return dict?.dictLabel.isNotEmpty == true ? dict?.dictLabel : null;
+  }
+
+  /// 根据字典类型和值查询字典项
+  Future<DictDataModel?> getDictByTypeAndValue(
+    String dictType,
+    String value,
+  ) async {
+    final dictList = await getDictByType(dictType);
+    if (dictList != null) {
+      final dict = dictList.firstWhere(
+        (element) => element.dictValue == value,
+        orElse: () => DictDataModel(
+          dictCode: 0,
+          dictSort: 0,
+          dictLabel: '',
+          dictValue: '',
+          dictType: dictType,
+        ),
+      );
+      // 检查是否找到了匹配的字典项
+      if (dict.dictCode != 0 ||
+          dict.dictLabel.isNotEmpty ||
+          dict.dictValue.isNotEmpty) {
+        return dict;
+      }
+    }
+    return null;
+  }
+
+  /// 清除指定字典类型的缓存
+  void clearDictCache(String dictType) {
+    _cache.remove(dictType);
+    //StorageUtils.remove('$_localStorgeKey$dictType');
+  }
+
+  /// 清除所有字典缓存
+  void clearAll() {
+    _cache.clear();
+    //StorageUtils.removeWithPrefix(_localStorgeKey);
+  }
+}