feat: 销售产品页签按需加载版本头和渠道下拉,避免分页拉取整份快照
All checks were successful
Build and Deploy / deploy (push) Successful in 1m1s

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zzh 2026-10-07 14:20:17 +08:00
parent a164d8c837
commit b38499963d
9 changed files with 172 additions and 16 deletions

View File

@ -19,4 +19,9 @@ public class SalesProductPublishListInput
/// 每页条数。 /// 每页条数。
/// </summary> /// </summary>
public int PageSize { get; set; } public int PageSize { get; set; }
/// <summary>
/// 是否返回发布快照正文。版本头只展示版本号和时间时传 false,避免传输并反序列化整份价格矩阵。
/// </summary>
public bool IncludeContent { get; set; } = true;
} }

View File

@ -0,0 +1,22 @@
namespace Oms.Application.Dtos.Channels;
/// <summary>
/// 服务渠道下拉项。只含展示所需字段,不分页。
/// </summary>
public class ServiceChannelOptionOutput
{
/// <summary>
/// 主键。
/// </summary>
public int Id { get; set; }
/// <summary>
/// 分组名称。
/// </summary>
public string? GroupName { get; set; }
/// <summary>
/// 渠道名称。
/// </summary>
public string ChannelName { get; set; } = string.Empty;
}

View File

@ -213,32 +213,65 @@ public partial class SalesProductService
private async Task<OmsPageResult<SalesProductPublishRecordOutput>> GetPublishListAsync(SalesProductPublishListInput input, string operatorType) private async Task<OmsPageResult<SalesProductPublishRecordOutput>> GetPublishListAsync(SalesProductPublishListInput input, string operatorType)
{ {
await GetProductOrThrowAsync(input.SalesProductId); await GetProductOrThrowAsync(input.SalesProductId);
// 版本列表不传正文时一次返回全部版本头,页面没有分页。
if (!input.IncludeContent)
{
var headers = await SalesProductRepository.GetAllPublishRecordHeadersAsync(input.SalesProductId, operatorType);
return new OmsPageResult<SalesProductPublishRecordOutput>
{
Items = headers.Select(entity => MapPublishRecordOutput(entity, includeContent: false)).ToList(),
Total = headers.Count
};
}
var pageIndex = OmsDocHelper.NormalizePage(input.Page); var pageIndex = OmsDocHelper.NormalizePage(input.Page);
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize); var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
var entities = await SalesProductRepository.GetPublishRecordsAsync(input.SalesProductId, operatorType, pageIndex, pageSize); var entities = await SalesProductRepository.GetPublishRecordsAsync(input.SalesProductId, operatorType, pageIndex, pageSize);
var total = await SalesProductRepository.CountPublishRecordsAsync(input.SalesProductId, operatorType); var total = await SalesProductRepository.CountPublishRecordsAsync(input.SalesProductId, operatorType);
return new OmsPageResult<SalesProductPublishRecordOutput> return new OmsPageResult<SalesProductPublishRecordOutput>
{ {
Items = entities.Select(entity => new SalesProductPublishRecordOutput Items = entities.Select(entity => MapPublishRecordOutput(entity, includeContent: true)).ToList(),
{
Id = entity.Id,
SalesProductId = entity.SalesProductId,
Version = entity.Version,
OperatorType = entity.OperatorType,
Status = EnumDisplayNames.Of(entity.Status),
PublishUser = entity.PublishUser,
PublishTime = entity.PublishTime,
Description = entity.Description,
AiAnalysisReport = entity.AiAnalysisReport,
Content = operatorType == PriceOperatorType
? OmsDocHelper.ParseSalesProductPricePublishContentForApi(entity.Content)
: OmsDocHelper.ToSnapshotApiContent(OmsDocHelper.DeserializeRulesSnapshot(entity.Content)),
CreateTime = entity.CreateTime
}).ToList(),
Total = total Total = total
}; };
} }
private SalesProductPublishRecordOutput MapPublishRecordOutput(SalesProductPublishRecord entity, bool includeContent)
{
var operatorType = entity.OperatorType;
return new SalesProductPublishRecordOutput
{
Id = entity.Id,
SalesProductId = entity.SalesProductId,
Version = entity.Version,
OperatorType = operatorType,
Status = EnumDisplayNames.Of(entity.Status),
PublishUser = entity.PublishUser,
PublishTime = entity.PublishTime,
Description = entity.Description,
AiAnalysisReport = includeContent ? entity.AiAnalysisReport : null,
Content = !includeContent
? null
: string.Equals(operatorType, PriceOperatorType, StringComparison.OrdinalIgnoreCase)
? OmsDocHelper.ParseSalesProductPricePublishContentForApi(entity.Content)
: OmsDocHelper.ToSnapshotApiContent(OmsDocHelper.DeserializeRulesSnapshot(entity.Content)),
CreateTime = entity.CreateTime
};
}
/// <summary>
/// 单条发布记录(含快照正文),供版本详情按需加载。
/// </summary>
private async Task<SalesProductPublishRecordOutput> GetPublishRecordCoreAsync(int id)
{
var entity = await SalesProductRepository.GetPublishRecordByIdAsync(id);
if (entity == null)
{
throw new Fake.Application.BusinessException("发布记录不存在");
}
return MapPublishRecordOutput(entity, includeContent: true);
}
/// <summary> /// <summary>
/// 组装规则发布快照(不落库),含黑名单/偏远/计重/进位明细。 /// 组装规则发布快照(不落库),含黑名单/偏远/计重/进位明细。
/// </summary> /// </summary>

