440 lines
13 KiB
Dart
440 lines
13 KiB
Dart
import 'package:asset_assistant/pages/country_add_page.dart';
|
||
import 'package:asset_assistant/utils/host_utils.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:dio/dio.dart';
|
||
|
||
// 国家数据模型(flag字段存储emoji)
|
||
class Country {
|
||
final String countryId;
|
||
final String name;
|
||
final String code;
|
||
final String? flag; // 国旗字段,存储emoji
|
||
|
||
Country({
|
||
required this.countryId,
|
||
required this.name,
|
||
required this.code,
|
||
this.flag,
|
||
});
|
||
|
||
// 从JSON构建对象
|
||
factory Country.fromJson(Map<String, dynamic> json) {
|
||
return Country(
|
||
countryId: json['country_id'],
|
||
name: json['name'],
|
||
code: json['code'],
|
||
flag: json['flag'], // 解析emoji字段
|
||
);
|
||
}
|
||
}
|
||
|
||
// 接口响应模型
|
||
class CountryResponse {
|
||
final bool success;
|
||
final String message;
|
||
final CountryData data;
|
||
|
||
CountryResponse({
|
||
required this.success,
|
||
required this.message,
|
||
required this.data,
|
||
});
|
||
|
||
factory CountryResponse.fromJson(Map<String, dynamic> json) {
|
||
return CountryResponse(
|
||
success: json['success'],
|
||
message: json['message'],
|
||
data: CountryData.fromJson(json['data']),
|
||
);
|
||
}
|
||
}
|
||
|
||
// 响应数据模型
|
||
class CountryData {
|
||
final int total;
|
||
final int page;
|
||
final int pageSize;
|
||
final List<Country> items;
|
||
|
||
CountryData({
|
||
required this.total,
|
||
required this.page,
|
||
required this.pageSize,
|
||
required this.items,
|
||
});
|
||
|
||
factory CountryData.fromJson(Map<String, dynamic> json) {
|
||
var itemsList = json['items'] as List? ?? [];
|
||
List<Country> items = itemsList.map((i) => Country.fromJson(i)).toList();
|
||
|
||
return CountryData(
|
||
total: json['total'],
|
||
page: json['page'],
|
||
pageSize: json['page_size'],
|
||
items: items,
|
||
);
|
||
}
|
||
}
|
||
|
||
class CountryPage extends StatefulWidget {
|
||
const CountryPage({super.key});
|
||
|
||
@override
|
||
State<CountryPage> createState() => _CountryPageState();
|
||
}
|
||
|
||
class _CountryPageState extends State<CountryPage> {
|
||
List<Country> _countries = [];
|
||
bool _isLoading = true;
|
||
String? _errorMessage;
|
||
int _currentPage = 1;
|
||
final int _pageSize = 20;
|
||
bool _hasMoreData = true;
|
||
late ScrollController _scrollController;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_scrollController = ScrollController();
|
||
_scrollController.addListener(_onScroll);
|
||
_fetchCountries();
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_scrollController.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
void _onScroll() {
|
||
if (_isLoading) return;
|
||
if (_hasMoreData &&
|
||
_scrollController.position.pixels >=
|
||
_scrollController.position.maxScrollExtent - 200) {
|
||
_loadMore();
|
||
}
|
||
}
|
||
|
||
Future<void> _fetchCountries({bool isRefresh = false}) async {
|
||
if (isRefresh) {
|
||
setState(() {
|
||
_currentPage = 1;
|
||
_hasMoreData = true;
|
||
});
|
||
}
|
||
|
||
if (!_hasMoreData && !isRefresh) return;
|
||
|
||
setState(() {
|
||
_isLoading = true;
|
||
});
|
||
|
||
try {
|
||
final baseUrl = HostUtils().currentHost;
|
||
final path = '/country/read';
|
||
final url = '$baseUrl$path';
|
||
debugPrint('====== 开始请求国家列表 ======');
|
||
debugPrint('请求URL: $url');
|
||
debugPrint('请求参数: page=$_currentPage, page_size=$_pageSize');
|
||
|
||
final dio = Dio();
|
||
final response = await dio.post(
|
||
url,
|
||
data: {
|
||
'page': _currentPage,
|
||
'page_size': _pageSize,
|
||
'name': '',
|
||
'code': '',
|
||
'country_id': '',
|
||
'flag': '',
|
||
},
|
||
options: Options(
|
||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||
),
|
||
);
|
||
|
||
debugPrint('请求成功,状态码: ${response.statusCode}');
|
||
debugPrint('响应数据: ${response.data}');
|
||
|
||
if (response.statusCode == 200) {
|
||
final CountryResponse countryResponse = CountryResponse.fromJson(
|
||
response.data,
|
||
);
|
||
|
||
if (countryResponse.success) {
|
||
setState(() {
|
||
if (isRefresh) {
|
||
_countries = countryResponse.data.items;
|
||
} else {
|
||
_countries.addAll(countryResponse.data.items);
|
||
}
|
||
|
||
_hasMoreData = _countries.length < countryResponse.data.total;
|
||
_currentPage++;
|
||
_errorMessage = null;
|
||
});
|
||
debugPrint('数据解析成功,当前列表总数: ${_countries.length}');
|
||
debugPrint('是否还有更多数据: $_hasMoreData,下一页: $_currentPage');
|
||
} else {
|
||
setState(() {
|
||
_errorMessage = countryResponse.message;
|
||
});
|
||
debugPrint('接口返回失败: ${countryResponse.message}');
|
||
}
|
||
} else {
|
||
setState(() {
|
||
_errorMessage = '服务器响应异常: ${response.statusCode}';
|
||
});
|
||
debugPrint('服务器响应异常: ${response.statusCode}');
|
||
}
|
||
} on DioException catch (e) {
|
||
String errorMsg = '网络请求失败';
|
||
if (e.response != null) {
|
||
errorMsg = '请求失败: ${e.response?.statusCode}';
|
||
debugPrint(
|
||
'请求错误,状态码: ${e.response?.statusCode},响应数据: ${e.response?.data}',
|
||
);
|
||
} else if (e.type == DioExceptionType.connectionTimeout) {
|
||
errorMsg = '连接超时,请检查网络';
|
||
debugPrint('连接超时: ${e.message}');
|
||
} else if (e.type == DioExceptionType.connectionError) {
|
||
errorMsg = '网络连接错误';
|
||
debugPrint('网络连接错误: ${e.message}');
|
||
} else {
|
||
debugPrint('Dio异常: ${e.type},消息: ${e.message}');
|
||
}
|
||
setState(() {
|
||
_errorMessage = errorMsg;
|
||
});
|
||
} catch (e) {
|
||
setState(() {
|
||
_errorMessage = '发生未知错误: $e';
|
||
});
|
||
debugPrint('未知错误: $e');
|
||
} finally {
|
||
setState(() {
|
||
_isLoading = false;
|
||
});
|
||
debugPrint('====== 请求结束 ======\n');
|
||
}
|
||
}
|
||
|
||
Future<void> _refresh() async {
|
||
debugPrint('触发下拉刷新');
|
||
await _fetchCountries(isRefresh: true);
|
||
}
|
||
|
||
void _loadMore() {
|
||
if (!_isLoading && _hasMoreData) {
|
||
debugPrint('触发加载更多,当前页: $_currentPage');
|
||
_fetchCountries();
|
||
}
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final theme = Theme.of(context);
|
||
|
||
return Scaffold(
|
||
appBar: AppBar(
|
||
title: const Text('国家列表'),
|
||
centerTitle: true,
|
||
elevation: 4,
|
||
shadowColor: Colors.black12,
|
||
backgroundColor: theme.colorScheme.surfaceContainerHighest,
|
||
leading: IconButton(
|
||
icon: const Icon(Icons.arrow_back),
|
||
onPressed: () {
|
||
Navigator.of(context).pop();
|
||
},
|
||
),
|
||
actions: [
|
||
IconButton(
|
||
icon: const Icon(Icons.add),
|
||
onPressed: () async {
|
||
debugPrint('点击新增按钮,跳转到新增页面');
|
||
final result = await Navigator.push(
|
||
context,
|
||
MaterialPageRoute(builder: (context) => const AddCountryPage()),
|
||
);
|
||
if (result == true) {
|
||
debugPrint('从新增页面返回,刷新列表数据');
|
||
_fetchCountries(isRefresh: true);
|
||
}
|
||
},
|
||
),
|
||
],
|
||
),
|
||
body: SafeArea(child: _buildBody(theme)),
|
||
);
|
||
}
|
||
|
||
Widget _buildBody(ThemeData theme) {
|
||
// 加载中且列表为空
|
||
if (_isLoading && _countries.isEmpty) {
|
||
return const Center(child: CircularProgressIndicator());
|
||
}
|
||
|
||
// 有错误信息且列表为空
|
||
if (_errorMessage != null && _countries.isEmpty) {
|
||
return Center(
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Text(
|
||
_errorMessage!,
|
||
style: TextStyle(color: theme.colorScheme.error),
|
||
),
|
||
const SizedBox(height: 16),
|
||
ElevatedButton(
|
||
onPressed: () => _fetchCountries(isRefresh: true),
|
||
child: const Text('重试'),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
// 无数据状态(列表为空且无错误)
|
||
if (_countries.isEmpty) {
|
||
return Center(
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
// 无数据背景图标
|
||
Icon(
|
||
Icons.flag_outlined,
|
||
size: 120,
|
||
color: theme.colorScheme.onSurface.withOpacity(0.1),
|
||
),
|
||
const SizedBox(height: 24),
|
||
// 无数据提示文字
|
||
Text(
|
||
'暂无国家数据',
|
||
style: theme.textTheme.titleLarge?.copyWith(
|
||
color: theme.colorScheme.onSurface.withOpacity(0.5),
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
// 刷新按钮
|
||
IconButton(
|
||
icon: Icon(
|
||
Icons.refresh,
|
||
size: 28,
|
||
color: theme.colorScheme.primary,
|
||
),
|
||
onPressed: () => _fetchCountries(isRefresh: true),
|
||
tooltip: '刷新数据',
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
// 正常列表展示
|
||
return RefreshIndicator(
|
||
onRefresh: _refresh,
|
||
child: ListView.builder(
|
||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||
itemCount: _countries.length + (_hasMoreData ? 1 : 0),
|
||
itemBuilder: (context, index) {
|
||
if (index < _countries.length) {
|
||
final country = _countries[index];
|
||
return _buildCountryItem(theme, country);
|
||
} else {
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||
child: Center(
|
||
child: _isLoading
|
||
? const CircularProgressIndicator()
|
||
: const Text('没有更多数据了'),
|
||
),
|
||
);
|
||
}
|
||
},
|
||
controller: _scrollController,
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildCountryItem(ThemeData theme, Country country) {
|
||
return Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Material(
|
||
color: Colors.transparent,
|
||
child: InkWell(
|
||
onTap: () {
|
||
// 可以添加点击事件
|
||
},
|
||
borderRadius: BorderRadius.circular(8),
|
||
splashColor: theme.colorScheme.primary.withAlpha(26),
|
||
highlightColor: theme.colorScheme.primary.withAlpha(13),
|
||
child: Container(
|
||
height: 64,
|
||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||
child: Row(
|
||
children: [
|
||
// 国旗Emoji展示区域
|
||
Container(
|
||
width: 40,
|
||
height: 40,
|
||
decoration: BoxDecoration(
|
||
color: theme.colorScheme.surfaceContainerHighest,
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: Center(
|
||
child: Text(
|
||
// 显示国旗emoji,如果没有则显示默认图标
|
||
country.flag != null && country.flag!.isNotEmpty
|
||
? country.flag!
|
||
: '',
|
||
style: const TextStyle(fontSize: 24), // 适当调整emoji大小
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(width: 16),
|
||
Expanded(
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
country.name,
|
||
style: theme.textTheme.titleMedium?.copyWith(
|
||
fontWeight: FontWeight.w500,
|
||
color: theme.colorScheme.onSurface,
|
||
),
|
||
),
|
||
const SizedBox(height: 4),
|
||
Text(
|
||
'代码: ${country.code}',
|
||
style: theme.textTheme.bodySmall?.copyWith(
|
||
color: theme.colorScheme.onSurfaceVariant,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
Icon(
|
||
Icons.arrow_forward_ios,
|
||
size: 18,
|
||
color: theme.hintColor,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
Divider(
|
||
height: 1,
|
||
thickness: 1,
|
||
indent: 72,
|
||
endIndent: 16,
|
||
color: theme.dividerColor,
|
||
),
|
||
],
|
||
);
|
||
}
|
||
}
|