更新城市选择
BIN
assets/city/city.xlsx
Normal file
24368
assets/city/city_en_US.json
Normal file
24398
assets/city/city_zh_CN.json
Normal file
24398
assets/city/city_zh_TW.json
Normal file
3800
assets/city/output.jsonl
Normal file
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 15 KiB |
@@ -565,5 +565,9 @@
|
|||||||
"联系人不能为空": "Contact cannot be empty",
|
"联系人不能为空": "Contact cannot be empty",
|
||||||
"联系电话不能为空": "Contact number cannot be empty",
|
"联系电话不能为空": "Contact number cannot be empty",
|
||||||
"配网成功": "Network configured successfully",
|
"配网成功": "Network configured successfully",
|
||||||
"配网失败": "Network configuration failed"
|
"配网失败": "Network configuration failed",
|
||||||
|
"选择城市": "Select City",
|
||||||
|
"请选择城市": "Please select a city",
|
||||||
|
"输入关键词": "Enter keywords",
|
||||||
|
"输入国家、省份或城市": "Enter country, province, or city"
|
||||||
}
|
}
|
||||||
@@ -565,5 +565,10 @@
|
|||||||
"联系人不能为空": "联系人不能为空",
|
"联系人不能为空": "联系人不能为空",
|
||||||
"联系电话不能为空": "联系电话不能为空",
|
"联系电话不能为空": "联系电话不能为空",
|
||||||
"配网成功": "配网成功",
|
"配网成功": "配网成功",
|
||||||
"配网失败": "配网失败"
|
"配网失败": "配网失败",
|
||||||
|
"选择城市": "选择城市",
|
||||||
|
"请选择城市": "请选择城市",
|
||||||
|
"输入关键词": "输入关键词",
|
||||||
|
"输入国家、省份或城市": "输入国家、省份或城市"
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -561,5 +561,9 @@
|
|||||||
"联系人不能为空": " 聯繫人不能為空",
|
"联系人不能为空": " 聯繫人不能為空",
|
||||||
"联系电话不能为空": "聯繫電話不能為空",
|
"联系电话不能为空": "聯繫電話不能為空",
|
||||||
"配网成功": "配網成功",
|
"配网成功": "配網成功",
|
||||||
"配网失败": "配網失敗"
|
"配网失败": "配網失敗",
|
||||||
|
"选择城市": "選擇城市",
|
||||||
|
"请选择城市": "請選擇城市",
|
||||||
|
"输入关键词": "輸入關鍵詞",
|
||||||
|
"输入国家、省份或城市": "輸入國家、省份或城市"
|
||||||
}
|
}
|
||||||
281
lib/common/pojo/city.dart
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:ef/base/getx/getx.dart';
|
||||||
|
import 'package:ef/ef.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
import 'package:vbvs_app/controller/setting/language/language_controller.dart';
|
||||||
|
import 'package:vbvs_app/language/AppLanguage.dart';
|
||||||
|
part 'city.g.dart';
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class CityModel {
|
||||||
|
int? id; // 城市id
|
||||||
|
String? value; // 显示值(国家/省份/城市名称)
|
||||||
|
String? label; // 标签(通常与value相同)
|
||||||
|
String? country; // 国家名称
|
||||||
|
String? province; // 省份名称
|
||||||
|
String? city; // 城市名称
|
||||||
|
String? UTC; // 时区
|
||||||
|
List<CityModel>? children;
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
String? keyword = "";
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
Color? color = Color(0xFFFFFFFF);
|
||||||
|
|
||||||
|
CityModel({
|
||||||
|
this.id,
|
||||||
|
this.value,
|
||||||
|
this.label,
|
||||||
|
this.country,
|
||||||
|
this.province,
|
||||||
|
this.city,
|
||||||
|
this.UTC,
|
||||||
|
this.children,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 从JSON反序列化时的异常处理
|
||||||
|
factory CityModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
try {
|
||||||
|
return _$CityModelFromJson(json);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error parsing CityModel: $e');
|
||||||
|
return CityModel(); // 返回空的CityModel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 序列化为JSON时的异常处理
|
||||||
|
Map<String, dynamic> toJson() => _$CityModelToJson(this);
|
||||||
|
|
||||||
|
// 获取显示名称(根据层级显示)
|
||||||
|
String get displayName {
|
||||||
|
if (city != null && city!.isNotEmpty) {
|
||||||
|
// 三级:国家-省份-城市
|
||||||
|
return '${country ?? ''}-${province ?? ''}-${city ?? value ?? ''}';
|
||||||
|
} else if (province != null && province!.isNotEmpty) {
|
||||||
|
// 二级:国家-省份
|
||||||
|
return '${country ?? ''}-${province ?? value ?? ''}';
|
||||||
|
} else {
|
||||||
|
// 一级:国家
|
||||||
|
return country ?? value ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CityModelController extends GetControllerEx<CityModel> {
|
||||||
|
List<CityModel> cityList = [];
|
||||||
|
LanguageController languageController = Get.find();
|
||||||
|
RxInt tmp = 1.obs;
|
||||||
|
RxBool isLoading = false.obs;
|
||||||
|
List<String>? searchResults = []; // 新增:搜索结果列表
|
||||||
|
|
||||||
|
CityModelController() {
|
||||||
|
attr = GetModel(CityModel()).obs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载城市数据并赋值给cityList
|
||||||
|
Future<void> loadAndSetCityData() async {
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
|
ef.log("开始加载城市数据...");
|
||||||
|
|
||||||
|
final data = await _loadCityData();
|
||||||
|
cityList = data;
|
||||||
|
|
||||||
|
ef.log("城市数据加载完成,共${cityList.length}个国家");
|
||||||
|
if (cityList.isNotEmpty) {
|
||||||
|
// 打印前几个国家用于调试
|
||||||
|
for (var i = 0; i < min(3, cityList.length); i++) {
|
||||||
|
final country = cityList[i];
|
||||||
|
ef.log(
|
||||||
|
"国家: ${country.value}, 省份数量: ${country.children?.length ?? 0}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
} catch (e) {
|
||||||
|
ef.log("加载城市数据失败:$e");
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内部加载方法
|
||||||
|
Future<List<CityModel>> _loadCityData() async {
|
||||||
|
try {
|
||||||
|
// 获取当前语言代码
|
||||||
|
String currentLanguageCode = AppLanguage().getCurrentLanguageCode();
|
||||||
|
|
||||||
|
// 根据当前语言代码构建文件名
|
||||||
|
final String fileName = 'assets/city/city_$currentLanguageCode.json';
|
||||||
|
|
||||||
|
// 读取对应语言的JSON文件
|
||||||
|
final String jsonString = await rootBundle.loadString(fileName);
|
||||||
|
|
||||||
|
// 解析JSON数据并转换为 CityModel 列表
|
||||||
|
final List<dynamic> jsonList = jsonDecode(jsonString);
|
||||||
|
|
||||||
|
// 将 Map 转换为 CityModel
|
||||||
|
return jsonList.map((json) => CityModel.fromJson(json)).toList();
|
||||||
|
} catch (e) {
|
||||||
|
ef.log("_loadCityData 失败:$e");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID查找城市(支持国家、省份、城市所有层级)
|
||||||
|
CityModel? findCityById(String id) {
|
||||||
|
if (cityList.isEmpty) {
|
||||||
|
ef.log("cityList为空,无法查找城市,请先调用 loadAndSetCityData()");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int? targetId = int.tryParse(id);
|
||||||
|
if (targetId == null) {
|
||||||
|
ef.log("ID格式错误:$id");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ef.log("开始查找ID为 $targetId 的城市数据...");
|
||||||
|
|
||||||
|
// 遍历三级结构
|
||||||
|
for (var country in cityList) {
|
||||||
|
// 检查国家
|
||||||
|
if (country.id == targetId) {
|
||||||
|
ef.log("找到匹配的国家: ${country.value}");
|
||||||
|
return CityModel(
|
||||||
|
id: country.id,
|
||||||
|
value: country.value,
|
||||||
|
label: country.label,
|
||||||
|
country: country.value,
|
||||||
|
province: null,
|
||||||
|
city: null,
|
||||||
|
UTC: country.UTC,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查省份
|
||||||
|
for (var province in country.children ?? []) {
|
||||||
|
if (province.id == targetId) {
|
||||||
|
ef.log("找到匹配的省份: ${province.value}, 国家: ${country.value}");
|
||||||
|
return CityModel(
|
||||||
|
id: province.id,
|
||||||
|
value: province.value,
|
||||||
|
label: province.label,
|
||||||
|
country: country.value,
|
||||||
|
province: province.value,
|
||||||
|
city: null,
|
||||||
|
UTC: province.UTC,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查城市
|
||||||
|
for (var city in province.children ?? []) {
|
||||||
|
if (city.id == targetId) {
|
||||||
|
ef.log(
|
||||||
|
"找到匹配的城市: ${city.value}, 省份: ${province.value}, 国家: ${country.value}");
|
||||||
|
return CityModel(
|
||||||
|
id: city.id,
|
||||||
|
value: city.value,
|
||||||
|
label: city.label,
|
||||||
|
country: country.value,
|
||||||
|
province: province.value,
|
||||||
|
city: city.value,
|
||||||
|
UTC: city.UTC,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ef.log("未找到ID为 $targetId 的城市数据");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据名称查找城市
|
||||||
|
CityModel? findCityByName(
|
||||||
|
String countryName, String provinceName, String cityName) {
|
||||||
|
if (cityList.isEmpty) {
|
||||||
|
ef.log("cityList为空,无法查找城市");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var country in cityList) {
|
||||||
|
if (country.value == countryName) {
|
||||||
|
for (var province in country.children ?? []) {
|
||||||
|
if (province.value == provinceName) {
|
||||||
|
for (var city in province.children ?? []) {
|
||||||
|
if (city.value == cityName) {
|
||||||
|
return city;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已加载数据
|
||||||
|
bool get isDataLoaded => cityList.isNotEmpty;
|
||||||
|
|
||||||
|
void searchCities(String keyword) {
|
||||||
|
model.keyword = keyword;
|
||||||
|
searchResults?.clear();
|
||||||
|
|
||||||
|
if (keyword.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final keywordLower = keyword.toLowerCase();
|
||||||
|
|
||||||
|
// 遍历所有城市数据
|
||||||
|
for (var country in cityList) {
|
||||||
|
final countryName = country.value ?? country.country ?? '';
|
||||||
|
|
||||||
|
for (var province in country.children ?? []) {
|
||||||
|
final provinceName = province.value ?? province.province ?? '';
|
||||||
|
|
||||||
|
for (var city in province.children ?? []) {
|
||||||
|
final cityName = city.value ?? city.city ?? '';
|
||||||
|
final displayName = '$countryName-$provinceName-$cityName';
|
||||||
|
|
||||||
|
// 模糊匹配
|
||||||
|
if (countryName.toLowerCase().contains(keywordLower) ||
|
||||||
|
provinceName.toLowerCase().contains(keywordLower) ||
|
||||||
|
cityName.toLowerCase().contains(keywordLower) ||
|
||||||
|
displayName.toLowerCase().contains(keywordLower)) {
|
||||||
|
searchResults?.add(displayName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据显示名称获取对应的 CityModel
|
||||||
|
CityModel? getCityByDisplayName(String displayName) {
|
||||||
|
final parts = displayName.split('-');
|
||||||
|
if (parts.length != 3) return null;
|
||||||
|
|
||||||
|
final countryName = parts[0];
|
||||||
|
final provinceName = parts[1];
|
||||||
|
final cityName = parts[2];
|
||||||
|
|
||||||
|
for (var country in cityList) {
|
||||||
|
if ((country.value ?? country.country ?? '') == countryName) {
|
||||||
|
for (var province in country.children ?? []) {
|
||||||
|
if ((province.value ?? province.province ?? '') == provinceName) {
|
||||||
|
for (var city in province.children ?? []) {
|
||||||
|
if ((city.value ?? city.city ?? '') == cityName) {
|
||||||
|
return city;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
31
lib/common/pojo/city.g.dart
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'city.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
CityModel _$CityModelFromJson(Map<String, dynamic> json) => CityModel(
|
||||||
|
id: (json['id'] as num?)?.toInt(),
|
||||||
|
value: json['value'] as String?,
|
||||||
|
label: json['label'] as String?,
|
||||||
|
country: json['country'] as String?,
|
||||||
|
province: json['province'] as String?,
|
||||||
|
city: json['city'] as String?,
|
||||||
|
UTC: json['UTC'] as String?,
|
||||||
|
children: (json['children'] as List<dynamic>?)
|
||||||
|
?.map((e) => CityModel.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$CityModelToJson(CityModel instance) => <String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'value': instance.value,
|
||||||
|
'label': instance.label,
|
||||||
|
'country': instance.country,
|
||||||
|
'province': instance.province,
|
||||||
|
'city': instance.city,
|
||||||
|
'UTC': instance.UTC,
|
||||||
|
'children': instance.children,
|
||||||
|
};
|
||||||
247
lib/common/util/ListSearchWidget.dart
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
import 'package:ef/ef.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:flutterflow_ui/flutterflow_ui.dart';
|
||||||
|
import 'package:vbvs_app/common/util/FitTool.dart';
|
||||||
|
import '../../common/util/MyUtils.dart';
|
||||||
|
|
||||||
|
class ListSearchWidget extends GetView {
|
||||||
|
final String? keyword;
|
||||||
|
final Color? color;
|
||||||
|
String? hint;
|
||||||
|
Function? onChange;
|
||||||
|
Function? findCallback;
|
||||||
|
final EdgeInsetsGeometry? padding;
|
||||||
|
final List<String>? searchResults; // 新增:搜索结果列表
|
||||||
|
final Function(String)? onResultTap; // 新增:点击结果的回调
|
||||||
|
final bool showResultList; // 新增:是否显示结果列表,默认不显示
|
||||||
|
|
||||||
|
ListSearchWidget({
|
||||||
|
required this.keyword,
|
||||||
|
required this.color,
|
||||||
|
this.hint = "请输入关键字",
|
||||||
|
this.findCallback,
|
||||||
|
this.onChange,
|
||||||
|
this.padding,
|
||||||
|
this.searchResults, // 搜索结果
|
||||||
|
this.onResultTap, // 点击结果回调
|
||||||
|
this.showResultList = false, // 默认不显示结果列表
|
||||||
|
});
|
||||||
|
|
||||||
|
final RxBool _showResults = false.obs; // 控制是否显示结果列表
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// 搜索框部分
|
||||||
|
Padding(
|
||||||
|
padding: padding ?? EdgeInsetsDirectional.fromSTEB(30.rpx, 0, 30.rpx, 0),
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
borderRadius: BorderRadius.circular(16.rpx),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(35.rpx, 0, 35.rpx, 0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 25.rpx,
|
||||||
|
height: 25.rpx,
|
||||||
|
decoration: BoxDecoration(),
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
'assets/img/icon/query.svg',
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
color: stringToColor("#333333"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
width: 100.rpx,
|
||||||
|
height: 60.rpx,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
child: Align(
|
||||||
|
alignment: AlignmentDirectional(-1, 0),
|
||||||
|
child: TextFormField(
|
||||||
|
autofocus: false,
|
||||||
|
obscureText: false,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
isDense: true,
|
||||||
|
labelStyle: TextStyle(
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
letterSpacing: 0.0,
|
||||||
|
),
|
||||||
|
hintText: hint,
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
letterSpacing: 0.0,
|
||||||
|
color: themeController.currentColor.sc4,
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: Color(0x00000000),
|
||||||
|
width: 1.rpx,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(8.rpx),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: Color(0x00000000),
|
||||||
|
width: 1.rpx,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(8.rpx),
|
||||||
|
),
|
||||||
|
errorBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
width: 1.rpx,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(8.rpx),
|
||||||
|
),
|
||||||
|
focusedErrorBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
width: 1.rpx,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(8.rpx),
|
||||||
|
),
|
||||||
|
filled: false,
|
||||||
|
fillColor: themeController.currentColor.sc22,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
vertical: 12.rpx, horizontal: 12.rpx),
|
||||||
|
),
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
color: Colors.black,
|
||||||
|
letterSpacing: 0.0,
|
||||||
|
),
|
||||||
|
onChanged: (d) {
|
||||||
|
onChange?.call(d);
|
||||||
|
// 输入时自动显示结果列表(如果开启了显示功能)
|
||||||
|
if (showResultList && d.isNotEmpty) {
|
||||||
|
_showResults.value = true;
|
||||||
|
} else {
|
||||||
|
_showResults.value = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onTap: () {
|
||||||
|
// 点击输入框时显示结果列表(如果开启了显示功能且有搜索结果)
|
||||||
|
if (showResultList && searchResults != null && searchResults!.isNotEmpty) {
|
||||||
|
_showResults.value = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
].divide(SizedBox(width: 6.rpx)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(26.rpx, 0, 0, 0),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
findCallback?.call();
|
||||||
|
// 点击搜索按钮后显示结果列表(如果开启了显示功能)
|
||||||
|
if (showResultList) {
|
||||||
|
_showResults.value = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 30.rpx,
|
||||||
|
child: VerticalDivider(
|
||||||
|
thickness: 2.rpx,
|
||||||
|
color: stringToColor("#333333"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'搜索'.tr,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontSize: 30.rpx,
|
||||||
|
letterSpacing: 0.0,
|
||||||
|
color: stringToColor("#333333"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
].divide(SizedBox(width: 26.rpx)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// 搜索结果列表(可选显示)
|
||||||
|
if (showResultList) ...[
|
||||||
|
Obx(() {
|
||||||
|
if (!_showResults.value || searchResults == null || searchResults!.isEmpty) {
|
||||||
|
return SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
margin: EdgeInsets.only(top: 10.rpx),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(8.rpx),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black12,
|
||||||
|
blurRadius: 4.rpx,
|
||||||
|
offset: Offset(0, 2.rpx),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxHeight: 200.rpx, // 限制最大高度
|
||||||
|
),
|
||||||
|
child: ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: ClampingScrollPhysics(),
|
||||||
|
itemCount: searchResults!.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final result = searchResults![index];
|
||||||
|
return ListTile(
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 20.rpx,
|
||||||
|
vertical: 8.rpx,
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
result,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
onResultTap?.call(result);
|
||||||
|
_showResults.value = false; // 选择后隐藏结果列表
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import 'package:json_annotation/json_annotation.dart';
|
|||||||
import 'package:vbvs_app/common/color/ServiceConstant.dart';
|
import 'package:vbvs_app/common/color/ServiceConstant.dart';
|
||||||
import 'package:vbvs_app/common/color/appConstants.dart';
|
import 'package:vbvs_app/common/color/appConstants.dart';
|
||||||
import 'package:vbvs_app/common/color/app_uri_status.dart';
|
import 'package:vbvs_app/common/color/app_uri_status.dart';
|
||||||
|
import 'package:vbvs_app/common/pojo/city.dart';
|
||||||
import 'package:vbvs_app/common/util/DailyLogUtils.dart';
|
import 'package:vbvs_app/common/util/DailyLogUtils.dart';
|
||||||
import 'package:vbvs_app/common/util/MyUtils.dart';
|
import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||||
import 'package:vbvs_app/enum/APPPackageType.dart';
|
import 'package:vbvs_app/enum/APPPackageType.dart';
|
||||||
@@ -48,6 +49,8 @@ class PersonController extends GetControllerEx<PersonModel> {
|
|||||||
RxString weight = "".obs;
|
RxString weight = "".obs;
|
||||||
RxString height = "".obs;
|
RxString height = "".obs;
|
||||||
DateTime? dateTime = DateTime.now(); //选择时间
|
DateTime? dateTime = DateTime.now(); //选择时间
|
||||||
|
CityModel? cityModel;
|
||||||
|
|
||||||
RxList diseaseList = [].obs;
|
RxList diseaseList = [].obs;
|
||||||
RxString update_person_mac = "".obs;
|
RxString update_person_mac = "".obs;
|
||||||
var after_deveice;
|
var after_deveice;
|
||||||
@@ -92,6 +95,10 @@ class PersonController extends GetControllerEx<PersonModel> {
|
|||||||
apiResponse.msg = "请输入身高".tr;
|
apiResponse.msg = "请输入身高".tr;
|
||||||
return apiResponse;
|
return apiResponse;
|
||||||
}
|
}
|
||||||
|
if (cityModel == null || cityModel!.id == null) {
|
||||||
|
apiResponse.msg = "请选择城市".tr;
|
||||||
|
return apiResponse;
|
||||||
|
}
|
||||||
|
|
||||||
var data = {
|
var data = {
|
||||||
"id": currentPersonId.value,
|
"id": currentPersonId.value,
|
||||||
@@ -103,6 +110,8 @@ class PersonController extends GetControllerEx<PersonModel> {
|
|||||||
"weight": weight!.value,
|
"weight": weight!.value,
|
||||||
"height": height.value,
|
"height": height.value,
|
||||||
"disease": selectedDiseaseIds.value,
|
"disease": selectedDiseaseIds.value,
|
||||||
|
"city_id": cityModel!.id,
|
||||||
|
"UTC": cityModel!.UTC,
|
||||||
};
|
};
|
||||||
var response =
|
var response =
|
||||||
await EasyDartModule.dio.put(queryUrl, data: jsonEncode(data));
|
await EasyDartModule.dio.put(queryUrl, data: jsonEncode(data));
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import 'package:syncfusion_localizations/syncfusion_localizations.dart';
|
|||||||
import 'package:vbvs_app/common/color/ServiceConstant.dart';
|
import 'package:vbvs_app/common/color/ServiceConstant.dart';
|
||||||
import 'package:vbvs_app/common/color/appConstants.dart';
|
import 'package:vbvs_app/common/color/appConstants.dart';
|
||||||
import 'package:vbvs_app/common/color/app_uri_status.dart';
|
import 'package:vbvs_app/common/color/app_uri_status.dart';
|
||||||
|
import 'package:vbvs_app/common/pojo/city.dart';
|
||||||
import 'package:vbvs_app/common/util/CheckNetwork.dart';
|
import 'package:vbvs_app/common/util/CheckNetwork.dart';
|
||||||
import 'package:vbvs_app/common/util/CommonVariables.dart';
|
import 'package:vbvs_app/common/util/CommonVariables.dart';
|
||||||
import 'package:vbvs_app/common/util/DailyLogUtils.dart';
|
import 'package:vbvs_app/common/util/DailyLogUtils.dart';
|
||||||
@@ -910,6 +911,16 @@ class MyApp extends StatelessWidget {
|
|||||||
return Obx(() {
|
return Obx(() {
|
||||||
var aa = languageController.appLocale.value;
|
var aa = languageController.appLocale.value;
|
||||||
return GetMaterialApp(
|
return GetMaterialApp(
|
||||||
|
builder: (context, child) {
|
||||||
|
final mediaQuery = MediaQuery.of(context);
|
||||||
|
|
||||||
|
return MediaQuery(
|
||||||
|
data: mediaQuery.copyWith(
|
||||||
|
textScaler: const TextScaler.linear(1.0), // 👈 新写法
|
||||||
|
),
|
||||||
|
child: child!,
|
||||||
|
);
|
||||||
|
},
|
||||||
translations: AppLanguage(),
|
translations: AppLanguage(),
|
||||||
// locale: const Locale("zh", "CN"),
|
// locale: const Locale("zh", "CN"),
|
||||||
locale: AppLanguage().currentLocale, // ✅ 动态读取当前语言
|
locale: AppLanguage().currentLocale, // ✅ 动态读取当前语言
|
||||||
@@ -958,6 +969,7 @@ class MyApp extends StatelessWidget {
|
|||||||
Get.lazyPut(() => AuthBindTelController()),
|
Get.lazyPut(() => AuthBindTelController()),
|
||||||
Get.lazyPut(() => MHTLoginController()),
|
Get.lazyPut(() => MHTLoginController()),
|
||||||
Get.lazyPut(() => MHTRegisterController()),
|
Get.lazyPut(() => MHTRegisterController()),
|
||||||
|
Get.lazyPut(() => CityModelController()),
|
||||||
]));
|
]));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,11 @@ import 'package:flutter_svg/svg.dart';
|
|||||||
import 'package:flutterflow_ui/flutterflow_ui.dart';
|
import 'package:flutterflow_ui/flutterflow_ui.dart';
|
||||||
import 'package:vbvs_app/common/color/appConstants.dart';
|
import 'package:vbvs_app/common/color/appConstants.dart';
|
||||||
import 'package:vbvs_app/common/color/app_uri_status.dart';
|
import 'package:vbvs_app/common/color/app_uri_status.dart';
|
||||||
|
import 'package:vbvs_app/common/pojo/city.dart';
|
||||||
|
import 'package:vbvs_app/common/util/EventBus.dart';
|
||||||
import 'package:vbvs_app/common/util/FitTool.dart';
|
import 'package:vbvs_app/common/util/FitTool.dart';
|
||||||
import 'package:vbvs_app/common/util/MyUtils.dart';
|
import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||||
|
import 'package:vbvs_app/common/util/eventType.dart';
|
||||||
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||||
import 'package:vbvs_app/component/tool/CustomCard.dart';
|
import 'package:vbvs_app/component/tool/CustomCard.dart';
|
||||||
import 'package:vbvs_app/component/tool/NewTopSlideNotification.dart';
|
import 'package:vbvs_app/component/tool/NewTopSlideNotification.dart';
|
||||||
@@ -50,6 +53,16 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
late StreamSubscription<SwitchLanguageEvent> subscription;
|
||||||
|
|
||||||
|
// 监听切换语言
|
||||||
|
subscription = EventBus().on<SwitchLanguageEvent>().listen((event) async {
|
||||||
|
final CityModelController cityController =
|
||||||
|
Get.find<CityModelController>();
|
||||||
|
ef.log("切换语言事件通知:${event.language}");
|
||||||
|
cityController.cityList = [];
|
||||||
|
await initializeCityData();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showPopup() {
|
void _showPopup() {
|
||||||
@@ -454,7 +467,8 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
|||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
),
|
),
|
||||||
cursorColor: Colors.white,
|
cursorColor: Colors.white,
|
||||||
initialValue: widget.device['person']['name'] == null ||
|
initialValue: widget.device['person'] == null ||
|
||||||
|
widget.device['person']['name'] == null ||
|
||||||
widget.device['person']['name'] == ""
|
widget.device['person']['name'] == ""
|
||||||
? "体征监测设备".tr
|
? "体征监测设备".tr
|
||||||
: widget.device['person']['name'],
|
: widget.device['person']['name'],
|
||||||
@@ -1093,6 +1107,22 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
|||||||
personController.dateTime =
|
personController.dateTime =
|
||||||
MyUtils.formatBirthdayTime(
|
MyUtils.formatBirthdayTime(
|
||||||
widget.device['person']['birthday']);
|
widget.device['person']['birthday']);
|
||||||
|
if (widget.device['person']['city_id'] != null) {
|
||||||
|
// 根据city_id查找完整的城市数据
|
||||||
|
final int cityId =
|
||||||
|
widget.device['person']['city_id'];
|
||||||
|
await initializeCityData();
|
||||||
|
final CityModel? completeCityData =
|
||||||
|
_findCityDataById(cityId);
|
||||||
|
|
||||||
|
if (completeCityData != null) {
|
||||||
|
personController.cityModel = completeCityData;
|
||||||
|
} else {
|
||||||
|
personController.cityModel = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
personController.cityModel = null;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
personController.update_person_mac.value =
|
personController.update_person_mac.value =
|
||||||
widget.device['mac'];
|
widget.device['mac'];
|
||||||
@@ -1104,6 +1134,7 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
|||||||
personController.height.value = "";
|
personController.height.value = "";
|
||||||
personController.weight.value = "";
|
personController.weight.value = "";
|
||||||
personController.diseaseList.value = [];
|
personController.diseaseList.value = [];
|
||||||
|
personController.cityModel = null;
|
||||||
}
|
}
|
||||||
await Get.toNamed("/updatePersonPage",
|
await Get.toNamed("/updatePersonPage",
|
||||||
arguments: widget.device['bind_type']);
|
arguments: widget.device['bind_type']);
|
||||||
@@ -1497,4 +1528,96 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
|||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 根据ID查找完整的城市数据
|
||||||
|
CityModel? _findCityDataById(int cityId) {
|
||||||
|
try {
|
||||||
|
if (cityId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 从加载的城市数据中查找
|
||||||
|
final cityData = Get.find<CityModelController>().cityList;
|
||||||
|
|
||||||
|
// 遍历三级数据结构查找匹配的ID
|
||||||
|
for (var country in cityData) {
|
||||||
|
// 检查国家节点
|
||||||
|
if (country.id == cityId) {
|
||||||
|
return CityModel(
|
||||||
|
id: country.id,
|
||||||
|
value: country.value,
|
||||||
|
label: country.label,
|
||||||
|
country: country.value ?? country.country,
|
||||||
|
province: null,
|
||||||
|
city: null,
|
||||||
|
UTC: country.UTC,
|
||||||
|
children: country.children,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查省份节点
|
||||||
|
for (var province in country.children ?? []) {
|
||||||
|
if (province.id == cityId) {
|
||||||
|
return CityModel(
|
||||||
|
id: province.id,
|
||||||
|
value: province.value,
|
||||||
|
label: province.label,
|
||||||
|
country: country.value ?? country.country,
|
||||||
|
province: province.value ?? province.province,
|
||||||
|
city: null,
|
||||||
|
UTC: province.UTC,
|
||||||
|
children: province.children,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查城市节点
|
||||||
|
for (var city in province.children ?? []) {
|
||||||
|
if (city.id == cityId) {
|
||||||
|
return CityModel(
|
||||||
|
id: city.id,
|
||||||
|
value: city.value,
|
||||||
|
label: city.label,
|
||||||
|
country: country.value ?? country.country,
|
||||||
|
province: province.value ?? province.province,
|
||||||
|
city: city.value ?? city.city,
|
||||||
|
UTC: city.UTC,
|
||||||
|
children: city.children,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
ef.log("根据ID查找城市数据失败:$e");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future initializeCityData() async {
|
||||||
|
final CityModelController cityController = Get.find<CityModelController>();
|
||||||
|
|
||||||
|
// 确保城市数据已加载
|
||||||
|
if (!cityController.isDataLoaded) {
|
||||||
|
await cityController.loadAndSetCityData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果device中有city_id,查找并设置城市数据
|
||||||
|
if (widget.device['person']['city_id'] != null) {
|
||||||
|
final String cityId = widget.device['person']['city_id'].toString();
|
||||||
|
ef.log("开始查找city_id: $cityId");
|
||||||
|
|
||||||
|
final CityModel? completeCityData = cityController.findCityById(cityId);
|
||||||
|
|
||||||
|
if (completeCityData != null) {
|
||||||
|
personController.cityModel = completeCityData;
|
||||||
|
ef.log("成功设置城市数据: ${completeCityData.displayName}");
|
||||||
|
} else {
|
||||||
|
personController.cityModel = CityModel(id: int.tryParse(cityId));
|
||||||
|
ef.log("未找到对应城市数据,创建默认CityModel");
|
||||||
|
}
|
||||||
|
|
||||||
|
personController.updateAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -283,6 +283,7 @@ import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
|
|||||||
import 'package:vbvs_app/controller/main_bottom/global_controller.dart';
|
import 'package:vbvs_app/controller/main_bottom/global_controller.dart';
|
||||||
import 'package:vbvs_app/controller/main_bottom/main_page_controller.dart';
|
import 'package:vbvs_app/controller/main_bottom/main_page_controller.dart';
|
||||||
import 'package:vbvs_app/controller/message/message_controller.dart';
|
import 'package:vbvs_app/controller/message/message_controller.dart';
|
||||||
|
import 'package:vbvs_app/controller/setting/language/language_controller.dart';
|
||||||
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
||||||
import 'package:vbvs_app/controller/user_info_controller.dart';
|
import 'package:vbvs_app/controller/user_info_controller.dart';
|
||||||
import 'package:vbvs_app/enum/APPPackageType.dart';
|
import 'package:vbvs_app/enum/APPPackageType.dart';
|
||||||
@@ -302,6 +303,7 @@ class MainPageBottomChange extends GetView<MainPageController> {
|
|||||||
late final List<Widget> arr;
|
late final List<Widget> arr;
|
||||||
late final List<BottomNavigationBarItem> bottomItems;
|
late final List<BottomNavigationBarItem> bottomItems;
|
||||||
DateTime? _lastBackPressedTime;
|
DateTime? _lastBackPressedTime;
|
||||||
|
LanguageController languageController = Get.find();
|
||||||
|
|
||||||
MainPageBottomChange({super.key}) {
|
MainPageBottomChange({super.key}) {
|
||||||
// ✅ 根据是否测试账号动态生成页面
|
// ✅ 根据是否测试账号动态生成页面
|
||||||
@@ -408,122 +410,133 @@ class MainPageBottomChange extends GetView<MainPageController> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return PopScope(
|
return Obx(() {
|
||||||
canPop: false,
|
final currentLanguage =
|
||||||
onPopInvokedWithResult: (disposition, result) async {
|
languageController.selectLanguage.value; // 监听此变量变化
|
||||||
UserInfoController userInfoController = Get.find();
|
return PopScope(
|
||||||
if (userInfoController.model.isProgrammaticPop) {
|
canPop: false,
|
||||||
userInfoController.model.isProgrammaticPop = false;
|
onPopInvokedWithResult: (disposition, result) async {
|
||||||
return;
|
UserInfoController userInfoController = Get.find();
|
||||||
}
|
if (userInfoController.model.isProgrammaticPop) {
|
||||||
|
userInfoController.model.isProgrammaticPop = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (Platform.isAndroid) {
|
// if (Platform.isAndroid) {
|
||||||
Get.back();
|
// Get.back();
|
||||||
}
|
// }
|
||||||
},
|
if (Platform.isAndroid) {
|
||||||
child: Obx(() {
|
var flag = await _handleBackPressed(context);
|
||||||
int currentIndex = controller.model.currentIndex;
|
if (flag) {
|
||||||
|
SystemNavigator.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Obx(() {
|
||||||
|
int currentIndex = controller.model.currentIndex;
|
||||||
|
|
||||||
// ✅ 防止 index 超出范围(例如测试账号少一个 tab)
|
// ✅ 防止 index 超出范围(例如测试账号少一个 tab)
|
||||||
if (currentIndex >= arr.length) {
|
if (currentIndex >= arr.length) {
|
||||||
currentIndex = 0;
|
currentIndex = 0;
|
||||||
controller.model.currentIndex = 0;
|
controller.model.currentIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (globalController.model.hideBottomNavigationBar == true) {
|
if (globalController.model.hideBottomNavigationBar == true) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: IndexedStack(
|
|
||||||
index: currentIndex,
|
|
||||||
children:
|
|
||||||
arr.map((page) => SizedBox.expand(child: page)).toList(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Container(
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
image: DecorationImage(
|
|
||||||
image: AssetImage('assets/img/bgImage.png'),
|
|
||||||
fit: BoxFit.fill,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Scaffold(
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
body: IndexedStack(
|
body: IndexedStack(
|
||||||
index: currentIndex,
|
index: currentIndex,
|
||||||
children:
|
children:
|
||||||
arr.map((page) => SizedBox.expand(child: page)).toList(),
|
arr.map((page) => SizedBox.expand(child: page)).toList(),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: Theme(
|
);
|
||||||
data: Theme.of(context).copyWith(
|
} else {
|
||||||
splashFactory: NoSplash.splashFactory,
|
return Container(
|
||||||
splashColor: Colors.transparent,
|
decoration: const BoxDecoration(
|
||||||
highlightColor: Colors.transparent,
|
image: DecorationImage(
|
||||||
|
image: AssetImage('assets/img/bgImage.png'),
|
||||||
|
fit: BoxFit.fill,
|
||||||
),
|
),
|
||||||
child: Container(
|
),
|
||||||
decoration: BoxDecoration(
|
child: Scaffold(
|
||||||
border: Border(
|
backgroundColor: Colors.transparent,
|
||||||
top: BorderSide(
|
body: IndexedStack(
|
||||||
color:
|
index: currentIndex,
|
||||||
themeController.currentColor.sc4.withOpacity(0.5),
|
children:
|
||||||
width: AppConstants().border_width,
|
arr.map((page) => SizedBox.expand(child: page)).toList(),
|
||||||
|
),
|
||||||
|
bottomNavigationBar: Theme(
|
||||||
|
data: Theme.of(context).copyWith(
|
||||||
|
splashFactory: NoSplash.splashFactory,
|
||||||
|
splashColor: Colors.transparent,
|
||||||
|
highlightColor: Colors.transparent,
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(
|
||||||
|
color:
|
||||||
|
themeController.currentColor.sc4.withOpacity(0.5),
|
||||||
|
width: AppConstants().border_width,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
child: BottomNavigationBar(
|
||||||
child: BottomNavigationBar(
|
unselectedItemColor: themeController.currentColor.sc4,
|
||||||
unselectedItemColor: themeController.currentColor.sc4,
|
selectedItemColor: themeController.currentColor.sc1,
|
||||||
selectedItemColor: themeController.currentColor.sc1,
|
backgroundColor: themeController.currentColor.sc5,
|
||||||
backgroundColor: themeController.currentColor.sc5,
|
selectedFontSize: 26.rpx,
|
||||||
selectedFontSize: 26.rpx,
|
unselectedFontSize: 26.rpx,
|
||||||
unselectedFontSize: 26.rpx,
|
type: BottomNavigationBarType.fixed,
|
||||||
type: BottomNavigationBarType.fixed,
|
currentIndex: currentIndex,
|
||||||
currentIndex: currentIndex,
|
onTap: (index) {
|
||||||
onTap: (index) {
|
Future.delayed(const Duration(milliseconds: 100), () {
|
||||||
Future.delayed(const Duration(milliseconds: 100), () {
|
UserInfoController userInfoController = Get.find();
|
||||||
UserInfoController userInfoController = Get.find();
|
bool isLoggedIn = userInfoController.model.login ==
|
||||||
bool isLoggedIn = userInfoController.model.login ==
|
LoginStatus.LOGIN.code;
|
||||||
LoginStatus.LOGIN.code;
|
|
||||||
|
|
||||||
// ✅ 仅非测试账号检查登录状态
|
// ✅ 仅非测试账号检查登录状态
|
||||||
if (!AppConstants.is_test_account &&
|
if (!AppConstants.is_test_account &&
|
||||||
(index == 2) &&
|
(index == 2) &&
|
||||||
!isLoggedIn) {
|
!isLoggedIn) {
|
||||||
TopSlideNotification.show(
|
TopSlideNotification.show(
|
||||||
context,
|
context,
|
||||||
text: "必须登录提示".tr,
|
text: "必须登录提示".tr,
|
||||||
textColor: themeController.currentColor.sc9,
|
textColor: themeController.currentColor.sc9,
|
||||||
);
|
);
|
||||||
Future.delayed(const Duration(milliseconds: 50), () {
|
Future.delayed(const Duration(milliseconds: 50),
|
||||||
if (Get.currentRoute == '/ePage' ||
|
|
||||||
Get.currentRoute == '/messagePage') {
|
|
||||||
Get.back();
|
|
||||||
}
|
|
||||||
Future.delayed(const Duration(milliseconds: 100),
|
|
||||||
() {
|
() {
|
||||||
Get.toNamed("/otherLoginPage");
|
if (Get.currentRoute == '/ePage' ||
|
||||||
|
Get.currentRoute == '/messagePage') {
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
Future.delayed(const Duration(milliseconds: 100),
|
||||||
|
() {
|
||||||
|
Get.toNamed("/otherLoginPage");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (controller.model.currentIndex != index) {
|
if (controller.model.currentIndex != index) {
|
||||||
globalController.model.hideBottomNavigationBar =
|
globalController.model.hideBottomNavigationBar =
|
||||||
false;
|
false;
|
||||||
globalController.updateAll();
|
globalController.updateAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
controller.model.currentIndex = index;
|
controller.model.currentIndex = index;
|
||||||
controller.updateAll();
|
controller.updateAll();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
items: bottomItems,
|
items: bottomItems,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
}
|
}),
|
||||||
}),
|
);
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _handleBackPressed(BuildContext context) async {
|
Future<bool> _handleBackPressed(BuildContext context) async {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:flutterflow_ui/flutterflow_ui.dart';
|
|||||||
import 'package:vbvs_app/common/color/ServiceConstant.dart';
|
import 'package:vbvs_app/common/color/ServiceConstant.dart';
|
||||||
import 'package:vbvs_app/common/color/appConstants.dart';
|
import 'package:vbvs_app/common/color/appConstants.dart';
|
||||||
import 'package:vbvs_app/common/color/app_uri_status.dart';
|
import 'package:vbvs_app/common/color/app_uri_status.dart';
|
||||||
|
import 'package:vbvs_app/common/pojo/city.dart';
|
||||||
import 'package:vbvs_app/common/util/FitTool.dart';
|
import 'package:vbvs_app/common/util/FitTool.dart';
|
||||||
import 'package:vbvs_app/common/util/MyUtils.dart';
|
import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||||
import 'package:vbvs_app/common/util/requestWithLog.dart';
|
import 'package:vbvs_app/common/util/requestWithLog.dart';
|
||||||
@@ -18,6 +19,7 @@ import 'package:vbvs_app/controller/person/person_controller.dart';
|
|||||||
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
||||||
import 'package:vbvs_app/controller/user_info_controller.dart';
|
import 'package:vbvs_app/controller/user_info_controller.dart';
|
||||||
import 'package:vbvs_app/model/api_response.dart';
|
import 'package:vbvs_app/model/api_response.dart';
|
||||||
|
import 'package:vbvs_app/pages/person/select_city.dart';
|
||||||
import 'package:vbvs_app/pages/person/select_time.dart';
|
import 'package:vbvs_app/pages/person/select_time.dart';
|
||||||
|
|
||||||
class PersonPage extends StatefulWidget {
|
class PersonPage extends StatefulWidget {
|
||||||
@@ -34,6 +36,9 @@ class _EPageState extends State<PersonPage> {
|
|||||||
PersonController personController = Get.find();
|
PersonController personController = Get.find();
|
||||||
ThemeController themeController = Get.find();
|
ThemeController themeController = Get.find();
|
||||||
|
|
||||||
|
final CityModelController cityController = Get.find<CityModelController>();
|
||||||
|
late Future<List<CityModel>> cityDataFuture;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -55,6 +60,10 @@ class _EPageState extends State<PersonPage> {
|
|||||||
personController.weight.value = "";
|
personController.weight.value = "";
|
||||||
personController.height.value = "";
|
personController.height.value = "";
|
||||||
personController.dateTime = null;
|
personController.dateTime = null;
|
||||||
|
|
||||||
|
cityDataFuture = cityController.loadAndSetCityData().then((success) {
|
||||||
|
return cityController.cityList;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -533,6 +542,65 @@ class _EPageState extends State<PersonPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
70.rpx, 50.rpx, 70.rpx, 0),
|
||||||
|
child: Container(
|
||||||
|
height: 100.rpx,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(50.rpx),
|
||||||
|
border: Border.all(
|
||||||
|
color: themeController.currentColor.sc4
|
||||||
|
.withOpacity(0.5),
|
||||||
|
width: AppConstants().border_width,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
FocusScope.of(context)
|
||||||
|
.requestFocus(FocusNode());
|
||||||
|
Future.delayed(Duration(milliseconds: 250),
|
||||||
|
() {
|
||||||
|
// 使用当前选中的城市数据,如果没有则创建默认
|
||||||
|
CityModel currentCity =
|
||||||
|
personController.cityModel ??
|
||||||
|
CityModel();
|
||||||
|
showCitySelectionDialog(
|
||||||
|
context,
|
||||||
|
selectedCity: currentCity,
|
||||||
|
onCityChanged: (CityModel newCity) {
|
||||||
|
// 处理城市选择变化
|
||||||
|
print(
|
||||||
|
'Selected city: ${newCity.toJson()}');
|
||||||
|
personController.cityModel = newCity;
|
||||||
|
personController.updateAll();
|
||||||
|
},
|
||||||
|
title: "选择城市".tr,
|
||||||
|
cityDataFuture:
|
||||||
|
cityDataFuture, // 传入预加载的数据
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
_getDetailedCityDisplayText(
|
||||||
|
personController.cityModel),
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Readex Pro',
|
||||||
|
color: personController.cityModel !=
|
||||||
|
null
|
||||||
|
? themeController.currentColor.sc3
|
||||||
|
: themeController.currentColor.sc4,
|
||||||
|
fontSize:
|
||||||
|
AppConstants().normal_text_fontSize,
|
||||||
|
letterSpacing: 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsetsDirectional.fromSTEB(
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
0, 117.rpx, 0, 0),
|
0, 117.rpx, 0, 0),
|
||||||
@@ -696,6 +764,29 @@ class _EPageState extends State<PersonPage> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _getDetailedCityDisplayText(CityModel? cityModel) {
|
||||||
|
if (cityModel == null) {
|
||||||
|
return '选择城市'.tr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据数据层级显示不同的格式
|
||||||
|
if (cityModel.city != null && cityModel.city!.isNotEmpty) {
|
||||||
|
// 三级数据:国家-省份-城市
|
||||||
|
return '${cityModel.country ?? ''}-${cityModel.province ?? ''}-${cityModel.city ?? cityModel.value ?? ''}';
|
||||||
|
} else if (cityModel.province != null && cityModel.province!.isNotEmpty) {
|
||||||
|
// 二级数据:国家-省份
|
||||||
|
return '${cityModel.country ?? ''}-${cityModel.province ?? cityModel.value ?? ''}';
|
||||||
|
} else if (cityModel.country != null && cityModel.country!.isNotEmpty) {
|
||||||
|
// 一级数据:国家
|
||||||
|
return cityModel.country!;
|
||||||
|
} else if (cityModel.value != null && cityModel.value!.isNotEmpty) {
|
||||||
|
// 只有 value 字段
|
||||||
|
return cityModel.value!;
|
||||||
|
} else {
|
||||||
|
return '选择城市'.tr;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateDeviceBindStatus(String mac) {
|
void updateDeviceBindStatus(String mac) {
|
||||||
|
|||||||
1221
lib/pages/person/select_city.dart
Normal file
@@ -1,11 +1,17 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:ef/ef.dart';
|
import 'package:ef/ef.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:vbvs_app/common/color/appConstants.dart';
|
import 'package:vbvs_app/common/color/appConstants.dart';
|
||||||
|
import 'package:vbvs_app/common/pojo/city.dart';
|
||||||
import 'package:vbvs_app/common/util/FitTool.dart';
|
import 'package:vbvs_app/common/util/FitTool.dart';
|
||||||
import 'package:vbvs_app/common/util/MyUtils.dart';
|
import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||||
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||||
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
||||||
|
import 'package:vbvs_app/language/AppLanguage.dart';
|
||||||
import 'package:vbvs_app/pages/common/selectDialog.dart';
|
import 'package:vbvs_app/pages/common/selectDialog.dart';
|
||||||
|
|
||||||
// Future showDateSelectionDialog(BuildContext context,
|
// Future showDateSelectionDialog(BuildContext context,
|
||||||
@@ -814,3 +820,4 @@ Future<void> showWeightPickerDialog(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:flutterflow_ui/flutterflow_ui.dart';
|
|||||||
import 'package:vbvs_app/common/color/ServiceConstant.dart';
|
import 'package:vbvs_app/common/color/ServiceConstant.dart';
|
||||||
import 'package:vbvs_app/common/color/appConstants.dart';
|
import 'package:vbvs_app/common/color/appConstants.dart';
|
||||||
import 'package:vbvs_app/common/color/app_uri_status.dart';
|
import 'package:vbvs_app/common/color/app_uri_status.dart';
|
||||||
|
import 'package:vbvs_app/common/pojo/city.dart';
|
||||||
import 'package:vbvs_app/common/util/FitTool.dart';
|
import 'package:vbvs_app/common/util/FitTool.dart';
|
||||||
import 'package:vbvs_app/common/util/MyUtils.dart';
|
import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||||
import 'package:vbvs_app/common/util/requestWithLog.dart';
|
import 'package:vbvs_app/common/util/requestWithLog.dart';
|
||||||
@@ -20,6 +21,7 @@ import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
|||||||
import 'package:vbvs_app/controller/user_info_controller.dart';
|
import 'package:vbvs_app/controller/user_info_controller.dart';
|
||||||
import 'package:vbvs_app/enum/BindType.dart';
|
import 'package:vbvs_app/enum/BindType.dart';
|
||||||
import 'package:vbvs_app/model/api_response.dart';
|
import 'package:vbvs_app/model/api_response.dart';
|
||||||
|
import 'package:vbvs_app/pages/person/select_city.dart';
|
||||||
import 'package:vbvs_app/pages/person/select_time.dart';
|
import 'package:vbvs_app/pages/person/select_time.dart';
|
||||||
|
|
||||||
class UpdatePersonPage extends StatefulWidget {
|
class UpdatePersonPage extends StatefulWidget {
|
||||||
@@ -38,6 +40,9 @@ class _UpdatePageState extends State<UpdatePersonPage> {
|
|||||||
BodyDeviceController bodyDeviceController = Get.find();
|
BodyDeviceController bodyDeviceController = Get.find();
|
||||||
ThemeController themeController = Get.find();
|
ThemeController themeController = Get.find();
|
||||||
|
|
||||||
|
final CityModelController cityController = Get.find<CityModelController>();
|
||||||
|
late Future<List<CityModel>> cityDataFuture;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -51,6 +56,9 @@ class _UpdatePageState extends State<UpdatePersonPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
cityDataFuture = cityController.loadAndSetCityData().then((success) {
|
||||||
|
return cityController.cityList;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -66,6 +74,7 @@ class _UpdatePageState extends State<UpdatePersonPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
|
resizeToAvoidBottomInset: false,
|
||||||
backgroundColor: Colors.transparent, // 加上这一行
|
backgroundColor: Colors.transparent, // 加上这一行
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: themeController.currentColor.sc17,
|
backgroundColor: themeController.currentColor.sc17,
|
||||||
@@ -575,6 +584,72 @@ class _UpdatePageState extends State<UpdatePersonPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
70.rpx, 50.rpx, 70.rpx, 0),
|
||||||
|
child: Container(
|
||||||
|
height: 100.rpx,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(50.rpx),
|
||||||
|
border: Border.all(
|
||||||
|
color: themeController.currentColor.sc4
|
||||||
|
.withOpacity(0.5),
|
||||||
|
width: AppConstants().border_width,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
if (widget.status == BindType.share.code) {
|
||||||
|
TopSlideNotification.show(context,
|
||||||
|
text: "被分享用户只能修改用户名称",
|
||||||
|
textColor:
|
||||||
|
themeController.currentColor.sc9);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FocusScope.of(context)
|
||||||
|
.requestFocus(FocusNode());
|
||||||
|
Future.delayed(Duration(milliseconds: 250),
|
||||||
|
() {
|
||||||
|
// 使用当前选中的城市数据,如果没有则创建默认
|
||||||
|
CityModel currentCity =
|
||||||
|
personController.cityModel ??
|
||||||
|
CityModel();
|
||||||
|
showCitySelectionDialog(
|
||||||
|
context,
|
||||||
|
selectedCity: currentCity,
|
||||||
|
onCityChanged: (CityModel newCity) {
|
||||||
|
// 处理城市选择变化
|
||||||
|
print(
|
||||||
|
'Selected city: ${newCity.toJson()}');
|
||||||
|
personController.cityModel = newCity;
|
||||||
|
personController.updateAll();
|
||||||
|
},
|
||||||
|
title: "选择城市".tr,
|
||||||
|
cityDataFuture:
|
||||||
|
cityDataFuture, // 传入预加载的数据
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
_getDetailedCityDisplayText(
|
||||||
|
personController.cityModel),
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Readex Pro',
|
||||||
|
color: personController.cityModel !=
|
||||||
|
null
|
||||||
|
? themeController.currentColor.sc3
|
||||||
|
: themeController.currentColor.sc4,
|
||||||
|
fontSize:
|
||||||
|
AppConstants().normal_text_fontSize,
|
||||||
|
letterSpacing: 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsetsDirectional.fromSTEB(
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
0, 117.rpx, 0, 0),
|
0, 117.rpx, 0, 0),
|
||||||
@@ -782,4 +857,29 @@ class _UpdatePageState extends State<UpdatePersonPage> {
|
|||||||
onFailure: (res) {},
|
onFailure: (res) {},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取城市显示文本
|
||||||
|
// 更详细的显示逻辑(可选)
|
||||||
|
String _getDetailedCityDisplayText(CityModel? cityModel) {
|
||||||
|
if (cityModel == null) {
|
||||||
|
return '选择城市'.tr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据数据层级显示不同的格式
|
||||||
|
if (cityModel.city != null && cityModel.city!.isNotEmpty) {
|
||||||
|
// 三级数据:国家-省份-城市
|
||||||
|
return '${cityModel.country ?? ''}-${cityModel.province ?? ''}-${cityModel.city ?? cityModel.value ?? ''}';
|
||||||
|
} else if (cityModel.province != null && cityModel.province!.isNotEmpty) {
|
||||||
|
// 二级数据:国家-省份
|
||||||
|
return '${cityModel.country ?? ''}-${cityModel.province ?? cityModel.value ?? ''}';
|
||||||
|
} else if (cityModel.country != null && cityModel.country!.isNotEmpty) {
|
||||||
|
// 一级数据:国家
|
||||||
|
return cityModel.country!;
|
||||||
|
} else if (cityModel.value != null && cityModel.value!.isNotEmpty) {
|
||||||
|
// 只有 value 字段
|
||||||
|
return cityModel.value!;
|
||||||
|
} else {
|
||||||
|
return '选择城市'.tr;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,13 +152,14 @@ class _LanguageSettingState extends State<LanguageSetting> {
|
|||||||
languageController
|
languageController
|
||||||
.updateAll();
|
.updateAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await AppLanguage()
|
||||||
|
.loadLanguage(language
|
||||||
|
.language_code); // 加载语言
|
||||||
EventBus().emit(
|
EventBus().emit(
|
||||||
SwitchLanguageEvent(
|
SwitchLanguageEvent(
|
||||||
language
|
language
|
||||||
.language_code));
|
.language_code));
|
||||||
await AppLanguage()
|
|
||||||
.loadLanguage(language
|
|
||||||
.language_code); // 加载语言
|
|
||||||
languageController
|
languageController
|
||||||
.updateAll();
|
.updateAll();
|
||||||
await ef.kvdb.write(
|
await ef.kvdb.write(
|
||||||
|
|||||||
@@ -1,366 +1,11 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:ef/ef.dart';
|
import 'package:ef/ef.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:vbvs_app/common/util/FitTool.dart';
|
import 'package:vbvs_app/common/util/FitTool.dart';
|
||||||
import 'package:vbvs_app/common/util/MyUtils.dart';
|
import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||||
import 'dart:ui' as ui;
|
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
//根据数据自定义
|
|
||||||
// class LineChartByRange extends StatefulWidget {
|
|
||||||
// final List<Map<String, dynamic>> showLabel;
|
|
||||||
// final int startTime;
|
|
||||||
// final int endTime;
|
|
||||||
// final int? threshold;
|
|
||||||
|
|
||||||
// const LineChartByRange({
|
|
||||||
// Key? key,
|
|
||||||
// required this.showLabel,
|
|
||||||
// required this.startTime,
|
|
||||||
// required this.endTime,
|
|
||||||
// this.threshold, // 新增
|
|
||||||
// }) : super(key: key);
|
|
||||||
|
|
||||||
// @override
|
|
||||||
// State<LineChartByRange> createState() => _LineChartByRangeState();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// class _LineChartByRangeState extends State<LineChartByRange> {
|
|
||||||
// Offset? selectedOffset;
|
|
||||||
// Map<String, dynamic>? selectedData;
|
|
||||||
|
|
||||||
// @override
|
|
||||||
// Widget build(BuildContext context) {
|
|
||||||
// if (widget.showLabel.isEmpty) return const SizedBox();
|
|
||||||
|
|
||||||
// int maxTimes = widget.showLabel
|
|
||||||
// .map((e) => e['times'] ?? 0)
|
|
||||||
// .reduce((a, b) => a > b ? a : b);
|
|
||||||
// int yMax = (maxTimes / 10).ceil() * 10;
|
|
||||||
// if (yMax == 0) yMax = 10;
|
|
||||||
|
|
||||||
// DateTime minTime = DateTime.fromMillisecondsSinceEpoch(widget.startTime);
|
|
||||||
// DateTime maxTime = DateTime.fromMillisecondsSinceEpoch(widget.endTime);
|
|
||||||
|
|
||||||
// return GestureDetector(
|
|
||||||
// onTapDown: (details) {
|
|
||||||
// RenderBox box = context.findRenderObject() as RenderBox;
|
|
||||||
// final localPosition = box.globalToLocal(details.globalPosition);
|
|
||||||
|
|
||||||
// // 查找是否点击到某个点
|
|
||||||
// for (var item in widget.showLabel) {
|
|
||||||
// int start = item['startTime'];
|
|
||||||
// int end = item['endTime'];
|
|
||||||
// int times = item['times'];
|
|
||||||
|
|
||||||
// double chartWidth = box.size.width - 40.rpx; // 与 painter 内一致处理
|
|
||||||
// double chartHeight = box.size.height - 30.rpx;
|
|
||||||
// double xStart = 20.rpx + 12.rpx;
|
|
||||||
|
|
||||||
// int totalDuration =
|
|
||||||
// maxTime.millisecondsSinceEpoch - minTime.millisecondsSinceEpoch;
|
|
||||||
|
|
||||||
// double startX = xStart +
|
|
||||||
// chartWidth *
|
|
||||||
// (start - minTime.millisecondsSinceEpoch) /
|
|
||||||
// totalDuration;
|
|
||||||
// double y = chartHeight * (1 - times / yMax);
|
|
||||||
|
|
||||||
// // 判断点击范围(圆点半径±6.rpx范围)
|
|
||||||
// if ((localPosition - Offset(startX, y)).distance < 10.rpx) {
|
|
||||||
// setState(() {
|
|
||||||
// selectedOffset = Offset(startX, y);
|
|
||||||
// selectedData = item;
|
|
||||||
// });
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// double endX = xStart +
|
|
||||||
// chartWidth *
|
|
||||||
// (end - minTime.millisecondsSinceEpoch) /
|
|
||||||
// totalDuration;
|
|
||||||
// if ((localPosition - Offset(endX, y)).distance < 10.rpx) {
|
|
||||||
// setState(() {
|
|
||||||
// selectedOffset = Offset(endX, y);
|
|
||||||
// selectedData = item;
|
|
||||||
// });
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 没点到,清除选中
|
|
||||||
// setState(() {
|
|
||||||
// selectedOffset = null;
|
|
||||||
// selectedData = null;
|
|
||||||
// });
|
|
||||||
// },
|
|
||||||
// child: Stack(
|
|
||||||
// children: [
|
|
||||||
// SizedBox(
|
|
||||||
// height: 500.rpx,
|
|
||||||
// child: CustomPaint(
|
|
||||||
// size: Size(double.infinity, 500.rpx),
|
|
||||||
// painter: _LineChartByRangePainter(
|
|
||||||
// data: widget.showLabel,
|
|
||||||
// yMax: yMax,
|
|
||||||
// minTime: minTime,
|
|
||||||
// maxTime: maxTime,
|
|
||||||
// threshold: widget.threshold, // 新增
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// if (selectedOffset != null && selectedData != null)
|
|
||||||
// Positioned(
|
|
||||||
// left: selectedOffset!.dx - 60.rpx,
|
|
||||||
// top: selectedOffset!.dy - 50.rpx,
|
|
||||||
// child: Container(
|
|
||||||
// padding:
|
|
||||||
// EdgeInsets.symmetric(horizontal: 12.rpx, vertical: 8.rpx),
|
|
||||||
// decoration: BoxDecoration(
|
|
||||||
// color: Colors.black.withOpacity(0.3),
|
|
||||||
// borderRadius: BorderRadius.circular(10.rpx),
|
|
||||||
// ),
|
|
||||||
// child: Text(
|
|
||||||
// '${DateFormat('HH:mm').format(DateTime.fromMillisecondsSinceEpoch(selectedData!['startTime']))} - '
|
|
||||||
// '${DateFormat('HH:mm').format(DateTime.fromMillisecondsSinceEpoch(selectedData!['endTime']))}\n'
|
|
||||||
// '次数: ${selectedData!['times']}',
|
|
||||||
// style: TextStyle(
|
|
||||||
// fontSize: 18.rpx,
|
|
||||||
// color: Colors.white,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// class _LineChartByRangePainter extends CustomPainter {
|
|
||||||
// final List<Map<String, dynamic>> data;
|
|
||||||
// final int yMax;
|
|
||||||
// final DateTime minTime;
|
|
||||||
// final DateTime maxTime;
|
|
||||||
// final int? threshold;
|
|
||||||
|
|
||||||
// _LineChartByRangePainter({
|
|
||||||
// required this.data,
|
|
||||||
// required this.yMax,
|
|
||||||
// required this.minTime,
|
|
||||||
// required this.maxTime,
|
|
||||||
// this.threshold,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// @override
|
|
||||||
// void paint(Canvas canvas, Size size) {
|
|
||||||
// double padding = 20.rpx;
|
|
||||||
// double labelInset = 12.rpx;
|
|
||||||
|
|
||||||
// final double xStart = padding + labelInset;
|
|
||||||
// final double xEnd = size.width - padding - labelInset;
|
|
||||||
// final double chartWidth = xEnd - xStart;
|
|
||||||
|
|
||||||
// double chartHeight = size.height - 30.rpx;
|
|
||||||
|
|
||||||
// int totalDuration =
|
|
||||||
// maxTime.millisecondsSinceEpoch - minTime.millisecondsSinceEpoch;
|
|
||||||
// if (totalDuration <= 0) return;
|
|
||||||
|
|
||||||
// Paint linePaint = Paint()
|
|
||||||
// ..style = PaintingStyle.stroke
|
|
||||||
// ..strokeWidth = 3.rpx
|
|
||||||
// ..color = stringToColor("#00C1AA")
|
|
||||||
// ..strokeCap = StrokeCap.round;
|
|
||||||
|
|
||||||
// Paint axisPaint = Paint()
|
|
||||||
// ..color = Colors.grey.withOpacity(0.4)
|
|
||||||
// ..strokeWidth = 1.rpx;
|
|
||||||
|
|
||||||
// Paint thresholdPaint = Paint()
|
|
||||||
// ..color = themeController.currentColor.sc9
|
|
||||||
// ..strokeWidth = 1.rpx;
|
|
||||||
|
|
||||||
// // 1. 阈值虚线(红色)
|
|
||||||
// if (threshold != null && threshold! >= 0 && threshold! <= yMax) {
|
|
||||||
// double yThreshold = chartHeight * (1 - threshold! / yMax);
|
|
||||||
// drawDashedLine(
|
|
||||||
// canvas,
|
|
||||||
// Offset(xStart, yThreshold),
|
|
||||||
// Offset(xEnd, yThreshold),
|
|
||||||
// thresholdPaint,
|
|
||||||
// dashWidth: 8.rpx,
|
|
||||||
// dashSpace: 6.rpx,
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 2. 绘制数据线段和圆点
|
|
||||||
// for (var item in data) {
|
|
||||||
// int start = item['startTime'];
|
|
||||||
// int end = item['endTime'];
|
|
||||||
// int times = item['times'];
|
|
||||||
|
|
||||||
// double startX = xStart +
|
|
||||||
// chartWidth * (start - minTime.millisecondsSinceEpoch) / totalDuration;
|
|
||||||
// double endX = xStart +
|
|
||||||
// chartWidth * (end - minTime.millisecondsSinceEpoch) / totalDuration;
|
|
||||||
// double y = chartHeight * (1 - times / yMax);
|
|
||||||
|
|
||||||
// // 设置颜色(根据 threshold 判断)
|
|
||||||
// Color pointColor;
|
|
||||||
// if (threshold != null && times >= threshold!) {
|
|
||||||
// pointColor = themeController.currentColor.sc9;
|
|
||||||
// } else {
|
|
||||||
// pointColor = stringToColor("#00C1AA");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Paint dynamicLinePaint = Paint()
|
|
||||||
// ..style = PaintingStyle.stroke
|
|
||||||
// ..strokeWidth = 3.rpx
|
|
||||||
// ..color = pointColor
|
|
||||||
// ..strokeCap = StrokeCap.round;
|
|
||||||
|
|
||||||
// Paint dynamicCirclePaint = Paint()
|
|
||||||
// ..style = PaintingStyle.fill
|
|
||||||
// ..color = pointColor;
|
|
||||||
|
|
||||||
// // 画线段
|
|
||||||
// canvas.drawLine(Offset(startX, y), Offset(endX, y), dynamicLinePaint);
|
|
||||||
|
|
||||||
// // 画起点和终点圆点
|
|
||||||
// canvas.drawCircle(Offset(startX, y), 6.rpx, dynamicCirclePaint);
|
|
||||||
// canvas.drawCircle(Offset(endX, y), 6.rpx, dynamicCirclePaint);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 3. Y轴辅助线和文字
|
|
||||||
// for (int i = 0; i <= 6; i++) {
|
|
||||||
// double y = chartHeight * i / 6;
|
|
||||||
|
|
||||||
// if (i == 6) {
|
|
||||||
// canvas.drawLine(Offset(xStart, y), Offset(xEnd, y), axisPaint);
|
|
||||||
// } else {
|
|
||||||
// drawDashedLine(
|
|
||||||
// canvas,
|
|
||||||
// Offset(xStart, y),
|
|
||||||
// Offset(xEnd, y),
|
|
||||||
// axisPaint,
|
|
||||||
// dashWidth: 8.rpx,
|
|
||||||
// dashSpace: 6.rpx,
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// TextPainter tp = TextPainter(
|
|
||||||
// text: TextSpan(
|
|
||||||
// text: '${yMax - (yMax * i / 6).round()}',
|
|
||||||
// style: TextStyle(
|
|
||||||
// fontSize: 18.rpx,
|
|
||||||
// color: themeController.currentColor.sc4,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// textDirection: ui.TextDirection.ltr,
|
|
||||||
// );
|
|
||||||
// tp.layout();
|
|
||||||
// tp.paint(canvas, Offset(0, y - tp.height / 2));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 4. X轴主线
|
|
||||||
// canvas.drawLine(
|
|
||||||
// Offset(xStart, chartHeight),
|
|
||||||
// Offset(xEnd, chartHeight),
|
|
||||||
// axisPaint,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// // 5. X轴时间文字(左右两侧)
|
|
||||||
// String leftLabel = DateFormat('HH:mm').format(minTime);
|
|
||||||
// TextPainter leftTp = TextPainter(
|
|
||||||
// text: TextSpan(
|
|
||||||
// text: leftLabel,
|
|
||||||
// style: TextStyle(
|
|
||||||
// fontSize: 18.rpx,
|
|
||||||
// color: themeController.currentColor.sc4,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// textDirection: ui.TextDirection.ltr,
|
|
||||||
// );
|
|
||||||
// leftTp.layout();
|
|
||||||
// leftTp.paint(canvas,
|
|
||||||
// Offset(padding + labelInset - leftTp.width / 2, chartHeight + 8.rpx));
|
|
||||||
|
|
||||||
// String rightLabel = DateFormat('HH:mm').format(maxTime);
|
|
||||||
// TextPainter rightTp = TextPainter(
|
|
||||||
// text: TextSpan(
|
|
||||||
// text: rightLabel,
|
|
||||||
// style: TextStyle(
|
|
||||||
// fontSize: 18.rpx,
|
|
||||||
// color: themeController.currentColor.sc4,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// textDirection: ui.TextDirection.ltr,
|
|
||||||
// );
|
|
||||||
// rightTp.layout();
|
|
||||||
// rightTp.paint(
|
|
||||||
// canvas,
|
|
||||||
// Offset(size.width - padding - labelInset - rightTp.width / 2,
|
|
||||||
// chartHeight + 8.rpx));
|
|
||||||
|
|
||||||
// // 6. 中间小时刻度
|
|
||||||
// int totalHours = maxTime.difference(minTime).inHours + 1;
|
|
||||||
// int startHour = minTime.hour;
|
|
||||||
|
|
||||||
// for (int i = 1; i < totalHours; i++) {
|
|
||||||
// double x = xStart + chartWidth * i / totalHours;
|
|
||||||
|
|
||||||
// int hourLabelNum = (startHour + i) % 24;
|
|
||||||
// String hourLabel = '$hourLabelNum';
|
|
||||||
|
|
||||||
// TextPainter tp = TextPainter(
|
|
||||||
// text: TextSpan(
|
|
||||||
// text: hourLabel,
|
|
||||||
// style: TextStyle(
|
|
||||||
// fontSize: 18.rpx,
|
|
||||||
// color: themeController.currentColor.sc4,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// textDirection: ui.TextDirection.ltr,
|
|
||||||
// );
|
|
||||||
// tp.layout();
|
|
||||||
// tp.paint(canvas, Offset(x - tp.width / 2, chartHeight + 8.rpx));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @override
|
|
||||||
// bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
|
||||||
|
|
||||||
// void drawDashedLine(
|
|
||||||
// Canvas canvas,
|
|
||||||
// Offset start,
|
|
||||||
// Offset end,
|
|
||||||
// Paint paint, {
|
|
||||||
// required double dashWidth,
|
|
||||||
// required double dashSpace,
|
|
||||||
// }) {
|
|
||||||
// final dx = end.dx - start.dx;
|
|
||||||
// final dy = end.dy - start.dy;
|
|
||||||
// final distance = sqrt(dx * dx + dy * dy);
|
|
||||||
// final direction = Offset(dx / distance, dy / distance);
|
|
||||||
|
|
||||||
// double drawn = 0;
|
|
||||||
// while (drawn < distance) {
|
|
||||||
// final from = start + direction * drawn;
|
|
||||||
// final to = start + direction * (drawn + dashWidth).clamp(0, distance);
|
|
||||||
// canvas.drawLine(from, to, paint);
|
|
||||||
// drawn += dashWidth + dashSpace;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:vbvs_app/common/util/FitTool.dart';
|
|
||||||
import 'package:vbvs_app/common/util/MyUtils.dart';
|
|
||||||
import 'dart:ui' as ui;
|
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
class LineChartByRange extends StatefulWidget {
|
class LineChartByRange extends StatefulWidget {
|
||||||
final List<Map<String, dynamic>> showLabel;
|
final List<Map<String, dynamic>> showLabel;
|
||||||
@@ -480,8 +125,10 @@ class _LineChartByRangeState extends State<LineChartByRange> {
|
|||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'${DateFormat('HH:mm').format(DateTime.fromMillisecondsSinceEpoch(selectedData!['startTime']))} - '
|
'${DateFormat('HH:mm').format(DateTime.fromMillisecondsSinceEpoch(selectedData!['startTime']))} - '
|
||||||
'${DateFormat('HH:mm').format(DateTime.fromMillisecondsSinceEpoch(selectedData!['endTime']))}\n'
|
'${DateFormat('HH:mm').format(DateTime.fromMillisecondsSinceEpoch(selectedData!['endTime']))}\n'
|
||||||
"时长".tr+ ' ${selectedData!['times']}',
|
"时长"
|
||||||
|
.tr +
|
||||||
|
' ${selectedData!['times']}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18.rpx,
|
fontSize: 18.rpx,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
@@ -554,9 +201,9 @@ class _LineChartByRangePainter extends CustomPainter {
|
|||||||
int end = item['endTime'];
|
int end = item['endTime'];
|
||||||
int times = item['times'];
|
int times = item['times'];
|
||||||
|
|
||||||
double startX = xStart +
|
double startX = xStart * 2 +
|
||||||
chartWidth * (start - minTime.millisecondsSinceEpoch) / totalDuration;
|
chartWidth * (start - minTime.millisecondsSinceEpoch) / totalDuration;
|
||||||
double endX = xStart +
|
double endX = xStart * 2 +
|
||||||
chartWidth * (end - minTime.millisecondsSinceEpoch) / totalDuration;
|
chartWidth * (end - minTime.millisecondsSinceEpoch) / totalDuration;
|
||||||
double y = chartHeight * (1 - times / maxY);
|
double y = chartHeight * (1 - times / maxY);
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,13 @@ class _SnoreViewWidgetWidgetState extends State<BreathePauseNewWidget> {
|
|||||||
|
|
||||||
List<Map<String, dynamic>> data =
|
List<Map<String, dynamic>> data =
|
||||||
(widget.sleepReport['asp'] as List).cast<Map<String, dynamic>>();
|
(widget.sleepReport['asp'] as List).cast<Map<String, dynamic>>();
|
||||||
|
// data = [
|
||||||
|
// {"st": 1763494195669, "value": 11},
|
||||||
|
// {"st": 1763494278485, "value": 18},
|
||||||
|
// {"st": 1763494293453, "value": 18},
|
||||||
|
// {"st": 1763494352321, "value": 14},
|
||||||
|
// {"st": 1763494606757, "value": 12}
|
||||||
|
// ];
|
||||||
List<Map<String, dynamic>> showLabel = convertToShowLabel(data);
|
List<Map<String, dynamic>> showLabel = convertToShowLabel(data);
|
||||||
double maxTimes = 70;
|
double maxTimes = 70;
|
||||||
try {
|
try {
|
||||||
@@ -217,7 +224,15 @@ class _SnoreViewWidgetWidgetState extends State<BreathePauseNewWidget> {
|
|||||||
final value = item['value'];
|
final value = item['value'];
|
||||||
|
|
||||||
if (value == currentValue) {
|
if (value == currentValue) {
|
||||||
|
// endTime = st;
|
||||||
|
result.add({
|
||||||
|
"startTime": startTime,
|
||||||
|
"endTime": endTime,
|
||||||
|
"times": currentValue,
|
||||||
|
});
|
||||||
|
startTime = st;
|
||||||
endTime = st;
|
endTime = st;
|
||||||
|
currentValue = value;
|
||||||
} else {
|
} else {
|
||||||
result.add({
|
result.add({
|
||||||
"startTime": startTime,
|
"startTime": startTime,
|
||||||
|
|||||||