新增农户功能升级:身份证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,182 @@
using AgriculturalPlatform.Api.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace AgriculturalPlatform.Api.Data;
public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{
public DbSet<User> Users => Set<User>();
public DbSet<Organization> Organizations => Set<Organization>();
public DbSet<Farmer> Farmers => Set<Farmer>();
public DbSet<Region> Regions => Set<Region>();
public DbSet<Product> Products => Set<Product>();
public DbSet<PurchaseOrder> PurchaseOrders => Set<PurchaseOrder>();
public DbSet<PaymentRecord> PaymentRecords => Set<PaymentRecord>();
public DbSet<Invoice> Invoices => Set<Invoice>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// 所有枚举属性以字符串形式存储,便于阅读
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
foreach (var property in entityType.GetProperties())
{
if (property.ClrType.IsEnum)
{
var converterType = typeof(EnumToStringConverter<>).MakeGenericType(property.ClrType);
var converter = (ValueConverter)Activator.CreateInstance(converterType)!;
property.SetValueConverter(converter);
}
}
}
// 用户
modelBuilder.Entity<User>()
.Property(u => u.Username).HasMaxLength(50);
modelBuilder.Entity<User>()
.HasIndex(u => u.Username).IsUnique();
modelBuilder.Entity<User>()
.HasOne(u => u.Org).WithMany().HasForeignKey(u => u.OrgId)
.OnDelete(DeleteBehavior.SetNull);
// 组织
modelBuilder.Entity<Organization>()
.Property(o => o.Type).HasMaxLength(32);
modelBuilder.Entity<Organization>()
.Property(o => o.Name).HasMaxLength(200);
modelBuilder.Entity<Organization>()
.HasIndex(o => new { o.Type, o.Name });
modelBuilder.Entity<Organization>()
.HasOne(o => o.Parent).WithMany().HasForeignKey(o => o.ParentId)
.OnDelete(DeleteBehavior.Restrict);
// 农户:身份证唯一
modelBuilder.Entity<Farmer>()
.Property(f => f.IdCard).HasMaxLength(18);
modelBuilder.Entity<Farmer>()
.HasIndex(f => f.IdCard).IsUnique();
modelBuilder.Entity<Farmer>()
.Property(f => f.Phone).HasMaxLength(20);
modelBuilder.Entity<Farmer>()
.HasIndex(f => f.Phone);
modelBuilder.Entity<Farmer>()
.Property(f => f.FarmerType).HasMaxLength(20);
modelBuilder.Entity<Farmer>()
.Property(f => f.IdCardFrontUrl).HasMaxLength(500);
modelBuilder.Entity<Farmer>()
.Property(f => f.IdCardBackUrl).HasMaxLength(500);
modelBuilder.Entity<Farmer>()
.Property(f => f.Province).HasMaxLength(50);
modelBuilder.Entity<Farmer>()
.Property(f => f.County).HasMaxLength(50);
modelBuilder.Entity<Farmer>()
.Property(f => f.Township).HasMaxLength(50);
modelBuilder.Entity<Farmer>()
.Property(f => f.Village).HasMaxLength(100);
modelBuilder.Entity<Farmer>()
.Property(f => f.GroupName).HasMaxLength(50);
modelBuilder.Entity<Farmer>()
.HasIndex(f => new { f.Province, f.County, f.Township });
// 行政区划(AreaCity 全国数据,表由导入脚本/启动建表创建)
modelBuilder.Entity<Region>()
.HasKey(r => r.Id);
modelBuilder.Entity<Region>()
.Property(r => r.Name).HasMaxLength(100);
modelBuilder.Entity<Region>()
.Property(r => r.ExtName).HasMaxLength(100);
modelBuilder.Entity<Region>()
.HasIndex(r => r.Pid);
modelBuilder.Entity<Region>()
.HasIndex(r => r.Deep);
// 品种
modelBuilder.Entity<Product>()
.Property(p => p.Name).HasMaxLength(100);
modelBuilder.Entity<Product>()
.HasIndex(p => p.Name).IsUnique();
// 收购单
modelBuilder.Entity<PurchaseOrder>()
.Property(p => p.OrderNo).HasMaxLength(32);
modelBuilder.Entity<PurchaseOrder>()
.HasIndex(p => p.OrderNo).IsUnique();
modelBuilder.Entity<PurchaseOrder>()
.HasIndex(p => new { p.Status, p.CreatedAt });
modelBuilder.Entity<PurchaseOrder>()
.HasOne(p => p.Farmer).WithMany().HasForeignKey(p => p.FarmerId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<PurchaseOrder>()
.HasOne(p => p.Product).WithMany().HasForeignKey(p => p.ProductId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<PurchaseOrder>()
.HasOne(p => p.PurchaserOrg).WithMany().HasForeignKey(p => p.PurchaserOrgId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<PurchaseOrder>()
.HasOne(p => p.Operator).WithMany().HasForeignKey(p => p.OperatorId)
.OnDelete(DeleteBehavior.SetNull);
// 支付
modelBuilder.Entity<PaymentRecord>()
.Property(p => p.PayNo).HasMaxLength(32);
modelBuilder.Entity<PaymentRecord>()
.HasIndex(p => p.PayNo).IsUnique();
modelBuilder.Entity<PaymentRecord>()
.HasIndex(p => new { p.Status, p.CreatedAt });
modelBuilder.Entity<PaymentRecord>()
.HasOne(p => p.Farmer).WithMany().HasForeignKey(p => p.FarmerId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<PaymentRecord>()
.HasOne(p => p.PurchaseOrder).WithMany().HasForeignKey(p => p.PurchaseOrderId)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<PaymentRecord>()
.HasOne(p => p.PurchaserOrg).WithMany().HasForeignKey(p => p.PurchaserOrgId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<PaymentRecord>()
.HasOne(p => p.Operator).WithMany().HasForeignKey(p => p.OperatorId)
.OnDelete(DeleteBehavior.SetNull);
// 发票
modelBuilder.Entity<Invoice>()
.Property(i => i.InvoiceNo).HasMaxLength(32);
modelBuilder.Entity<Invoice>()
.HasIndex(i => i.InvoiceNo).IsUnique();
modelBuilder.Entity<Invoice>()
.Property(i => i.BatchNo).HasMaxLength(32);
modelBuilder.Entity<Invoice>()
.HasIndex(i => i.BatchNo);
modelBuilder.Entity<Invoice>()
.HasIndex(i => new { i.Status, i.IssueDate });
modelBuilder.Entity<Invoice>()
.HasOne(i => i.Farmer).WithMany().HasForeignKey(i => i.FarmerId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Invoice>()
.HasOne(i => i.PurchaserOrg).WithMany().HasForeignKey(i => i.PurchaserOrgId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Invoice>()
.HasOne(i => i.Product).WithMany().HasForeignKey(i => i.ProductId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Invoice>()
.HasOne(i => i.PurchaseOrder).WithMany().HasForeignKey(i => i.PurchaseOrderId)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<Invoice>()
.HasOne(i => i.Operator).WithMany().HasForeignKey(i => i.OperatorId)
.OnDelete(DeleteBehavior.SetNull);
// 金额/重量精度
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
foreach (var property in entityType.GetProperties())
{
if (property.ClrType == typeof(decimal) || property.ClrType == typeof(decimal?))
{
property.SetPrecision(18);
property.SetScale(2);
}
}
}
}
}