diff --git a/src/Api/TakeoutSaaS.TenantApi/Contracts/Store/StoreDeliveryContracts.cs b/src/Api/TakeoutSaaS.TenantApi/Contracts/Store/StoreDeliveryContracts.cs
new file mode 100644
index 0000000..aa45951
--- /dev/null
+++ b/src/Api/TakeoutSaaS.TenantApi/Contracts/Store/StoreDeliveryContracts.cs
@@ -0,0 +1,147 @@
+namespace TakeoutSaaS.TenantApi.Contracts.Store;
+
+///
+/// 半径梯度。
+///
+public sealed class RadiusTierDto
+{
+ ///
+ /// Id。
+ ///
+ public string Id { get; set; } = string.Empty;
+ ///
+ /// MinDistance。
+ ///
+ public decimal MinDistance { get; set; }
+ ///
+ /// MaxDistance。
+ ///
+ public decimal MaxDistance { get; set; }
+ ///
+ /// DeliveryFee。
+ ///
+ public decimal DeliveryFee { get; set; }
+ ///
+ /// EtaMinutes。
+ ///
+ public int EtaMinutes { get; set; }
+ ///
+ /// MinOrderAmount。
+ ///
+ public decimal MinOrderAmount { get; set; }
+ ///
+ /// Color。
+ ///
+ public string Color { get; set; } = "#1677ff";
+}
+
+///
+/// 多边形区域。
+///
+public sealed class PolygonZoneDto
+{
+ ///
+ /// Id。
+ ///
+ public string Id { get; set; } = string.Empty;
+ ///
+ /// Name。
+ ///
+ public string Name { get; set; } = string.Empty;
+ ///
+ /// Color。
+ ///
+ public string Color { get; set; } = "#1677ff";
+ ///
+ /// DeliveryFee。
+ ///
+ public decimal DeliveryFee { get; set; }
+ ///
+ /// EtaMinutes。
+ ///
+ public int EtaMinutes { get; set; }
+ ///
+ /// MinOrderAmount。
+ ///
+ public decimal MinOrderAmount { get; set; }
+ ///
+ /// Priority。
+ ///
+ public int Priority { get; set; }
+}
+
+///
+/// 通用配送设置。
+///
+public sealed class DeliveryGeneralSettingsDto
+{
+ ///
+ /// EtaAdjustmentMinutes。
+ ///
+ public int EtaAdjustmentMinutes { get; set; }
+ ///
+ /// FreeDeliveryThreshold。
+ ///
+ public decimal? FreeDeliveryThreshold { get; set; }
+ ///
+ /// HourlyCapacityLimit。
+ ///
+ public int HourlyCapacityLimit { get; set; }
+ ///
+ /// MaxDeliveryDistance。
+ ///
+ public decimal MaxDeliveryDistance { get; set; }
+}
+
+///
+/// 门店配送设置聚合。
+///
+public sealed class StoreDeliverySettingsDto
+{
+ ///
+ /// StoreId。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+ ///
+ /// Mode。
+ ///
+ public string Mode { get; set; } = "radius";
+ ///
+ /// RadiusTiers。
+ ///
+ public List RadiusTiers { get; set; } = [];
+ ///
+ /// PolygonZones。
+ ///
+ public List PolygonZones { get; set; } = [];
+ ///
+ /// GeneralSettings。
+ ///
+ public DeliveryGeneralSettingsDto GeneralSettings { get; set; } = new();
+}
+
+///
+/// 复制配送设置请求。
+///
+public sealed class CopyStoreDeliverySettingsRequest
+{
+ ///
+ /// SourceStoreId。
+ ///
+ public string SourceStoreId { get; set; } = string.Empty;
+ ///
+ /// TargetStoreIds。
+ ///
+ public List TargetStoreIds { get; set; } = [];
+}
+
+///
+/// 复制结果。
+///
+public sealed class CopyStoreDeliverySettingsResult
+{
+ ///
+ /// CopiedCount。
+ ///
+ public int CopiedCount { get; set; }
+}
diff --git a/src/Api/TakeoutSaaS.TenantApi/Contracts/Store/StoreDineInContracts.cs b/src/Api/TakeoutSaaS.TenantApi/Contracts/Store/StoreDineInContracts.cs
new file mode 100644
index 0000000..210108d
--- /dev/null
+++ b/src/Api/TakeoutSaaS.TenantApi/Contracts/Store/StoreDineInContracts.cs
@@ -0,0 +1,240 @@
+namespace TakeoutSaaS.TenantApi.Contracts.Store;
+
+///
+/// 堂食基础设置。
+///
+public sealed class DineInBasicSettingsDto
+{
+ ///
+ /// Enabled。
+ ///
+ public bool Enabled { get; set; }
+ ///
+ /// DefaultDiningMinutes。
+ ///
+ public int DefaultDiningMinutes { get; set; }
+ ///
+ /// OvertimeReminderMinutes。
+ ///
+ public int OvertimeReminderMinutes { get; set; }
+}
+
+///
+/// 堂食区域。
+///
+public sealed class DineInAreaDto
+{
+ ///
+ /// Id。
+ ///
+ public string Id { get; set; } = string.Empty;
+ ///
+ /// Name。
+ ///
+ public string Name { get; set; } = string.Empty;
+ ///
+ /// Description。
+ ///
+ public string Description { get; set; } = string.Empty;
+ ///
+ /// Sort。
+ ///
+ public int Sort { get; set; }
+}
+
+///
+/// 堂食桌位。
+///
+public sealed class DineInTableDto
+{
+ ///
+ /// Id。
+ ///
+ public string Id { get; set; } = string.Empty;
+ ///
+ /// AreaId。
+ ///
+ public string AreaId { get; set; } = string.Empty;
+ ///
+ /// Code。
+ ///
+ public string Code { get; set; } = string.Empty;
+ ///
+ /// Seats。
+ ///
+ public int Seats { get; set; }
+ ///
+ /// Status。
+ ///
+ public string Status { get; set; } = "free";
+ ///
+ /// Tags。
+ ///
+ public List Tags { get; set; } = [];
+}
+
+///
+/// 堂食设置聚合。
+///
+public sealed class StoreDineInSettingsDto
+{
+ ///
+ /// StoreId。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+ ///
+ /// BasicSettings。
+ ///
+ public DineInBasicSettingsDto BasicSettings { get; set; } = new();
+ ///
+ /// Areas。
+ ///
+ public List Areas { get; set; } = [];
+ ///
+ /// Tables。
+ ///
+ public List Tables { get; set; } = [];
+}
+
+///
+/// 保存基础设置请求。
+///
+public sealed class SaveStoreDineInBasicSettingsRequest
+{
+ ///
+ /// StoreId。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+ ///
+ /// BasicSettings。
+ ///
+ public DineInBasicSettingsDto BasicSettings { get; set; } = new();
+}
+
+///
+/// 保存区域请求。
+///
+public sealed class SaveDineInAreaRequest
+{
+ ///
+ /// StoreId。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+ ///
+ /// Area。
+ ///
+ public DineInAreaDto Area { get; set; } = new();
+}
+
+///
+/// 删除区域请求。
+///
+public sealed class DeleteDineInAreaRequest
+{
+ ///
+ /// StoreId。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+ ///
+ /// AreaId。
+ ///
+ public string AreaId { get; set; } = string.Empty;
+}
+
+///
+/// 保存桌位请求。
+///
+public sealed class SaveDineInTableRequest
+{
+ ///
+ /// StoreId。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+ ///
+ /// Table。
+ ///
+ public DineInTableDto Table { get; set; } = new();
+}
+
+///
+/// 删除桌位请求。
+///
+public sealed class DeleteDineInTableRequest
+{
+ ///
+ /// StoreId。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+ ///
+ /// TableId。
+ ///
+ public string TableId { get; set; } = string.Empty;
+}
+
+///
+/// 批量生成桌位请求。
+///
+public sealed class BatchCreateDineInTablesRequest
+{
+ ///
+ /// StoreId。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+ ///
+ /// AreaId。
+ ///
+ public string AreaId { get; set; } = string.Empty;
+ ///
+ /// CodePrefix。
+ ///
+ public string CodePrefix { get; set; } = string.Empty;
+ ///
+ /// StartNumber。
+ ///
+ public int StartNumber { get; set; }
+ ///
+ /// Count。
+ ///
+ public int Count { get; set; }
+ ///
+ /// Seats。
+ ///
+ public int Seats { get; set; }
+}
+
+///
+/// 批量生成桌位结果。
+///
+public sealed class BatchCreateDineInTablesResultDto
+{
+ ///
+ /// CreatedTables。
+ ///
+ public List CreatedTables { get; set; } = [];
+}
+
+///
+/// 复制堂食设置请求。
+///
+public sealed class CopyStoreDineInSettingsRequest
+{
+ ///
+ /// SourceStoreId。
+ ///
+ public string SourceStoreId { get; set; } = string.Empty;
+ ///
+ /// TargetStoreIds。
+ ///
+ public List TargetStoreIds { get; set; } = [];
+}
+
+///
+/// 复制结果。
+///
+public sealed class CopyStoreDineInSettingsResult
+{
+ ///
+ /// CopiedCount。
+ ///
+ public int CopiedCount { get; set; }
+}
diff --git a/src/Api/TakeoutSaaS.TenantApi/Contracts/Store/StoreFeesContracts.cs b/src/Api/TakeoutSaaS.TenantApi/Contracts/Store/StoreFeesContracts.cs
new file mode 100644
index 0000000..7c94586
--- /dev/null
+++ b/src/Api/TakeoutSaaS.TenantApi/Contracts/Store/StoreFeesContracts.cs
@@ -0,0 +1,127 @@
+namespace TakeoutSaaS.TenantApi.Contracts.Store;
+
+///
+/// 阶梯包装费。
+///
+public sealed class PackagingFeeTierDto
+{
+ ///
+ /// Id。
+ ///
+ public string Id { get; set; } = string.Empty;
+ ///
+ /// MinAmount。
+ ///
+ public decimal MinAmount { get; set; }
+ ///
+ /// MaxAmount。
+ ///
+ public decimal? MaxAmount { get; set; }
+ ///
+ /// Fee。
+ ///
+ public decimal Fee { get; set; }
+ ///
+ /// Sort。
+ ///
+ public int Sort { get; set; }
+}
+
+///
+/// 附加费用项。
+///
+public sealed class AdditionalFeeItemDto
+{
+ ///
+ /// Enabled。
+ ///
+ public bool Enabled { get; set; }
+ ///
+ /// Amount。
+ ///
+ public decimal Amount { get; set; }
+}
+
+///
+/// 其他费用。
+///
+public sealed class StoreOtherFeesDto
+{
+ ///
+ /// Cutlery。
+ ///
+ public AdditionalFeeItemDto Cutlery { get; set; } = new();
+ ///
+ /// Rush。
+ ///
+ public AdditionalFeeItemDto Rush { get; set; } = new();
+}
+
+///
+/// 门店费用设置。
+///
+public sealed class StoreFeesSettingsDto
+{
+ ///
+ /// StoreId。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+ ///
+ /// MinimumOrderAmount。
+ ///
+ public decimal MinimumOrderAmount { get; set; }
+ ///
+ /// BaseDeliveryFee。
+ ///
+ public decimal BaseDeliveryFee { get; set; }
+ ///
+ /// FreeDeliveryThreshold。
+ ///
+ public decimal? FreeDeliveryThreshold { get; set; }
+ ///
+ /// PackagingFeeMode。
+ ///
+ public string PackagingFeeMode { get; set; } = "order";
+ ///
+ /// OrderPackagingFeeMode。
+ ///
+ public string OrderPackagingFeeMode { get; set; } = "fixed";
+ ///
+ /// FixedPackagingFee。
+ ///
+ public decimal FixedPackagingFee { get; set; }
+ ///
+ /// PackagingFeeTiers。
+ ///
+ public List PackagingFeeTiers { get; set; } = [];
+ ///
+ /// OtherFees。
+ ///
+ public StoreOtherFeesDto OtherFees { get; set; } = new();
+}
+
+///
+/// 复制费用请求。
+///
+public sealed class CopyStoreFeesSettingsRequest
+{
+ ///
+ /// SourceStoreId。
+ ///
+ public string SourceStoreId { get; set; } = string.Empty;
+ ///
+ /// TargetStoreIds。
+ ///
+ public List TargetStoreIds { get; set; } = [];
+}
+
+///
+/// 复制结果。
+///
+public sealed class CopyStoreFeesSettingsResult
+{
+ ///
+ /// CopiedCount。
+ ///
+ public int CopiedCount { get; set; }
+}
diff --git a/src/Api/TakeoutSaaS.TenantApi/Contracts/Store/StoreHoursContracts.cs b/src/Api/TakeoutSaaS.TenantApi/Contracts/Store/StoreHoursContracts.cs
new file mode 100644
index 0000000..4d5aafb
--- /dev/null
+++ b/src/Api/TakeoutSaaS.TenantApi/Contracts/Store/StoreHoursContracts.cs
@@ -0,0 +1,229 @@
+namespace TakeoutSaaS.TenantApi.Contracts.Store;
+
+///
+/// 时段类型。
+///
+public enum StoreHourSlotType
+{
+ ///
+ /// 营业时段。
+ ///
+ Business = 1,
+
+ ///
+ /// 配送时段。
+ ///
+ Delivery = 2,
+
+ ///
+ /// 自提时段。
+ ///
+ Pickup = 3
+}
+
+///
+/// 特殊日期类型。
+///
+public enum StoreHolidayType
+{
+ ///
+ /// 休息日。
+ ///
+ Closed = 1,
+
+ ///
+ /// 特殊营业日。
+ ///
+ Special = 2
+}
+
+///
+/// 时段 DTO。
+///
+public sealed class StoreHourTimeSlotDto
+{
+ ///
+ /// Id。
+ ///
+ public string Id { get; set; } = string.Empty;
+ ///
+ /// Type。
+ ///
+ public int Type { get; set; }
+ ///
+ /// StartTime。
+ ///
+ public string StartTime { get; set; } = string.Empty;
+ ///
+ /// EndTime。
+ ///
+ public string EndTime { get; set; } = string.Empty;
+ ///
+ /// Capacity。
+ ///
+ public int? Capacity { get; set; }
+ ///
+ /// Remark。
+ ///
+ public string? Remark { get; set; }
+}
+
+///
+/// 每日营业时间 DTO。
+///
+public sealed class StoreHourDayHoursDto
+{
+ ///
+ /// DayOfWeek。
+ ///
+ public int DayOfWeek { get; set; }
+ ///
+ /// IsOpen。
+ ///
+ public bool IsOpen { get; set; }
+ ///
+ /// Slots。
+ ///
+ public List Slots { get; set; } = [];
+}
+
+///
+/// 特殊日期 DTO。
+///
+public sealed class StoreHourHolidayDto
+{
+ ///
+ /// Id。
+ ///
+ public string Id { get; set; } = string.Empty;
+ ///
+ /// StartDate。
+ ///
+ public string StartDate { get; set; } = string.Empty;
+ ///
+ /// EndDate。
+ ///
+ public string EndDate { get; set; } = string.Empty;
+ ///
+ /// Type。
+ ///
+ public int Type { get; set; }
+ ///
+ /// StartTime。
+ ///
+ public string? StartTime { get; set; }
+ ///
+ /// EndTime。
+ ///
+ public string? EndTime { get; set; }
+ ///
+ /// Reason。
+ ///
+ public string Reason { get; set; } = string.Empty;
+ ///
+ /// Remark。
+ ///
+ public string? Remark { get; set; }
+}
+
+///
+/// 门店营业时间聚合。
+///
+public sealed class StoreHoursDto
+{
+ ///
+ /// StoreId。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+ ///
+ /// WeeklyHours。
+ ///
+ public List WeeklyHours { get; set; } = [];
+ ///
+ /// Holidays。
+ ///
+ public List Holidays { get; set; } = [];
+}
+
+///
+/// 保存每周时段请求。
+///
+public sealed class SaveWeeklyHoursRequest
+{
+ ///
+ /// StoreId。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+ ///
+ /// WeeklyHours。
+ ///
+ public List WeeklyHours { get; set; } = [];
+}
+
+///
+/// 保存特殊日期请求。
+///
+public sealed class SaveHolidayRequest
+{
+ ///
+ /// StoreId。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+ ///
+ /// Holiday。
+ ///
+ public StoreHourHolidayDto Holiday { get; set; } = new();
+}
+
+///
+/// 删除特殊日期请求。
+///
+public sealed class DeleteHolidayRequest
+{
+ ///
+ /// Id。
+ ///
+ public string Id { get; set; } = string.Empty;
+}
+
+///
+/// 复制营业时间请求。
+///
+public sealed class CopyStoreHoursRequest
+{
+ ///
+ /// SourceStoreId。
+ ///
+ public string SourceStoreId { get; set; } = string.Empty;
+ ///
+ /// TargetStoreIds。
+ ///
+ public List TargetStoreIds { get; set; } = [];
+ ///
+ /// IncludeWeeklyHours。
+ ///
+ public bool? IncludeWeeklyHours { get; set; }
+ ///
+ /// IncludeHolidays。
+ ///
+ public bool? IncludeHolidays { get; set; }
+}
+
+///
+/// 复制结果。
+///
+public sealed class CopyStoreHoursResult
+{
+ ///
+ /// CopiedCount。
+ ///
+ public int CopiedCount { get; set; }
+ ///
+ /// IncludeWeeklyHours。
+ ///
+ public bool IncludeWeeklyHours { get; set; }
+ ///
+ /// IncludeHolidays。
+ ///
+ public bool IncludeHolidays { get; set; }
+}
diff --git a/src/Api/TakeoutSaaS.TenantApi/Contracts/Store/StorePickupContracts.cs b/src/Api/TakeoutSaaS.TenantApi/Contracts/Store/StorePickupContracts.cs
new file mode 100644
index 0000000..50e7247
--- /dev/null
+++ b/src/Api/TakeoutSaaS.TenantApi/Contracts/Store/StorePickupContracts.cs
@@ -0,0 +1,250 @@
+namespace TakeoutSaaS.TenantApi.Contracts.Store;
+
+///
+/// 自提基础设置。
+///
+public sealed class PickupBasicSettingsDto
+{
+ ///
+ /// AllowSameDayPickup。
+ ///
+ public bool AllowSameDayPickup { get; set; }
+ ///
+ /// BookingDays。
+ ///
+ public int BookingDays { get; set; }
+ ///
+ /// MaxItemsPerOrder。
+ ///
+ public int? MaxItemsPerOrder { get; set; }
+}
+
+///
+/// 自提大时段。
+///
+public sealed class PickupSlotDto
+{
+ ///
+ /// Id。
+ ///
+ public string Id { get; set; } = string.Empty;
+ ///
+ /// Name。
+ ///
+ public string Name { get; set; } = string.Empty;
+ ///
+ /// StartTime。
+ ///
+ public string StartTime { get; set; } = string.Empty;
+ ///
+ /// EndTime。
+ ///
+ public string EndTime { get; set; } = string.Empty;
+ ///
+ /// CutoffMinutes。
+ ///
+ public int CutoffMinutes { get; set; }
+ ///
+ /// Capacity。
+ ///
+ public int Capacity { get; set; }
+ ///
+ /// ReservedCount。
+ ///
+ public int ReservedCount { get; set; }
+ ///
+ /// DayOfWeeks。
+ ///
+ public List DayOfWeeks { get; set; } = [];
+ ///
+ /// Enabled。
+ ///
+ public bool Enabled { get; set; }
+}
+
+///
+/// 精细规则。
+///
+public sealed class PickupFineRuleDto
+{
+ ///
+ /// IntervalMinutes。
+ ///
+ public int IntervalMinutes { get; set; }
+ ///
+ /// SlotCapacity。
+ ///
+ public int SlotCapacity { get; set; }
+ ///
+ /// DayStartTime。
+ ///
+ public string DayStartTime { get; set; } = string.Empty;
+ ///
+ /// DayEndTime。
+ ///
+ public string DayEndTime { get; set; } = string.Empty;
+ ///
+ /// MinAdvanceHours。
+ ///
+ public int MinAdvanceHours { get; set; }
+ ///
+ /// DayOfWeeks。
+ ///
+ public List DayOfWeeks { get; set; } = [];
+}
+
+///
+/// 预览时段。
+///
+public sealed class PickupPreviewSlotDto
+{
+ ///
+ /// Time。
+ ///
+ public string Time { get; set; } = string.Empty;
+ ///
+ /// Status。
+ ///
+ public string Status { get; set; } = "available";
+ ///
+ /// RemainingCount。
+ ///
+ public int RemainingCount { get; set; }
+}
+
+///
+/// 预览日期。
+///
+public sealed class PickupPreviewDayDto
+{
+ ///
+ /// Date。
+ ///
+ public string Date { get; set; } = string.Empty;
+ ///
+ /// Label。
+ ///
+ public string Label { get; set; } = string.Empty;
+ ///
+ /// SubLabel。
+ ///
+ public string SubLabel { get; set; } = string.Empty;
+ ///
+ /// Slots。
+ ///
+ public List Slots { get; set; } = [];
+}
+
+///
+/// 门店自提设置。
+///
+public sealed class StorePickupSettingsDto
+{
+ ///
+ /// StoreId。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+ ///
+ /// Mode。
+ ///
+ public string Mode { get; set; } = "big";
+ ///
+ /// BasicSettings。
+ ///
+ public PickupBasicSettingsDto BasicSettings { get; set; } = new();
+ ///
+ /// BigSlots。
+ ///
+ public List BigSlots { get; set; } = [];
+ ///
+ /// FineRule。
+ ///
+ public PickupFineRuleDto FineRule { get; set; } = new();
+ ///
+ /// PreviewDays。
+ ///
+ public List PreviewDays { get; set; } = [];
+}
+
+///
+/// 保存基础设置请求。
+///
+public sealed class SavePickupBasicSettingsRequest
+{
+ ///
+ /// StoreId。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+ ///
+ /// Mode。
+ ///
+ public string? Mode { get; set; }
+ ///
+ /// BasicSettings。
+ ///
+ public PickupBasicSettingsDto BasicSettings { get; set; } = new();
+}
+
+///
+/// 保存大时段请求。
+///
+public sealed class SavePickupSlotsRequest
+{
+ ///
+ /// StoreId。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+ ///
+ /// Mode。
+ ///
+ public string? Mode { get; set; }
+ ///
+ /// Slots。
+ ///
+ public List Slots { get; set; } = [];
+}
+
+///
+/// 保存精细规则请求。
+///
+public sealed class SavePickupFineRuleRequest
+{
+ ///
+ /// StoreId。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+ ///
+ /// Mode。
+ ///
+ public string? Mode { get; set; }
+ ///
+ /// FineRule。
+ ///
+ public PickupFineRuleDto FineRule { get; set; } = new();
+}
+
+///
+/// 复制自提设置请求。
+///
+public sealed class CopyStorePickupSettingsRequest
+{
+ ///
+ /// SourceStoreId。
+ ///
+ public string SourceStoreId { get; set; } = string.Empty;
+ ///
+ /// TargetStoreIds。
+ ///
+ public List TargetStoreIds { get; set; } = [];
+}
+
+///
+/// 复制结果。
+///
+public sealed class CopyStorePickupSettingsResult
+{
+ ///
+ /// CopiedCount。
+ ///
+ public int CopiedCount { get; set; }
+}
diff --git a/src/Api/TakeoutSaaS.TenantApi/Contracts/Store/StoreStaffContracts.cs b/src/Api/TakeoutSaaS.TenantApi/Contracts/Store/StoreStaffContracts.cs
new file mode 100644
index 0000000..c2a5dde
--- /dev/null
+++ b/src/Api/TakeoutSaaS.TenantApi/Contracts/Store/StoreStaffContracts.cs
@@ -0,0 +1,299 @@
+namespace TakeoutSaaS.TenantApi.Contracts.Store;
+
+///
+/// 分页结构。
+///
+public sealed class PaginatedResultDto
+{
+ ///
+ /// Items。
+ ///
+ public List Items { get; set; } = [];
+ ///
+ /// Total。
+ ///
+ public int Total { get; set; }
+ ///
+ /// Page。
+ ///
+ public int Page { get; set; }
+ ///
+ /// PageSize。
+ ///
+ public int PageSize { get; set; }
+}
+
+///
+/// 员工档案。
+///
+public sealed class StoreStaffItemDto
+{
+ ///
+ /// Id。
+ ///
+ public string Id { get; set; } = string.Empty;
+ ///
+ /// Name。
+ ///
+ public string Name { get; set; } = string.Empty;
+ ///
+ /// Phone。
+ ///
+ public string Phone { get; set; } = string.Empty;
+ ///
+ /// Email。
+ ///
+ public string Email { get; set; } = string.Empty;
+ ///
+ /// RoleType。
+ ///
+ public string RoleType { get; set; } = "cashier";
+ ///
+ /// Status。
+ ///
+ public string Status { get; set; } = "active";
+ ///
+ /// Permissions。
+ ///
+ public List Permissions { get; set; } = [];
+ ///
+ /// AvatarColor。
+ ///
+ public string AvatarColor { get; set; } = "#1677ff";
+ ///
+ /// HiredAt。
+ ///
+ public string HiredAt { get; set; } = string.Empty;
+}
+
+///
+/// 班次时间段模板。
+///
+public sealed class ShiftTemplateItemDto
+{
+ ///
+ /// StartTime。
+ ///
+ public string StartTime { get; set; } = string.Empty;
+ ///
+ /// EndTime。
+ ///
+ public string EndTime { get; set; } = string.Empty;
+}
+
+///
+/// 门店班次模板。
+///
+public sealed class StoreShiftTemplatesDto
+{
+ ///
+ /// Morning。
+ ///
+ public ShiftTemplateItemDto Morning { get; set; } = new();
+ ///
+ /// Evening。
+ ///
+ public ShiftTemplateItemDto Evening { get; set; } = new();
+ ///
+ /// Full。
+ ///
+ public ShiftTemplateItemDto Full { get; set; } = new();
+}
+
+///
+/// 员工单日排班。
+///
+public sealed class StaffDayShiftDto
+{
+ ///
+ /// DayOfWeek。
+ ///
+ public int DayOfWeek { get; set; }
+ ///
+ /// ShiftType。
+ ///
+ public string ShiftType { get; set; } = "off";
+ ///
+ /// StartTime。
+ ///
+ public string StartTime { get; set; } = string.Empty;
+ ///
+ /// EndTime。
+ ///
+ public string EndTime { get; set; } = string.Empty;
+}
+
+///
+/// 员工排班。
+///
+public sealed class StaffScheduleDto
+{
+ ///
+ /// StaffId。
+ ///
+ public string StaffId { get; set; } = string.Empty;
+ ///
+ /// Shifts。
+ ///
+ public List Shifts { get; set; } = [];
+}
+
+///
+/// 门店排班聚合。
+///
+public sealed class StoreStaffScheduleDto
+{
+ ///
+ /// StoreId。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+ ///
+ /// WeekStartDate。
+ ///
+ public string WeekStartDate { get; set; } = string.Empty;
+ ///
+ /// Templates。
+ ///
+ public StoreShiftTemplatesDto Templates { get; set; } = new();
+ ///
+ /// Schedules。
+ ///
+ public List Schedules { get; set; } = [];
+}
+
+///
+/// 保存员工请求。
+///
+public sealed class SaveStoreStaffRequest
+{
+ ///
+ /// StoreId。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+ ///
+ /// Id。
+ ///
+ public string? Id { get; set; }
+ ///
+ /// Name。
+ ///
+ public string Name { get; set; } = string.Empty;
+ ///
+ /// Phone。
+ ///
+ public string Phone { get; set; } = string.Empty;
+ ///
+ /// Email。
+ ///
+ public string Email { get; set; } = string.Empty;
+ ///
+ /// RoleType。
+ ///
+ public string RoleType { get; set; } = "cashier";
+ ///
+ /// Status。
+ ///
+ public string Status { get; set; } = "active";
+ ///
+ /// Permissions。
+ ///
+ public List Permissions { get; set; } = [];
+}
+
+///
+/// 删除员工请求。
+///
+public sealed class DeleteStoreStaffRequest
+{
+ ///
+ /// StoreId。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+ ///
+ /// StaffId。
+ ///
+ public string StaffId { get; set; } = string.Empty;
+}
+
+///
+/// 保存班次模板请求。
+///
+public sealed class SaveStoreStaffTemplatesRequest
+{
+ ///
+ /// StoreId。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+ ///
+ /// Templates。
+ ///
+ public StoreShiftTemplatesDto Templates { get; set; } = new();
+}
+
+///
+/// 保存个人排班请求。
+///
+public sealed class SaveStoreStaffPersonalScheduleRequest
+{
+ ///
+ /// StoreId。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+ ///
+ /// StaffId。
+ ///
+ public string StaffId { get; set; } = string.Empty;
+ ///
+ /// Shifts。
+ ///
+ public List Shifts { get; set; } = [];
+}
+
+///
+/// 保存周排班请求。
+///
+public sealed class SaveStoreStaffWeeklyScheduleRequest
+{
+ ///
+ /// StoreId。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+ ///
+ /// Schedules。
+ ///
+ public List Schedules { get; set; } = [];
+}
+
+///
+/// 复制排班请求。
+///
+public sealed class CopyStoreStaffScheduleRequest
+{
+ ///
+ /// SourceStoreId。
+ ///
+ public string SourceStoreId { get; set; } = string.Empty;
+ ///
+ /// TargetStoreIds。
+ ///
+ public List TargetStoreIds { get; set; } = [];
+ ///
+ /// CopyScope。
+ ///
+ public string CopyScope { get; set; } = string.Empty;
+}
+
+///
+/// 复制排班结果。
+///
+public sealed class CopyStoreStaffScheduleResult
+{
+ ///
+ /// CopiedCount。
+ ///
+ public int CopiedCount { get; set; }
+ ///
+ /// CopyScope。
+ ///
+ public string CopyScope { get; set; } = "template_and_schedule";
+}
diff --git a/src/Api/TakeoutSaaS.TenantApi/Controllers/StoreApiHelpers.cs b/src/Api/TakeoutSaaS.TenantApi/Controllers/StoreApiHelpers.cs
new file mode 100644
index 0000000..2c7978f
--- /dev/null
+++ b/src/Api/TakeoutSaaS.TenantApi/Controllers/StoreApiHelpers.cs
@@ -0,0 +1,379 @@
+using System.Globalization;
+using System.Text.Json;
+using Microsoft.EntityFrameworkCore;
+using TakeoutSaaS.Application.App.Stores.Services;
+using TakeoutSaaS.Domain.Merchants.Enums;
+using TakeoutSaaS.Domain.Stores.Entities;
+using TakeoutSaaS.Domain.Stores.Enums;
+using TakeoutSaaS.Infrastructure.App.Persistence;
+using TakeoutSaaS.Shared.Abstractions.Constants;
+using TakeoutSaaS.Shared.Abstractions.Exceptions;
+
+namespace TakeoutSaaS.TenantApi.Controllers;
+
+internal static class StoreApiHelpers
+{
+ private static readonly string[] AvatarColors =
+ [
+ "#f56a00",
+ "#7265e6",
+ "#52c41a",
+ "#fa8c16",
+ "#1890ff",
+ "#bfbfbf",
+ "#13c2c2",
+ "#eb2f96"
+ ];
+
+ public static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
+ {
+ PropertyNameCaseInsensitive = true
+ };
+
+ public static (long TenantId, long MerchantId) GetTenantMerchantContext(StoreContextService storeContextService)
+ {
+ var (_, tenantId, merchantId) = storeContextService.GetRequiredContext();
+ return (tenantId, merchantId);
+ }
+
+ public static long ParseRequiredSnowflake(string? value, string fieldName)
+ {
+ if (!long.TryParse(value, out var id) || id <= 0)
+ {
+ throw new BusinessException(ErrorCodes.BadRequest, $"{fieldName} 非法");
+ }
+
+ return id;
+ }
+
+ public static long? ParseSnowflakeOrNull(string? value)
+ {
+ return long.TryParse(value, out var id) && id > 0 ? id : null;
+ }
+
+ public static List ParseSnowflakeList(IEnumerable? values)
+ {
+ if (values is null)
+ {
+ return [];
+ }
+
+ return values
+ .Select(ParseSnowflakeOrNull)
+ .Where(id => id.HasValue)
+ .Select(id => id!.Value)
+ .Distinct()
+ .ToList();
+ }
+
+ public static TimeSpan ParseRequiredTime(string? value, string fieldName)
+ {
+ if (string.IsNullOrWhiteSpace(value) ||
+ !TimeSpan.TryParseExact(value, "hh\\:mm", CultureInfo.InvariantCulture, out var parsed))
+ {
+ throw new BusinessException(ErrorCodes.BadRequest, $"{fieldName} 时间格式必须为 HH:mm");
+ }
+
+ return parsed;
+ }
+
+ public static string ToHHmm(TimeSpan value)
+ {
+ return value.ToString("hh\\:mm", CultureInfo.InvariantCulture);
+ }
+
+ public static string? ToHHmm(TimeSpan? value)
+ {
+ return value.HasValue ? ToHHmm(value.Value) : null;
+ }
+
+ public static DateTime ParseDateOnly(string? value, string fieldName)
+ {
+ if (string.IsNullOrWhiteSpace(value) ||
+ !DateTime.TryParseExact(value, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var parsed))
+ {
+ throw new BusinessException(ErrorCodes.BadRequest, $"{fieldName} 日期格式必须为 yyyy-MM-dd");
+ }
+
+ return parsed.Date;
+ }
+
+ public static string ToDateOnly(DateTime value)
+ {
+ return value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
+ }
+
+ public static DayOfWeek UiDayOfWeekToDotNet(int uiDayOfWeek)
+ {
+ return uiDayOfWeek switch
+ {
+ 0 => DayOfWeek.Monday,
+ 1 => DayOfWeek.Tuesday,
+ 2 => DayOfWeek.Wednesday,
+ 3 => DayOfWeek.Thursday,
+ 4 => DayOfWeek.Friday,
+ 5 => DayOfWeek.Saturday,
+ 6 => DayOfWeek.Sunday,
+ _ => throw new BusinessException(ErrorCodes.BadRequest, "dayOfWeek 必须在 0-6 之间")
+ };
+ }
+
+ public static int DotNetDayOfWeekToUi(DayOfWeek dayOfWeek)
+ {
+ return dayOfWeek switch
+ {
+ DayOfWeek.Monday => 0,
+ DayOfWeek.Tuesday => 1,
+ DayOfWeek.Wednesday => 2,
+ DayOfWeek.Thursday => 3,
+ DayOfWeek.Friday => 4,
+ DayOfWeek.Saturday => 5,
+ DayOfWeek.Sunday => 6,
+ _ => 0
+ };
+ }
+
+ public static string SerializeWeekdays(IEnumerable? uiDaysOfWeek)
+ {
+ var normalized = (uiDaysOfWeek ?? [])
+ .Distinct()
+ .Where(day => day is >= 0 and <= 6)
+ .OrderBy(day => day)
+ .ToList();
+
+ return normalized.Count == 0 ? string.Empty : string.Join(',', normalized);
+ }
+
+ public static List DeserializeWeekdays(string? storedValue)
+ {
+ if (string.IsNullOrWhiteSpace(storedValue))
+ {
+ return [];
+ }
+
+ var values = storedValue
+ .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
+ .Select(item => int.TryParse(item, out var parsed) ? parsed : -1)
+ .Select(day =>
+ {
+ // 兼容旧数据(1-7)
+ if (day is >= 1 and <= 7)
+ {
+ return day == 7 ? 6 : day - 1;
+ }
+
+ return day;
+ })
+ .Where(day => day is >= 0 and <= 6)
+ .Distinct()
+ .OrderBy(day => day)
+ .ToList();
+
+ return values;
+ }
+
+ public static BusinessHourType ToBusinessHourType(int slotType)
+ {
+ return slotType switch
+ {
+ 1 => BusinessHourType.Normal,
+ 2 => BusinessHourType.PickupOrDelivery,
+ 3 => BusinessHourType.ReservationOnly,
+ _ => throw new BusinessException(ErrorCodes.BadRequest, "slot.type 非法")
+ };
+ }
+
+ public static int ToSlotType(BusinessHourType hourType)
+ {
+ return hourType switch
+ {
+ BusinessHourType.Normal => 1,
+ BusinessHourType.PickupOrDelivery => 2,
+ BusinessHourType.ReservationOnly => 3,
+ _ => 1
+ };
+ }
+
+ public static StaffRoleType ToStaffRoleType(string? value)
+ {
+ return (value ?? string.Empty).Trim().ToLowerInvariant() switch
+ {
+ "manager" => StaffRoleType.Admin,
+ "cashier" => StaffRoleType.FrontDesk,
+ "chef" => StaffRoleType.Kitchen,
+ "courier" => StaffRoleType.Courier,
+ _ => StaffRoleType.FrontDesk
+ };
+ }
+
+ public static string ToStaffRoleTypeText(StaffRoleType value)
+ {
+ return value switch
+ {
+ StaffRoleType.Admin => "manager",
+ StaffRoleType.FrontDesk => "cashier",
+ StaffRoleType.Kitchen => "chef",
+ StaffRoleType.Courier => "courier",
+ StaffRoleType.Operator => "manager",
+ _ => "cashier"
+ };
+ }
+
+ public static StaffStatus ToStaffStatus(string? value)
+ {
+ return (value ?? string.Empty).Trim().ToLowerInvariant() switch
+ {
+ "active" => StaffStatus.Active,
+ "leave" => StaffStatus.Disabled,
+ "resigned" => StaffStatus.Resigned,
+ _ => StaffStatus.Active
+ };
+ }
+
+ public static string ToStaffStatusText(StaffStatus value)
+ {
+ return value switch
+ {
+ StaffStatus.Active => "active",
+ StaffStatus.Disabled => "leave",
+ StaffStatus.Resigned => "resigned",
+ _ => "active"
+ };
+ }
+
+ public static StoreTableStatus ToTableStatus(string? value)
+ {
+ return (value ?? string.Empty).Trim().ToLowerInvariant() switch
+ {
+ "free" => StoreTableStatus.Idle,
+ "disabled" => StoreTableStatus.Disabled,
+ "dining" => StoreTableStatus.Occupied,
+ "reserved" => StoreTableStatus.Cleaning,
+ _ => StoreTableStatus.Idle
+ };
+ }
+
+ public static string ToTableStatusText(StoreTableStatus value)
+ {
+ return value switch
+ {
+ StoreTableStatus.Idle => "free",
+ StoreTableStatus.Disabled => "disabled",
+ StoreTableStatus.Occupied => "dining",
+ StoreTableStatus.Cleaning => "reserved",
+ _ => "free"
+ };
+ }
+
+ public static StorePickupMode ToPickupMode(string? value)
+ {
+ return string.Equals(value, "fine", StringComparison.OrdinalIgnoreCase)
+ ? StorePickupMode.Fine
+ : StorePickupMode.Big;
+ }
+
+ public static string ToPickupModeText(StorePickupMode value)
+ {
+ return value == StorePickupMode.Fine ? "fine" : "big";
+ }
+
+ public static StoreDeliveryMode ToDeliveryMode(string? value)
+ {
+ return string.Equals(value, "polygon", StringComparison.OrdinalIgnoreCase)
+ ? StoreDeliveryMode.Polygon
+ : StoreDeliveryMode.Radius;
+ }
+
+ public static string ToDeliveryModeText(StoreDeliveryMode value)
+ {
+ return value == StoreDeliveryMode.Polygon ? "polygon" : "radius";
+ }
+
+ public static StoreStaffShiftType ToShiftType(string? value)
+ {
+ return (value ?? string.Empty).Trim().ToLowerInvariant() switch
+ {
+ "morning" => StoreStaffShiftType.Morning,
+ "evening" => StoreStaffShiftType.Evening,
+ "full" => StoreStaffShiftType.Full,
+ "off" => StoreStaffShiftType.Off,
+ _ => StoreStaffShiftType.Off
+ };
+ }
+
+ public static string ToShiftTypeText(StoreStaffShiftType value)
+ {
+ return value switch
+ {
+ StoreStaffShiftType.Morning => "morning",
+ StoreStaffShiftType.Evening => "evening",
+ StoreStaffShiftType.Full => "full",
+ StoreStaffShiftType.Off => "off",
+ _ => "off"
+ };
+ }
+
+ public static async Task EnsureStoreAccessibleAsync(
+ TakeoutAppDbContext dbContext,
+ long tenantId,
+ long merchantId,
+ long storeId,
+ CancellationToken cancellationToken)
+ {
+ var store = await dbContext.Stores
+ .FirstOrDefaultAsync(x => x.Id == storeId && x.TenantId == tenantId && x.MerchantId == merchantId, cancellationToken);
+
+ if (store is null)
+ {
+ throw new BusinessException(ErrorCodes.NotFound, "门店不存在或无权限访问");
+ }
+
+ return store;
+ }
+
+ public static async Task> FilterAccessibleStoreIdsAsync(
+ TakeoutAppDbContext dbContext,
+ long tenantId,
+ long merchantId,
+ IEnumerable storeIds,
+ CancellationToken cancellationToken)
+ {
+ var ids = storeIds.Distinct().ToList();
+ if (ids.Count == 0)
+ {
+ return [];
+ }
+
+ return await dbContext.Stores
+ .AsNoTracking()
+ .Where(x => x.TenantId == tenantId && x.MerchantId == merchantId && ids.Contains(x.Id))
+ .Select(x => x.Id)
+ .ToHashSetAsync(cancellationToken);
+ }
+
+ public static string ResolveAvatarColor(string? seed)
+ {
+ var source = string.IsNullOrWhiteSpace(seed) ? "store-staff" : seed;
+ var hash = 0;
+ foreach (var ch in source)
+ {
+ hash = (hash * 31 + ch) & int.MaxValue;
+ }
+
+ return AvatarColors[hash % AvatarColors.Length];
+ }
+
+ public static string ResolveWeekStartDate(string? requestedWeekStartDate)
+ {
+ if (!string.IsNullOrWhiteSpace(requestedWeekStartDate) &&
+ DateTime.TryParseExact(requestedWeekStartDate, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var parsed))
+ {
+ return ToDateOnly(parsed);
+ }
+
+ var today = DateTime.UtcNow.Date;
+ var diff = today.DayOfWeek == DayOfWeek.Sunday ? -6 : 1 - (int)today.DayOfWeek;
+ var monday = today.AddDays(diff);
+ return ToDateOnly(monday);
+ }
+}
diff --git a/src/Api/TakeoutSaaS.TenantApi/Controllers/StoreDeliveryController.cs b/src/Api/TakeoutSaaS.TenantApi/Controllers/StoreDeliveryController.cs
new file mode 100644
index 0000000..4f0d09f
--- /dev/null
+++ b/src/Api/TakeoutSaaS.TenantApi/Controllers/StoreDeliveryController.cs
@@ -0,0 +1,316 @@
+using System.Text.Json;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using TakeoutSaaS.Application.App.Stores.Services;
+using TakeoutSaaS.Domain.Stores.Entities;
+using TakeoutSaaS.Domain.Stores.Enums;
+using TakeoutSaaS.Infrastructure.App.Persistence;
+using TakeoutSaaS.Shared.Abstractions.Results;
+using TakeoutSaaS.Shared.Web.Api;
+using TakeoutSaaS.TenantApi.Contracts.Store;
+
+namespace TakeoutSaaS.TenantApi.Controllers;
+
+///
+/// 门店配送设置模块。
+///
+[ApiVersion("1.0")]
+[Authorize]
+[Route("api/tenant/v{version:apiVersion}/store")]
+public sealed class StoreDeliveryController(
+ TakeoutAppDbContext dbContext,
+ StoreContextService storeContextService) : BaseApiController
+{
+ ///
+ /// 获取门店配送设置。
+ ///
+ [HttpGet("delivery")]
+ [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
+ public async Task> Get([FromQuery] string storeId, CancellationToken cancellationToken)
+ {
+ var parsedStoreId = StoreApiHelpers.ParseRequiredSnowflake(storeId, "storeId");
+ var (tenantId, merchantId) = StoreApiHelpers.GetTenantMerchantContext(storeContextService);
+ await StoreApiHelpers.EnsureStoreAccessibleAsync(dbContext, tenantId, merchantId, parsedStoreId, cancellationToken);
+
+ var setting = await dbContext.StoreDeliverySettings
+ .AsNoTracking()
+ .FirstOrDefaultAsync(x => x.TenantId == tenantId && x.StoreId == parsedStoreId, cancellationToken);
+ var polygonZones = await dbContext.StoreDeliveryZones
+ .AsNoTracking()
+ .Where(x => x.TenantId == tenantId && x.StoreId == parsedStoreId)
+ .OrderBy(x => x.Priority)
+ .ThenBy(x => x.SortOrder)
+ .ToListAsync(cancellationToken);
+
+ var radiusTiers = ParseRadiusTiers(setting?.RadiusTiersJson);
+ return ApiResponse.Ok(new StoreDeliverySettingsDto
+ {
+ StoreId = parsedStoreId.ToString(),
+ Mode = StoreApiHelpers.ToDeliveryModeText(setting?.Mode ?? StoreDeliveryMode.Radius),
+ RadiusTiers = radiusTiers,
+ PolygonZones = polygonZones.Select(MapPolygonZone).ToList(),
+ GeneralSettings = new DeliveryGeneralSettingsDto
+ {
+ EtaAdjustmentMinutes = setting?.EtaAdjustmentMinutes ?? 10,
+ FreeDeliveryThreshold = setting?.FreeDeliveryThreshold ?? 30m,
+ HourlyCapacityLimit = setting?.HourlyCapacityLimit ?? 50,
+ MaxDeliveryDistance = setting?.MaxDeliveryDistance ?? 5m
+ }
+ });
+ }
+
+ ///
+ /// 保存门店配送设置。
+ ///
+ [HttpPost("delivery/save")]
+ [ProducesResponseType(typeof(ApiResponse