Files
asset_assistant/frontend/asset_assistant/lib/pages/country_page.dart
2025-11-19 17:13:25 +08:00

440 lines
13 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: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,
),
],
);
}
}