新增农户功能升级:身份证OCR/OSS上传、头像采集、同名村检测、Region热度排序及表单体验优化

- Region 表新增热度列,热门省份优先显示
- 区县下拉按地级市分组、已录区县优先、支持全省搜索
- 组选择内置一组至二十组
- 银行卡号独立行+粗体预览,开户行独立行
- 新增农户类型:农户/个体户/合作社/经营集体/公司
- 身份证正反面支持高拍仪/摄像头OCR与手机扫码上传,附件存本地/OSS
- 新增农户头像采集组件(摄像头/本地上传),预留人脸识别收购扩展
- 同名村弹窗提醒(防选错乡镇)
- 编辑窗体禁止遮罩/Esc误关,整体加宽适配高频操作
- 修复二维码局域网手机无法访问(监听0.0.0.0、局域网IP识别、Vite代理兜底)
This commit is contained in:
2026-08-13 16:07:35 +08:00
commit c377421a3e
93 changed files with 12934 additions and 0 deletions
@@ -0,0 +1,120 @@
using AgriculturalPlatform.Api.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace AgriculturalPlatform.Api.Controllers;
/// <summary>
/// 行政区划接口:
/// 省、市、县、乡镇来自 AreaCity 全国公开行政区划数据(Regions 表,深 0~3);
/// 村、组来自农户档案表(Famers 表已录入数据,去重后作为下拉选项)。
/// </summary>
[ApiController]
[Route("api/regions")]
[Authorize]
public class RegionsController(AppDbContext db) : ControllerBase
{
/// <summary>省(自治区/直辖市)列表,热门省份(热度值高)优先</summary>
[HttpGet("provinces")]
public async Task<ActionResult<string[]>> Provinces()
{
var items = await db.Regions
.Where(r => r.Deep == 0)
.OrderByDescending(r => r.Hot)
.ThenBy(r => r.Id)
.Select(r => r.ExtName)
.ToListAsync();
return Ok(items);
}
/// <summary>
/// 按省获取县(区/县级市)列表,附带所属地级市(city)用于前端分组,
/// used=true 表示数据库农户表中已录入过该区县(前端优先展示)。
/// </summary>
[HttpGet("counties")]
public async Task<ActionResult> Counties(string? province)
{
if (string.IsNullOrWhiteSpace(province)) return BadRequest(new { message = "请先选择省份" });
var prov = await db.Regions.FirstOrDefaultAsync(r => r.Deep == 0 && r.ExtName == province);
if (prov == null) return Ok(Array.Empty<object>());
var items = await (from c in db.Regions
join city in db.Regions on c.Pid equals city.Id
where city.Pid == prov.Id && c.Deep == 2
orderby city.Id, c.Id
select new { name = c.ExtName, city = city.ExtName })
.ToListAsync();
var usedNames = await db.Farmers
.Where(f => f.County != null && f.County != "")
.Select(f => f.County)
.Distinct()
.ToListAsync();
var used = new HashSet<string>(usedNames, StringComparer.OrdinalIgnoreCase);
return Ok(items.Select(c => new { c.name, c.city, used = used.Contains(c.name) }));
}
/// <summary>按县获取乡镇(街道)列表(公开数据 + 农户表已录入数据补充)</summary>
[HttpGet("townships")]
public async Task<ActionResult<string[]>> Townships(string? county, string? city)
{
if (string.IsNullOrWhiteSpace(county)) return BadRequest(new { message = "请先选择县(区)" });
var countyQuery = db.Regions.Where(r => r.Deep == 2 && r.ExtName == county);
if (!string.IsNullOrWhiteSpace(city))
{
var cityRow = await db.Regions.FirstOrDefaultAsync(r => r.Deep == 1 && r.ExtName == city);
if (cityRow != null) countyQuery = countyQuery.Where(r => r.Pid == cityRow.Id);
}
var ids = await countyQuery.Select(r => r.Id).ToListAsync();
var builtin = ids.Count > 0
? await db.Regions.Where(r => r.Deep == 3 && ids.Contains(r.Pid))
.OrderBy(r => r.Id).Select(r => r.ExtName).ToListAsync()
: [];
var fromDb = await db.Farmers
.Where(f => f.County == county && f.Township != null && f.Township != "")
.Select(f => f.Township)
.Distinct()
.ToListAsync();
return Ok(builtin.Union(fromDb).OrderBy(x => x).ToArray());
}
/// <summary>
/// 村列表(来自农户表,按县/乡镇过滤去重),返回 { name, township } 对象数组。
/// 不传 township 时返回全县所有村(含所属乡镇),供前端做同名村检测。
/// </summary>
[HttpGet("villages")]
public async Task<ActionResult> Villages(string? county, string? township)
{
var q = db.Farmers.Where(f => f.Village != null && f.Village != "");
if (!string.IsNullOrWhiteSpace(township))
q = q.Where(f => f.Township == township);
else if (!string.IsNullOrWhiteSpace(county))
q = q.Where(f => f.County == county);
var items = await q
.Select(f => new { f.Village, f.Township })
.Distinct()
.OrderBy(x => x.Village)
.ToListAsync();
return Ok(items.Select(x => new { name = x.Village, township = x.Township ?? "" }));
}
/// <summary>组列表(来自农户表,按县/乡镇/村过滤去重)</summary>
[HttpGet("groups")]
public async Task<ActionResult<string[]>> Groups(string? county, string? township, string? village)
{
var q = db.Farmers.Where(f => f.GroupName != null && f.GroupName != "");
if (!string.IsNullOrWhiteSpace(village))
q = q.Where(f => f.Village == village);
else if (!string.IsNullOrWhiteSpace(township))
q = q.Where(f => f.Township == township);
else if (!string.IsNullOrWhiteSpace(county))
q = q.Where(f => f.County == county);
var items = await q.Select(f => f.GroupName).Distinct().OrderBy(x => x).ToListAsync();
return Ok(items);
}
}