View File

@ -386,6 +386,15 @@ public partial class SalesProductService : ApplicationService
return GetPublishListAsync(input, RulesOperatorType); return GetPublishListAsync(input, RulesOperatorType);
} }
/// <summary>
/// 查询单条发布记录,含快照正文。
/// </summary>
[HttpGet("/channel/sales-product/publish/get")]
public Task<SalesProductPublishRecordOutput> GetPublishRecordAsync([FromQuery] int id)
{
return GetPublishRecordCoreAsync(id);
}
/// <summary> /// <summary>
/// 预览即将发布的快照 JSON(不落库),用于核对黑名单/计重等是否已固化进 Content。 /// 预览即将发布的快照 JSON(不落库),用于核对黑名单/计重等是否已固化进 Content。
/// </summary> /// </summary>

View File

@ -25,6 +25,22 @@ public partial class ServiceChannelService : ApplicationService
private OmsRbacUserDisplayNameResolver UserDisplayNameResolver => private OmsRbacUserDisplayNameResolver UserDisplayNameResolver =>
LazyServiceProvider.GetRequiredService<OmsRbacUserDisplayNameResolver>(); LazyServiceProvider.GetRequiredService<OmsRbacUserDisplayNameResolver>();
/// <summary>
/// 服务渠道下拉。返回全部渠道的名称,不分页。
/// </summary>
[HttpGet("/channel/service-channel/options")]
public async Task<List<ServiceChannelOptionOutput>> GetOptionsAsync()
{
var channels = await ServiceChannelRepository.GetOptionRowsAsync();
var groupMap = await GetGroupMapAsync();
return channels.Select(channel => new ServiceChannelOptionOutput
{
Id = channel.Id,
ChannelName = channel.ChannelName,
GroupName = channel.GroupId.HasValue && groupMap.TryGetValue(channel.GroupId.Value, out var name) ? name : null
}).ToList();
}
/// <summary> /// <summary>
/// 查询服务渠道列表。 /// 查询服务渠道列表。
/// </summary> /// </summary>

View File

@ -50,6 +50,16 @@ public interface ISalesProductRepository : IRepository<SalesProduct>
Task<List<SalesProductPublishRecord>> GetPublishRecordsAsync(int salesProductId, string operatorType, int pageIndex, int pageSize, CancellationToken cancellationToken = default); Task<List<SalesProductPublishRecord>> GetPublishRecordsAsync(int salesProductId, string operatorType, int pageIndex, int pageSize, CancellationToken cancellationToken = default);
/// <summary>
/// 发布记录头(不含 Content / AI 报告),供版本号展示。
/// </summary>
Task<List<SalesProductPublishRecord>> GetPublishRecordHeadersAsync(int salesProductId, string operatorType, int pageIndex, int pageSize, CancellationToken cancellationToken = default);
/// <summary>
/// 某产品某类发布记录的全部版本头,不含快照正文。
/// </summary>
Task<List<SalesProductPublishRecord>> GetAllPublishRecordHeadersAsync(int salesProductId, string operatorType, CancellationToken cancellationToken = default);
Task<int> CountPublishRecordsAsync(int salesProductId, string operatorType, CancellationToken cancellationToken = default); Task<int> CountPublishRecordsAsync(int salesProductId, string operatorType, CancellationToken cancellationToken = default);
Task<SalesProductPublishRecord> InsertPublishRecordAsync(SalesProductPublishRecord record, CancellationToken cancellationToken = default); Task<SalesProductPublishRecord> InsertPublishRecordAsync(SalesProductPublishRecord record, CancellationToken cancellationToken = default);

