新增农户功能升级:身份证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,122 @@
using AgriculturalPlatform.Api.Data;
using AgriculturalPlatform.Api.Models;
using AgriculturalPlatform.Api.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace AgriculturalPlatform.Api.Controllers;
/// <summary>管理功能:演示数据等</summary>
[ApiController]
[Route("api/admin")]
[Authorize]
public class AdminController(AppDbContext db, CurrentUserService currentUser) : ControllerBase
{
/// <summary>
/// 创建当日演示数据:随机生成 20-60 条蚕茧收购单(已完成)
/// 等级为「正茧 / 双宫 / 黄斑 / 血茧」之一,其中 95% 概率为正茧。
/// </summary>
[HttpPost("demo-data")]
public async Task<ActionResult> CreateDemoData()
{
var rnd = Random.Shared;
var now = DateTime.Now;
var todayStart = now.Date;
// 1. 品种:蚕茧(不存在则自动创建)
var cocoon = await db.Products.FirstOrDefaultAsync(p => p.Name == "蚕茧");
if (cocoon is null)
{
cocoon = new Product
{
Name = "蚕茧",
Category = "经济作物",
Unit = "公斤",
Spec = "鲜茧含水率≤15%",
Price = 30m,
CreatedAt = now
};
db.Products.Add(cocoon);
await db.SaveChangesAsync();
}
// 2. 农户(活跃)
var farmerIds = await db.Farmers
.Where(f => f.Status == FarmerStatus.Active)
.Select(f => f.Id).ToListAsync();
if (farmerIds.Count == 0)
return BadRequest(new { message = "暂无农户数据,请先在「农户管理」中创建农户" });
// 3. 收购组织:优先当前用户所属组织(保证数据可见),否则随机
var cu = currentUser.Get();
int orgId;
if (cu?.OrgId is { } oid && await db.Organizations.AnyAsync(o => o.Id == oid))
orgId = oid;
else
{
var orgIds = await db.Organizations.Select(o => o.Id).ToListAsync();
if (orgIds.Count == 0)
return BadRequest(new { message = "暂无组织数据,请先在「组织管理」中创建组织" });
orgId = orgIds[rnd.Next(orgIds.Count)];
}
// 4. 随机生成 20-60 条
var count = rnd.Next(20, 61);
var baseSeq = await db.PurchaseOrders.CountAsync(x =>
x.CreatedAt >= todayStart && x.CreatedAt < todayStart.AddDays(1));
var grades = new[] { "双宫", "黄斑", "血茧" };
var orders = new List<PurchaseOrder>();
for (var i = 0; i < count; i++)
{
// 等级:95% 正茧,其余在双宫/黄斑/血茧中随机
var grade = rnd.Next(100) < 95 ? "正茧" : grades[rnd.Next(grades.Length)];
// 单价(元/公斤)按等级定价
var unitPrice = grade switch
{
"正茧" => rnd.Next(2800, 3601) / 100m, // 28.00 - 36.00
"双宫" => rnd.Next(1800, 2401) / 100m, // 18.00 - 24.00
"黄斑" => rnd.Next(1000, 1501) / 100m, // 10.00 - 15.00
_ => rnd.Next(500, 901) / 100m // 血茧 5.00 - 9.00
};
// 重量:净重 15-150kg,皮重 3-9kg
var net = rnd.Next(1500, 15000) / 100m;
var tare = rnd.Next(300, 900) / 100m;
// 时间:今日 06:00-22:00 随机进场
var weighIn = todayStart.AddHours(rnd.Next(6, 22)).AddMinutes(rnd.Next(0, 60));
var weighOut = weighIn.AddMinutes(rnd.Next(5, 25));
orders.Add(new PurchaseOrder
{
OrderNo = $"CG{todayStart:yyyyMMdd}-{baseSeq + i + 1:D4}",
FarmerId = farmerIds[rnd.Next(farmerIds.Count)],
ProductId = cocoon.Id,
PurchaserOrgId = orgId,
Grade = grade,
Unit = "公斤",
UnitPrice = unitPrice,
GrossWeight = net + tare,
TareWeight = tare,
NetWeight = net,
Amount = Math.Round(net * unitPrice, 2),
WeighCount = 2,
Status = PurchaseStatus.Completed,
OperatorId = cu?.Id,
WeighInAt = weighIn,
WeighOutAt = weighOut,
CreatedAt = weighIn,
UpdatedAt = weighOut,
Notes = "演示数据"
});
}
db.PurchaseOrders.AddRange(orders);
await db.SaveChangesAsync();
return Ok(new { created = orders.Count, from = todayStart, to = now });
}
}