add
This commit is contained in:
@@ -98,9 +98,9 @@ BEGIN
|
|||||||
CREATE OR REPLACE VIEW country_info_view AS
|
CREATE OR REPLACE VIEW country_info_view AS
|
||||||
SELECT
|
SELECT
|
||||||
u.id AS country_id,
|
u.id AS country_id,
|
||||||
n.name AS country_name, -- 国家名称
|
n.name AS name, -- 国家名称
|
||||||
c.code AS country_code, -- 国家代码
|
c.code AS code, -- 国家代码
|
||||||
f.flag AS country_flag -- 国旗信息
|
f.flag AS flag -- 国旗信息
|
||||||
FROM
|
FROM
|
||||||
country u
|
country u
|
||||||
LEFT JOIN
|
LEFT JOIN
|
||||||
|
|||||||
@@ -3,20 +3,27 @@ import 'package:asset_assistant/utils/host_utils.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
|
||||||
// 国家数据模型
|
// 国家数据模型(新增flag字段)
|
||||||
class Country {
|
class Country {
|
||||||
final String countryId;
|
final String countryId;
|
||||||
final String name;
|
final String name;
|
||||||
final String code;
|
final String code;
|
||||||
|
final String? flag; // 新增国旗字段
|
||||||
|
|
||||||
Country({required this.countryId, required this.name, required this.code});
|
Country({
|
||||||
|
required this.countryId,
|
||||||
|
required this.name,
|
||||||
|
required this.code,
|
||||||
|
this.flag, // 新增参数
|
||||||
|
});
|
||||||
|
|
||||||
// 从JSON构建对象
|
// 从JSON构建对象(添加flag字段解析)
|
||||||
factory Country.fromJson(Map<String, dynamic> json) {
|
factory Country.fromJson(Map<String, dynamic> json) {
|
||||||
return Country(
|
return Country(
|
||||||
countryId: json['country_id'],
|
countryId: json['country_id'],
|
||||||
name: json['name'],
|
name: json['name'],
|
||||||
code: json['code'],
|
code: json['code'],
|
||||||
|
flag: json['flag'], // 解析国旗字段
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,14 +90,33 @@ class _CountryPageState extends State<CountryPage> {
|
|||||||
int _currentPage = 1;
|
int _currentPage = 1;
|
||||||
final int _pageSize = 20;
|
final int _pageSize = 20;
|
||||||
bool _hasMoreData = true;
|
bool _hasMoreData = true;
|
||||||
|
late ScrollController _scrollController; // 优化滚动控制器
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_scrollController = ScrollController();
|
||||||
|
_scrollController.addListener(_onScroll); // 注册滚动监听
|
||||||
_fetchCountries();
|
_fetchCountries();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载国家列表数据
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_scrollController.dispose(); // 释放资源
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 滚动监听处理加载更多
|
||||||
|
void _onScroll() {
|
||||||
|
if (_isLoading) return;
|
||||||
|
if (_hasMoreData &&
|
||||||
|
_scrollController.position.pixels >=
|
||||||
|
_scrollController.position.maxScrollExtent - 200) {
|
||||||
|
_loadMore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载国家列表数据(使用POST请求)
|
||||||
Future<void> _fetchCountries({bool isRefresh = false}) async {
|
Future<void> _fetchCountries({bool isRefresh = false}) async {
|
||||||
if (isRefresh) {
|
if (isRefresh) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -109,25 +135,27 @@ class _CountryPageState extends State<CountryPage> {
|
|||||||
final baseUrl = HostUtils().currentHost;
|
final baseUrl = HostUtils().currentHost;
|
||||||
final path = '/country/read';
|
final path = '/country/read';
|
||||||
final url = '$baseUrl$path';
|
final url = '$baseUrl$path';
|
||||||
// 打印请求基本信息
|
|
||||||
debugPrint('====== 开始请求国家列表 ======');
|
debugPrint('====== 开始请求国家列表 ======');
|
||||||
debugPrint('请求URL: $url');
|
debugPrint('请求URL: $url');
|
||||||
debugPrint('请求参数: page=$_currentPage, page_size=$_pageSize');
|
debugPrint('请求参数: page=$_currentPage, page_size=$_pageSize');
|
||||||
|
|
||||||
final dio = Dio();
|
final dio = Dio();
|
||||||
|
// 使用POST请求,通过data传递表单数据(适配后端form接收方式)
|
||||||
final response = await dio.post(
|
final response = await dio.post(
|
||||||
url,
|
url,
|
||||||
queryParameters: {
|
data: {
|
||||||
'page': _currentPage,
|
'page': _currentPage,
|
||||||
'page_size': _pageSize,
|
'page_size': _pageSize,
|
||||||
'name': '',
|
'name': '',
|
||||||
'code': '',
|
'code': '',
|
||||||
'country_id': '',
|
'country_id': '',
|
||||||
|
'flag': '', // 新增国旗查询参数
|
||||||
},
|
},
|
||||||
options: Options(headers: {'Content-Type': 'application/json'}),
|
options: Options(
|
||||||
|
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// 打印响应状态
|
|
||||||
debugPrint('请求成功,状态码: ${response.statusCode}');
|
debugPrint('请求成功,状态码: ${response.statusCode}');
|
||||||
debugPrint('响应数据: ${response.data}');
|
debugPrint('响应数据: ${response.data}');
|
||||||
|
|
||||||
@@ -149,7 +177,6 @@ class _CountryPageState extends State<CountryPage> {
|
|||||||
_currentPage++;
|
_currentPage++;
|
||||||
_errorMessage = null;
|
_errorMessage = null;
|
||||||
});
|
});
|
||||||
// 打印数据处理结果
|
|
||||||
debugPrint('数据解析成功,当前列表总数: ${_countries.length}');
|
debugPrint('数据解析成功,当前列表总数: ${_countries.length}');
|
||||||
debugPrint('是否还有更多数据: $_hasMoreData,下一页: $_currentPage');
|
debugPrint('是否还有更多数据: $_hasMoreData,下一页: $_currentPage');
|
||||||
} else {
|
} else {
|
||||||
@@ -232,7 +259,6 @@ class _CountryPageState extends State<CountryPage> {
|
|||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
debugPrint('点击新增按钮,跳转到新增页面');
|
debugPrint('点击新增按钮,跳转到新增页面');
|
||||||
// 跳转到新增页面,返回时刷新列表
|
|
||||||
final result = await Navigator.push(
|
final result = await Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (context) => const AddCountryPage()),
|
MaterialPageRoute(builder: (context) => const AddCountryPage()),
|
||||||
@@ -294,21 +320,12 @@ class _CountryPageState extends State<CountryPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 监听滚动加载更多
|
controller: _scrollController, // 使用优化后的滚动控制器
|
||||||
controller: ScrollController()
|
|
||||||
..addListener(() {
|
|
||||||
if (_isLoading) return;
|
|
||||||
if (_hasMoreData &&
|
|
||||||
ScrollController().position.pixels >=
|
|
||||||
ScrollController().position.maxScrollExtent - 200) {
|
|
||||||
_loadMore();
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建国家列表项
|
// 构建国家列表项(新增国旗显示)
|
||||||
Widget _buildCountryItem(ThemeData theme, Country country) {
|
Widget _buildCountryItem(ThemeData theme, Country country) {
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -327,18 +344,27 @@ class _CountryPageState extends State<CountryPage> {
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
// 国旗显示区域
|
||||||
Container(
|
Container(
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 40,
|
height: 40,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainerHighest,
|
color: theme.colorScheme.surfaceContainerHighest,
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
image: country.flag != null && country.flag!.isNotEmpty
|
||||||
|
? DecorationImage(
|
||||||
|
image: NetworkImage(country.flag!),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
child: Icon(
|
child: country.flag == null || country.flag!.isEmpty
|
||||||
Icons.flag,
|
? Icon(
|
||||||
size: 24,
|
Icons.flag,
|
||||||
color: theme.colorScheme.secondary,
|
size: 24,
|
||||||
),
|
color: theme.colorScheme.secondary,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ type ReadRequest struct {
|
|||||||
CountryID string `form:"country_id"` // 国家ID,可选
|
CountryID string `form:"country_id"` // 国家ID,可选
|
||||||
Name string `form:"name"` // 国家名称,可选
|
Name string `form:"name"` // 国家名称,可选
|
||||||
Code string `form:"code"` // 国家代码,可选
|
Code string `form:"code"` // 国家代码,可选
|
||||||
|
Flag string `form:"flag"` // 国旗信息,新增可选参数
|
||||||
Page string `form:"page"` // 页码,可选
|
Page string `form:"page"` // 页码,可选
|
||||||
PageSize string `form:"page_size"` // 每页条数,可选
|
PageSize string `form:"page_size"` // 每页条数,可选
|
||||||
}
|
}
|
||||||
@@ -31,11 +32,12 @@ type ReadData struct {
|
|||||||
Items []CountryInfoViewItem `json:"items"` // 数据列表
|
Items []CountryInfoViewItem `json:"items"` // 数据列表
|
||||||
}
|
}
|
||||||
|
|
||||||
// CountryInfoViewItem 视图数据项结构
|
// CountryInfoViewItem 视图数据项结构,新增国旗字段
|
||||||
type CountryInfoViewItem struct {
|
type CountryInfoViewItem struct {
|
||||||
CountryID string `json:"country_id"` // 国家ID
|
CountryID string `json:"country_id"` // 国家ID
|
||||||
Name string `json:"name"` // 国家名称
|
Name string `json:"name"` // 国家名称
|
||||||
Code string `json:"code"` // 国家代码
|
Code string `json:"code"` // 国家代码
|
||||||
|
Flag string `json:"flag"` // 国旗信息,新增字段
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadResponse 读取响应结构
|
// ReadResponse 读取响应结构
|
||||||
@@ -76,19 +78,6 @@ func ReadHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证查询条件至少有一个不为空
|
|
||||||
if req.CountryID == "" && req.Name == "" && req.Code == "" {
|
|
||||||
zap.L().Warn("⚠️ 请求参数验证失败",
|
|
||||||
zap.String("req_id", reqID),
|
|
||||||
zap.String("reason", "country_id、name、code不能同时为空"),
|
|
||||||
)
|
|
||||||
c.JSON(http.StatusBadRequest, ReadResponse{
|
|
||||||
Success: false,
|
|
||||||
Message: "请求参数错误:country_id、name、code不能同时为空",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理分页参数默认值
|
// 处理分页参数默认值
|
||||||
page, err := strconv.Atoi(req.Page)
|
page, err := strconv.Atoi(req.Page)
|
||||||
if err != nil || page < 1 {
|
if err != nil || page < 1 {
|
||||||
@@ -104,6 +93,7 @@ func ReadHandler(c *gin.Context) {
|
|||||||
zap.String("country_id", req.CountryID),
|
zap.String("country_id", req.CountryID),
|
||||||
zap.String("name", req.Name),
|
zap.String("name", req.Name),
|
||||||
zap.String("code", req.Code),
|
zap.String("code", req.Code),
|
||||||
|
zap.String("flag", req.Flag), // 新增国旗查询参数日志
|
||||||
zap.Int("page", page),
|
zap.Int("page", page),
|
||||||
zap.Int("page_size", pageSize),
|
zap.Int("page_size", pageSize),
|
||||||
)
|
)
|
||||||
@@ -128,9 +118,15 @@ func ReadHandler(c *gin.Context) {
|
|||||||
args = append(args, "%"+req.Code+"%")
|
args = append(args, "%"+req.Code+"%")
|
||||||
paramIndex++
|
paramIndex++
|
||||||
}
|
}
|
||||||
|
// 新增国旗查询条件
|
||||||
|
if req.Flag != "" {
|
||||||
|
whereClauses = append(whereClauses, "flag LIKE $"+strconv.Itoa(paramIndex))
|
||||||
|
args = append(args, "%"+req.Flag+"%")
|
||||||
|
paramIndex++
|
||||||
|
}
|
||||||
|
|
||||||
// 构建基础SQL
|
// 构建基础SQL,新增flag字段查询
|
||||||
baseSQL := "SELECT country_id, name, code FROM country_info_view"
|
baseSQL := "SELECT country_id, name, code, flag FROM country_info_view"
|
||||||
countSQL := "SELECT COUNT(*) FROM country_info_view"
|
countSQL := "SELECT COUNT(*) FROM country_info_view"
|
||||||
if len(whereClauses) > 0 {
|
if len(whereClauses) > 0 {
|
||||||
whereStr := " WHERE " + strings.Join(whereClauses, " AND ")
|
whereStr := " WHERE " + strings.Join(whereClauses, " AND ")
|
||||||
@@ -141,11 +137,11 @@ func ReadHandler(c *gin.Context) {
|
|||||||
// 计算分页偏移量
|
// 计算分页偏移量
|
||||||
offset := (page - 1) * pageSize
|
offset := (page - 1) * pageSize
|
||||||
|
|
||||||
// 拼接分页SQL(使用fmt.Sprintf更清晰)
|
// 拼接分页SQL
|
||||||
querySQL := fmt.Sprintf("%s ORDER BY country_id LIMIT $%d OFFSET $%d", baseSQL, paramIndex, paramIndex+1)
|
querySQL := fmt.Sprintf("%s ORDER BY country_id LIMIT $%d OFFSET $%d", baseSQL, paramIndex, paramIndex+1)
|
||||||
args = append(args, pageSize, offset)
|
args = append(args, pageSize, offset)
|
||||||
|
|
||||||
// 查询总条数(修正参数传递方式)
|
// 查询总条数
|
||||||
var total int64
|
var total int64
|
||||||
countArgs := args[:len(args)-2] // 排除分页参数
|
countArgs := args[:len(args)-2] // 排除分页参数
|
||||||
err = db.DB.QueryRow(countSQL, countArgs...).Scan(&total)
|
err = db.DB.QueryRow(countSQL, countArgs...).Scan(&total)
|
||||||
@@ -176,11 +172,11 @@ func ReadHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
// 处理查询结果
|
// 处理查询结果,新增flag字段扫描
|
||||||
var items []CountryInfoViewItem
|
var items []CountryInfoViewItem
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var item CountryInfoViewItem
|
var item CountryInfoViewItem
|
||||||
if err := rows.Scan(&item.CountryID, &item.Name, &item.Code); err != nil {
|
if err := rows.Scan(&item.CountryID, &item.Name, &item.Code, &item.Flag); err != nil {
|
||||||
zap.L().Error("❌ 解析查询结果失败",
|
zap.L().Error("❌ 解析查询结果失败",
|
||||||
zap.String("req_id", reqID),
|
zap.String("req_id", reqID),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
|
|||||||
Reference in New Issue
Block a user