ar_tourism_flutter_unity/lib/pages/publish/publish.dart
2025-05-14 17:04:13 +08:00

1247 lines
39 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:ar_tourism_flutter_unity/components/CustomAlertDialog.dart';
import 'package:flutter/material.dart';
import 'package:dotted_border/dotted_border.dart';
import 'package:intl/intl.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../apis/app.dart';
import '../../components/appbar.dart';
import '../../components/cached_image.dart';
// 添加 AreaState 类来管理每个区域的状态
class AreaState {
final List<Offset> seats;
String time;
String price;
int selectedSectionIndex;
AreaState({
List<Offset>? seats,
this.time = '',
this.price = '',
this.selectedSectionIndex = 0,
}) : seats = seats ?? [];
}
class PublishPage extends StatefulWidget {
const PublishPage({super.key});
@override
State<PublishPage> createState() => _PublishPageState();
}
class _PublishPageState extends State<PublishPage> with WidgetsBindingObserver {
// 添加总价变量
double _totalPrice = 0.0;
// 1. 使用常量存储固定值
static const Color gradientColor1 = Color(0xFF80DAA4);
static const Color gradientColor2 = Color(0xFF79DDED);
// 2. 缓存常用样式
static const TextStyle whiteTextStyle =
TextStyle(color: Colors.white, fontSize: 14);
static const TextStyle smallWhiteTextStyle =
TextStyle(color: Colors.white, fontSize: 12);
// 3. 控制器
final ScrollController _horizontalController = ScrollController();
final ScrollController _verticalController = ScrollController();
// 替换原有的区域相关状态变量
final Map<String, AreaState> areaStates = {};
// 用于记录所有可用的区域
Set<String> availableAreas = {};
bool _isExpanded = false;
// 添加新的状态变量
int horizontalCount = 0;
int verticalCount = 0;
List<Map<String, dynamic>> seats = [];
bool isLoading = true;
String? bgPicViews;
String? error;
// 现有的状态变量
String? selectedLocation = '济南';
String? selectedPlace = '泉城广场';
// int selectedSectionIndexA = 0;
// int selectedSectionIndexB = 0;
// List<Offset> areaASeats = [];
// List<Offset> areaBSeats = [];
// String areaATime = '';
// String areaAPrice = '';
// String areaBTime = '';
// String areaBPrice = '';
// 4. 预计算的位置数据
late final List<Offset> gradientRectPositions;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_checkAuthStatus();
}
// 在 _PublishPageState 类中添加认证状态检查方法
Future<bool> _checkRealNameAuthStatus() async {
try {
SharedPreferences prefs = await SharedPreferences.getInstance();
String phone = prefs.getString('phone') ?? '';
if (phone.isEmpty) {
return false;
}
// 调用API获取用户信息包括认证状态
final response = await userApi.getSetting(phone);
print("获取用户信息: $response");
print("获取用户信息: ${response['isAuthenticated']}");
if (response['code'] == 200) {
// 获取认证状态
bool isAuthenticated = response['authenticationStatus'] == '1';
// 更新本地存储
await prefs.setBool('isRealNameAuth', isAuthenticated);
print('从服务器获取的认证状态: $isAuthenticated');
return isAuthenticated;
}
return false;
} catch (e) {
print('检查认证状态失败: $e');
// 出错时返回本地存储的状态(作为后备方案)
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getBool('isRealNameAuth') ?? false;
}
}
// 添加认证状态检查方法
Future<void> _checkAuthStatus() async {
// 使用方法检查认证状态
bool isRealNameAuth = await _checkRealNameAuthStatus();
print("认证状态: $isRealNameAuth");
if (!isRealNameAuth && mounted) {
// 用户未认证,显示对话框
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return CustomAlertDialog(
title: '需要实名认证',
content: '发布作品需要先完成实名认证',
onConfirm: () {
Navigator.of(context).pop();
Navigator.pushNamed(context, '/realName');
},
onCancel: () {
Navigator.of(context).pop();
Navigator.pushNamedAndRemoveUntil(
context,
'/tabs',
(route) => false,
arguments: {'initialIndex': 0},
);
},
confirmText: '去认证',
cancelText: '取消',
);
},
);
}
}
// 添加计算总价的方法
void _calculateTotalPrice() {
double total = 0.0;
// 遍历所有选中的区域
for (var entry in areaStates.entries) {
if (entry.value.seats.isNotEmpty) {
// 获取价格字符串去掉¥符号转换为double
String priceStr = entry.value.price.replaceAll('', '');
double price = double.tryParse(priceStr) ?? 0.0;
// 乘以该区域选中的座位数
total += price * entry.value.seats.length;
}
}
setState(() {
_totalPrice = total;
});
print('当前总价: ¥${_totalPrice.toStringAsFixed(2)}');
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_horizontalController.dispose();
_verticalController.dispose();
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
_checkCollection();
}
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
// 每次依赖变化时都检查作品集和获取数据
_checkCollection();
_fetchCityAndScenic();
}
// 检查作品集是否存在
Future<void> _checkCollection() async {
if (!mounted) return;
try {
final response = await userApi.getMyCollection();
print('检查作品集结果: $response');
if (mounted && response['total'] == 0) {
_showCreateCollectionDialog();
}
} catch (e) {
print('检查作品集失败: $e');
}
}
// 添加显示创建作品集对话框方法
void _showCreateCollectionDialog() {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return CustomAlertDialog(
title: '当前暂无作品集,请立即前往去创建',
content: '',
onConfirm: () {
Navigator.of(context).pop();
// 使用pushNamedAndRemoveUntil并确保设置初始索引为2(CenterPage)
Navigator.pushNamedAndRemoveUntil(
context,
'/tabs',
(route) => false,
arguments: {'initialIndex': 2, 'openAssetPicker': true}, // 传递参数设置初始选中的tab并标记打开相册
);
},
onCancel: () {
Navigator.of(context).pop();
Navigator.pushNamedAndRemoveUntil(
context,
'/tabs',
(route) => false,
arguments: {'initialIndex': 0}, // 取消
);
},
confirmText: '去创建',
cancelText: '取消',
);
},
);
}
// 6. 预加载图片资源
Future<void> _precacheImages() async {
await precacheImage(const AssetImage('images/choice.png'), context);
await precacheImage(const AssetImage('images/arraw.png'), context);
await precacheImage(const AssetImage('images/right_arraw.png'), context);
}
// 获取下拉城市和景区 默认显示济南 泉城广场getCityAndScenic
Future<void> _fetchCityAndScenic() async {
var result = await userApi.getCityAndScenic();
print("获取下拉城市和景区: $result");
List<dynamic> cityList = result['cityList'] ?? [];
List<dynamic> scenicList = result['scenicList'] ?? [];
setState(() {
// 更新城市和景区列表
selectedLocation =
cityList.isNotEmpty ? cityList[0]['city'] : '济南'; // 默认值
selectedPlace =
scenicList.isNotEmpty ? scenicList[0]['scenic'] : '泉城广场'; // 默认值
cities = cityList.map((city) => city['city'] as String).toList();
scenics = scenicList.map((scenic) => scenic['scenic'] as String).toList();
});
// 使用获取的城市和景区数据获取资源位列表
_fetchSeatData(selectedLocation!, selectedPlace!);
}
// 新增状态变量
List<String> cities = [];
List<String> scenics = [];
// 获取资源位列表
Future<void> _fetchSeatData(String city, String scenic) async {
setState(() => isLoading = true);
try {
final response = await userApi.getPublishList({
'city': city,
'scenic': scenic,
});
setState(() {
if (response['code'] == 200 && response['sourceList'] != null) {
horizontalCount = response['horizontalCount'];
verticalCount = response['verticalCount'];
bgPicViews = response['preview'];
seats = (response['sourceList'] as List)
.map((item) => Map<String, dynamic>.from(item))
.toList();
} else {
// 重置数据
horizontalCount = 0;
verticalCount = 0;
seats = [];
}
isLoading = false;
});
} catch (e) {
setState(() {
error = '获取数据失败';
isLoading = false;
});
}
}
Map<String, dynamic> _getSeatByCoords(int x, int y) {
// print("@@@@@@@@@@@@@@@@@@@@rowIndex: $rowIndex");
// print("@@@@@@@@@@@@@@@@@@@@colIndex: $colIndex");
try {
return seats.firstWhere(
(seat) => seat['cX'] == x && seat['cY'] == y,
orElse: () => {'sourceStatus': '4'}, // 直接返回默认的不可选状态
);
} catch (e) {
// 如果发生任何错误,返回默认的不可选状态
return {'sourceStatus': '4'};
}
}
// 添加获取价格的方法
Future<List<Map<String, dynamic>>> _fetchPriceOptions(int sourceId) async {
final response = await userApi.getPrice(sourceId);
if (response['code'] == 200) {
return List<Map<String, dynamic>>.from(response['data']);
}
return [];
}
// 修改格式化日期的方法,将天数转为整数
String _formatDateFromDays(double days) {
// 将天数转为整数
int intDays = days.toInt();
final startDate = DateTime.now().add(Duration(days: 1));
final endDate = startDate.add(Duration(days: intDays));
return '${DateFormat('MM.dd').format(startDate)}--${DateFormat('MM.dd').format(endDate)}';
}
@override
Widget build(BuildContext context) {
// if (isLoading) {
// return const Scaffold(
// body: Center(
// child: CircularProgressIndicator(),
// ),
// );
// }
// if (!isLoggedIn) {
// return const SizedBox.shrink();
// }
return Scaffold(
appBar: const CustomAppBar(title: '发布', showLeading: false),
body: Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(color: Color(0xff0C0C16)),
child: Column(
children: [
_buildDropdowns(showPreview: seats.isNotEmpty),
const SizedBox(height: 10),
if (seats.isNotEmpty) ...[
_buildRectangleButtons(),
const SizedBox(height: 10),
],
Expanded(
child: isLoading
? const Center(
child: CircularProgressIndicator(
color: Colors.white,
),
)
: seats.isEmpty
? const Center(
child: Text(
'该景区暂无资源位',
style: TextStyle(
color: Colors.white,
fontSize: 16,
),
),
)
: Scrollbar(
thickness: 2.0,
radius: const Radius.circular(8.0),
child: SingleChildScrollView(
controller: _verticalController,
child: Row(
children: [
RepaintBoundary(child: _buildLeftSidebar()),
_buildSeatMatrix(),
],
),
),
),
),
if (seats.isNotEmpty) _buildExpandableSection(),
],
),
),
);
}
// 8. 优化下拉菜单构建
Widget _buildDropdowns({bool showPreview = false}) {
const dropdownStyle = TextStyle(color: Colors.white, fontSize: 14);
return Container(
height: 30,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.white.withOpacity(0.3),
width: 1.0,
),
),
),
child: Row(
children: [
// 城市下拉菜单
DropdownButton<String>(
value: selectedLocation,
dropdownColor: Colors.transparent,
underline: const SizedBox.shrink(),
style: dropdownStyle,
iconEnabledColor: Colors.white,
items: cities.map((city) {
return DropdownMenuItem<String>(
value: city,
child: Text(city, style: dropdownStyle),
);
}).toList(),
onChanged: (value) {
setState(() {
selectedLocation = value!;
// 选择城市后,重新获取景区列表
selectedPlace = scenics.isNotEmpty ? scenics[0] : '';
_fetchSeatData(selectedLocation!, selectedPlace!);
});
},
),
const SizedBox(width: 16),
// 景区下拉菜单
DropdownButton<String>(
value: selectedPlace,
dropdownColor: Colors.transparent,
underline: const SizedBox.shrink(),
style: dropdownStyle,
iconEnabledColor: Colors.white,
items: scenics.map((scenic) {
return DropdownMenuItem<String>(
value: scenic,
child: Text(scenic, style: dropdownStyle),
);
}).toList(),
onChanged: (value) {
setState(() {
selectedPlace = value!;
_fetchSeatData(selectedLocation!, selectedPlace!);
});
},
),
const Spacer(),
if (showPreview) _buildPreviewButton(),
],
),
);
}
// 9. 抽离下拉按钮构建
Widget _buildDropdownButton(String value, TextStyle style) {
return DropdownButton<String>(
value: value,
dropdownColor: Colors.transparent,
underline: const SizedBox.shrink(),
style: style,
iconEnabledColor: Colors.white,
items: [
DropdownMenuItem<String>(
value: value,
child: Text(value, style: style),
),
],
onChanged: (_) {},
);
}
// 10. 抽离预览按钮构建
Widget _buildPreviewButton() {
return GestureDetector(
onTap: _showPreviewDialog,
child: ShaderMask(
shaderCallback: (Rect bounds) {
return const LinearGradient(
colors: [gradientColor1, gradientColor2],
).createShader(bounds);
},
child: const Text('全局预览', style: whiteTextStyle),
),
);
}
// 11. 优化预览对话框显示
void _showPreviewDialog() {
showDialog(
context: context,
builder: (BuildContext context) {
final screenSize = MediaQuery.of(context).size;
final maxWidth = screenSize.width * 0.85;
final maxHeight = screenSize.height * 0.85;
return Dialog(
backgroundColor: const Color(0xFF1F212E),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
child: Padding(
padding: const EdgeInsets.all(8),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: const Icon(Icons.close, color: Colors.white, size: 20),
),
],
),
const SizedBox(height: 8),
if (bgPicViews != null)
Flexible(
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: maxWidth,
maxHeight: maxHeight,
),
child: InteractiveViewer(
minScale: 0.5,
maxScale: 3.0,
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: CachedImage(
imageUrl: bgPicViews!,
width: maxWidth,
height: maxHeight,
fit: BoxFit.contain,
placeholder: (context, url) => SizedBox(
width: maxWidth,
height: maxHeight * 0.5,
child: const Center(
child: CircularProgressIndicator(
color: Colors.white,
),
),
),
errorWidget: (context, url, error) => SizedBox(
width: maxWidth,
height: maxHeight * 0.5,
child: const Center(
child: Text(
'图片加载失败',
style: TextStyle(color: Colors.white),
),
),
),
),
),
),
),
)
else
SizedBox(
width: maxWidth,
height: maxHeight * 0.5,
child: const Center(
child: Text(
'暂无预览图',
style: TextStyle(color: Colors.white),
),
),
),
const SizedBox(height: 16),
],
),
),
);
},
);
}
// 12. 优化矩形按钮行构建
Widget _buildRectangleButtons() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildButtonWithLabel("已售", Colors.red),
_buildButtonWithLabel(
"已选", // 显示文本
Colors.transparent,
imagePath: 'images/choice.png', // 使用图片路径
),
_buildButtonWithLabel(
"A区(最佳展示区)",
Color(0xffFFB969),
isBordered: true,
),
_buildButtonWithLabel(
"B区",
Color(0xFF96FBE9).withOpacity(0.8),
isBordered: true,
),
_buildButtonWithLabel("不可选", Colors.grey, isDisabled: true),
],
);
}
// 13. 优化左侧边栏构建
Widget _buildLeftSidebar() {
return Container(
width: 25,
margin: const EdgeInsets.only(top: 30),
color: Colors.black,
// 使用 ListView.builder 代替直接生成列表,实现按需加载
child: ListView.builder(
itemCount: verticalCount,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (context, index) => _SidebarItem(index: index),
),
);
}
// 14. 优化座位矩阵构建
Widget _buildSeatMatrix() {
return Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
controller: _horizontalController,
child: Column(
children: [
_buildHeaderRow(),
...List.generate(verticalCount, (y) => _buildSeatRow(y)),
],
),
),
);
}
// 15. 优化表头行构建
Widget _buildHeaderRow() {
return Row(
children: List.generate(
horizontalCount,
(x) => Container(
width: 30,
height: 25,
alignment: Alignment.center,
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [
Color(0x3380DAA4),
Color(0x3379DDED),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Text(
'${x}',
style: smallWhiteTextStyle,
),
),
),
);
}
// 16. 优化座位行构建
Widget _buildSeatRow(int y) {
return Row(
children: List.generate(
horizontalCount,
(x) {
final currentSeat = _getSeatByCoords(x, y);
return GestureDetector(
onTap: () {
if (currentSeat['sourceStatus'] == '0') {
final currentOffset = Offset(x.toDouble(), y.toDouble());
String part = currentSeat['part'] ?? '';
int sourceId = currentSeat['sourceId'] ?? 0;
// 添加座位坐标日志
print('选择的座位坐标: x=$x, y=$y, part=$part, sourceId=$sourceId');
setState(() {
// 确保区域状态存在
areaStates.putIfAbsent(part, () => AreaState());
var areaState = areaStates[part]!;
if (areaState.seats.contains(currentOffset)) {
areaState.seats.remove(currentOffset);
print('移除座位: $currentOffset from $part区');
if (areaStates.values
.every((state) => state.seats.isEmpty)) {
_isExpanded = false;
}
// 计算新的总价
_calculateTotalPrice();
} else {
_isExpanded = true;
areaState.seats.add(currentOffset);
print('添加座位: $currentOffset to $part区');
_fetchPriceOptions(sourceId).then((priceOptions) {
if (priceOptions.isNotEmpty) {
setState(() {
final formattedPrice =
(priceOptions[0]['price'] / 1000)
.toStringAsFixed(1);
areaState.selectedSectionIndex = 1;
areaState.time =
_formatDateFromDays(priceOptions[0]['days']);
areaState.price = '$formattedPrice';
_calculateTotalPrice(); // 更新总价
print(
'默认选择: ${part}区 - ${priceOptions[0]['days']}天 - 时间: ${areaState.time} - 价格: ${areaState.price}');
});
}
});
}
});
}
},
child: _buildSeatCell(x, y, currentSeat),
);
},
),
);
}
// 一些可用的格子 可选
// 修改 _buildSeatCell 方法
Widget _buildSeatCell(int x, int y, Map<String, dynamic> seat) {
String sourceStatus = seat['sourceStatus'] ?? '4';
String part = seat['part'] ?? '';
bool isSelected = false;
// 检查是否被选中 - 使用新的 areaStates
Offset currentOffset = Offset(x.toDouble(), y.toDouble());
var areaState = areaStates[part];
isSelected = areaState?.seats.contains(currentOffset) ?? false;
return Container(
width: 30,
height: 30,
alignment: Alignment.center,
child: Stack(
alignment: Alignment.center,
children: [
if (isSelected)
Image.asset(
'images/choice.png',
width: 30,
height: 30,
fit: BoxFit.cover,
)
else if (sourceStatus == '1' || sourceStatus == '2')
//
Container(
decoration: BoxDecoration(
color: Colors.red,
border: Border.all(color: Colors.red, width: 0),
borderRadius: BorderRadius.circular(6),
),
)
else if (sourceStatus == '3')
Container(
decoration: BoxDecoration(
color: Colors.grey,
border: Border.all(color: Colors.grey, width: 0),
borderRadius: BorderRadius.circular(6),
),
)
else if (sourceStatus == '0')
Container(
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: part == 'A'
? const Color(0xffFFB969).withOpacity(0.6)
: const Color(0xFF96FBE9).withOpacity(0.8),
),
borderRadius: BorderRadius.circular(6),
),
)
else
DottedBorder(
borderType: BorderType.Rect,
color: const Color(0xff374747),
strokeWidth: 1,
dashPattern: const [4, 8],
child: Container(
width: 28,
height: 28,
),
),
// Text(
// '($x,$y)',
// style: const TextStyle(color: Colors.white, fontSize: 6),
// ),
],
),
);
}
// 已售 已选等样式
Widget _buildButtonWithLabel(String? label, Color color,
{bool isBordered = false,
bool isDisabled = false,
bool isDashed = false,
String? imagePath}) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
if (isDashed)
DottedBorder(
borderType: BorderType.RRect,
radius: const Radius.circular(4),
color: color,
strokeWidth: 1,
dashPattern: const [4, 4],
child: Container(
width: 13,
height: 13,
),
)
else
Container(
width: 17,
height: 17,
decoration: BoxDecoration(
color: isBordered ? Colors.transparent : color,
border: isBordered ? Border.all(color: color) : null,
borderRadius: BorderRadius.circular(4),
),
child: imagePath != null
? Image.asset(
imagePath,
width: 17,
height: 17,
fit: BoxFit.fill,
)
: null,
),
const SizedBox(width: 4),
if (label != null)
Text(
label,
style: const TextStyle(color: Color(0xFF9799A5), fontSize: 10), // 修改文字颜色
),
],
);
}
// 底部可舒展的展示时间
Widget _buildExpandableSection() {
return Column(
children: [
const SizedBox(height: 10),
// 展示时间和A区B区使用统一容器
Container(
decoration: const BoxDecoration(
color: Color(0xff1F212E),
borderRadius: BorderRadius.all(Radius.circular(6)),
),
margin: const EdgeInsets.symmetric(horizontal: 8),
child: Column(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
RichText(
text: const TextSpan(
children: [
TextSpan(
text: '展示时间',
style: TextStyle(color: Colors.white),
),
TextSpan(
text: '每日12点更新',
style: TextStyle(color: Color(0xFFEE7371)), // 修改为红色
),
],
),
),
GestureDetector(
onTap: () {
setState(() {
_isExpanded = !_isExpanded;
});
},
child: Image.asset(
_isExpanded ? 'images/down.png' : 'images/up.png',
width: 12,
height: 12,
),
),
],
),
),
if (_isExpanded) _buildContent(),
],
),
),
// 选择作品行保持独立
_buildChoiceContent(),
],
);
}
Widget _buildChoiceContent() {
if (areaStates.values.every((state) => state.seats.isEmpty)) {
return Container();
}
return Container(
width: double.infinity,
padding: const EdgeInsets.all(8),
margin: const EdgeInsets.all(5),
child: Row(
children: [
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
...areaStates.entries
.where((entry) => entry.value.seats.isNotEmpty)
.map((entry) => _buildAreaBox(
entry.key,
entry.value.seats,
entry.value.time,
entry.value.price,
)),
],
),
),
),
const SizedBox(width: 10),
GestureDetector(
onTap: () async {
if (areaStates.values.every((state) => state.seats.isEmpty))
return;
List<Map<String, dynamic>> selectedPositions = [];
await Future.wait(areaStates.entries
.where((entry) => entry.value.seats.isNotEmpty)
.map((entry) async {
for (var seat in entry.value.seats) {
final seatInfo =
_getSeatByCoords(seat.dx.toInt(), seat.dy.toInt());
final position =
'${seatInfo['part']}${seat.dx.toInt().toString().padLeft(2, '0')}${seat.dy.toInt().toString().padLeft(2, '0')}';
final priceOptions =
await _fetchPriceOptions(seatInfo['sourceId']);
if (priceOptions.isNotEmpty) {
int selectedIndex = entry.value.selectedSectionIndex - 1;
if (selectedIndex >= 0 &&
selectedIndex < priceOptions.length) {
int days = priceOptions[selectedIndex]['days'].toInt();
selectedPositions.add({
'position': position, // 确保这个字段存在
'sourceId': seatInfo['sourceId'],
'day': days,
'totalPrice': _totalPrice,
});
}
}
}
}));
print("-------------selectedPositions: $selectedPositions");
// print("-------------总价: ¥${_totalPrice.toStringAsFixed(2)}");
if (selectedPositions.isNotEmpty) {
Navigator.pushNamed(
context,
'/choiceWorks',
arguments: selectedPositions
);
}
},
child: Row(
children: [
const Text(
"选择作品",
style: TextStyle(color: Color(0xFF7CDCCC), fontSize: 14),
),
const SizedBox(width: 5),
Image.asset(
'images/right_arraw.png',
width: 10,
height: 10,
color: const Color(0xFF7CDCCC), // 设置箭头颜色
),
],
),
),
],
),
);
}
// 修改 _buildAreaBox 方法
Widget _buildAreaBox(String area, List<Offset> seats, String time, String price) {
double containerWidth = seats.length <= 3 ? 140.0 : seats.length * 30.0;
return Container(
padding: const EdgeInsets.all(5),
margin: const EdgeInsets.only(right: 8),
decoration: BoxDecoration(
color: const Color(0xFF96FBE9).withOpacity(0.2),
borderRadius: BorderRadius.circular(5),
border: Border.all(color: const Color(0xFF96FBE9)),
),
child: Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
seats.map((seat) {
final seatInfo = _getSeatByCoords(seat.dx.toInt(), seat.dy.toInt());
final part = seatInfo['part'] ?? '';
return '$part${seat.dx.toInt().toString().padLeft(2, '0')}${seat.dy.toInt().toString().padLeft(2, '0')}';
}).join(''),
style: const TextStyle(color: Colors.white, fontSize: 12),
),
const SizedBox(height: 4),
SizedBox(
width: containerWidth,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
time,
style: const TextStyle(color: Colors.white, fontSize: 12, height: 1.2),
),
const SizedBox(width: 15),
Text(
price,
style: const TextStyle(
color: Color(0xFFEE7371), // 修改为红色
fontSize: 12,
height: 1.2,
),
),
],
),
),
],
),
InkWell(
onTap: () {
setState(() {
areaStates[area]?.seats.clear();
if (areaStates.values.every((state) => state.seats.isEmpty)) {
_isExpanded = false;
}
});
},
child: const Icon(
Icons.close,
color: Colors.white,
size: 15,
),
),
],
),
);
}
Widget _buildContent() {
return Container(
color: const Color(0xff1F212E),
padding: const EdgeInsets.symmetric(horizontal: 16), // 与展示时间保持相同的左对齐
child: Column(
children: [
...areaStates.entries
.where((entry) => entry.value.seats.isNotEmpty)
.map((entry) => FutureBuilder<List<Map<String, dynamic>>>(
future: _fetchPriceOptions(_getSeatByCoords(
entry.value.seats.first.dx.toInt(),
entry.value.seats.first.dy.toInt(),
)['sourceId']),
builder: (context, snapshot) {
if (snapshot.hasData) {
return _buildHorizontalScrollArea(
'${entry.key}',
snapshot.data!,
part: entry.key,
);
}
return const CircularProgressIndicator();
},
)),
],
),
);
}
Widget _buildHorizontalScrollArea(
String section,
List<Map<String, dynamic>> priceOptions, {
required String part,
}) {
return Container(
margin: const EdgeInsets.only(bottom: 10),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
Text(section, style: const TextStyle(color: Colors.white)),
...priceOptions.asMap().entries.map((entry) {
final index = entry.key + 1;
final option = entry.value;
final intDays = option['days'].toInt();
final formattedPrice =
(option['price'] / 1000).toStringAsFixed(1);
return GestureDetector(
onTap: () {
setState(() {
// 更新选中区域的状态
var areaState = areaStates[part]!;
areaState.selectedSectionIndex = index;
areaState.time = _formatDateFromDays(option['days']);
areaState.price = '$formattedPrice';
print(
'切换选择: ${part}区 - ${intDays}天 - 时间: ${areaState.time} - 价格: ${areaState.price}');
// 计算新的总价
_calculateTotalPrice();
});
},
child: _buildInfoBox(
section,
"${intDays}天(${_formatDateFromDays(option['days'])}",
"$formattedPrice",
areaStates[part]?.selectedSectionIndex == index,
),
);
}).toList(),
],
),
),
);
}
Widget _buildInfoBox(
String section, String duration, String price, bool isSelected) {
return Container(
height: 30,
margin: const EdgeInsets.symmetric(horizontal: 2),
// padding: const EdgeInsets.all(5),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5),
decoration: BoxDecoration(
color: isSelected
? const Color(0xFF96FBE9).withOpacity(0.2)
: Colors.transparent, // 选中时变为绿色
border: Border.all(
color: isSelected ? const Color(0xFF96FBE9) : Colors.white, // Update border color when selected
),
borderRadius: BorderRadius.circular(4),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(duration,
style: const TextStyle(
color: Colors.white,
fontSize: 12,
height: 1.5,
)),
Text(price,
style: const TextStyle(
color: Color(0xFF7CDCCC),
fontSize: 16,
height: 1.2,
)),
],
),
);
}
}
// 26. 抽离边栏项为独立Widget
class _SidebarItem extends StatelessWidget {
final int index;
const _SidebarItem({Key? key, required this.index}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: 30,
alignment: Alignment.center,
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [
Color(0x3380DAA4),
Color(0x3379DDED),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Text(
'$index',
style: const TextStyle(color: Colors.white, fontSize: 12),
),
);
}
}