diff --git a/backend/sql/04_country.sql b/backend/sql/04_country.sql index aa6f0e5..2be446a 100644 --- a/backend/sql/04_country.sql +++ b/backend/sql/04_country.sql @@ -98,9 +98,9 @@ BEGIN CREATE OR REPLACE VIEW country_info_view AS SELECT u.id AS country_id, - n.name AS country_name, -- 国家名称 - c.code AS country_code, -- 国家代码 - f.flag AS country_flag -- 国旗信息 + n.name AS name, -- 国家名称 + c.code AS code, -- 国家代码 + f.flag AS flag -- 国旗信息 FROM country u LEFT JOIN diff --git a/frontend/asset_assistant/lib/pages/country_page.dart b/frontend/asset_assistant/lib/pages/country_page.dart index 41ade2b..d53b2d6 100644 --- a/frontend/asset_assistant/lib/pages/country_page.dart +++ b/frontend/asset_assistant/lib/pages/country_page.dart @@ -3,20 +3,27 @@ import 'package:asset_assistant/utils/host_utils.dart'; import 'package:flutter/material.dart'; import 'package:dio/dio.dart'; -// 国家数据模型 +// 国家数据模型(新增flag字段) class Country { final String countryId; final String name; 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 json) { return Country( countryId: json['country_id'], name: json['name'], code: json['code'], + flag: json['flag'], // 解析国旗字段 ); } } @@ -83,14 +90,33 @@ class _CountryPageState extends State { 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(); + } + } + + // 加载国家列表数据(使用POST请求) Future _fetchCountries({bool isRefresh = false}) async { if (isRefresh) { setState(() { @@ -109,25 +135,27 @@ class _CountryPageState extends State { 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(); + // 使用POST请求,通过data传递表单数据(适配后端form接收方式) final response = await dio.post( url, - queryParameters: { + data: { 'page': _currentPage, 'page_size': _pageSize, 'name': '', 'code': '', '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.data}'); @@ -149,7 +177,6 @@ class _CountryPageState extends State { _currentPage++; _errorMessage = null; }); - // 打印数据处理结果 debugPrint('数据解析成功,当前列表总数: ${_countries.length}'); debugPrint('是否还有更多数据: $_hasMoreData,下一页: $_currentPage'); } else { @@ -232,7 +259,6 @@ class _CountryPageState extends State { icon: const Icon(Icons.add), onPressed: () async { debugPrint('点击新增按钮,跳转到新增页面'); - // 跳转到新增页面,返回时刷新列表 final result = await Navigator.push( context, MaterialPageRoute(builder: (context) => const AddCountryPage()), @@ -294,21 +320,12 @@ class _CountryPageState extends State { ); } }, - // 监听滚动加载更多 - controller: ScrollController() - ..addListener(() { - if (_isLoading) return; - if (_hasMoreData && - ScrollController().position.pixels >= - ScrollController().position.maxScrollExtent - 200) { - _loadMore(); - } - }), + controller: _scrollController, // 使用优化后的滚动控制器 ), ); } - // 构建国家列表项 + // 构建国家列表项(新增国旗显示) Widget _buildCountryItem(ThemeData theme, Country country) { return Column( mainAxisSize: MainAxisSize.min, @@ -327,18 +344,27 @@ class _CountryPageState extends State { padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ + // 国旗显示区域 Container( width: 40, height: 40, decoration: BoxDecoration( color: theme.colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(8), + image: country.flag != null && country.flag!.isNotEmpty + ? DecorationImage( + image: NetworkImage(country.flag!), + fit: BoxFit.cover, + ) + : null, ), - child: Icon( - Icons.flag, - size: 24, - color: theme.colorScheme.secondary, - ), + child: country.flag == null || country.flag!.isEmpty + ? Icon( + Icons.flag, + size: 24, + color: theme.colorScheme.secondary, + ) + : null, ), const SizedBox(width: 16), Expanded( diff --git a/frontend/asset_assistant/lib/pages/read.go b/frontend/asset_assistant/lib/pages/read.go index bb93583..6166042 100644 --- a/frontend/asset_assistant/lib/pages/read.go +++ b/frontend/asset_assistant/lib/pages/read.go @@ -19,6 +19,7 @@ type ReadRequest struct { CountryID string `form:"country_id"` // 国家ID,可选 Name string `form:"name"` // 国家名称,可选 Code string `form:"code"` // 国家代码,可选 + Flag string `form:"flag"` // 国旗信息,新增可选参数 Page string `form:"page"` // 页码,可选 PageSize string `form:"page_size"` // 每页条数,可选 } @@ -31,11 +32,12 @@ type ReadData struct { Items []CountryInfoViewItem `json:"items"` // 数据列表 } -// CountryInfoViewItem 视图数据项结构 +// CountryInfoViewItem 视图数据项结构,新增国旗字段 type CountryInfoViewItem struct { CountryID string `json:"country_id"` // 国家ID Name string `json:"name"` // 国家名称 Code string `json:"code"` // 国家代码 + Flag string `json:"flag"` // 国旗信息,新增字段 } // ReadResponse 读取响应结构 @@ -76,19 +78,6 @@ func ReadHandler(c *gin.Context) { 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) if err != nil || page < 1 { @@ -104,6 +93,7 @@ func ReadHandler(c *gin.Context) { zap.String("country_id", req.CountryID), zap.String("name", req.Name), zap.String("code", req.Code), + zap.String("flag", req.Flag), // 新增国旗查询参数日志 zap.Int("page", page), zap.Int("page_size", pageSize), ) @@ -128,9 +118,15 @@ func ReadHandler(c *gin.Context) { args = append(args, "%"+req.Code+"%") paramIndex++ } + // 新增国旗查询条件 + if req.Flag != "" { + whereClauses = append(whereClauses, "flag LIKE $"+strconv.Itoa(paramIndex)) + args = append(args, "%"+req.Flag+"%") + paramIndex++ + } - // 构建基础SQL - baseSQL := "SELECT country_id, name, code FROM country_info_view" + // 构建基础SQL,新增flag字段查询 + baseSQL := "SELECT country_id, name, code, flag FROM country_info_view" countSQL := "SELECT COUNT(*) FROM country_info_view" if len(whereClauses) > 0 { whereStr := " WHERE " + strings.Join(whereClauses, " AND ") @@ -141,11 +137,11 @@ func ReadHandler(c *gin.Context) { // 计算分页偏移量 offset := (page - 1) * pageSize - // 拼接分页SQL(使用fmt.Sprintf更清晰) + // 拼接分页SQL querySQL := fmt.Sprintf("%s ORDER BY country_id LIMIT $%d OFFSET $%d", baseSQL, paramIndex, paramIndex+1) args = append(args, pageSize, offset) - // 查询总条数(修正参数传递方式) + // 查询总条数 var total int64 countArgs := args[:len(args)-2] // 排除分页参数 err = db.DB.QueryRow(countSQL, countArgs...).Scan(&total) @@ -176,11 +172,11 @@ func ReadHandler(c *gin.Context) { } defer rows.Close() - // 处理查询结果 + // 处理查询结果,新增flag字段扫描 var items []CountryInfoViewItem for rows.Next() { 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.String("req_id", reqID), zap.Error(err), @@ -228,4 +224,4 @@ func ReadHandler(c *gin.Context) { Items: items, }, }) -} +} \ No newline at end of file