1247 lines
39 KiB
Dart
1247 lines
39 KiB
Dart
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),
|
||
),
|
||
);
|
||
}
|
||
}
|