Compare commits

...

2 Commits

Author SHA1 Message Date
20ef495571 非营业状态 增加时间选择 2026-01-12 16:41:36 +08:00
285a20f070 地址切换 2026-01-12 09:13:37 +08:00
10 changed files with 467 additions and 117 deletions

View File

@@ -7,9 +7,12 @@ class AppTheme {
static const Color themeColor = Color(0xFF0c83c3);
//是否开放域名切换
static const bool is_show_host = true;
//http://192.168.110.222:8080/
//http://192.168.110.44:8080/
static const String test_service_url = "https://beta-esg.api.lnh2e.com/";
static String test_service_url = "https://beta-esg.api.lnh2e.com/";
static const String release_service_url = "";
//加氢站相关查询
@@ -23,10 +26,10 @@ class AppTheme {
static const Color darkThemeColor = Color(0xFF032896);
static const String android_key ="335642645";
static const String android_appsecret="39628204345a4240b5b645b68a5896c7";
static const String ios_key="335642649";
static const String ios_appsecret="173bc08bd5df422da20c8e3ffbf0521b";
static const String android_key = "335642645";
static const String android_appsecret = "39628204345a4240b5b645b68a5896c7";
static const String ios_key = "335642649";
static const String ios_appsecret = "173bc08bd5df422da20c8e3ffbf0521b";
/// 亮色主题样式
static ThemeData light = ThemeData(

View File

@@ -1,6 +1,7 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'package:ln_jq_app/storage_service.dart';
/// 专门用于处理和添加 Token 的拦截器
@@ -13,7 +14,7 @@ class TokenInterceptor extends Interceptor {
TokenInterceptor({this.tokenKey = 'Authorization', this.sourceKey = 'source'});
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
void onRequest(RequestOptions options, RequestInterceptorHandler handler) async{
// 从 StorageService 中获取已保存的 token
final String? token = StorageService.to.token;
@@ -38,6 +39,9 @@ class TokenInterceptor extends Interceptor {
options.headers[sourceKey] = platformSource;
}
options.headers['appVersion'] = await getVersion();
options.headers['brand'] = await getDeviceModel();
// 调用 handler.next(options) 以继续执行请求
// 这一步至关重要,否则请求会被中断
super.onRequest(options, handler);

View File

@@ -65,6 +65,8 @@ void main() async {
}
void initHttpSet() {
AppTheme.test_service_url = StorageService.to.hostUrl ?? AppTheme.test_service_url;
// 设置基础 URL
HttpService.to.setBaseUrl(AppTheme.test_service_url);
//指定请求头

View File

@@ -1,32 +1,47 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'package:intl/intl.dart';
import 'package:ln_jq_app/common/model/base_model.dart';
import 'package:ln_jq_app/pages/login/view.dart';
import '../../../common/styles/theme.dart';
import '../../../storage_service.dart';
class ReservationController extends GetxController with BaseControllerMixin {
@override
String get builderId => 'b_reservation'; // 确保ID与View中一致
String get builderId => 'b_reservation';
// --- 运营状态下拉菜单所需的状态 ---
// 下拉菜单的选项列表
final List<String> operationStatusOptions = ["营运中", "维修中", "暂停营业", "站点关闭"];
// 当前选中的值,默认为'运营中'
String selectedOperationStatus = "营运中";
// --- 其它状态下的时间选择 ---
DateTime? customStartTime;
DateTime? customEndTime;
String get customStartTimeStr => customStartTime != null
? DateFormat('yyyy-MM-dd HH:mm').format(customStartTime!)
: '点击选择开始时间';
String get customEndTimeStr => customEndTime != null
? DateFormat('yyyy-MM-dd HH:mm').format(customEndTime!)
: '点击选择结束时间';
@override
void onInit() {
super.onInit();
// 1. 初始化默认时间
customStartTime = DateTime.now();
customEndTime = customStartTime!.add(const Duration(days: 1));
renderData();
}
String name = "";
String address = "";
String phone = "";
String costPrice = ""; //氢气价格
String customerPrice = ""; //官方价格
String costPrice = "";
String customerPrice = "";
String startBusiness = "";
String endBusiness = "";
String timeStr = "";
@@ -35,7 +50,6 @@ class ReservationController extends GetxController with BaseControllerMixin {
Future<void> renderData() async {
showLoading("加载中");
try {
var responseData = await HttpService.to.get(
'appointment/station/getStationInfoById?hydrogenId=${StorageService.to.userId}',
@@ -49,32 +63,30 @@ class ReservationController extends GetxController with BaseControllerMixin {
try {
var result = BaseModel.fromJson(responseData.data);
name = result.data["name"];
name = result.data["name"] ?? "";
hydrogenId = result.data["hydrogenId"].toString();
address = result.data["address"];
var rawCostPrice = result.data["costPrice"];
if (rawCostPrice != null && rawCostPrice.toString().isNotEmpty) {
costPrice = "¥$rawCostPrice";
} else {
costPrice = "暂无价格";
}
var customerPriceTemp = result.data["customerPrice"];
if (customerPriceTemp != null && customerPriceTemp.toString().isNotEmpty) {
customerPrice = "$customerPriceTemp";
} else {
customerPrice = "暂无价格";
}
address = result.data["address"] ?? "";
phone = result.data["phone"];
startBusiness = result.data["startBusiness"];
endBusiness = result.data["endBusiness"];
var rawCostPrice = result.data["costPrice"];
costPrice = (rawCostPrice != null && rawCostPrice.toString().isNotEmpty)
? "¥$rawCostPrice"
: "暂无价格";
var customerPriceTemp = result.data["customerPrice"];
customerPrice =
(customerPriceTemp != null && customerPriceTemp.toString().isNotEmpty)
? "$customerPriceTemp"
: "暂无价格";
phone = result.data["phone"] ?? "";
startBusiness = result.data["startBusiness"] ?? "";
endBusiness = result.data["endBusiness"] ?? "";
operatingEnterprise = result.data["operatingEnterprise"].toString();
String temp = result.data["siteStatusName"].toString();
selectedOperationStatus = (temp.isEmpty || temp == "null") ? "营运中" : temp;
if (startBusiness.isNotEmpty && endBusiness.isNotEmpty) {
// 营业时间为24小时的特殊处理
if (startBusiness == "00:00:00" && endBusiness == "23:59:59") {
timeStr = "24小时营业";
} else {
@@ -85,11 +97,9 @@ class ReservationController extends GetxController with BaseControllerMixin {
}
operatingEnterprise = operatingEnterprise.isEmpty ? "暂未设置" : operatingEnterprise;
updateUi();
dismissLoading();
} catch (e) {
// 如果解析 JSON 失败
dismissLoading();
showToast('数据异常');
}
@@ -98,7 +108,6 @@ class ReservationController extends GetxController with BaseControllerMixin {
}
}
/// 更新运营状态的方法
void onOperationStatusChanged(String? newValue) {
if (newValue != null) {
selectedOperationStatus = newValue;
@@ -106,9 +115,102 @@ class ReservationController extends GetxController with BaseControllerMixin {
}
}
void saveInfo() async {
showLoading("保存中");
/// 使用底部滚轮形式选择时间(下拉框效果)
void pickDateTime(BuildContext context, bool isStart) {
DateTime now = DateTime.now();
DateTime initialDate = isStart ? (customStartTime ?? now) : (customEndTime ?? now);
// 确保初始时间不早于最小允许时间
// 将 minimumDate 稍微提前一点点比如1分钟
DateTime minLimit = isStart
? now.subtract(const Duration(minutes: 1))
: (customStartTime ?? now).subtract(const Duration(minutes: 1));
if (initialDate.isBefore(minLimit)) {
initialDate = isStart ? now : (customStartTime ?? now);
}
DateTime tempDate = initialDate;
Get.bottomSheet(
Container(
height: 300,
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CupertinoButton(
onPressed: () => Get.back(),
child: const Text('取消', style: TextStyle(color: Colors.grey)),
),
CupertinoButton(
onPressed: () {
if (isStart) {
customStartTime = tempDate;
// 如果开始时间变动后,结束时间早于开始时间,自动顺延一天
if (customEndTime != null &&
customEndTime!.isBefore(customStartTime!)) {
customEndTime = customStartTime!.add(const Duration(days: 1));
}
} else {
if (tempDate.isBefore(customStartTime ?? DateTime.now())) {
showToast('结束时间不能早于开始时间');
return;
}
customEndTime = tempDate;
}
updateUi();
Get.back();
},
child: const Text(
'确定',
style: TextStyle(
color: AppTheme.themeColor,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
const Divider(height: 1),
Expanded(
child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.dateAndTime,
initialDateTime: initialDate,
minimumDate: minLimit,
use24hFormat: true,
onDateTimeChanged: (DateTime newDate) {
tempDate = newDate;
},
),
),
],
),
),
backgroundColor: Colors.transparent,
);
}
void saveInfo() async {
if (selectedOperationStatus != "营运中") {
if (customStartTime == null || customEndTime == null) {
showToast("请选择开始和结束时间");
return;
}
}
showLoading("保存中");
try {
var responseData = await HttpService.to.post(
'appointment/station/updateStationStatus',
@@ -123,11 +225,17 @@ class ReservationController extends GetxController with BaseControllerMixin {
: selectedOperationStatus == "暂停营业"
? "3"
: 0,
'beginTime': selectedOperationStatus == "营运中"
? null
: DateFormat('yyyy-MM-dd HH:mm:ss').format(customStartTime!),
'endTime': selectedOperationStatus == "营运中"
? null
: DateFormat('yyyy-MM-dd HH:mm:ss').format(customEndTime!),
'updateTime': getNowDateTimeString(),
},
);
if (responseData == null && responseData!.data == null) {
if (responseData == null || responseData!.data == null) {
dismissLoading();
showToast('服务暂不可用,请稍后');
return;
@@ -143,10 +251,7 @@ class ReservationController extends GetxController with BaseControllerMixin {
}
void logout() async {
// TODO: 在这里执行退出登录的逻辑
//清理本地缓存的用户信息 导航到登录页面
await StorageService.to.clearLoginInfo();
Get.offAll(() => LoginPage());
}
}

View File

@@ -127,14 +127,12 @@ class ReservationPage extends GetView<ReservationController> {
_buildSectionTitle('运营信息'),
Text('运营状态', style: TextStyle(color: Colors.grey[600], fontSize: 14)),
const SizedBox(height: 8),
//下拉选择框
// 下拉选择框
DropdownButtonFormField<String>(
value: controller.selectedOperationStatus,
items: controller.operationStatusOptions.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
return DropdownMenuItem<String>(value: value, child: Text(value));
}).toList(),
onChanged: controller.onOperationStatusChanged,
decoration: InputDecoration(
@@ -142,8 +140,27 @@ class ReservationPage extends GetView<ReservationController> {
contentPadding: const EdgeInsets.symmetric(horizontal: 12.0),
),
),
const SizedBox(height: 12),
_buildDisplayField(label: '营业时间', value: controller.timeStr),
const SizedBox(height: 16),
// 根据状态动态显示 UI
if (controller.selectedOperationStatus == "营运中")
_buildDisplayField(label: '营业时间', value: controller.timeStr)
else
Column(
children: [
_buildClickField(
label: '开始时间',
value: controller.customStartTimeStr,
onTap: () => controller.pickDateTime(context, true),
),
_buildClickField(
label: '结束时间',
value: controller.customEndTimeStr,
onTap: () => controller.pickDateTime(context, false),
),
],
),
_buildDisplayField(label: '联系电话', value: controller.phone),
const SizedBox(height: 24),
@@ -162,70 +179,6 @@ class ReservationPage extends GetView<ReservationController> {
);
}
/// 构建静态提示信息卡片
Widget _buildTipsCard() {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
_buildInfoItem(Icons.info_outline, '请确保信息准确无误'),
const SizedBox(height: 10),
_buildInfoItem(Icons.help_outline, '价格信息将实时更新到用户端'),
const SizedBox(height: 10),
_buildInfoItem(Icons.headset_mic_outlined, '如有疑问请联系技术支持: 400-021-1773'),
const SizedBox(height: 10),
Row(
children: [
Icon(Icons.verified_outlined, color: Colors.blue, size: 20),
const SizedBox(width: 10),
Expanded(
child: FutureBuilder<String>(
future: getVersion(),
builder: (context, snapshot) {
// 判断是否还在加载
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("");
}
// 如果加载完成且有数据
if (snapshot.hasData) {
return TextX.labelSmall("当前版本: ${snapshot.data}",color: Colors.black54,);
}
// 错误处理
return const Text("");
},
),
),
],
)
],
),
),
);
}
/// 构建退出登录按钮
Widget _buildLogoutButton() {
return ElevatedButton(
onPressed: controller.logout,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
minimumSize: const Size(double.infinity, 48),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
elevation: 2,
),
child: const Text(
'退出登录',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
);
}
/// 构建带标题的表单区域
Widget _buildSectionTitle(String title) {
return Padding(
@@ -253,7 +206,7 @@ class ReservationPage extends GetView<ReservationController> {
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 12.0),
decoration: BoxDecoration(
color: Colors.grey[200], // 使用灰色背景以区分
color: Colors.grey[200],
borderRadius: BorderRadius.circular(8.0),
border: Border.all(color: Colors.grey[300]!),
),
@@ -267,6 +220,104 @@ class ReservationPage extends GetView<ReservationController> {
);
}
/// 构建一个“可点击”的选择行
Widget _buildClickField({required String label, required String value, required VoidCallback onTap}) {
return Padding(
padding: const EdgeInsets.only(bottom: 12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: TextStyle(color: Colors.grey[600], fontSize: 14)),
const SizedBox(height: 8),
InkWell(
onTap: onTap,
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 12.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8.0),
border: Border.all(color: Colors.blue.withOpacity(0.5)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
value,
style: const TextStyle(fontSize: 14, color: Colors.black87),
),
const Icon(Icons.calendar_month, size: 18, color: Colors.blue),
],
),
),
),
],
),
);
}
/// 构建静态提示信息卡片
Widget _buildTipsCard() {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
_buildInfoItem(Icons.info_outline, '请确保信息准确无误'),
const SizedBox(height: 10),
_buildInfoItem(Icons.help_outline, '价格信息将实时更新到用户端'),
const SizedBox(height: 10),
_buildInfoItem(Icons.headset_mic_outlined, '如有疑问请联系技术支持: 400-021-1773'),
const SizedBox(height: 10),
Row(
children: [
Icon(Icons.verified_outlined, color: Colors.blue, size: 20),
const SizedBox(width: 10),
Expanded(
child: FutureBuilder<String>(
future: getVersion(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("");
}
if (snapshot.hasData) {
return TextX.labelSmall(
"当前版本: ${snapshot.data}",
color: Colors.black54,
);
}
return const Text("");
},
),
),
],
),
],
),
),
);
}
/// 构建退出登录按钮
Widget _buildLogoutButton() {
return ElevatedButton(
onPressed: controller.logout,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
minimumSize: const Size(double.infinity, 48),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
elevation: 2,
),
child: const Text(
'退出登录',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
);
}
/// 构建带图标的提示信息行
Widget _buildInfoItem(IconData icon, String text) {
return Row(

View File

@@ -394,14 +394,16 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
},
);
var result = BaseModel.fromJson(responseData?.data);
if (responseData == null) {
dismissLoading();
showToast('服务暂不可用,请稍后');
showToast(result.error);
return;
}
dismissLoading();
var result = BaseModel.fromJson(responseData.data);
if (result.code == 0) {
showSuccessToast("预约成功");

View File

@@ -1,5 +1,7 @@
import 'package:aliyun_push_flutter/aliyun_push_flutter.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'package:ln_jq_app/common/login_util.dart';
import 'package:ln_jq_app/common/model/base_model.dart';
@@ -8,6 +10,7 @@ import 'package:ln_jq_app/common/styles/theme.dart';
import 'package:ln_jq_app/pages/b_page/base_widgets/view.dart';
import 'package:ln_jq_app/pages/c_page/base_widgets/view.dart';
import 'package:ln_jq_app/pages/login/controller.dart';
import 'package:ln_jq_app/pages/url_host/view.dart';
import 'package:ln_jq_app/storage_service.dart';
class LoginPage extends StatefulWidget {
@@ -414,7 +417,31 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
init: LoginController(),
id: 'login',
builder: (controller) {
return Scaffold(body: _buildView(controller));
return Scaffold(
body: Stack(
children: [
Positioned.fill(child: _buildView(controller)),
if (AppTheme.is_show_host)
Positioned(
top: 40.h,
right: 20.w,
child: TextButton(
onPressed: () {
Get.to(() => const UrlHostPage());
},
child: const Text(
"域名配置",
style: TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
);
},
);
}

View File

@@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'package:ln_jq_app/common/styles/theme.dart';
import 'package:ln_jq_app/storage_service.dart';
class UrlHostController extends GetxController {
final TextEditingController urlController = TextEditingController();
// 预设的域名列表
final List<String> presetUrls = [
'https://beta-esg.api.lnh2e.com/', // 测试环境
'http://192.168.110.44:8080/', // 沈辰本地
'http://192.168.110.222:8080/', // 何斐本地
];
final List<String> urlNames = [
'测试环境',
'沈辰本地环境',
'何斐本地环境',
];
@override
void onInit() {
super.onInit();
// 初始化时,尝试从 StorageService 获取已保存的域名
// 如果没有保存过,则使用当前的全局配置
String? savedUrl = StorageService.to.hostUrl;
if (savedUrl != null && savedUrl.isNotEmpty) {
urlController.text = savedUrl;
} else {
urlController.text = AppTheme.test_service_url;
}
}
/// 当用户点击列表中的某一项时,将其填入编辑框
void selectUrl(String url) {
urlController.text = url;
}
/// 保存配置并关闭页面
void saveAndExit() async {
String newUrl = urlController.text.trim();
if (newUrl.isEmpty) {
showToast('请输入有效的域名');
return;
}
// 保存到本地存储,以便下次启动时加载
await StorageService.to.saveHostUrl(newUrl);
AppTheme.test_service_url = newUrl;
//设置框架
HttpService.to.setBaseUrl(AppTheme.test_service_url);
showSuccessToast('域名已更新:${AppTheme.test_service_url}');
Get.back();
}
@override
void onClose() {
urlController.dispose();
super.onClose();
}
}

View File

@@ -0,0 +1,82 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'controller.dart';
class UrlHostPage extends GetView<UrlHostController> {
const UrlHostPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
Get.put(UrlHostController());
return Scaffold(
appBar: AppBar(
title: const Text('域名配置'),
centerTitle: true,
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
'当前环境配置',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
TextField(
controller: controller.urlController,
decoration: InputDecoration(
hintText: '请输入或选择API域名',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
),
),
const SizedBox(height: 24),
const Text(
'预设环境',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Expanded(
child: ListView.builder(
itemCount: controller.presetUrls.length,
itemBuilder: (context, index) {
final url = controller.presetUrls[index];
return Card(
elevation: 1,
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
title: Text(controller.urlNames[index], style: const TextStyle(fontSize: 14)),
subtitle: Text(url, style: const TextStyle(fontSize: 14)),
trailing: const Icon(Icons.touch_app, size: 18, color: Colors.grey),
onTap: () {
// 点击列表项:先填入编辑框,然后直接保存退出
controller.selectUrl(url);
controller.saveAndExit();
},
),
);
},
),
),
ElevatedButton(
onPressed: controller.saveAndExit,
style: ElevatedButton.styleFrom(
minimumSize: const Size(double.infinity, 48),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text('保存配置', style: TextStyle(fontSize: 16)),
),
],
),
),
);
}
}

View File

@@ -28,6 +28,8 @@ class StorageService extends GetxService {
// 新增:用于标记“绑定车辆”弹窗是否已在本会话中显示过
static const String _bindDialogShownKey = 'bind_vehicle_dialog_shown';
static const String _hostUrlKey = 'host_url';
static StorageService get to => Get.find();
Future<StorageService> init() async {
@@ -36,6 +38,13 @@ class StorageService extends GetxService {
}
// --- Getters ---
String? get hostUrl => _box.read<String?>(_hostUrlKey);
///:保存自定义域名
Future<void> saveHostUrl(String url) async {
await _box.write(_hostUrlKey, url);
}
bool get isLoggedIn => _box.read<String?>(_tokenKey)?.isNotEmpty ?? false;
String? get token => _box.read<String?>(_tokenKey);