新增农户功能升级:身份证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,17 @@
namespace AgriculturalPlatform.Api.Dtos;
// ---------- 认证 ----------
public record LoginRequest(string Username, string Password);
public record LoginResult(string Token, UserInfoDto User);
public record UserInfoDto(
int Id, string Username, string RealName, string Phone, string Role,
int? OrgId, string? OrgName, string? OrgType, bool IsActive);
public record ChangePasswordRequest(string OldPassword, string NewPassword);
// ---------- 通用分页 ----------
public record PagedResult<T>(IEnumerable<T> Items, int Total);
@@ -0,0 +1,35 @@
namespace AgriculturalPlatform.Api.Dtos;
/// <summary>大屏概览 KPI(按日期区间统计)</summary>
public record OverviewDto(
decimal Amount, int Orders, decimal NetWeight, decimal Paid,
decimal AvgPrice, int FarmerCount, int WeighingCount);
/// <summary>趋势点</summary>
public record TrendPoint(string Date, decimal Amount, decimal NetWeight);
/// <summary>占比点</summary>
public record RatioPoint(string Name, decimal Value);
/// <summary>农户收购金额排行点</summary>
public record FarmerTopPoint(int FarmerId, string Name, decimal Amount, int Count, decimal NetWeight);
/// <summary>站点对比点</summary>
public record StationPoint(int OrgId, string Name, decimal Amount, int Count, decimal NetWeight);
/// <summary>收购单价排行点(按品种平均单价)</summary>
public record PriceTopPoint(string Name, decimal AvgPrice, decimal NetWeight, int Count);
/// <summary>农户收入排行点(收入 = 区间收购金额,含已付)</summary>
public record IncomeTopPoint(int FarmerId, string Name, decimal Amount, decimal Paid, int Count);
/// <summary>实时过磅(进行中的收购单)</summary>
public record RealtimeWeighing(
int Id, string OrderNo, string FarmerName, string ProductName,
string Status, decimal GrossWeight, DateTime WeighInAt);
/// <summary>天气信息</summary>
public record WeatherInfo(
string City, string Region, string Text, string Icon,
decimal TempC, decimal FeelsLike, decimal Humidity, string Wind,
decimal TodayMin, decimal TodayMax, DateTime UpdateTime, string Source);
@@ -0,0 +1,30 @@
using AgriculturalPlatform.Api.Models;
namespace AgriculturalPlatform.Api.Dtos;
public record FarmerDto(
int Id, string Name, string IdCard, string Gender, string FarmerType, string Phone,
string Province, string County, string Township, string Village, string GroupName,
string Address, string BankName, string BankAccount, int CreditScore,
string IdCardFrontUrl, string IdCardBackUrl, string AvatarUrl,
string Status, string Notes, DateTime CreatedAt);
public record FarmerSaveRequest(
string Name, string IdCard, string Gender, string Phone,
string Province, string County, string Township, string Village, string GroupName,
string Address, string BankName, string BankAccount, int CreditScore = 80,
string FarmerType = "农户",
string IdCardFrontUrl = "", string IdCardBackUrl = "", string AvatarUrl = "",
string Status = "Active", string Notes = "");
public static class FarmerMappers
{
public static FarmerDto ToDto(this Farmer f) => new(
f.Id, f.Name, f.IdCard, f.Gender, f.FarmerType, f.Phone,
f.Province, f.County, f.Township, f.Village, f.GroupName,
f.Address, f.BankName, f.BankAccount, f.CreditScore,
f.IdCardFrontUrl, f.IdCardBackUrl, f.AvatarUrl,
f.Status.ToString(), f.Notes, f.CreatedAt);
public static FarmerDto[] ToDtos(this IEnumerable<Farmer> items) => items.Select(ToDto).ToArray();
}
@@ -0,0 +1,50 @@
using AgriculturalPlatform.Api.Models;
namespace AgriculturalPlatform.Api.Dtos;
public record InvoiceDto(
int Id, string InvoiceNo, string? BatchNo, string InvoiceKind,
int FarmerId, string FarmerName, string? FarmerIdCard,
int PurchaserOrgId, string PurchaserOrgName, int? PurchaseOrderId, string? OrderNo,
int ProductId, string ProductName, decimal Quantity, string Unit, decimal UnitPrice,
decimal Amount, decimal TaxRate, decimal TaxAmount, DateTime IssueDate,
string Status, string? OperatorName, DateTime? ReversedAt, string ReverseReason, DateTime CreatedAt);
/// <summary>开具销售发票:可指定收购单,也可手工录入</summary>
public record InvoiceCreateRequest(
int FarmerId, int PurchaserOrgId, int ProductId, int? PurchaseOrderId,
decimal Quantity, string Unit, decimal UnitPrice, decimal Amount,
decimal TaxRate = 0, string InvoiceKind = "销售发票");
/// <summary>发票作废</summary>
public record InvoiceReverseRequest(string Reason);
/// <summary>开票结果回填:Status 取值 Issued/Failed/Abnormal</summary>
public record InvoiceResultRequest(string Status, string? Message = null);
/// <summary>收购单维度开票状态行(反向开票清单数据源:过磅称重有效收购单)</summary>
public record OrderInvoiceDto(
int Id, string OrderNo, DateTime? WeighOutAt,
int FarmerId, string FarmerName, string? FarmerIdCard,
int ProductId, string ProductName, string Unit, decimal NetWeight, decimal UnitPrice, decimal Amount,
int PurchaserOrgId, string PurchaserOrgName, string? PurchaserTaxNo,
int? InvoiceId, string? InvoiceNo, string? BatchNo, string InvoiceStatus, DateTime? IssueDate);
/// <summary>创建开票批次(批量开票)</summary>
public record CreateBatchRequest(string? From, string? To, long[] OrderIds);
/// <summary>开票批次进度</summary>
public record BatchProgressDto(string BatchNo, int Total, int Pending, int Issued, int Failed, int Abnormal, bool Done);
public static class InvoiceMappers
{
public static InvoiceDto ToDto(this Invoice i) => new(
i.Id, i.InvoiceNo, i.BatchNo, i.InvoiceKind,
i.FarmerId, i.Farmer?.Name ?? "", i.Farmer?.IdCard ?? "",
i.PurchaserOrgId, i.PurchaserOrg?.Name ?? "", i.PurchaseOrderId, i.PurchaseOrder?.OrderNo,
i.ProductId, i.Product?.Name ?? "", i.Quantity, i.Unit, i.UnitPrice,
i.Amount, i.TaxRate, i.TaxAmount, i.IssueDate,
i.Status.ToString(), i.Operator?.RealName ?? "", i.ReversedAt, i.ReverseReason, i.CreatedAt);
public static InvoiceDto[] ToDtos(this IEnumerable<Invoice> items) => items.Select(ToDto).ToArray();
}
@@ -0,0 +1,20 @@
using AgriculturalPlatform.Api.Models;
namespace AgriculturalPlatform.Api.Dtos;
public record OrgDto(
int Id, string Type, string Name, int? ParentId, string? ParentName,
string ContactPerson, string Phone, string Address, string TaxNo, bool IsActive, DateTime CreatedAt);
public record OrgSaveRequest(
string Type, string Name, int? ParentId, string ContactPerson,
string Phone, string Address, string TaxNo, bool IsActive = true);
public static class OrgMappers
{
public static OrgDto ToDto(this Organization o) => new(
o.Id, o.Type.ToString(), o.Name, o.ParentId, o.Parent?.Name,
o.ContactPerson, o.Phone, o.Address, o.TaxNo, o.IsActive, o.CreatedAt);
public static OrgDto[] ToDtos(this IEnumerable<Organization> items) => items.Select(ToDto).ToArray();
}
@@ -0,0 +1,27 @@
using AgriculturalPlatform.Api.Models;
namespace AgriculturalPlatform.Api.Dtos;
public record PaymentDto(
int Id, string PayNo, int? PurchaseOrderId, string? OrderNo,
int FarmerId, string FarmerName, decimal Amount, string Method,
string Status, string TradeNo, string? OperatorName, DateTime? PaidAt,
string Notes, DateTime CreatedAt);
/// <summary>发起支付(按收购单结算,可为空表示对农户批量结算)</summary>
public record PaymentCreateRequest(
int? PurchaseOrderId, int FarmerId, decimal Amount, string Method, string Notes = "");
/// <summary>确认支付结果:Status 缺省视为成功,可选 Success/Failed/Abnormal</summary>
public record PaymentConfirmRequest(string? TradeNo = null, string? Status = null, string? Message = null);
public static class PaymentMappers
{
public static PaymentDto ToDto(this PaymentRecord p) => new(
p.Id, p.PayNo, p.PurchaseOrderId, p.PurchaseOrder?.OrderNo,
p.FarmerId, p.Farmer?.Name ?? "", p.Amount, p.Method.ToString(),
p.Status.ToString(), p.TradeNo, p.Operator?.RealName ?? "",
p.PaidAt, p.Notes, p.CreatedAt);
public static PaymentDto[] ToDtos(this IEnumerable<PaymentRecord> items) => items.Select(ToDto).ToArray();
}
@@ -0,0 +1,19 @@
using AgriculturalPlatform.Api.Models;
namespace AgriculturalPlatform.Api.Dtos;
public record ProductDto(
int Id, string Name, string Category, string Unit, string Spec,
decimal Price, string Status, DateTime CreatedAt);
public record ProductSaveRequest(
string Name, string Category, string Unit, string Spec, decimal Price,
string Status = "Active");
public static class ProductMappers
{
public static ProductDto ToDto(this Product p) => new(
p.Id, p.Name, p.Category, p.Unit, p.Spec, p.Price, p.Status.ToString(), p.CreatedAt);
public static ProductDto[] ToDtos(this IEnumerable<Product> items) => items.Select(ToDto).ToArray();
}
@@ -0,0 +1,32 @@
using AgriculturalPlatform.Api.Models;
namespace AgriculturalPlatform.Api.Dtos;
public record PurchaseDto(
int Id, string OrderNo, int FarmerId, string FarmerName, string? FarmerPhone,
int ProductId, string ProductName, string Category, int PurchaserOrgId, string PurchaserOrgName,
string Grade, string Unit, decimal UnitPrice, decimal GrossWeight, decimal TareWeight,
decimal NetWeight, decimal Amount, int WeighCount, string Status,
string? OperatorName, DateTime WeighInAt, DateTime? WeighOutAt, string Notes, DateTime CreatedAt);
/// <summary>新建收购单(第一次过磅:称毛重)</summary>
public record PurchaseCreateRequest(
int FarmerId, int ProductId, int PurchaserOrgId, string Grade,
decimal GrossWeight, string Unit = "公斤", decimal UnitPrice = 0, string Notes = "");
/// <summary>回皮完成(第二次过磅:称皮重,计算净重与金额)</summary>
public record PurchaseTareRequest(decimal TareWeight);
public static class PurchaseMappers
{
public static PurchaseDto ToDto(this PurchaseOrder p) => new(
p.Id, p.OrderNo,
p.FarmerId, p.Farmer?.Name ?? "", p.Farmer?.Phone ?? "",
p.ProductId, p.Product?.Name ?? "", p.Product?.Category ?? "",
p.PurchaserOrgId, p.PurchaserOrg?.Name ?? "",
p.Grade, p.Unit, p.UnitPrice, p.GrossWeight, p.TareWeight,
p.NetWeight, p.Amount, p.WeighCount, p.Status.ToString(),
p.Operator?.RealName ?? "", p.WeighInAt, p.WeighOutAt, p.Notes, p.CreatedAt);
public static PurchaseDto[] ToDtos(this IEnumerable<PurchaseOrder> items) => items.Select(ToDto).ToArray();
}
@@ -0,0 +1,19 @@
namespace AgriculturalPlatform.Api.Dtos;
/// <summary>汇总明细行</summary>
public record SummaryRow(string Key, string Label, decimal Value, int Count);
/// <summary>收购汇总(按维度:日/月/品种/农户/站点)</summary>
public record PurchaseSummaryResult(
string Dimension, DateTime From, DateTime To,
decimal TotalAmount, decimal TotalNetWeight, int TotalCount, SummaryRow[] Rows);
/// <summary>付款统计</summary>
public record PaymentSummaryResult(
DateTime From, DateTime To, decimal TotalPaid, decimal TotalPending, int PayCount,
SummaryRow[] ByMethod, SummaryRow[] ByStatus);
/// <summary>开票统计</summary>
public record InvoiceSummaryResult(
DateTime From, DateTime To, decimal TotalAmount, decimal TotalTax, int IssueCount,
SummaryRow[] ByProduct, SummaryRow[] ByStatus);
@@ -0,0 +1,20 @@
using AgriculturalPlatform.Api.Models;
namespace AgriculturalPlatform.Api.Dtos;
public record UserDto(
int Id, string Username, string RealName, string Phone, string Role,
int? OrgId, string? OrgName, bool IsActive, DateTime? LastLoginAt, DateTime CreatedAt);
public record UserSaveRequest(
string Username, string RealName, string Phone, string Role,
int? OrgId, bool IsActive = true, string? Password = null);
public static class UserMappers
{
public static UserDto ToDto(this User u) => new(
u.Id, u.Username, u.RealName, u.Phone, u.Role.ToString(),
u.OrgId, u.Org?.Name, u.IsActive, u.LastLoginAt, u.CreatedAt);
public static UserDto[] ToDtos(this IEnumerable<User> items) => items.Select(ToDto).ToArray();
}