✨ Edit alert configuration
This commit is contained in:
		
							
								
								
									
										38
									
								
								lib/controllers/alert.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								lib/controllers/alert.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | import 'dart:convert'; | ||||||
|  |  | ||||||
|  | import 'package:dietary_guard/models/alert_configuration.dart'; | ||||||
|  | import 'package:get/get.dart'; | ||||||
|  | import 'package:shared_preferences/shared_preferences.dart'; | ||||||
|  |  | ||||||
|  | class AlertController extends GetxController { | ||||||
|  |   List<AlertConfiguration> configuration = List.empty(); | ||||||
|  |  | ||||||
|  |   late final SharedPreferences _prefs; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void onInit() async { | ||||||
|  |     _prefs = await SharedPreferences.getInstance(); | ||||||
|  |     loadAlertConfiguration(); | ||||||
|  |     super.onInit(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void loadAlertConfiguration() { | ||||||
|  |     final raw = _prefs.getString("alert_configuration"); | ||||||
|  |     if (raw == null) return; | ||||||
|  |     configuration = List<AlertConfiguration>.from(jsonDecode(raw).map( | ||||||
|  |       (x) => AlertConfiguration.fromJson(x), | ||||||
|  |     )); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> setAlertConfiguration(List<AlertConfiguration> value) async { | ||||||
|  |     await _prefs.setString( | ||||||
|  |       "alert_configuration", | ||||||
|  |       jsonEncode(value.map((x) => x.toJson()).toList()), | ||||||
|  |     ); | ||||||
|  |     loadAlertConfiguration(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void clearAlertConfiguration() { | ||||||
|  |     _prefs.remove("alert_configuration"); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | import 'package:dietary_guard/controllers/alert.dart'; | ||||||
| import 'package:dietary_guard/controllers/food_data.dart'; | import 'package:dietary_guard/controllers/food_data.dart'; | ||||||
| import 'package:dietary_guard/screens/query.dart'; | import 'package:dietary_guard/screens/query.dart'; | ||||||
| import 'package:dietary_guard/screens/settings.dart'; | import 'package:dietary_guard/screens/settings.dart'; | ||||||
| @@ -62,6 +63,7 @@ class MyApp extends StatelessWidget { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   void _initializeProviders(BuildContext context) async { |   void _initializeProviders(BuildContext context) async { | ||||||
|     Get.put(FoodDataController()); |     Get.lazyPut(() => FoodDataController()); | ||||||
|  |     Get.lazyPut(() => AlertController()); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								lib/models/alert_configuration.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								lib/models/alert_configuration.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | class AlertConfiguration { | ||||||
|  |   int nutrientId; | ||||||
|  |   double maxValue; | ||||||
|  |   double minValue; | ||||||
|  |  | ||||||
|  |   AlertConfiguration({ | ||||||
|  |     required this.nutrientId, | ||||||
|  |     required this.maxValue, | ||||||
|  |     required this.minValue, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   Map<String, dynamic> toJson() => { | ||||||
|  |         'nutrient_id': nutrientId, | ||||||
|  |         'min_value': minValue, | ||||||
|  |         'max_value': maxValue, | ||||||
|  |       }; | ||||||
|  |  | ||||||
|  |   factory AlertConfiguration.fromJson(Map<String, dynamic> json) => | ||||||
|  |       AlertConfiguration( | ||||||
|  |         nutrientId: json['nutrient_id'], | ||||||
|  |         minValue: json['min_value'], | ||||||
|  |         maxValue: json['max_value'], | ||||||
|  |       ); | ||||||
|  | } | ||||||
| @@ -98,7 +98,7 @@ class Nutrients { | |||||||
| } | } | ||||||
|  |  | ||||||
| class FoodSearchCriteria { | class FoodSearchCriteria { | ||||||
|   List<Type> dataType; |   List<Type?> dataType; | ||||||
|   String query; |   String query; | ||||||
|   String generalSearchInput; |   String generalSearchInput; | ||||||
|   int pageNumber; |   int pageNumber; | ||||||
| @@ -107,7 +107,7 @@ class FoodSearchCriteria { | |||||||
|   int numberOfResultsPerPage; |   int numberOfResultsPerPage; | ||||||
|   int pageSize; |   int pageSize; | ||||||
|   bool requireAllWords; |   bool requireAllWords; | ||||||
|   List<Type> foodTypes; |   List<Type?> foodTypes; | ||||||
|  |  | ||||||
|   FoodSearchCriteria({ |   FoodSearchCriteria({ | ||||||
|     required this.dataType, |     required this.dataType, | ||||||
| @@ -124,8 +124,8 @@ class FoodSearchCriteria { | |||||||
|  |  | ||||||
|   factory FoodSearchCriteria.fromJson(Map<String, dynamic> json) => |   factory FoodSearchCriteria.fromJson(Map<String, dynamic> json) => | ||||||
|       FoodSearchCriteria( |       FoodSearchCriteria( | ||||||
|         dataType: |         dataType: List<Type>.from( | ||||||
|             List<Type>.from(json["dataType"].map((x) => typeValues.map[x]!)), |             json["dataType"]?.map((x) => typeValues.map[x]) ?? List.empty()), | ||||||
|         query: json["query"], |         query: json["query"], | ||||||
|         generalSearchInput: json["generalSearchInput"], |         generalSearchInput: json["generalSearchInput"], | ||||||
|         pageNumber: json["pageNumber"], |         pageNumber: json["pageNumber"], | ||||||
| @@ -134,8 +134,8 @@ class FoodSearchCriteria { | |||||||
|         numberOfResultsPerPage: json["numberOfResultsPerPage"], |         numberOfResultsPerPage: json["numberOfResultsPerPage"], | ||||||
|         pageSize: json["pageSize"], |         pageSize: json["pageSize"], | ||||||
|         requireAllWords: json["requireAllWords"], |         requireAllWords: json["requireAllWords"], | ||||||
|         foodTypes: |         foodTypes: List<Type>.from( | ||||||
|             List<Type>.from(json["foodTypes"].map((x) => typeValues.map[x]!)), |             json["foodTypes"]?.map((x) => typeValues.map[x]) ?? List.empty()), | ||||||
|       ); |       ); | ||||||
|  |  | ||||||
|   Map<String, dynamic> toJson() => { |   Map<String, dynamic> toJson() => { | ||||||
| @@ -162,10 +162,10 @@ final typeValues = | |||||||
| class FoodData { | class FoodData { | ||||||
|   int fdcId; |   int fdcId; | ||||||
|   String description; |   String description; | ||||||
|   String commonNames; |   String? commonNames; | ||||||
|   String additionalDescriptions; |   String? additionalDescriptions; | ||||||
|   Type dataType; |   Type? dataType; | ||||||
|   int ndbNumber; |   int? ndbNumber; | ||||||
|   DateTime publishedDate; |   DateTime publishedDate; | ||||||
|   FoodCategory? foodCategory; |   FoodCategory? foodCategory; | ||||||
|   DateTime? mostRecentAcquisitionDate; |   DateTime? mostRecentAcquisitionDate; | ||||||
| @@ -205,7 +205,7 @@ class FoodData { | |||||||
|         description: json["description"], |         description: json["description"], | ||||||
|         commonNames: json["commonNames"], |         commonNames: json["commonNames"], | ||||||
|         additionalDescriptions: json["additionalDescriptions"], |         additionalDescriptions: json["additionalDescriptions"], | ||||||
|         dataType: typeValues.map[json["dataType"]]!, |         dataType: typeValues.map[json["dataType"]], | ||||||
|         ndbNumber: json["ndbNumber"], |         ndbNumber: json["ndbNumber"], | ||||||
|         publishedDate: DateTime.parse(json["publishedDate"]), |         publishedDate: DateTime.parse(json["publishedDate"]), | ||||||
|         foodCategory: foodCategoryValues.map[json["foodCategory"]], |         foodCategory: foodCategoryValues.map[json["foodCategory"]], | ||||||
| @@ -264,7 +264,7 @@ class FoodNutrient { | |||||||
|   int nutrientId; |   int nutrientId; | ||||||
|   String nutrientName; |   String nutrientName; | ||||||
|   String nutrientNumber; |   String nutrientNumber; | ||||||
|   UnitName unitName; |   UnitName? unitName; | ||||||
|   DerivationCode? derivationCode; |   DerivationCode? derivationCode; | ||||||
|   String? derivationDescription; |   String? derivationDescription; | ||||||
|   int? derivationId; |   int? derivationId; | ||||||
| @@ -305,15 +305,15 @@ class FoodNutrient { | |||||||
|         nutrientId: json["nutrientId"], |         nutrientId: json["nutrientId"], | ||||||
|         nutrientName: json["nutrientName"], |         nutrientName: json["nutrientName"], | ||||||
|         nutrientNumber: json["nutrientNumber"], |         nutrientNumber: json["nutrientNumber"], | ||||||
|         unitName: unitNameValues.map[json["unitName"]]!, |         unitName: unitNameValues.map[json["unitName"]], | ||||||
|         derivationCode: derivationCodeValues.map[json["derivationCode"]]!, |         derivationCode: derivationCodeValues.map[json["derivationCode"]], | ||||||
|         derivationDescription: json["derivationDescription"], |         derivationDescription: json["derivationDescription"], | ||||||
|         derivationId: json["derivationId"], |         derivationId: json["derivationId"], | ||||||
|         value: json["value"]?.toDouble(), |         value: json["value"]?.toDouble(), | ||||||
|         foodNutrientSourceId: json["foodNutrientSourceId"], |         foodNutrientSourceId: json["foodNutrientSourceId"], | ||||||
|         foodNutrientSourceCode: json["foodNutrientSourceCode"], |         foodNutrientSourceCode: json["foodNutrientSourceCode"], | ||||||
|         foodNutrientSourceDescription: foodNutrientSourceDescriptionValues |         foodNutrientSourceDescription: foodNutrientSourceDescriptionValues | ||||||
|             .map[json["foodNutrientSourceDescription"]]!, |             .map[json["foodNutrientSourceDescription"]], | ||||||
|         rank: json["rank"], |         rank: json["rank"], | ||||||
|         indentLevel: json["indentLevel"], |         indentLevel: json["indentLevel"], | ||||||
|         foodNutrientId: json["foodNutrientId"], |         foodNutrientId: json["foodNutrientId"], | ||||||
|   | |||||||
| @@ -9,7 +9,9 @@ class FoodDetailsScreen extends StatelessWidget { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return Scaffold( |     return Material( | ||||||
|  |       color: Theme.of(context).colorScheme.surface, | ||||||
|  |       child: Scaffold( | ||||||
|         appBar: AppBar( |         appBar: AppBar( | ||||||
|           title: Text(item.description), |           title: Text(item.description), | ||||||
|         ), |         ), | ||||||
| @@ -40,6 +42,7 @@ class FoodDetailsScreen extends StatelessWidget { | |||||||
|             ) |             ) | ||||||
|           ], |           ], | ||||||
|         ).paddingSymmetric(vertical: 24), |         ).paddingSymmetric(vertical: 24), | ||||||
|  |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -45,7 +45,9 @@ class _QueryScreenState extends State<QueryScreen> { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return SafeArea( |     return Material( | ||||||
|  |       color: Theme.of(context).colorScheme.surface, | ||||||
|  |       child: SafeArea( | ||||||
|         child: Column( |         child: Column( | ||||||
|           children: [ |           children: [ | ||||||
|             SearchBar( |             SearchBar( | ||||||
| @@ -82,7 +84,7 @@ class _QueryScreenState extends State<QueryScreen> { | |||||||
|                       openBuilder: (_, __) => FoodDetailsScreen(item: item), |                       openBuilder: (_, __) => FoodDetailsScreen(item: item), | ||||||
|                       openElevation: 0, |                       openElevation: 0, | ||||||
|                       closedElevation: 0, |                       closedElevation: 0, | ||||||
|                     closedColor: Colors.transparent, |                       closedColor: Theme.of(context).colorScheme.surface, | ||||||
|                       openColor: Colors.transparent, |                       openColor: Colors.transparent, | ||||||
|                     ); |                     ); | ||||||
|                   }, |                   }, | ||||||
| @@ -90,6 +92,7 @@ class _QueryScreenState extends State<QueryScreen> { | |||||||
|               ), |               ), | ||||||
|           ], |           ], | ||||||
|         ), |         ), | ||||||
|  |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,4 +1,6 @@ | |||||||
|  | import 'package:dietary_guard/controllers/alert.dart'; | ||||||
| import 'package:dietary_guard/controllers/food_data.dart'; | import 'package:dietary_guard/controllers/food_data.dart'; | ||||||
|  | import 'package:dietary_guard/models/alert_configuration.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:get/get.dart'; | import 'package:get/get.dart'; | ||||||
|  |  | ||||||
| @@ -12,10 +14,25 @@ class SettingsScreen extends StatefulWidget { | |||||||
| class _SettingsScreenState extends State<SettingsScreen> { | class _SettingsScreenState extends State<SettingsScreen> { | ||||||
|   final TextEditingController _fdcApiKeyController = TextEditingController(); |   final TextEditingController _fdcApiKeyController = TextEditingController(); | ||||||
|  |  | ||||||
|  |   List<AlertConfiguration> _currentAlerts = List.empty(growable: true); | ||||||
|  |  | ||||||
|  |   void _addAlert() { | ||||||
|  |     setState(() { | ||||||
|  |       _currentAlerts.add(AlertConfiguration( | ||||||
|  |         nutrientId: 0, | ||||||
|  |         maxValue: 0, | ||||||
|  |         minValue: 0, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   Future<void> _applySettings() async { |   Future<void> _applySettings() async { | ||||||
|     final FoodDataController data = Get.find(); |     final FoodDataController data = Get.find(); | ||||||
|     await data.setApiKey(_fdcApiKeyController.text); |     await data.setApiKey(_fdcApiKeyController.text); | ||||||
|  |  | ||||||
|  |     final AlertController alert = Get.find(); | ||||||
|  |     await alert.setAlertConfiguration(_currentAlerts); | ||||||
|  |  | ||||||
|     ScaffoldMessenger.of(context).showSnackBar(SnackBar( |     ScaffoldMessenger.of(context).showSnackBar(SnackBar( | ||||||
|       content: Text('settingsApplied'.tr), |       content: Text('settingsApplied'.tr), | ||||||
|     )); |     )); | ||||||
| @@ -26,6 +43,9 @@ class _SettingsScreenState extends State<SettingsScreen> { | |||||||
|     final FoodDataController data = Get.find(); |     final FoodDataController data = Get.find(); | ||||||
|     _fdcApiKeyController.text = data.getApiKey() ?? ''; |     _fdcApiKeyController.text = data.getApiKey() ?? ''; | ||||||
|  |  | ||||||
|  |     final AlertController alert = Get.find(); | ||||||
|  |     _currentAlerts = List.from(alert.configuration, growable: true); | ||||||
|  |  | ||||||
|     super.initState(); |     super.initState(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -35,36 +55,116 @@ class _SettingsScreenState extends State<SettingsScreen> { | |||||||
|     super.dispose(); |     super.dispose(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   Widget _buildSectionHeader(String title) { | ||||||
|  |     return Container( | ||||||
|  |       color: Theme.of(context).colorScheme.secondaryContainer, | ||||||
|  |       padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 8), | ||||||
|  |       child: Text( | ||||||
|  |         title, | ||||||
|  |         style: Theme.of(context).textTheme.bodyLarge, | ||||||
|  |       ), | ||||||
|  |     ).marginOnly(bottom: 16); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return Scaffold( |     return Material( | ||||||
|  |       color: Theme.of(context).colorScheme.surface, | ||||||
|  |       child: Scaffold( | ||||||
|         appBar: AppBar( |         appBar: AppBar( | ||||||
|           title: Text('settings'.tr), |           title: Text('settings'.tr), | ||||||
|         ), |         ), | ||||||
|         body: ListView( |         body: ListView( | ||||||
|           children: [ |           children: [ | ||||||
|           const SizedBox(height: 8), |             _buildSectionHeader('settingsAlertSection'.tr), | ||||||
|  |             Column( | ||||||
|  |               children: [ | ||||||
|  |                 ...(_currentAlerts.map((x) => Row( | ||||||
|  |                       children: [ | ||||||
|  |                         Expanded( | ||||||
|  |                           child: TextFormField( | ||||||
|  |                             initialValue: x.nutrientId.toString(), | ||||||
|  |                             decoration: InputDecoration( | ||||||
|  |                               border: const OutlineInputBorder(), | ||||||
|  |                               label: Text("alertNutrientId".tr), | ||||||
|  |                               isDense: true, | ||||||
|  |                             ), | ||||||
|  |                             onTapOutside: (_) => | ||||||
|  |                                 FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|  |                             onChanged: (value) { | ||||||
|  |                               x.nutrientId = int.tryParse(value) ?? 0; | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                         const SizedBox(width: 6), | ||||||
|  |                         Expanded( | ||||||
|  |                           child: TextFormField( | ||||||
|  |                             initialValue: x.maxValue.toString(), | ||||||
|  |                             decoration: InputDecoration( | ||||||
|  |                               border: const OutlineInputBorder(), | ||||||
|  |                               label: Text("alertMaxValue".tr), | ||||||
|  |                               isDense: true, | ||||||
|  |                             ), | ||||||
|  |                             onTapOutside: (_) => | ||||||
|  |                                 FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|  |                             onChanged: (value) { | ||||||
|  |                               x.maxValue = double.tryParse(value) ?? 0; | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                         const SizedBox(width: 6), | ||||||
|  |                         Expanded( | ||||||
|  |                           child: TextFormField( | ||||||
|  |                             initialValue: x.minValue.toString(), | ||||||
|  |                             decoration: InputDecoration( | ||||||
|  |                               border: const OutlineInputBorder(), | ||||||
|  |                               label: Text("alertMinValue".tr), | ||||||
|  |                               isDense: true, | ||||||
|  |                             ), | ||||||
|  |                             onTapOutside: (_) => | ||||||
|  |                                 FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|  |                             onChanged: (value) { | ||||||
|  |                               x.minValue = double.tryParse(value) ?? 0; | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                       ], | ||||||
|  |                     ))), | ||||||
|  |                 Row( | ||||||
|  |                   mainAxisAlignment: MainAxisAlignment.center, | ||||||
|  |                   children: [ | ||||||
|  |                     TextButton.icon( | ||||||
|  |                       label: Text('newAlert'.tr), | ||||||
|  |                       onPressed: () => _addAlert(), | ||||||
|  |                     ), | ||||||
|  |                   ], | ||||||
|  |                 ).paddingSymmetric(vertical: 12, horizontal: 24), | ||||||
|  |               ], | ||||||
|  |             ).paddingSymmetric(horizontal: 24), | ||||||
|  |             _buildSectionHeader('settingsDataSection'.tr), | ||||||
|             TextField( |             TextField( | ||||||
|               controller: _fdcApiKeyController, |               controller: _fdcApiKeyController, | ||||||
|               obscureText: true, |               obscureText: true, | ||||||
|               decoration: const InputDecoration( |               decoration: const InputDecoration( | ||||||
|               border: OutlineInputBorder(), |                 border: UnderlineInputBorder(), | ||||||
|                 label: Text("FDC API Key"), |                 label: Text("FDC API Key"), | ||||||
|               isDense: true, |                 isDense: false, | ||||||
|             ), |  | ||||||
|               ), |               ), | ||||||
|  |               onTapOutside: (_) => | ||||||
|  |                   FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|  |             ).paddingSymmetric(horizontal: 24), | ||||||
|             Row( |             Row( | ||||||
|             mainAxisAlignment: MainAxisAlignment.end, |               mainAxisAlignment: MainAxisAlignment.center, | ||||||
|               children: [ |               children: [ | ||||||
|               ElevatedButton.icon( |                 TextButton.icon( | ||||||
|                 icon: const Icon(Icons.save, size: 16), |  | ||||||
|                   label: Text('apply'.tr), |                   label: Text('apply'.tr), | ||||||
|                   onPressed: () => _applySettings(), |                   onPressed: () => _applySettings(), | ||||||
|                 ), |                 ), | ||||||
|               ], |               ], | ||||||
|           ).paddingSymmetric(vertical: 12), |             ).paddingSymmetric(vertical: 12, horizontal: 24), | ||||||
|           ], |           ], | ||||||
|       ).paddingSymmetric(horizontal: 24), |         ), | ||||||
|  |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,7 +3,13 @@ const i18nEnglish = { | |||||||
|   'settings': 'Settings', |   'settings': 'Settings', | ||||||
|   'preparingData': 'Preparing data...', |   'preparingData': 'Preparing data...', | ||||||
|   'settingsApplied': 'Settings Applied', |   'settingsApplied': 'Settings Applied', | ||||||
|  |   'settingsDataSection': 'Data Source', | ||||||
|  |   'settingsAlertSection': 'Alert', | ||||||
|  |   'newAlert': 'New Alert', | ||||||
|   'apply': 'Apply', |   'apply': 'Apply', | ||||||
|   'searchHistoryNotIncluded': 'Search History not Included, yet', |   'searchHistoryNotIncluded': 'Search History not Included, yet', | ||||||
|   'nutrients': 'Nutrients', |   'nutrients': 'Nutrients', | ||||||
|  |   'alertNutrientId': 'Nutrient ID', | ||||||
|  |   'alertMaxValue': 'Max', | ||||||
|  |   'alertMinValue': 'Min', | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -3,7 +3,13 @@ const i18nSimplifiedChinese = { | |||||||
|   'settings': '设置', |   'settings': '设置', | ||||||
|   'preparingData': '准备数据中…', |   'preparingData': '准备数据中…', | ||||||
|   'settingsApplied': '设置已应用', |   'settingsApplied': '设置已应用', | ||||||
|  |   'settingsDataSection': '数据源', | ||||||
|  |   'settingsAlertSection': '危险告警', | ||||||
|  |   'newAlert': '新告警', | ||||||
|   'apply': '应用', |   'apply': '应用', | ||||||
|   'searchHistoryNotIncluded': '搜索记录还没实现', |   'searchHistoryNotIncluded': '搜索记录还没实现', | ||||||
|   'nutrients': '营养物质', |   'nutrients': '营养物质', | ||||||
|  |   'alertNutrientId': '营养物质编号', | ||||||
|  |   'alertMaxValue': '告警上限', | ||||||
|  |   'alertMinValue': '告警下限', | ||||||
| }; | }; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user