View File

@ -15,6 +15,11 @@ public interface IServiceChannelRepository : IRepository<ServiceChannel>
Task<List<ServiceChannelGroup>> GetGroupsAsync(CancellationToken cancellationToken = default); Task<List<ServiceChannelGroup>> GetGroupsAsync(CancellationToken cancellationToken = default);
/// <summary>
/// 下拉选项:仅主键、分组、渠道名,不分页。
/// </summary>
Task<List<ServiceChannel>> GetOptionRowsAsync(CancellationToken cancellationToken = default);
Task<List<ServiceChannelPrice>> GetPricesAsync(int serviceChannelId, CancellationToken cancellationToken = default); Task<List<ServiceChannelPrice>> GetPricesAsync(int serviceChannelId, CancellationToken cancellationToken = default);
Task<ServiceChannelPrice?> GetPriceByIdAsync(int id, CancellationToken cancellationToken = default); Task<ServiceChannelPrice?> GetPriceByIdAsync(int id, CancellationToken cancellationToken = default);

View File

@ -186,6 +186,48 @@ public class SalesProductRepository : OmsSqlSugarRepository<SalesProduct>, ISale
.ToPageListAsync(pageIndex, pageSize, cancellationToken); .ToPageListAsync(pageIndex, pageSize, cancellationToken);
} }
public async Task<List<SalesProductPublishRecord>> GetPublishRecordHeadersAsync(int salesProductId, string operatorType, int pageIndex, int pageSize, CancellationToken cancellationToken = default)
{
var db = await GetDbContextAsync(cancellationToken);
return await db.Queryable<SalesProductPublishRecord>()
.Where(x => x.SalesProductId == salesProductId && x.OperatorType == operatorType)
.OrderBy(x => x.PublishTime, SqlSugar.OrderByType.Desc)
.Select(x => new SalesProductPublishRecord
{
Id = x.Id,
SalesProductId = x.SalesProductId,
Version = x.Version,
OperatorType = x.OperatorType,
Status = x.Status,
PublishUser = x.PublishUser,
PublishTime = x.PublishTime,
Description = x.Description,
CreateTime = x.CreateTime
})
.ToPageListAsync(pageIndex, pageSize, cancellationToken);
}
public async Task<List<SalesProductPublishRecord>> GetAllPublishRecordHeadersAsync(int salesProductId, string operatorType, CancellationToken cancellationToken = default)
{
var db = await GetDbContextAsync(cancellationToken);
return await db.Queryable<SalesProductPublishRecord>()
.Where(x => x.SalesProductId == salesProductId && x.OperatorType == operatorType)
.OrderBy(x => x.PublishTime, SqlSugar.OrderByType.Desc)
.Select(x => new SalesProductPublishRecord
{
Id = x.Id,
SalesProductId = x.SalesProductId,
Version = x.Version,
OperatorType = x.OperatorType,
Status = x.Status,
PublishUser = x.PublishUser,
PublishTime = x.PublishTime,
Description = x.Description,
CreateTime = x.CreateTime
})
.ToListAsync(cancellationToken);
}
public async Task<int> CountPublishRecordsAsync(int salesProductId, string operatorType, CancellationToken cancellationToken = default) public async Task<int> CountPublishRecordsAsync(int salesProductId, string operatorType, CancellationToken cancellationToken = default)
{ {
var db = await GetDbContextAsync(cancellationToken); var db = await GetDbContextAsync(cancellationToken);

View File

@ -35,6 +35,20 @@ public class ServiceChannelRepository : OmsSqlSugarRepository<ServiceChannel>, I
return await db.Queryable<ServiceChannelGroup>().ToListAsync(cancellationToken); return await db.Queryable<ServiceChannelGroup>().ToListAsync(cancellationToken);
} }
public async Task<List<ServiceChannel>> GetOptionRowsAsync(CancellationToken cancellationToken = default)
{
var db = await GetDbContextAsync(cancellationToken);
return await db.Queryable<ServiceChannel>()
.OrderBy(x => x.Id)
.Select(x => new ServiceChannel
{
Id = x.Id,
GroupId = x.GroupId,
ChannelName = x.ChannelName
})
.ToListAsync(cancellationToken);
}
public async Task<List<ServiceChannelPrice>> GetPricesAsync(int serviceChannelId, CancellationToken cancellationToken = default) public async Task<List<ServiceChannelPrice>> GetPricesAsync(int serviceChannelId, CancellationToken cancellationToken = default)
{ {
var db = await GetDbContextAsync(cancellationToken); var db = await GetDbContextAsync(cancellationToken);