更新样式

This commit is contained in:
wyf
2025-12-01 15:41:34 +08:00
parent 991bf97fd1
commit 1cc26aa46d
18 changed files with 502 additions and 677 deletions

View File

@@ -414,8 +414,8 @@
"呼吸数据介绍": "Respiration data refers to basic respiration metrics during sleep, serving as core indicators for evaluating sleep respiration quality and screening sleep breathing disorders.",
"心率散点图": "Heart rate scatter plot",
"心率散点图介绍": "The ECG scatter plot is a nonlinear graphical method for recording continuous heart rate RR interval diagrams, also called scatter plots as they consist of scattered points.",
"今日数据": "yesterday",
"昨日数据": "today",
"今日数据": "today",
"昨日数据": "yesterday",
"次": "times",
"秒": "sec",
"暂无": "None",

View File

@@ -221,6 +221,39 @@ class CityModelController extends GetControllerEx<CityModel> {
// 检查是否已加载数据
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);
// }
// }
// }
// }
// }
void searchCities(String keyword) {
model.keyword = keyword;
searchResults?.clear();
@@ -231,23 +264,43 @@ class CityModelController extends GetControllerEx<CityModel> {
final keywordLower = keyword.toLowerCase();
// 遍历所有城市数据
for (var country in cityList) {
final countryName = country.value ?? country.country ?? '';
for (var province in country.children ?? []) {
// 如果该国家没有 children一级结构
if (country.children == null || country.children!.isEmpty) {
final name = countryName;
if (name.toLowerCase().contains(keywordLower)) {
searchResults?.add(name);
}
continue;
}
// 二层循环:省份或城市
for (var province in country.children!) {
final provinceName = province.value ?? province.province ?? '';
final level2Name = '$countryName-$provinceName';
for (var city in province.children ?? []) {
// 省本身是否匹配(适用于 国家→省 二级结构)
if (province.children == null || province.children!.isEmpty) {
if (countryName.toLowerCase().contains(keywordLower) ||
provinceName.toLowerCase().contains(keywordLower) ||
level2Name.toLowerCase().contains(keywordLower)) {
searchResults?.add(level2Name);
}
continue;
}
// 有第三级城市 → 遍历城市
for (var city in province.children!) {
final cityName = city.value ?? city.city ?? '';
final displayName = '$countryName-$provinceName-$cityName';
final level3Name = '$countryName-$provinceName-$cityName';
// 模糊匹配
if (countryName.toLowerCase().contains(keywordLower) ||
provinceName.toLowerCase().contains(keywordLower) ||
cityName.toLowerCase().contains(keywordLower) ||
displayName.toLowerCase().contains(keywordLower)) {
searchResults?.add(displayName);
level3Name.toLowerCase().contains(keywordLower)) {
searchResults?.add(level3Name);
}
}
}
@@ -255,27 +308,77 @@ class CityModelController extends GetControllerEx<CityModel> {
}
// 根据显示名称获取对应的 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;
// }
CityModel? getCityByDisplayName(String displayName) {
final parts = displayName.split('-');
if (parts.length != 3) return null;
if (parts.isEmpty) return null;
final countryName = parts[0];
final provinceName = parts[1];
final cityName = parts[2];
final provinceName = parts.length >= 2 ? parts[1] : null;
final cityName = parts.length >= 3 ? parts[2] : null;
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) {
final cName = country.value ?? country.country ?? '';
if (cName != countryName) continue;
// 一级结构:直接 return 国家
if (provinceName == null) {
return country;
}
// 如果没有 children就不可能有省或市
if (country.children == null || country.children!.isEmpty) {
return null;
}
// 二级匹配:省或城市
for (var province in country.children!) {
final pName = province.value ?? province.province ?? '';
if (pName != provinceName) continue;
// 二级结构:国家-省
if (cityName == null) {
return province;
}
// 有城市才继续
if (province.children == null || province.children!.isEmpty) {
return null;
}
// 三级结构:国家-省-市
for (var city in province.children!) {
final ciName = city.value ?? city.city ?? '';
if (ciName == cityName) {
return city;
}
}
}
}
}
}
return null;
}
}

View File

@@ -3,6 +3,7 @@ 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 'package:vbvs_app/component/tool/ClickableContainer.dart';
import '../../common/util/MyUtils.dart';
class ListSearchWidget extends GetView {
@@ -38,7 +39,8 @@ class ListSearchWidget extends GetView {
children: [
// 搜索框部分
Padding(
padding: padding ?? EdgeInsetsDirectional.fromSTEB(30.rpx, 0, 30.rpx, 0),
padding:
padding ?? EdgeInsetsDirectional.fromSTEB(30.rpx, 0, 30.rpx, 0),
child: Container(
width: double.infinity,
decoration: BoxDecoration(
@@ -46,7 +48,7 @@ class ListSearchWidget extends GetView {
borderRadius: BorderRadius.circular(16.rpx),
),
child: Padding(
padding: EdgeInsetsDirectional.fromSTEB(35.rpx, 0, 35.rpx, 0),
padding: EdgeInsetsDirectional.fromSTEB(35.rpx, 0, 0.rpx, 0),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -139,7 +141,9 @@ class ListSearchWidget extends GetView {
},
onTap: () {
// 点击输入框时显示结果列表(如果开启了显示功能且有搜索结果)
if (showResultList && searchResults != null && searchResults!.isNotEmpty) {
if (showResultList &&
searchResults != null &&
searchResults!.isNotEmpty) {
_showResults.value = true;
}
},
@@ -151,8 +155,12 @@ class ListSearchWidget extends GetView {
),
),
Padding(
padding: EdgeInsetsDirectional.fromSTEB(26.rpx, 0, 0, 0),
child: InkWell(
padding: EdgeInsetsDirectional.fromSTEB(0.rpx, 0, 0, 0),
child: ClickableContainer(
backgroundColor: Colors.transparent,
highlightColor:
themeController.currentColor.sc4.withOpacity(0.8),
padding: EdgeInsets.fromLTRB(0, 0, 35.rpx, 0),
onTap: () {
findCallback?.call();
// 点击搜索按钮后显示结果列表(如果开启了显示功能)
@@ -192,7 +200,9 @@ class ListSearchWidget extends GetView {
// 搜索结果列表(可选显示)
if (showResultList) ...[
Obx(() {
if (!_showResults.value || searchResults == null || searchResults!.isEmpty) {
if (!_showResults.value ||
searchResults == null ||
searchResults!.isEmpty) {
return SizedBox.shrink();
}

View File

@@ -0,0 +1,46 @@
import 'package:dio/dio.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:vbvs_app/common/util/MyUtils.dart';
import 'package:vbvs_app/controller/login/login_controller.dart';
import 'package:vbvs_app/pages/common/selectDialog.dart';
class ApiService {
static Dio dio = Dio();
static Dio request = Dio();
static Dio requestNoInfo = Dio();
static Dio requestNoError = Dio(); //不处理错误的请求,用于设备操作上报
static Dio reservation = Dio();
static void init() {
reservationInit();
}
static void reservationInit() {
reservation.options.baseUrl = "https://crm-api.swes.com.cn";
reservation.options.connectTimeout = const Duration(seconds: 15);
reservation.options.receiveTimeout = const Duration(seconds: 10);
reservation.options.sendTimeout = const Duration(seconds: 10);
reservation.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
print("---> ${options.method}, ${options.path}, ${options.data}");
options.headers['token'] =
"bMAQVR1x48t66u8EDYSftAJGo17r0rIB3z15JgyyoGz1rAEZHs1htHOCorYFJ2RT";
return handler.next(options);
},
onResponse: (response, ResponseInterceptorHandler handler) {
print("<--- ${response.statusCode} ${response.data}");
return handler.next(response);
},
onError: (e, handler) {
// 错误处理,例如打印错误信息
showToast("网络异常或服务连接异常,请稍候再试", color: color_error);
print("DioError: $e");
return handler.reject(e);
},
));
}
}

View File

@@ -20,7 +20,7 @@ import 'package:vbvs_app/common/pojo/city.dart';
import 'package:vbvs_app/common/util/CheckNetwork.dart';
import 'package:vbvs_app/common/util/CommonVariables.dart';
import 'package:vbvs_app/common/util/DailyLogUtils.dart';
import 'package:vbvs_app/common/util/Dio.dart';
// import 'package:vbvs_app/common/util/Dio.dart';
import 'package:vbvs_app/common/util/FitTool.dart';
import 'package:vbvs_app/common/util/JPushUtil.dart';
import 'package:vbvs_app/common/util/MyUtils.dart';
@@ -32,6 +32,7 @@ import 'package:vbvs_app/controller/device/device_calibration_controller.dart';
import 'package:vbvs_app/controller/device/device_share_controller.dart';
import 'package:vbvs_app/controller/device/device_share_list_controller.dart';
import 'package:vbvs_app/controller/device/device_type_controller.dart';
import 'package:vbvs_app/controller/home/Dio.dart';
import 'package:vbvs_app/controller/home/home_controller.dart';
import 'package:vbvs_app/controller/login/login_controller.dart';
import 'package:vbvs_app/controller/main_bottom/global_controller.dart';
@@ -94,6 +95,12 @@ Future<void> main() async {
final ThemeController themeController = Get.put(ThemeController());
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
statusBarColor: Colors.transparent, // 状态栏透明
statusBarIconBrightness: Brightness.light, // Android白色图标
statusBarBrightness: Brightness.dark, // iOS白色图标
));
final Locale? deviceLocale = Get.deviceLocale;
print('系统语言: ${deviceLocale?.languageCode}');

View File

@@ -64,12 +64,15 @@ Widget getOnePickers(
bool looping = false,
void Function(int)? onChanged,
bool isMonthName = false,
Key? pickerKey,
}) {
ThemeController themeController = Get.find();
final bool isEn = Get.locale?.languageCode.startsWith('en') ?? false;
return Obx(() {
final dynamicKey = ValueKey('picker_${arr.length}_${selectedIndex.value}');
return CupertinoPicker.builder(
key: pickerKey ?? dynamicKey,
itemExtent: 90.rpx,
useMagnifier: false,
magnification: 1,

View File

@@ -30,9 +30,11 @@ class _BodyDevicePageState extends State<BodyDeviceWidget> {
final BodyDeviceController bodyDeviceController = Get.find();
HomeController homeController = Get.find();
final GlobalKey addIconKey = GlobalKey();
final ScrollController _scrollController = ScrollController();
final ScrollController _myDeviceScrollController = ScrollController();
final ScrollController _cloudDeviceScrollController = ScrollController();
OverlayEntry? _popupEntry;
Timer? _timer;
late PageController _pageController;
void _showPopup() {
final renderBox =
@@ -42,20 +44,16 @@ class _BodyDevicePageState extends State<BodyDeviceWidget> {
final size = renderBox.size;
double popupWidth = 190.rpx;
// 移除之前的弹窗
_popupEntry?.remove();
// 创建新的OverlayEntry
_popupEntry = OverlayEntry(
builder: (context) => Stack(
children: [
// 半透明背景,点击后关闭弹窗
ModalBarrier(
dismissible: true,
color: Colors.transparent,
onDismiss: _hidePopup,
),
// 弹窗内容
Positioned(
top: position.dy + size.height + 26.rpx,
left: position.dx + size.width - popupWidth - 40.rpx,
@@ -81,36 +79,6 @@ class _BodyDevicePageState extends State<BodyDeviceWidget> {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: 11.rpx),
// ClickableContainer(
// padding: EdgeInsets.symmetric(vertical: 10.rpx),
// backgroundColor: Colors.transparent,
// highlightColor:
// themeController.currentColor.sc16.withOpacity(0.1),
// borderRadius: 0.rpx,
// onTap: () {
// print('点击扫一扫');
// _hidePopup();
// TopSlideNotification.show(
// context,
// text: "待开发.提示".tr,
// textColor: themeController.currentColor.sc2,
// );
// },
// child: Container(
// width: double.infinity,
// child: Center(
// child: Text(
// '扫一扫.标题'.tr,
// style: TextStyle(
// fontSize: AppConstants().normal_text_fontSize,
// color: themeController.currentColor.sc3,
// ),
// ),
// ),
// ),
// ),
// SizedBox(height: 35.rpx),
ClickableContainer(
padding: EdgeInsets.symmetric(vertical: 10.rpx),
backgroundColor: Colors.transparent,
@@ -147,7 +115,6 @@ class _BodyDevicePageState extends State<BodyDeviceWidget> {
),
);
// 插入新的OverlayEntry
Overlay.of(context)!.insert(_popupEntry!);
}
@@ -161,6 +128,10 @@ class _BodyDevicePageState extends State<BodyDeviceWidget> {
bodyDeviceController.keyWord.value = "";
super.initState();
// 初始化PageController根据当前类型设置初始页面
_pageController = PageController(
initialPage: bodyDeviceController.model.type == 1 ? 0 : 1);
// 处理传入的type参数
if (widget.type != null && widget.type is Map) {
final bindType = widget.type['bind_type'];
@@ -169,23 +140,22 @@ class _BodyDevicePageState extends State<BodyDeviceWidget> {
if (bindType != null) {
bodyDeviceController.model.type = bindType;
homeController.model.type = bindType;
// 更新PageController到正确的位置
_pageController = PageController(
initialPage: bodyDeviceController.model.type == 1 ? 0 : 1);
}
// 初次请求设备列表
_fetchDeviceList().then((_) {
if (mac != null) {
// 延迟执行以确保列表已渲染
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToDeviceWithMac(mac);
});
}
});
} else {
// 没有传入type时的默认逻辑
_fetchDeviceList();
}
// 每5秒定时请求一次
_timer = Timer.periodic(Duration(seconds: 5), (timer) {
_fetchDeviceList();
});
@@ -209,28 +179,54 @@ class _BodyDevicePageState extends State<BodyDeviceWidget> {
final deviceList = bodyDeviceController.deviceList.value;
final index = deviceList.indexWhere((device) => device['mac'] == mac);
if (index != -1 && _scrollController.hasClients) {
// 动态计算高度:最小为 501.rpx最大为 26.6% 屏幕高度
if (index != -1) {
final screenHeight = MediaQuery.of(Get.context!).size.height;
final dynamicItemHeight = (screenHeight * 0.266).rpx;
final itemHeight =
dynamicItemHeight < 501.rpx ? 501.rpx : dynamicItemHeight;
final spacing = 25.rpx;
final targetPosition = index * (itemHeight + spacing);
_scrollController.animateTo(
// 根据当前类型选择对应的ScrollController
final currentScrollController =
bodyDeviceController.model.type == 1
? _myDeviceScrollController
: _cloudDeviceScrollController;
if (currentScrollController.hasClients) {
currentScrollController.animateTo(
targetPosition,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
}
}
}
// 标签切换回调
void _onTabChanged(int index) {
_pageController.animateToPage(index,
duration: const Duration(milliseconds: 300), curve: Curves.easeInOut);
}
// 页面切换回调
void _onPageChanged(int index) {
int newType = index == 0 ? 1 : 2;
if (bodyDeviceController.model.type != newType) {
bodyDeviceController.model.type = newType;
homeController.model.type = newType;
bodyDeviceController.updateAll();
homeController.updateAll();
_fetchDeviceList();
}
}
@override
void dispose() {
_timer?.cancel(); // 页面销毁时取消定时器
_scrollController.dispose();
_timer?.cancel();
_myDeviceScrollController.dispose();
_cloudDeviceScrollController.dispose();
_pageController.dispose();
super.dispose();
}
@@ -238,7 +234,10 @@ class _BodyDevicePageState extends State<BodyDeviceWidget> {
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, bodysize) => GestureDetector(
// onTap: () => FocusScope.of(context).unfocus(),,
onTap: () {
_hidePopup();
FocusScope.of(context).unfocus();
},
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
@@ -262,7 +261,6 @@ class _BodyDevicePageState extends State<BodyDeviceWidget> {
child: Stack(
alignment: Alignment.center,
children: [
/// 居中标题
Text(
'体征检测设备.标题'.tr,
style: TextStyle(
@@ -288,7 +286,6 @@ class _BodyDevicePageState extends State<BodyDeviceWidget> {
highlightColor: themeController.currentColor.sc16,
padding: EdgeInsets.all(8.rpx),
onTap: () {
// 点击图标时,展示弹窗
if (_popupEntry == null) {
_showPopup();
} else {
@@ -310,7 +307,7 @@ class _BodyDevicePageState extends State<BodyDeviceWidget> {
centerTitle: false,
),
body: GestureDetector(
onTap: _hidePopup, // 点击空白处自动关闭弹窗
onTap: _hidePopup,
child: SafeArea(
top: true,
child: Padding(
@@ -335,6 +332,7 @@ class _BodyDevicePageState extends State<BodyDeviceWidget> {
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// 标签切换部分 - 保持原有样式
Stack(
alignment: Alignment.bottomLeft,
children: [
@@ -348,19 +346,7 @@ class _BodyDevicePageState extends State<BodyDeviceWidget> {
.currentColor.sc3,
borderRadius: 8.rpx,
padding: EdgeInsets.all(0),
onTap: () async {
bodyDeviceController.model.type =
1;
homeController.model.type = 1;
await bodyDeviceController
.getDeviceList();
await bodyDeviceController
.getDeviceList();
await bodyDeviceController
.getSleepReport();
bodyDeviceController.updateAll();
homeController.updateAll();
},
onTap: () => _onTabChanged(0),
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
@@ -400,19 +386,7 @@ class _BodyDevicePageState extends State<BodyDeviceWidget> {
.currentColor.sc3,
borderRadius: 8.rpx,
padding: EdgeInsets.all(0),
onTap: () async {
homeController.model.type = 2;
bodyDeviceController.model.type =
2;
await bodyDeviceController
.getDeviceList();
await bodyDeviceController
.getDeviceList();
await bodyDeviceController
.getSleepReport();
bodyDeviceController.updateAll();
homeController.updateAll();
},
onTap: () => _onTabChanged(1),
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
@@ -451,7 +425,7 @@ class _BodyDevicePageState extends State<BodyDeviceWidget> {
],
),
Obx(() {
// 横线宽度固定为180.rpx
// 保持原有的横线宽度180.rpx
double lineWidth = 180.rpx;
return AnimatedPositioned(
duration: Duration(milliseconds: 300),
@@ -475,6 +449,7 @@ class _BodyDevicePageState extends State<BodyDeviceWidget> {
}),
],
),
// 搜索框部分保持不变
Container(
width:
MediaQuery.sizeOf(context).width * 0.38,
@@ -636,21 +611,27 @@ class _BodyDevicePageState extends State<BodyDeviceWidget> {
),
),
),
// 使用PageView替换原来的单一列表
Expanded(
child: PageView(
controller: _pageController,
onPageChanged: _onPageChanged,
children: [
// 我的e护页面
Obx(() {
final isEmpty =
bodyDeviceController.deviceList.value.isEmpty;
return Expanded(
child: isEmpty
final myDeviceList = bodyDeviceController.deviceList.value
.where((device) => device['type'] == 1 || device['bind_type'] == 1)
.toList();
return myDeviceList.isEmpty
? NullDataWidget()
: Padding(
padding: EdgeInsetsDirectional.fromSTEB(
30.rpx, 26.rpx, 30.rpx, 0),
child: SingleChildScrollView(
controller: _scrollController,
controller: _myDeviceScrollController,
child: Column(
mainAxisSize: MainAxisSize.max,
children: bodyDeviceController
.deviceList.value
children: myDeviceList
.map((device) =>
DeviceDataComponentWidget(
device: device))
@@ -658,12 +639,38 @@ class _BodyDevicePageState extends State<BodyDeviceWidget> {
.divide(SizedBox(height: 25.rpx)),
),
),
);
}),
// 云关爱页面
Obx(() {
final cloudDeviceList = bodyDeviceController.deviceList.value
.where((device) => device['type'] == 2 || device['bind_type'] == 2)
.toList();
return cloudDeviceList.isEmpty
? NullDataWidget()
: Padding(
padding: EdgeInsetsDirectional.fromSTEB(
30.rpx, 26.rpx, 30.rpx, 0),
child: SingleChildScrollView(
controller: _cloudDeviceScrollController,
child: Column(
mainAxisSize: MainAxisSize.max,
children: cloudDeviceList
.map((device) =>
DeviceDataComponentWidget(
device: device))
.toList()
.divide(SizedBox(height: 25.rpx)),
),
),
);
}),
],
),
),
],
),
),
),
),
),

View File

@@ -691,6 +691,10 @@ class _CalibrationPageState extends State<CalibrationPage> {
progressApi, isSecondStep);
},
onFailure: (res) {
deviceCalibrationController
.process.value = 0;
deviceCalibrationController
.bed_calibration.value = 0;
deviceCalibrationController.flag.value =
0;
blueteethBindController.cid!.value = "";
@@ -757,6 +761,10 @@ class _CalibrationPageState extends State<CalibrationPage> {
progressApi, isSecondStep);
},
onFailure: (res) {
deviceCalibrationController
.process.value = 0;
deviceCalibrationController
.bed_calibration.value = 0;
deviceCalibrationController.flag.value =
0;
blueteethBindController.cid!.value = "";
@@ -935,7 +943,7 @@ class _CalibrationPageState extends State<CalibrationPage> {
} else {
deviceCalibrationController.statusContext.value = "";
//当前步骤执行失败
deviceCalibrationController.bed_calibration.value == 0;
deviceCalibrationController.bed_calibration.value = 0;
_pollingTimer?.cancel();
blueteethBindController.cid?.value = "";
deviceCalibrationController.process.value = 0;
@@ -985,6 +993,8 @@ class _CalibrationPageState extends State<CalibrationPage> {
}
},
onFailure: (res) {
deviceCalibrationController.process.value = 0;
deviceCalibrationController.bed_calibration.value = 0;
deviceCalibrationController.flag.value = 0;
_pollingTimer?.cancel();
blueteethBindController.cid!.value = "";

View File

@@ -685,6 +685,10 @@ class _CalibrationPersonPageState extends State<CalibrationPersonPage> {
progressApi, isSecondStep);
},
onFailure: (res) {
deviceCalibrationController
.process.value = 0;
deviceCalibrationController
.bed_calibration.value = 0;
deviceCalibrationController.flag.value =
0;
blueteethBindController.cid!.value = "";
@@ -751,6 +755,10 @@ class _CalibrationPersonPageState extends State<CalibrationPersonPage> {
progressApi, isSecondStep);
},
onFailure: (res) {
deviceCalibrationController
.process.value = 0;
deviceCalibrationController
.bed_calibration.value = 0;
deviceCalibrationController.flag.value =
0;
blueteethBindController.cid!.value = "";
@@ -934,7 +942,7 @@ class _CalibrationPersonPageState extends State<CalibrationPersonPage> {
} else {
deviceCalibrationController.statusContext.value = "";
//当前步骤执行失败
deviceCalibrationController.bed_calibration.value == 0;
deviceCalibrationController.bed_calibration.value = 0;
_pollingTimer?.cancel();
blueteethBindController.cid?.value = "";
deviceCalibrationController.process.value = 0;
@@ -984,6 +992,8 @@ class _CalibrationPersonPageState extends State<CalibrationPersonPage> {
}
},
onFailure: (res) {
deviceCalibrationController.process.value = 0;
deviceCalibrationController.bed_calibration.value = 0;
deviceCalibrationController.flag.value = 0;
_pollingTimer?.cancel();
blueteethBindController.cid!.value = "";

View File

@@ -275,6 +275,43 @@ class _OtherLoginPageState extends State<OtherLoginPage> {
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: EdgeInsetsDirectional.fromSTEB(
26.rpx, 0, 0, 0),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
InkWell(
onTap: () async {},
child: Container(
alignment: Alignment.center,
// constraints: BoxConstraints(
// minWidth: 150.rpx,
// ),
child: Text(
"+86",
style: TextStyle(
fontFamily: 'Readex Pro',
color: themeController
.currentColor.sc4,
fontSize: AppConstants()
.middler_text_fontSize,
letterSpacing: 0,
),
),
),
),
SizedBox(
height: 30.rpx,
child: VerticalDivider(
thickness: 2.rpx,
color: themeController
.currentColor.sc4,
),
),
].divide(SizedBox(width: 10.rpx)),
),
),
Expanded(
child: Container(
child: Align(

View File

@@ -690,7 +690,7 @@ class _MinePageState extends State<MinePage> {
mainAxisSize: MainAxisSize.max,
children: [
Text(
'V1.0.2511.18',
'V1.0.2511.21',
style: TextStyle(
fontFamily: 'Inter',
// color: Color(0xFFD9E3EB),

View File

@@ -281,476 +281,6 @@ Future showCitySelectionDialog(
);
}
// 加载中弹窗
Widget _buildLoadingDialog(ThemeController themeController) {
return Stack(
children: [
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Material(
color: Colors.transparent,
child: Dialog(
backgroundColor: themeController.currentColor.sc17,
insetPadding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(0),
),
child: Container(
width: double.infinity,
padding: EdgeInsets.fromLTRB(30.rpx, 40.rpx, 30.rpx, 90.rpx),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(
color: themeController.currentColor.sc2,
),
SizedBox(height: 20.rpx),
Text(
"加载中...".tr,
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize: 28.rpx,
),
),
],
),
),
),
),
),
],
);
}
// 错误弹窗
Widget _buildErrorDialog(
ThemeController themeController, BuildContext context) {
return Stack(
children: [
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Material(
color: Colors.transparent,
child: Dialog(
backgroundColor: themeController.currentColor.sc17,
insetPadding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(0),
),
child: Container(
width: double.infinity,
padding: EdgeInsets.fromLTRB(30.rpx, 40.rpx, 30.rpx, 90.rpx),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"数据加载失败".tr,
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize: 28.rpx,
),
),
SizedBox(height: 20.rpx),
ClickableContainer(
onTap: () => Navigator.of(context).pop(),
backgroundColor: Colors.transparent,
highlightColor: Colors.transparent,
padding: EdgeInsets.all(0),
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 30.rpx, vertical: 15.rpx),
decoration: BoxDecoration(
color: themeController.currentColor.sc2,
borderRadius: BorderRadius.circular(8.rpx),
),
child: Text(
"关闭".tr,
style: TextStyle(
color: Colors.white,
fontSize: 28.rpx,
),
),
),
),
],
),
),
),
),
),
],
);
}
// 城市选择器弹窗
Widget _buildCityPickerDialog(
BuildContext context,
ThemeController themeController,
bool isChinese,
String title,
List<CityModel> cityData,
Function? onCityChanged, {
required RxList<String> countries,
required RxList<String> provinces,
required RxList<String> cities,
required RxInt countryIndex,
required RxInt provinceIndex,
required RxInt cityIndex,
}) {
// 内部更新方法
void updateCities() {
try {
if (provinces.isEmpty || countries.isEmpty) return;
final selectedCountry = countries[countryIndex.value];
final selectedProvince = provinces[provinceIndex.value];
for (var country in cityData) {
if (country.value == selectedCountry) {
for (var province in country.children ?? []) {
if (province.value == selectedProvince) {
// 安全地处理城市列表
final cityList = (province.children ?? [])
.map((city) => city?.value ?? city?.city ?? '')
.where((cityName) =>
(cityName as String).isNotEmpty) // 明确转换为 String
.toList()
.cast<String>();
cities.value = cityList;
cityIndex.value = cities.isNotEmpty ? 0 : 0;
return;
}
}
}
}
cities.value = [];
cityIndex.value = 0;
} catch (e) {
ef.log("更新城市列表失败:$e");
cities.value = [];
cityIndex.value = 0;
}
}
void updateProvinces() {
try {
if (countries.isEmpty) return;
final selectedCountry = countries[countryIndex.value];
for (var country in cityData) {
if (country.value == selectedCountry) {
// 安全地处理省份列表
final provinceList = (country.children ?? [])
.map((province) => province?.value ?? province?.province ?? '')
.where((provinceName) => provinceName.isNotEmpty)
.toList()
.cast<String>();
provinces.value = provinceList;
provinceIndex.value = provinces.isNotEmpty ? 0 : 0;
// 更新城市
updateCities();
return;
}
}
provinces.value = [];
provinceIndex.value = 0;
cities.value = [];
cityIndex.value = 0;
} catch (e) {
ef.log("更新省份列表失败:$e");
provinces.value = [];
provinceIndex.value = 0;
cities.value = [];
cityIndex.value = 0;
}
}
CityModel? getSelectedCityData() {
try {
if (countries.isEmpty) return null;
final selectedCountry = countries[countryIndex.value];
// 查找匹配的国家
for (var country in cityData) {
final countryName = country.value ?? country.country;
if (countryName == selectedCountry) {
// 情况1: 只有国家一级(没有省份和城市)
if ((country.children == null || country.children!.isEmpty) &&
(country.city != null || country.value != null)) {
return country; // 直接返回国家数据
}
// 情况2: 有省份但没有城市数据
if (provinces.isNotEmpty && provinceIndex.value < provinces.length) {
final selectedProvince = provinces[provinceIndex.value];
for (var province in country.children ?? []) {
final provinceName = province.value ?? province.province;
if (provinceName == selectedProvince) {
// 情况2.1: 省份有城市数据
if (cities.isNotEmpty && cityIndex.value < cities.length) {
final selectedCityName = cities[cityIndex.value];
for (var city in province.children ?? []) {
final cityName = city.value ?? city.city;
if (cityName == selectedCityName) {
return city; // 返回城市数据
}
}
}
// 情况2.2: 省份没有城市数据,但省份本身有信息
if ((province.children == null || province.children!.isEmpty) &&
(province.city != null || province.value != null)) {
return province; // 返回省份数据
}
}
}
}
// 情况3: 有省份但没有选择具体省份,返回国家数据
return country;
}
}
return null;
} catch (e) {
ef.log("获取选中城市数据失败:$e");
return null;
}
}
return Scaffold(
resizeToAvoidBottomInset: false, // 在这里也设置
backgroundColor: Colors.transparent,
body: Stack(
children: [
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Material(
color: Colors.transparent,
child: Dialog(
backgroundColor: themeController.currentColor.sc17,
insetPadding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(0),
),
child: Container(
width: double.infinity,
padding: EdgeInsets.fromLTRB(30.rpx, 10.rpx, 30.rpx, 0.rpx),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ClickableContainer(
backgroundColor: Colors.transparent,
highlightColor: Colors.transparent,
padding: EdgeInsets.zero,
onTap: () => Navigator.of(context).pop(),
child: Container(
width: 110.rpx,
height: 60.rpx,
alignment: Alignment.center,
child: Text("取消".tr,
style: TextStyle(
fontSize: 30.rpx, color: Colors.white)),
),
),
Text(
title,
style: TextStyle(
fontFamily: 'Readex Pro',
color: themeController.currentColor.sc3,
fontSize: 30.rpx,
),
),
Obx(() {
CityModelController cityModelController = Get.find();
cityModelController.tmp;
ef.log("${cityModelController.tmp.value}");
return ClickableContainer(
backgroundColor: Colors.transparent,
highlightColor: Colors.transparent,
padding: EdgeInsets.zero,
// onTap: () {
// final selectedCityData = getSelectedCityData();
// if (selectedCityData != null) {
// onCityChanged?.call(selectedCityData);
// }
// Navigator.of(context).pop();
// },
onTap: () {
final selectedCityData = getSelectedCityData();
if (selectedCityData != null) {
// 根据ID查找完整的层级数据并补全
final fullCityData = findCompleteCityDataById(
selectedCityData.id, cityData);
if (fullCityData != null) {
// 使用完整的数据
onCityChanged?.call(fullCityData);
} else {
// 如果没有找到完整数据,使用当前选中的数据
onCityChanged?.call(selectedCityData);
}
}
Navigator.of(context).pop();
},
child: Container(
width: 110.rpx,
height: 60.rpx,
alignment: Alignment.center,
child: Text("确定".tr,
style: TextStyle(
fontSize: 30.rpx,
color: themeController.currentColor.sc2,
)),
),
);
}),
],
),
Padding(
padding: EdgeInsetsDirectional.fromSTEB(0, 0.rpx, 0, 0),
child: ListSearchWidget(
keyword: cityModelController.model.keyword,
color: cityModelController.model.color,
hint: "输入国家、省份或城市".tr,
onChange: (d) {
cityModelController.model.keyword = d;
// 实时搜索
cityModelController.searchCities(d);
},
findCallback: () {
// 点击搜索按钮时搜索
cityModelController.searchCities(
cityModelController.model.keyword ?? "");
},
padding: EdgeInsets.fromLTRB(
0.rpx,
30.rpx,
0.rpx,
10.rpx,
),
showResultList: true, // 开启结果列表
searchResults:
cityModelController.searchResults, // 搜索结果
onResultTap: (result) {
// 处理选择结果
final selectedCity =
cityModelController.getCityByDisplayName(result);
if (selectedCity != null) {
final fullCityData = findCompleteCityDataById(
selectedCity.id, cityData);
// 更新选中的城市
PersonController personController = Get.find();
personController.cityModel = fullCityData;
personController.updateAll();
ef.log("选择了城市: ${selectedCity.displayName}");
// 关闭弹窗(如果是在弹窗中使用)
Navigator.of(context).pop();
}
},
),
),
SizedBox(height: 20.rpx),
Stack(
children: [
Positioned.fill(
child: IgnorePointer(
child: Center(
child: Container(
height: 90.rpx,
margin: EdgeInsets.symmetric(horizontal: 0.rpx),
decoration: BoxDecoration(
color: themeController.currentColor.sc2,
borderRadius: BorderRadius.circular(16.rpx),
),
),
),
),
),
Container(
child: Padding(
padding: EdgeInsets.fromLTRB(20.rpx, 0, 20.rpx, 0),
child: Row(
children: [
Expanded(
child: Obx(() {
CityModelController cityModelController =
Get.find();
cityModelController.tmp;
ef.log("${cityModelController.tmp.value}");
return getOnePickers(
context,
countries,
countryIndex,
unit: "",
onChanged: (_) => updateProvinces(),
);
}),
),
Expanded(
child: Obx(() {
CityModelController cityModelController =
Get.find();
cityModelController.tmp;
ef.log("${cityModelController.tmp.value}");
return getOnePickers(
context,
provinces,
provinceIndex,
unit: "",
onChanged: (_) => updateCities(),
);
}),
),
Expanded(
child: Obx(() {
CityModelController cityModelController =
Get.find();
cityModelController.tmp;
ef.log("${cityModelController.tmp.value}");
return getOnePickers(
context,
cities,
cityIndex,
unit: "",
);
}),
),
],
),
),
),
],
),
],
),
),
),
),
),
],
),
);
}
// 根据ID查找完整的城市数据包含国家、省份、城市信息
CityModel? findCompleteCityDataById(int? id, List<CityModel> cityData) {
if (id == null) return null;
@@ -1095,6 +625,8 @@ Widget _buildCityPickerContent(
countryIndex,
unit: "",
onChanged: (_) => updateProvinces(),
pickerKey: ValueKey(
'country_${DateTime.now().millisecondsSinceEpoch}'), // 添加动态 key
);
}),
),
@@ -1110,6 +642,8 @@ Widget _buildCityPickerContent(
provinceIndex,
unit: "",
onChanged: (_) => updateCities(),
pickerKey: ValueKey(
'province_${DateTime.now().millisecondsSinceEpoch}'), // 添加动态 key
);
}),
),
@@ -1124,6 +658,8 @@ Widget _buildCityPickerContent(
cities,
cityIndex,
unit: "",
pickerKey: ValueKey(
'city_${DateTime.now().millisecondsSinceEpoch}'), // 添加动态 key
);
}),
),

View File

@@ -199,6 +199,7 @@ class _LineChartByRangePainter extends CustomPainter {
for (var item in data) {
int start = item['startTime'];
int end = item['endTime'];
// int times = item['times'];
int times = item['times'];
double startX = xStart * 2 +

View File

@@ -29,13 +29,71 @@ class SleepRadarChart extends StatelessWidget {
);
}
// Widget _buildRadarChart() {
// return AspectRatio(
// aspectRatio: 1.3,
// child: RadarChart(
// RadarChartData(
// dataSets: [
// // 今日数据
// RadarDataSet(
// dataEntries: data
// .map((e) => RadarEntry(value: (e['t'] as num).toDouble()))
// .toList(),
// borderColor: stringToColor("#00C1AA"),
// borderWidth: 2,
// fillColor: Colors.transparent,
// entryRadius: 0,
// ),
// // 昨日数据
// RadarDataSet(
// dataEntries: data
// .map((e) => RadarEntry(value: (e['y'] as num).toDouble()))
// .toList(),
// borderColor: stringToColor("#FFD251"),
// borderWidth: 2,
// fillColor: Colors.transparent,
// entryRadius: 0,
// ),
// ],
// radarBackgroundColor: stringToColor("#343844").withOpacity(0.6),
// radarBorderData: BorderSide(
// color: themeController.currentColor.sc4, width: 0.5.rpx),
// radarShape: RadarShape.polygon,
// titlePositionPercentageOffset: 0.2,
// titleTextStyle: TextStyle(
// fontSize: AppConstants().normal_text_fontSize,
// color: themeController.currentColor.sc3),
// getTitle: (index, angle) {
// return RadarChartTitle(text: data[index]['name'] ?? '未知'.tr);
// },
// tickCount: 5,
// ticksTextStyle:
// const TextStyle(color: Colors.transparent, fontSize: 10),
// gridBorderData: BorderSide(color: Colors.transparent, width: 1),
// tickBorderData: BorderSide(
// color: themeController.currentColor.sc4, width: 0.5.rpx),
// ),
// swapAnimationDuration: const Duration(milliseconds: 400),
// ),
// );
// }
Widget _buildRadarChart() {
return AspectRatio(
aspectRatio: 1.3,
child: RadarChart(
RadarChartData(
dataSets: [
// 今日数据
// 判断 t 是否全为 0今日
final bool isTodayAllZero =
data.every((e) => (e['t'] as num).toDouble() == 0);
// 判断 y 是否全为 0昨日
final bool isYesterdayAllZero =
data.every((e) => (e['y'] as num).toDouble() == 0);
// 构建 dataSets
final List<RadarDataSet> dataSets = [];
// 今日数据(非全 0 才加入)
if (!isTodayAllZero) {
dataSets.add(
RadarDataSet(
dataEntries: data
.map((e) => RadarEntry(value: (e['t'] as num).toDouble()))
@@ -45,7 +103,12 @@ class SleepRadarChart extends StatelessWidget {
fillColor: Colors.transparent,
entryRadius: 0,
),
// 昨日数据
);
}
// 昨日数据(非全 0 才加入)
if (!isYesterdayAllZero) {
dataSets.add(
RadarDataSet(
dataEntries: data
.map((e) => RadarEntry(value: (e['y'] as num).toDouble()))
@@ -55,7 +118,14 @@ class SleepRadarChart extends StatelessWidget {
fillColor: Colors.transparent,
entryRadius: 0,
),
],
);
}
return AspectRatio(
aspectRatio: 1.3,
child: RadarChart(
RadarChartData(
dataSets: dataSets,
radarBackgroundColor: stringToColor("#343844").withOpacity(0.6),
radarBorderData: BorderSide(
color: themeController.currentColor.sc4, width: 0.5.rpx),
@@ -63,7 +133,8 @@ class SleepRadarChart extends StatelessWidget {
titlePositionPercentageOffset: 0.2,
titleTextStyle: TextStyle(
fontSize: AppConstants().normal_text_fontSize,
color: themeController.currentColor.sc3),
color: themeController.currentColor.sc3,
),
getTitle: (index, angle) {
return RadarChartTitle(text: data[index]['name'] ?? '未知'.tr);
},

View File

@@ -83,8 +83,13 @@ class _BreatheCardState extends State<BreatheCard>
return Container();
}
// List data = widget.sleepReport['brs'] ?? [];
List data = widget.sleepReport['brs'] ?? [];
data = data.where((item) {
return item['show'] != false; // 只保留 show 不为 false 的元素
}).toList(); // 添加 .toList()
return Container(
width: double.infinity,
decoration: BoxDecoration(

View File

@@ -25,6 +25,7 @@ Widget DailyDataWidget(
GlobalKey breatheCardKey,
dynamic data,
) {
List<Widget> _buildSectionList() {
EdgeInsetsDirectional padding =
EdgeInsetsDirectional.fromSTEB(30.rpx, 0, 30.rpx, 25.rpx);

View File

@@ -15,7 +15,8 @@ class HeartRateCard extends StatefulWidget {
State<HeartRateCard> createState() => _HeartRateCardState();
}
class _HeartRateCardState extends State<HeartRateCard> with TickerProviderStateMixin {
class _HeartRateCardState extends State<HeartRateCard>
with TickerProviderStateMixin {
final GlobalKey _highlightKey = GlobalKey();
AnimationController? _animationController;
bool _shouldAnimate = false;
@@ -32,7 +33,8 @@ class _HeartRateCardState extends State<HeartRateCard> with TickerProviderStateM
}
WidgetsBinding.instance.addPostFrameCallback((_) {
if (widget.highlightItem != null && _highlightKey.currentContext != null) {
if (widget.highlightItem != null &&
_highlightKey.currentContext != null) {
Scrollable.ensureVisible(
_highlightKey.currentContext!,
duration: Duration(milliseconds: 500),
@@ -86,8 +88,16 @@ class _HeartRateCardState extends State<HeartRateCard> with TickerProviderStateM
return Container();
}
// List data = widget.sleepReport['hrs'] ?? [];
// data = data.where((item) {
// return item['show'] != false; // 只保留 show 不为 false 的元素
// });
List data = widget.sleepReport['hrs'] ?? [];
data = data.where((item) {
return item['show'] != false; // 只保留 show 不为 false 的元素
}).toList(); // 添加 .toList()
return Container(
width: double.infinity,
decoration: BoxDecoration(
@@ -104,8 +114,8 @@ class _HeartRateCardState extends State<HeartRateCard> with TickerProviderStateM
children: List.generate(data.length, (index) {
final item = data[index];
item['showTip'] = true;
final bool isHighlighted = _shouldAnimate &&
item['id'] == _highlightedId;
final bool isHighlighted =
_shouldAnimate && item['id'] == _highlightedId;
return SizedBox(
width: (MediaQuery.of(context).size.width - 160.rpx) / 3,
@@ -118,7 +128,8 @@ class _HeartRateCardState extends State<HeartRateCard> with TickerProviderStateM
? BoxDecoration(
border: Border.all(
color: themeController.currentColor.sc2
.withOpacity(_animationController?.value ?? 0),
.withOpacity(
_animationController?.value ?? 0),
width: 1.rpx,
),
borderRadius: BorderRadius.circular(8),

View File

@@ -843,39 +843,6 @@ class _NewSleepReportPageState extends State<NewSleepReportPage> {
),
),
),
// Obx(() {
// if (sleepReportController.isLoading.value) {
// return Center(
// child: CircularProgressIndicator(
// strokeWidth: 2,
// valueColor: AlwaysStoppedAnimation<Color>(
// themeController.currentColor.sc1,
// ),
// ),
// );
// }
// switch (sleepReportController.model.type) {
// case 1:
// return DailyDataWidget(
// sleepReport,
// sleepCardKey,
// heartRateCardKey,
// breatheCardKey,
// widget.data);
// case 2:
// return WeekDataWidget(
// sleepReport,
// widget.data,
// );
// case 3:
// return MonthDataWidget(
// sleepReport,
// widget.data,
// );
// default:
// return NullDataWidget();
// }
// }),
Obx(() {
if (sleepReportController.isLoading.value) {
return Center(