Commit 2748a56b8b75a8271852871e9520bb56ae933472

Authored by 李曜臣
1 parent 9a5ffaaa

实现标签模块

Showing 19 changed files with 1626 additions and 55 deletions
label-template-template-1773988794039 (1).json renamed to label-template-template-1773999322132.json
1 1 {
2   - "id": "template-1773988794039",
  2 + "id": "template-1773999322132",
3 3 "name": "未命名模板",
4 4 "labelType": "PRICE",
5   - "unit": "inch",
6   - "width": 4,
7   - "height": 6,
  5 + "unit": "cm",
  6 + "width": 21,
  7 + "height": 29.7,
8 8 "appliedLocation": "ALL",
9 9 "showRuler": true,
10 10 "showGrid": true,
11 11 "elements": [
12 12 {
13   - "id": "el-1773989351080-vqc03nr",
14   - "type": "IMAGE",
15   - "x": 32,
16   - "y": 24,
17   - "width": 60,
18   - "height": 60,
19   - "rotation": "horizontal",
20   - "border": "none",
21   - "config": {
22   - "src": "",
23   - "scaleMode": "contain"
24   - }
25   - },
26   - {
27   - "id": "el-1773989452538-0ejrxoe",
  13 + "id": "el-1773999335969-ugvoy6v",
28 14 "type": "TEXT_STATIC",
29   - "x": 32,
30   - "y": 104,
  15 + "x": 392,
  16 + "y": 48,
31 17 "width": 120,
32 18 "height": 24,
33 19 "rotation": "horizontal",
... ... @@ -41,10 +27,10 @@
41 27 }
42 28 },
43 29 {
44   - "id": "el-1773989466493-ibbroio",
  30 + "id": "el-1773999339450-cc9y2lv",
45 31 "type": "QRCODE",
46   - "x": 32,
47   - "y": 136,
  32 + "x": 200,
  33 + "y": 24,
48 34 "width": 80,
49 35 "height": 80,
50 36 "rotation": "horizontal",
... ... @@ -55,10 +41,10 @@
55 41 }
56 42 },
57 43 {
58   - "id": "el-1773989469008-f1l39qj",
  44 + "id": "el-1773999341556-c795unj",
59 45 "type": "BARCODE",
60   - "x": 0,
61   - "y": 224,
  46 + "x": 8,
  47 + "y": 48,
62 48 "width": 160,
63 49 "height": 48,
64 50 "rotation": "horizontal",
... ... @@ -71,20 +57,20 @@
71 57 }
72 58 },
73 59 {
74   - "id": "el-1773989473436-j7fdeh2",
  60 + "id": "el-1773999346493-onxkjxn",
75 61 "type": "BLANK",
76   - "x": 32,
77   - "y": 288,
78   - "width": 48,
79   - "height": 32,
  62 + "x": 40,
  63 + "y": 120,
  64 + "width": 40,
  65 + "height": 24,
80 66 "rotation": "horizontal",
81 67 "border": "none",
82 68 "config": {}
83 69 },
84 70 {
85   - "id": "el-1773989483341-ifwcyjj",
  71 + "id": "el-1773999351836-igrbrib",
86 72 "type": "TEXT_PRICE",
87   - "x": 152,
  73 + "x": 520,
88 74 "y": 24,
89 75 "width": 80,
90 76 "height": 24,
... ... @@ -101,10 +87,24 @@
101 87 }
102 88 },
103 89 {
104   - "id": "el-1773989498031-e4d61j8",
  90 + "id": "el-1773999357328-0jof4kh",
  91 + "type": "IMAGE",
  92 + "x": 480,
  93 + "y": 96,
  94 + "width": 60,
  95 + "height": 60,
  96 + "rotation": "horizontal",
  97 + "border": "none",
  98 + "config": {
  99 + "src": "",
  100 + "scaleMode": "contain"
  101 + }
  102 + },
  103 + {
  104 + "id": "el-1773999360132-bwle4nb",
105 105 "type": "IMAGE",
106   - "x": 192,
107   - "y": 56,
  106 + "x": 152,
  107 + "y": 120,
108 108 "width": 60,
109 109 "height": 60,
110 110 "rotation": "horizontal",
... ... @@ -115,10 +115,10 @@
115 115 }
116 116 },
117 117 {
118   - "id": "el-1773989505076-1lxccx7",
  118 + "id": "el-1773999364492-yxs89ov",
119 119 "type": "TEXT_PRODUCT",
120   - "x": 200,
121   - "y": 136,
  120 + "x": 272,
  121 + "y": 120,
122 122 "width": 120,
123 123 "height": 24,
124 124 "rotation": "horizontal",
... ... @@ -132,10 +132,10 @@
132 132 }
133 133 },
134 134 {
135   - "id": "el-1773989509805-ax3392v",
  135 + "id": "el-1773999367121-3uesy24",
136 136 "type": "TEXT_STATIC",
137   - "x": 192,
138   - "y": 160,
  137 + "x": 48,
  138 + "y": 216,
139 139 "width": 120,
140 140 "height": 24,
141 141 "rotation": "horizontal",
... ... @@ -149,10 +149,10 @@
149 149 }
150 150 },
151 151 {
152   - "id": "el-1773989512993-xt8bg7q",
  152 + "id": "el-1773999369332-fib58lw",
153 153 "type": "QRCODE",
154   - "x": 184,
155   - "y": 184,
  154 + "x": 160,
  155 + "y": 208,
156 156 "width": 80,
157 157 "height": 80,
158 158 "rotation": "horizontal",
... ... @@ -163,10 +163,10 @@
163 163 }
164 164 },
165 165 {
166   - "id": "el-1773989525383-eji8p2s",
  166 + "id": "el-1773999371501-p1ot3id",
167 167 "type": "BARCODE",
168   - "x": 0,
169   - "y": 288,
  168 + "x": 280,
  169 + "y": 224,
170 170 "width": 160,
171 171 "height": 48,
172 172 "rotation": "horizontal",
... ... @@ -179,10 +179,10 @@
179 179 }
180 180 },
181 181 {
182   - "id": "el-1773989540159-dr2avdf",
  182 + "id": "el-1773999374282-ux37cow",
183 183 "type": "NUTRITION",
184   - "x": 184,
185   - "y": 280,
  184 + "x": 480,
  185 + "y": 200,
186 186 "width": 200,
187 187 "height": 120,
188 188 "rotation": "horizontal",
... ... @@ -196,10 +196,10 @@
196 196 }
197 197 },
198 198 {
199   - "id": "el-1773989549679-mcxrdnw",
  199 + "id": "el-1773999377353-n90ved8",
200 200 "type": "TEXT_PRICE",
201   - "x": 24,
202   - "y": 352,
  201 + "x": 664,
  202 + "y": 152,
203 203 "width": 80,
204 204 "height": 24,
205 205 "rotation": "horizontal",
... ... @@ -213,6 +213,113 @@
213 213 "fontWeight": "bold",
214 214 "textAlign": "right"
215 215 }
  216 + },
  217 + {
  218 + "id": "el-1773999389265-sukmurs",
  219 + "type": "DATE",
  220 + "x": 40,
  221 + "y": 320,
  222 + "width": 120,
  223 + "height": 24,
  224 + "rotation": "horizontal",
  225 + "border": "none",
  226 + "config": {
  227 + "format": "YYYY-MM-DD",
  228 + "offsetDays": 0
  229 + }
  230 + },
  231 + {
  232 + "id": "el-1773999392135-3ub707o",
  233 + "type": "TIME",
  234 + "x": 144,
  235 + "y": 320,
  236 + "width": 100,
  237 + "height": 24,
  238 + "rotation": "horizontal",
  239 + "border": "none",
  240 + "config": {
  241 + "format": "HH:mm",
  242 + "offsetDays": 0
  243 + }
  244 + },
  245 + {
  246 + "id": "el-1773999394947-3y8li6m",
  247 + "type": "DURATION",
  248 + "x": 272,
  249 + "y": 320,
  250 + "width": 120,
  251 + "height": 24,
  252 + "rotation": "horizontal",
  253 + "border": "none",
  254 + "config": {
  255 + "format": "YYYY-MM-DD",
  256 + "offsetDays": 3
  257 + }
  258 + },
  259 + {
  260 + "id": "el-1773999399456-nbglmfa",
  261 + "type": "IMAGE",
  262 + "x": 464,
  263 + "y": 304,
  264 + "width": 60,
  265 + "height": 60,
  266 + "rotation": "horizontal",
  267 + "border": "none",
  268 + "config": {
  269 + "src": "",
  270 + "scaleMode": "contain"
  271 + }
  272 + },
  273 + {
  274 + "id": "el-1773999401976-wzmsx8f",
  275 + "type": "TEXT_STATIC",
  276 + "x": 600,
  277 + "y": 320,
  278 + "width": 120,
  279 + "height": 24,
  280 + "rotation": "horizontal",
  281 + "border": "none",
  282 + "config": {
  283 + "text": "文本",
  284 + "fontFamily": "Arial",
  285 + "fontSize": 14,
  286 + "fontWeight": "normal",
  287 + "textAlign": "left"
  288 + }
  289 + },
  290 + {
  291 + "id": "el-1773999407826-91if0px",
  292 + "type": "TEXT_STATIC",
  293 + "x": 40,
  294 + "y": 368,
  295 + "width": 120,
  296 + "height": 24,
  297 + "rotation": "horizontal",
  298 + "border": "none",
  299 + "config": {
  300 + "text": "文本",
  301 + "fontFamily": "Arial",
  302 + "fontSize": 14,
  303 + "fontWeight": "normal",
  304 + "textAlign": "left"
  305 + }
  306 + },
  307 + {
  308 + "id": "el-1773999417908-lje3b3w",
  309 + "type": "TEXT_STATIC",
  310 + "x": 200,
  311 + "y": 368,
  312 + "width": 120,
  313 + "height": 24,
  314 + "rotation": "horizontal",
  315 + "border": "none",
  316 + "config": {
  317 + "text": "文本",
  318 + "fontFamily": "Arial",
  319 + "fontSize": 14,
  320 + "fontWeight": "normal",
  321 + "textAlign": "left"
  322 + }
216 323 }
217 324 ]
218 325 }
219 326 \ No newline at end of file
... ...
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelPreviewResolveInputVo.cs 0 → 100644
  1 +using System.Collections.Generic;
  2 +
  3 +namespace FoodLabeling.Application.Contracts.Dtos.Label;
  4 +
  5 +public class LabelPreviewResolveInputVo
  6 +{
  7 + /// <summary>
  8 + /// 标签编码(fl_label.LabelCode)
  9 + /// </summary>
  10 + public string LabelCode { get; set; } = string.Empty;
  11 +
  12 + /// <summary>
  13 + /// 选择用于预览的产品Id(fl_product.Id)
  14 + /// 如果不传,默认取该标签绑定的第一个产品
  15 + /// </summary>
  16 + public string? ProductId { get; set; }
  17 +
  18 + /// <summary>
  19 + /// 打印输入(前端传,用于 PRINT_INPUT 元素)
  20 + /// key 建议使用模板元素的 InputKey
  21 + /// </summary>
  22 + public Dictionary<string, object>? PrintInputJson { get; set; }
  23 +}
  24 +
... ...
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelTemplatePreviewDto.cs 0 → 100644
  1 +using System.Collections.Generic;
  2 +using System.Text.Json.Serialization;
  3 +using FoodLabeling.Application.Contracts.Dtos.LabelTemplate;
  4 +
  5 +namespace FoodLabeling.Application.Contracts.Dtos.Label;
  6 +
  7 +/// <summary>
  8 +/// 预览输出:与前端 LabelCanvas/LabelPreviewOnly 的 LabelTemplate 结构尽量一致
  9 +/// </summary>
  10 +public class LabelTemplatePreviewDto
  11 +{
  12 + [JsonPropertyName("id")]
  13 + public string Id { get; set; } = string.Empty;
  14 +
  15 + [JsonPropertyName("name")]
  16 + public string Name { get; set; } = string.Empty;
  17 +
  18 + [JsonPropertyName("labelType")]
  19 + public string LabelType { get; set; } = string.Empty;
  20 +
  21 + [JsonPropertyName("unit")]
  22 + public string Unit { get; set; } = string.Empty;
  23 +
  24 + [JsonPropertyName("width")]
  25 + public decimal Width { get; set; }
  26 +
  27 + [JsonPropertyName("height")]
  28 + public decimal Height { get; set; }
  29 +
  30 + [JsonPropertyName("appliedLocation")]
  31 + public string AppliedLocation { get; set; } = "ALL";
  32 +
  33 + [JsonPropertyName("showRuler")]
  34 + public bool ShowRuler { get; set; }
  35 +
  36 + [JsonPropertyName("showGrid")]
  37 + public bool ShowGrid { get; set; }
  38 +
  39 + [JsonPropertyName("elements")]
  40 + public List<LabelTemplateElementDto> Elements { get; set; } = new();
  41 +}
  42 +
... ...
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductCreateInputVo.cs 0 → 100644
  1 +namespace FoodLabeling.Application.Contracts.Dtos.Product;
  2 +
  3 +public class ProductCreateInputVo
  4 +{
  5 + public string ProductCode { get; set; } = string.Empty;
  6 +
  7 + public string ProductName { get; set; } = string.Empty;
  8 +
  9 + public string? CategoryName { get; set; }
  10 +
  11 + public string? ProductImageUrl { get; set; }
  12 +
  13 + public bool State { get; set; } = true;
  14 +}
  15 +
... ...
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductGetListInputVo.cs 0 → 100644
  1 +using FoodLabeling.Application.Contracts.Dtos.Common;
  2 +using Volo.Abp.Application.Dtos;
  3 +
  4 +namespace FoodLabeling.Application.Contracts.Dtos.Product;
  5 +
  6 +/// <summary>
  7 +/// 产品分页查询入参
  8 +/// </summary>
  9 +public class ProductGetListInputVo : PagedAndSortedResultRequestDto
  10 +{
  11 + /// <summary>
  12 + /// 模糊搜索(ProductCode/ProductName/CategoryName)
  13 + /// </summary>
  14 + public string? Keyword { get; set; }
  15 +
  16 + /// <summary>
  17 + /// 启用状态
  18 + /// </summary>
  19 + public bool? State { get; set; }
  20 +}
  21 +
... ...
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductGetListOutputDto.cs 0 → 100644
  1 +namespace FoodLabeling.Application.Contracts.Dtos.Product;
  2 +
  3 +public class ProductGetListOutputDto
  4 +{
  5 + public string Id { get; set; } = string.Empty;
  6 +
  7 + public string ProductCode { get; set; } = string.Empty;
  8 +
  9 + public string ProductName { get; set; } = string.Empty;
  10 +
  11 + public string? CategoryName { get; set; }
  12 +
  13 + public string? ProductImageUrl { get; set; }
  14 +
  15 + public bool State { get; set; }
  16 +
  17 + /// <summary>
  18 + /// 该产品关联的标签数量(fl_label_product + fl_label)
  19 + /// </summary>
  20 + public long NoOfLabels { get; set; }
  21 +}
  22 +
... ...
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductGetOutputDto.cs 0 → 100644
  1 +using System.Collections.Generic;
  2 +
  3 +namespace FoodLabeling.Application.Contracts.Dtos.Product;
  4 +
  5 +public class ProductGetOutputDto
  6 +{
  7 + public string Id { get; set; } = string.Empty;
  8 +
  9 + public string ProductCode { get; set; } = string.Empty;
  10 +
  11 + public string ProductName { get; set; } = string.Empty;
  12 +
  13 + public string? CategoryName { get; set; }
  14 +
  15 + public string? ProductImageUrl { get; set; }
  16 +
  17 + public bool State { get; set; }
  18 +
  19 + /// <summary>
  20 + /// 该产品关联的门店Id列表(来自 fl_location_product)
  21 + /// </summary>
  22 + public List<string> LocationIds { get; set; } = new();
  23 +}
  24 +
... ...
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductUpdateInputVo.cs 0 → 100644
  1 +namespace FoodLabeling.Application.Contracts.Dtos.Product;
  2 +
  3 +public class ProductUpdateInputVo : ProductCreateInputVo
  4 +{
  5 +}
  6 +
... ...
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationCreateInputVo.cs 0 → 100644
  1 +using System.Collections.Generic;
  2 +
  3 +namespace FoodLabeling.Application.Contracts.Dtos.ProductLocation;
  4 +
  5 +public class ProductLocationCreateInputVo
  6 +{
  7 + /// <summary>
  8 + /// 门店Id
  9 + /// </summary>
  10 + public string LocationId { get; set; } = string.Empty;
  11 +
  12 + /// <summary>
  13 + /// 产品Id列表
  14 + /// </summary>
  15 + public List<string> ProductIds { get; set; } = new();
  16 +}
  17 +
... ...
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationGetListInputVo.cs 0 → 100644
  1 +using FoodLabeling.Application.Contracts.Dtos.Common;
  2 +using Volo.Abp.Application.Dtos;
  3 +
  4 +namespace FoodLabeling.Application.Contracts.Dtos.ProductLocation;
  5 +
  6 +/// <summary>
  7 +/// 产品-门店分页查询入参
  8 +/// </summary>
  9 +public class ProductLocationGetListInputVo : PagedAndSortedResultRequestDto
  10 +{
  11 + /// <summary>
  12 + /// 门店Id(location.Id,string 表示)
  13 + /// </summary>
  14 + public string? LocationId { get; set; }
  15 +
  16 + /// <summary>
  17 + /// 产品Id(fl_product.Id,string)
  18 + /// </summary>
  19 + public string? ProductId { get; set; }
  20 +}
  21 +
... ...
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationGetListOutputDto.cs 0 → 100644
  1 +using System.Collections.Generic;
  2 +
  3 +namespace FoodLabeling.Application.Contracts.Dtos.ProductLocation;
  4 +
  5 +public class ProductLocationGetListOutputDto
  6 +{
  7 + public string Id { get; set; } = string.Empty;
  8 +
  9 + public string LocationId { get; set; } = string.Empty;
  10 + public string? LocationCode { get; set; }
  11 + public string? LocationName { get; set; }
  12 +
  13 + public string ProductId { get; set; } = string.Empty;
  14 + public string? ProductCode { get; set; }
  15 + public string? ProductName { get; set; }
  16 + public string? ProductImageUrl { get; set; }
  17 +}
  18 +
... ...
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationGetOutputDto.cs 0 → 100644
  1 +using System.Collections.Generic;
  2 +
  3 +namespace FoodLabeling.Application.Contracts.Dtos.ProductLocation;
  4 +
  5 +public class ProductLocationGetOutputDto
  6 +{
  7 + public string LocationId { get; set; } = string.Empty;
  8 +
  9 + public List<ProductLocationGetListOutputDto> Products { get; set; } = new();
  10 +}
  11 +
... ...
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationUpdateInputVo.cs 0 → 100644
  1 +using System.Collections.Generic;
  2 +
  3 +namespace FoodLabeling.Application.Contracts.Dtos.ProductLocation;
  4 +
  5 +public class ProductLocationUpdateInputVo
  6 +{
  7 + /// <summary>
  8 + /// 产品Id列表(将替换当前门店下的全部产品关联)
  9 + /// </summary>
  10 + public List<string> ProductIds { get; set; } = new();
  11 +}
  12 +
... ...
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IProductAppService.cs 0 → 100644
  1 +using FoodLabeling.Application.Contracts.Dtos.Common;
  2 +using FoodLabeling.Application.Contracts.Dtos.Product;
  3 +using Volo.Abp.Application.Dtos;
  4 +using Volo.Abp.Application.Services;
  5 +
  6 +namespace FoodLabeling.Application.Contracts.IServices;
  7 +
  8 +/// <summary>
  9 +/// 产品管理接口(Products,fl_product)
  10 +/// </summary>
  11 +public interface IProductAppService : IApplicationService
  12 +{
  13 + /// <summary>
  14 + /// 产品分页列表
  15 + /// </summary>
  16 + Task<PagedResultWithPageDto<ProductGetListOutputDto>> GetListAsync(ProductGetListInputVo input);
  17 +
  18 + /// <summary>
  19 + /// 产品详情
  20 + /// </summary>
  21 + Task<ProductGetOutputDto> GetAsync(string id);
  22 +
  23 + /// <summary>
  24 + /// 新增产品
  25 + /// </summary>
  26 + Task<ProductGetOutputDto> CreateAsync(ProductCreateInputVo input);
  27 +
  28 + /// <summary>
  29 + /// 编辑产品
  30 + /// </summary>
  31 + Task<ProductGetOutputDto> UpdateAsync(string id, ProductUpdateInputVo input);
  32 +
  33 + /// <summary>
  34 + /// 删除产品(逻辑删除)
  35 + /// </summary>
  36 + Task DeleteAsync(string id);
  37 +}
  38 +
... ...
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IProductLocationAppService.cs 0 → 100644
  1 +using FoodLabeling.Application.Contracts.Dtos.Common;
  2 +using FoodLabeling.Application.Contracts.Dtos.ProductLocation;
  3 +using Volo.Abp.Application.Dtos;
  4 +using Volo.Abp.Application.Services;
  5 +
  6 +namespace FoodLabeling.Application.Contracts.IServices;
  7 +
  8 +/// <summary>
  9 +/// 产品-门店关联管理(fl_location_product)
  10 +/// </summary>
  11 +public interface IProductLocationAppService : IApplicationService
  12 +{
  13 + /// <summary>
  14 + /// 关联分页列表(可按门店Id/产品Id过滤)
  15 + /// </summary>
  16 + Task<PagedResultWithPageDto<ProductLocationGetListOutputDto>> GetListAsync(ProductLocationGetListInputVo input);
  17 +
  18 + /// <summary>
  19 + /// 门店下的全部产品(id=LocationId)
  20 + /// </summary>
  21 + Task<ProductLocationGetOutputDto> GetAsync(string id);
  22 +
  23 + /// <summary>
  24 + /// 新增/批量建立门店与产品的关联
  25 + /// </summary>
  26 + Task<ProductLocationGetOutputDto> CreateAsync(ProductLocationCreateInputVo input);
  27 +
  28 + /// <summary>
  29 + /// 编辑:替换该门店下的全部产品关联
  30 + /// </summary>
  31 + Task<ProductLocationGetOutputDto> UpdateAsync(string id, ProductLocationUpdateInputVo input);
  32 +
  33 + /// <summary>
  34 + /// 删除:删除该门店的全部产品关联
  35 + /// </summary>
  36 + Task DeleteAsync(string id);
  37 +}
  38 +
... ...
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLocationProductDbEntity.cs 0 → 100644
  1 +using SqlSugar;
  2 +
  3 +namespace FoodLabeling.Application.Services.DbModels;
  4 +
  5 +[SugarTable("fl_location_product")]
  6 +public class FlLocationProductDbEntity
  7 +{
  8 + [SugarColumn(IsPrimaryKey = true)]
  9 + public string Id { get; set; } = string.Empty;
  10 +
  11 + public string LocationId { get; set; } = string.Empty;
  12 +
  13 + public string ProductId { get; set; } = string.Empty;
  14 +}
  15 +
... ...
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductAppService.cs 0 → 100644
  1 +using FoodLabeling.Application.Contracts.Dtos.Common;
  2 +using FoodLabeling.Application.Contracts.Dtos.Product;
  3 +using FoodLabeling.Application.Contracts.IServices;
  4 +using FoodLabeling.Application.Services.DbModels;
  5 +using SqlSugar;
  6 +using Volo.Abp;
  7 +using Volo.Abp.Application.Services;
  8 +using Volo.Abp.Guids;
  9 +using Volo.Abp.Uow;
  10 +using Yi.Framework.SqlSugarCore.Abstractions;
  11 +
  12 +namespace FoodLabeling.Application.Services;
  13 +
  14 +/// <summary>
  15 +/// 产品管理(Products)
  16 +/// </summary>
  17 +public class ProductAppService : ApplicationService, IProductAppService
  18 +{
  19 + private readonly ISqlSugarDbContext _dbContext;
  20 + private readonly IGuidGenerator _guidGenerator;
  21 +
  22 + public ProductAppService(ISqlSugarDbContext dbContext, IGuidGenerator guidGenerator)
  23 + {
  24 + _dbContext = dbContext;
  25 + _guidGenerator = guidGenerator;
  26 + }
  27 +
  28 + public async Task<PagedResultWithPageDto<ProductGetListOutputDto>> GetListAsync(ProductGetListInputVo input)
  29 + {
  30 + RefAsync<int> total = 0;
  31 + var keyword = input.Keyword?.Trim();
  32 +
  33 + var query = _dbContext.SqlSugarClient.Queryable<FlProductDbEntity>()
  34 + .Where(x => !x.IsDeleted)
  35 + .WhereIF(!string.IsNullOrWhiteSpace(keyword), x =>
  36 + x.ProductCode.Contains(keyword!) ||
  37 + x.ProductName.Contains(keyword!) ||
  38 + (x.CategoryName != null && x.CategoryName.Contains(keyword!)))
  39 + .WhereIF(input.State != null, x => x.State == input.State);
  40 +
  41 + if (!string.IsNullOrWhiteSpace(input.Sorting))
  42 + {
  43 + query = query.OrderBy(input.Sorting);
  44 + }
  45 + else
  46 + {
  47 + query = query.OrderByDescending(x => x.ProductName);
  48 + }
  49 +
  50 + var entities = await query.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
  51 + var ids = entities.Select(x => x.Id).ToList();
  52 +
  53 + // Count labels for each product
  54 + var countMap = new Dictionary<string, long>();
  55 + if (ids.Count > 0)
  56 + {
  57 + var countRows = await _dbContext.SqlSugarClient.Queryable<FlLabelProductDbEntity, FlLabelDbEntity>(
  58 + (lp, l) => lp.LabelId == l.Id)
  59 + .Where((lp, l) => !l.IsDeleted && ids.Contains(lp.ProductId))
  60 + .GroupBy((lp, l) => lp.ProductId)
  61 + .Select((lp, l) => new { ProductId = lp.ProductId, Count = SqlFunc.AggregateCount(lp.Id) })
  62 + .ToListAsync();
  63 +
  64 + countMap = countRows.ToDictionary(x => x.ProductId, x => (long)x.Count);
  65 + }
  66 +
  67 + var items = entities.Select(x => new ProductGetListOutputDto
  68 + {
  69 + Id = x.Id,
  70 + ProductCode = x.ProductCode,
  71 + ProductName = x.ProductName,
  72 + CategoryName = x.CategoryName,
  73 + ProductImageUrl = x.ProductImageUrl,
  74 + State = x.State,
  75 + NoOfLabels = countMap.TryGetValue(x.Id, out var count) ? count : 0
  76 + }).ToList();
  77 +
  78 + return BuildPagedResult(input.SkipCount, input.MaxResultCount, total, items);
  79 + }
  80 +
  81 + public async Task<ProductGetOutputDto> GetAsync(string id)
  82 + {
  83 + var productId = id?.Trim();
  84 + if (string.IsNullOrWhiteSpace(productId))
  85 + {
  86 + throw new UserFriendlyException("产品Id不能为空");
  87 + }
  88 +
  89 + var entity = await _dbContext.SqlSugarClient.Queryable<FlProductDbEntity>()
  90 + .FirstAsync(x => !x.IsDeleted && x.Id == productId);
  91 +
  92 + if (entity is null)
  93 + {
  94 + throw new UserFriendlyException("产品不存在");
  95 + }
  96 +
  97 + var locationIds = await _dbContext.SqlSugarClient.Queryable<FlLocationProductDbEntity>()
  98 + .Where(x => x.ProductId == productId)
  99 + .Select(x => x.LocationId)
  100 + .Distinct()
  101 + .ToListAsync();
  102 +
  103 + return new ProductGetOutputDto
  104 + {
  105 + Id = entity.Id,
  106 + ProductCode = entity.ProductCode,
  107 + ProductName = entity.ProductName,
  108 + CategoryName = entity.CategoryName,
  109 + ProductImageUrl = entity.ProductImageUrl,
  110 + State = entity.State,
  111 + LocationIds = locationIds
  112 + };
  113 + }
  114 +
  115 + [UnitOfWork]
  116 + public async Task<ProductGetOutputDto> CreateAsync(ProductCreateInputVo input)
  117 + {
  118 + var code = input.ProductCode?.Trim();
  119 + var name = input.ProductName?.Trim();
  120 + if (string.IsNullOrWhiteSpace(code) || string.IsNullOrWhiteSpace(name))
  121 + {
  122 + throw new UserFriendlyException("产品编码和名称不能为空");
  123 + }
  124 +
  125 + var duplicated = await _dbContext.SqlSugarClient.Queryable<FlProductDbEntity>()
  126 + .AnyAsync(x => !x.IsDeleted && (x.ProductCode == code));
  127 + if (duplicated)
  128 + {
  129 + throw new UserFriendlyException("产品编码已存在");
  130 + }
  131 +
  132 + var entity = new FlProductDbEntity
  133 + {
  134 + Id = _guidGenerator.Create().ToString(),
  135 + IsDeleted = false,
  136 + ProductCode = code,
  137 + ProductName = name,
  138 + CategoryName = input.CategoryName?.Trim(),
  139 + ProductImageUrl = input.ProductImageUrl?.Trim(),
  140 + State = input.State
  141 + };
  142 +
  143 + await _dbContext.SqlSugarClient.Insertable(entity).ExecuteCommandAsync();
  144 + return await GetAsync(entity.Id);
  145 + }
  146 +
  147 + [UnitOfWork]
  148 + public async Task<ProductGetOutputDto> UpdateAsync(string id, ProductUpdateInputVo input)
  149 + {
  150 + var productId = id?.Trim();
  151 + if (string.IsNullOrWhiteSpace(productId))
  152 + {
  153 + throw new UserFriendlyException("产品Id不能为空");
  154 + }
  155 +
  156 + var entity = await _dbContext.SqlSugarClient.Queryable<FlProductDbEntity>()
  157 + .FirstAsync(x => !x.IsDeleted && x.Id == productId);
  158 + if (entity is null)
  159 + {
  160 + throw new UserFriendlyException("产品不存在");
  161 + }
  162 +
  163 + var code = input.ProductCode?.Trim();
  164 + var name = input.ProductName?.Trim();
  165 + if (string.IsNullOrWhiteSpace(code) || string.IsNullOrWhiteSpace(name))
  166 + {
  167 + throw new UserFriendlyException("产品编码和名称不能为空");
  168 + }
  169 +
  170 + var duplicated = await _dbContext.SqlSugarClient.Queryable<FlProductDbEntity>()
  171 + .AnyAsync(x => !x.IsDeleted && x.Id != productId && x.ProductCode == code);
  172 + if (duplicated)
  173 + {
  174 + throw new UserFriendlyException("产品编码已存在");
  175 + }
  176 +
  177 + entity.ProductCode = code;
  178 + entity.ProductName = name;
  179 + entity.CategoryName = input.CategoryName?.Trim();
  180 + entity.ProductImageUrl = input.ProductImageUrl?.Trim();
  181 + entity.State = input.State;
  182 +
  183 + await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync();
  184 + return await GetAsync(productId);
  185 + }
  186 +
  187 + [UnitOfWork]
  188 + public async Task DeleteAsync(string id)
  189 + {
  190 + var productId = id?.Trim();
  191 + if (string.IsNullOrWhiteSpace(productId))
  192 + {
  193 + throw new UserFriendlyException("产品Id不能为空");
  194 + }
  195 +
  196 + var entity = await _dbContext.SqlSugarClient.Queryable<FlProductDbEntity>()
  197 + .FirstAsync(x => !x.IsDeleted && x.Id == productId);
  198 + if (entity is null)
  199 + {
  200 + throw new UserFriendlyException("产品不存在");
  201 + }
  202 +
  203 + entity.IsDeleted = true;
  204 + await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync();
  205 + }
  206 +
  207 + private static PagedResultWithPageDto<T> BuildPagedResult<T>(int skipCount, int maxResultCount, int total, List<T> items)
  208 + {
  209 + var pageSize = maxResultCount <= 0 ? items.Count : maxResultCount;
  210 + var pageIndex = pageSize <= 0 ? 1 : (skipCount / pageSize) + 1;
  211 + var totalPages = pageSize <= 0 ? 0 : (int)Math.Ceiling(total / (double)pageSize);
  212 + return new PagedResultWithPageDto<T>
  213 + {
  214 + PageIndex = pageIndex,
  215 + PageSize = pageSize,
  216 + TotalCount = total,
  217 + TotalPages = totalPages,
  218 + Items = items
  219 + };
  220 + }
  221 +}
  222 +
... ...
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductLocationAppService.cs 0 → 100644
  1 +using FoodLabeling.Application.Contracts.Dtos.Common;
  2 +using FoodLabeling.Application.Contracts.Dtos.ProductLocation;
  3 +using FoodLabeling.Application.Contracts.IServices;
  4 +using FoodLabeling.Application.Services.DbModels;
  5 +using FoodLabeling.Domain.Entities;
  6 +using SqlSugar;
  7 +using Volo.Abp;
  8 +using Volo.Abp.Application.Services;
  9 +using Volo.Abp.Guids;
  10 +using Volo.Abp.Uow;
  11 +using Yi.Framework.SqlSugarCore.Abstractions;
  12 +
  13 +namespace FoodLabeling.Application.Services;
  14 +
  15 +/// <summary>
  16 +/// 产品-门店关联管理(fl_location_product)
  17 +/// </summary>
  18 +public class ProductLocationAppService : ApplicationService, IProductLocationAppService
  19 +{
  20 + private readonly ISqlSugarDbContext _dbContext;
  21 + private readonly IGuidGenerator _guidGenerator;
  22 +
  23 + public ProductLocationAppService(ISqlSugarDbContext dbContext, IGuidGenerator guidGenerator)
  24 + {
  25 + _dbContext = dbContext;
  26 + _guidGenerator = guidGenerator;
  27 + }
  28 +
  29 + public async Task<PagedResultWithPageDto<ProductLocationGetListOutputDto>> GetListAsync(ProductLocationGetListInputVo input)
  30 + {
  31 + RefAsync<int> total = 0;
  32 +
  33 + var locationId = input.LocationId?.Trim();
  34 + var productId = input.ProductId?.Trim();
  35 +
  36 + var query = _dbContext.SqlSugarClient
  37 + .Queryable<FlLocationProductDbEntity, FlProductDbEntity>((lp, p) => lp.ProductId == p.Id)
  38 + .Where((lp, p) => p.IsDeleted == false);
  39 +
  40 + if (!string.IsNullOrWhiteSpace(locationId))
  41 + {
  42 + query = query.Where((lp, p) => lp.LocationId == locationId);
  43 + }
  44 +
  45 + if (!string.IsNullOrWhiteSpace(productId))
  46 + {
  47 + query = query.Where((lp, p) => lp.ProductId == productId);
  48 + }
  49 +
  50 + // 默认排序
  51 + query = string.IsNullOrWhiteSpace(input.Sorting)
  52 + ? query.OrderBy((lp, p) => p.ProductName)
  53 + : query.OrderBy(input.Sorting);
  54 +
  55 + var entities = await query
  56 + .Select((lp, p) => new ProductLocationGetListOutputDto
  57 + {
  58 + Id = lp.Id,
  59 + LocationId = lp.LocationId,
  60 + ProductId = p.Id,
  61 + ProductCode = p.ProductCode,
  62 + ProductName = p.ProductName,
  63 + ProductImageUrl = p.ProductImageUrl,
  64 + LocationCode = null,
  65 + LocationName = null
  66 + })
  67 + .ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
  68 +
  69 + // 拉取门店信息用于输出
  70 + var locationIdSet = entities
  71 + .Select(x => x.LocationId)
  72 + .Where(x => !string.IsNullOrWhiteSpace(x))
  73 + .Distinct()
  74 + .ToList();
  75 +
  76 + var locationGuidList = locationIdSet
  77 + .Where(x => Guid.TryParse(x, out _))
  78 + .Select(Guid.Parse)
  79 + .Distinct()
  80 + .ToList();
  81 +
  82 + var locationMap = new Dictionary<Guid, LocationAggregateRoot>();
  83 + if (locationGuidList.Count > 0)
  84 + {
  85 + var locations = await _dbContext.SqlSugarClient.Queryable<LocationAggregateRoot>()
  86 + .Where(x => !x.IsDeleted)
  87 + .Where(x => locationGuidList.Contains(x.Id))
  88 + .ToListAsync();
  89 +
  90 + locationMap = locations.ToDictionary(x => x.Id, x => x);
  91 + }
  92 +
  93 + var items = entities.Select(x =>
  94 + {
  95 + string? locationCode = null;
  96 + string? locationName = null;
  97 + if (Guid.TryParse(x.LocationId, out var gid) && locationMap.TryGetValue(gid, out var loc))
  98 + {
  99 + locationCode = loc.LocationCode;
  100 + locationName = loc.LocationName ?? loc.LocationCode;
  101 + }
  102 +
  103 + return new ProductLocationGetListOutputDto
  104 + {
  105 + Id = x.Id,
  106 + LocationId = x.LocationId,
  107 + LocationCode = locationCode,
  108 + LocationName = locationName,
  109 + ProductId = x.ProductId,
  110 + ProductCode = x.ProductCode,
  111 + ProductName = x.ProductName,
  112 + ProductImageUrl = x.ProductImageUrl
  113 + };
  114 + }).ToList();
  115 +
  116 + return BuildPagedResult(input.SkipCount, input.MaxResultCount, total, items);
  117 + }
  118 +
  119 + public async Task<ProductLocationGetOutputDto> GetAsync(string id)
  120 + {
  121 + var locationId = id?.Trim();
  122 + if (string.IsNullOrWhiteSpace(locationId))
  123 + {
  124 + throw new UserFriendlyException("门店Id不能为空");
  125 + }
  126 +
  127 + var rows = await _dbContext.SqlSugarClient
  128 + .Queryable<FlLocationProductDbEntity, FlProductDbEntity>((lp, p) => lp.ProductId == p.Id)
  129 + .Where((lp, p) => lp.LocationId == locationId && !p.IsDeleted)
  130 + .Select((lp, p) => new ProductLocationGetListOutputDto
  131 + {
  132 + Id = lp.Id,
  133 + LocationId = lp.LocationId,
  134 + ProductId = p.Id,
  135 + ProductCode = p.ProductCode,
  136 + ProductName = p.ProductName,
  137 + ProductImageUrl = p.ProductImageUrl
  138 + })
  139 + .ToListAsync();
  140 +
  141 + if (rows.Count == 0)
  142 + {
  143 + return new ProductLocationGetOutputDto
  144 + {
  145 + LocationId = locationId,
  146 + Products = new List<ProductLocationGetListOutputDto>()
  147 + };
  148 + }
  149 +
  150 + // 门店信息
  151 + string? locationCode = null;
  152 + string? locationName = null;
  153 + if (Guid.TryParse(locationId, out var gid))
  154 + {
  155 + var loc = await _dbContext.SqlSugarClient.Queryable<LocationAggregateRoot>()
  156 + .FirstAsync(x => !x.IsDeleted && x.Id == gid);
  157 + if (loc != null)
  158 + {
  159 + locationCode = loc.LocationCode;
  160 + locationName = loc.LocationName ?? loc.LocationCode;
  161 + }
  162 + }
  163 +
  164 + var products = rows.Select(x => new ProductLocationGetListOutputDto
  165 + {
  166 + Id = x.Id,
  167 + LocationId = x.LocationId,
  168 + LocationCode = locationCode,
  169 + LocationName = locationName,
  170 + ProductId = x.ProductId,
  171 + ProductCode = x.ProductCode,
  172 + ProductName = x.ProductName,
  173 + ProductImageUrl = x.ProductImageUrl
  174 + }).ToList();
  175 +
  176 + return new ProductLocationGetOutputDto
  177 + {
  178 + LocationId = locationId,
  179 + Products = products
  180 + };
  181 + }
  182 +
  183 + [UnitOfWork]
  184 + public async Task<ProductLocationGetOutputDto> CreateAsync(ProductLocationCreateInputVo input)
  185 + {
  186 + var locationId = input.LocationId?.Trim();
  187 + if (string.IsNullOrWhiteSpace(locationId))
  188 + {
  189 + throw new UserFriendlyException("门店Id不能为空");
  190 + }
  191 +
  192 + var productIds = input.ProductIds?
  193 + .Where(x => !string.IsNullOrWhiteSpace(x))
  194 + .Select(x => x.Trim())
  195 + .Distinct()
  196 + .ToList() ?? new List<string>();
  197 +
  198 + if (productIds.Count == 0)
  199 + {
  200 + throw new UserFriendlyException("ProductIds至少1个");
  201 + }
  202 +
  203 + // 校验门店存在
  204 + if (!Guid.TryParse(locationId, out var gid))
  205 + {
  206 + throw new UserFriendlyException("门店Id格式不正确");
  207 + }
  208 +
  209 + var locExists = await _dbContext.SqlSugarClient.Queryable<LocationAggregateRoot>()
  210 + .AnyAsync(x => !x.IsDeleted && x.Id == gid);
  211 + if (!locExists)
  212 + {
  213 + throw new UserFriendlyException("门店不存在");
  214 + }
  215 +
  216 + // 校验产品存在
  217 + var productExistsCount = await _dbContext.SqlSugarClient.Queryable<FlProductDbEntity>()
  218 + .Where(x => !x.IsDeleted && productIds.Contains(x.Id))
  219 + .CountAsync();
  220 + if (productExistsCount != productIds.Count)
  221 + {
  222 + throw new UserFriendlyException("产品不存在");
  223 + }
  224 +
  225 + // 去重:避免唯一键重复
  226 + var existingProductIds = await _dbContext.SqlSugarClient.Queryable<FlLocationProductDbEntity>()
  227 + .Where(x => x.LocationId == locationId && productIds.Contains(x.ProductId))
  228 + .Select(x => x.ProductId)
  229 + .Distinct()
  230 + .ToListAsync();
  231 +
  232 + var newProductIds = productIds.Where(x => !existingProductIds.Contains(x)).ToList();
  233 + if (newProductIds.Count == 0)
  234 + {
  235 + return await GetAsync(locationId);
  236 + }
  237 +
  238 + var rows = newProductIds.Select(pid => new FlLocationProductDbEntity
  239 + {
  240 + Id = _guidGenerator.Create().ToString(),
  241 + LocationId = locationId,
  242 + ProductId = pid
  243 + }).ToList();
  244 +
  245 + await _dbContext.SqlSugarClient.Insertable(rows).ExecuteCommandAsync();
  246 + return await GetAsync(locationId);
  247 + }
  248 +
  249 + [UnitOfWork]
  250 + public async Task<ProductLocationGetOutputDto> UpdateAsync(string id, ProductLocationUpdateInputVo input)
  251 + {
  252 + var locationId = id?.Trim();
  253 + if (string.IsNullOrWhiteSpace(locationId))
  254 + {
  255 + throw new UserFriendlyException("门店Id不能为空");
  256 + }
  257 +
  258 + var productIds = input.ProductIds?
  259 + .Where(x => !string.IsNullOrWhiteSpace(x))
  260 + .Select(x => x.Trim())
  261 + .Distinct()
  262 + .ToList() ?? new List<string>();
  263 +
  264 + if (productIds.Count == 0)
  265 + {
  266 + throw new UserFriendlyException("ProductIds至少1个");
  267 + }
  268 +
  269 + if (!Guid.TryParse(locationId, out var gid))
  270 + {
  271 + throw new UserFriendlyException("门店Id格式不正确");
  272 + }
  273 +
  274 + var locExists = await _dbContext.SqlSugarClient.Queryable<LocationAggregateRoot>()
  275 + .AnyAsync(x => !x.IsDeleted && x.Id == gid);
  276 + if (!locExists)
  277 + {
  278 + throw new UserFriendlyException("门店不存在");
  279 + }
  280 +
  281 + var productExistsCount = await _dbContext.SqlSugarClient.Queryable<FlProductDbEntity>()
  282 + .Where(x => !x.IsDeleted && productIds.Contains(x.Id))
  283 + .CountAsync();
  284 + if (productExistsCount != productIds.Count)
  285 + {
  286 + throw new UserFriendlyException("产品不存在");
  287 + }
  288 +
  289 + // 替换:先删后建
  290 + await _dbContext.SqlSugarClient.Deleteable<FlLocationProductDbEntity>()
  291 + .Where(x => x.LocationId == locationId)
  292 + .ExecuteCommandAsync();
  293 +
  294 + var rows = productIds.Select(pid => new FlLocationProductDbEntity
  295 + {
  296 + Id = _guidGenerator.Create().ToString(),
  297 + LocationId = locationId,
  298 + ProductId = pid
  299 + }).ToList();
  300 +
  301 + await _dbContext.SqlSugarClient.Insertable(rows).ExecuteCommandAsync();
  302 + return await GetAsync(locationId);
  303 + }
  304 +
  305 + [UnitOfWork]
  306 + public async Task DeleteAsync(string id)
  307 + {
  308 + var locationId = id?.Trim();
  309 + if (string.IsNullOrWhiteSpace(locationId))
  310 + {
  311 + throw new UserFriendlyException("门店Id不能为空");
  312 + }
  313 +
  314 + await _dbContext.SqlSugarClient.Deleteable<FlLocationProductDbEntity>()
  315 + .Where(x => x.LocationId == locationId)
  316 + .ExecuteCommandAsync();
  317 + }
  318 +
  319 + private static PagedResultWithPageDto<T> BuildPagedResult<T>(int skipCount, int maxResultCount, int total, List<T> items)
  320 + {
  321 + var pageSize = maxResultCount <= 0 ? items.Count : maxResultCount;
  322 + var pageIndex = pageSize <= 0 ? 1 : (skipCount / pageSize) + 1;
  323 + var totalPages = pageSize <= 0 ? 0 : (int)Math.Ceiling(total / (double)pageSize);
  324 + return new PagedResultWithPageDto<T>
  325 + {
  326 + PageIndex = pageIndex,
  327 + PageSize = pageSize,
  328 + TotalCount = total,
  329 + TotalPages = totalPages,
  330 + Items = items
  331 + };
  332 + }
  333 +}
  334 +
... ...
项目相关文档/标签模块接口对接说明.md 0 → 100644
  1 +## 概述
  2 +
  3 +美国版后端采用 ABP 动态接口(ConventionalControllers),宿主统一前缀为 `api/app`。
  4 +Swagger 地址:
  5 +
  6 +- `http://localhost:19001/swagger`
  7 +
  8 +说明:
  9 +- 接口最终 URL 以 Swagger 展示为准(可在 Swagger 里搜索 `LabelCategory / LabelType / LabelMultipleOption / LabelTemplate / Label`)。
  10 +- 本模块后端接口以各 AppService 的方法签名自动暴露。
  11 +- 返回分页统一包含 `PageIndex / PageSize / TotalCount / TotalPages / Items`。
  12 +
  13 +---
  14 +
  15 +## Swagger 中如何找到
  16 +
  17 +1. 启动后端宿主(`Yi.Abp.Web`),端口 `19001`。
  18 +2. 打开 `http://localhost:19001/swagger`。
  19 +3. 在接口分组里搜索以下关键词之一:
  20 + - `label-category`
  21 + - `label-type`
  22 + - `label-multiple-option`
  23 + - `label-template`
  24 + - `label`
  25 +
  26 +---
  27 +
  28 +## 接口 1:Label Categories(标签分类)
  29 +
  30 +### 1.1 分页列表
  31 +
  32 +方法:`GET /api/app/label-category`
  33 +
  34 +入参(`LabelCategoryGetListInputVo`,查询参数):
  35 +
  36 +- `skipCount`(int)
  37 +- `maxResultCount`(int)
  38 +- `sorting`(string,可选)
  39 +- `keyword`(string,可选)
  40 +- `state`(boolean,可选)
  41 +
  42 +示例(查询参数):
  43 +
  44 +```json
  45 +{
  46 + "skipCount": 0,
  47 + "maxResultCount": 10,
  48 + "keyword": "Prep",
  49 + "state": true
  50 +}
  51 +```
  52 +
  53 +### 1.2 详情
  54 +
  55 +方法:`GET /api/app/label-category/{id}`
  56 +
  57 +入参:
  58 +
  59 +- `id`:分类 Id(字符串)
  60 +
  61 +### 1.3 新增
  62 +
  63 +方法:`POST /api/app/label-category`
  64 +
  65 +入参(Body:`LabelCategoryCreateInputVo`):
  66 +
  67 +```json
  68 +{
  69 + "categoryCode": "CAT_PREP",
  70 + "categoryName": "Prep",
  71 + "categoryPhotoUrl": "https://cdn.example.com/cat-prep.png",
  72 + "state": true,
  73 + "orderNum": 1
  74 +}
  75 +```
  76 +
  77 +### 1.4 编辑
  78 +
  79 +方法:`PUT /api/app/label-category/{id}`
  80 +
  81 +入参(Body:`LabelCategoryUpdateInputVo`,字段同创建):
  82 +
  83 +```json
  84 +{
  85 + "categoryCode": "CAT_PREP",
  86 + "categoryName": "Prep",
  87 + "categoryPhotoUrl": null,
  88 + "state": true,
  89 + "orderNum": 2
  90 +}
  91 +```
  92 +
  93 +### 1.5 删除(逻辑删除)
  94 +
  95 +方法:`DELETE /api/app/label-category/{id}`
  96 +
  97 +入参:
  98 +
  99 +- `id`:分类 Id(字符串)
  100 +
  101 +删除校验:
  102 +- 若该分类已被 `fl_label` 引用,则抛出友好错误,禁止删除。
  103 +
  104 +---
  105 +
  106 +## 接口 2:Label Types(标签类型)
  107 +
  108 +### 2.1 分页列表
  109 +
  110 +方法:`GET /api/app/label-type`
  111 +
  112 +入参(`LabelTypeGetListInputVo`,查询参数):
  113 +
  114 +```json
  115 +{
  116 + "skipCount": 0,
  117 + "maxResultCount": 10,
  118 + "keyword": "Defrost",
  119 + "state": true
  120 +}
  121 +```
  122 +
  123 +### 2.2 详情
  124 +
  125 +方法:`GET /api/app/label-type/{id}`
  126 +
  127 +入参:
  128 +
  129 +- `id`:类型 Id(字符串)
  130 +
  131 +### 2.3 新增
  132 +
  133 +方法:`POST /api/app/label-type`
  134 +
  135 +入参(Body:`LabelTypeCreateInputVo`):
  136 +
  137 +```json
  138 +{
  139 + "typeCode": "TYPE_DEFROST",
  140 + "typeName": "Defrost",
  141 + "state": true,
  142 + "orderNum": 1
  143 +}
  144 +```
  145 +
  146 +### 2.4 编辑
  147 +
  148 +方法:`PUT /api/app/label-type/{id}`
  149 +
  150 +入参(Body:`LabelTypeUpdateInputVo`,字段同创建):
  151 +
  152 +```json
  153 +{
  154 + "typeCode": "TYPE_DEFROST",
  155 + "typeName": "Defrost",
  156 + "state": true,
  157 + "orderNum": 2
  158 +}
  159 +```
  160 +
  161 +### 2.5 删除(逻辑删除)
  162 +
  163 +方法:`DELETE /api/app/label-type/{id}`
  164 +
  165 +删除校验:
  166 +- 若该类型已被 `fl_label` 引用,则禁止删除。
  167 +
  168 +---
  169 +
  170 +## 接口 3:Multiple Options(多选项字典)
  171 +
  172 +### 3.1 分页列表
  173 +
  174 +方法:`GET /api/app/label-multiple-option`
  175 +
  176 +入参(`LabelMultipleOptionGetListInputVo`,查询参数):
  177 +
  178 +```json
  179 +{
  180 + "skipCount": 0,
  181 + "maxResultCount": 10,
  182 + "keyword": "Allergens",
  183 + "state": true
  184 +}
  185 +```
  186 +
  187 +### 3.2 详情
  188 +
  189 +方法:`GET /api/app/label-multiple-option/{id}`
  190 +
  191 +入参:
  192 +
  193 +- `id`:多选项 Id(字符串)
  194 +
  195 +### 3.3 新增
  196 +
  197 +方法:`POST /api/app/label-multiple-option`
  198 +
  199 +入参(Body:`LabelMultipleOptionCreateInputVo`):
  200 +
  201 +```json
  202 +{
  203 + "optionCode": "OPT_ALLERGENS",
  204 + "optionName": "Allergens",
  205 + "optionValuesJson": ["Peanuts", "Dairy", "Gluten", "Soy"],
  206 + "state": true,
  207 + "orderNum": 1
  208 +}
  209 +```
  210 +
  211 +### 3.4 编辑
  212 +
  213 +方法:`PUT /api/app/label-multiple-option/{id}`
  214 +
  215 +入参(Body:`LabelMultipleOptionUpdateInputVo`,字段同创建):
  216 +
  217 +```json
  218 +{
  219 + "optionCode": "OPT_ALLERGENS",
  220 + "optionName": "Allergens",
  221 + "optionValuesJson": ["Peanuts", "Dairy"],
  222 + "state": true,
  223 + "orderNum": 2
  224 +}
  225 +```
  226 +
  227 +### 3.5 删除(逻辑删除)
  228 +
  229 +方法:`DELETE /api/app/label-multiple-option/{id}`
  230 +
  231 +---
  232 +
  233 +## 接口 4:Label Templates(标签模板)
  234 +
  235 +说明:
  236 +- 模板标识入参 `id` 使用 `fl_label_template.TemplateCode`。
  237 +- 创建/编辑的 Body 字段名对齐你前端 editor JSON(`id/name/appliedLocation/elements/config`)。
  238 +
  239 +### 4.1 分页列表
  240 +
  241 +方法:`GET /api/app/label-template`
  242 +
  243 +入参(`LabelTemplateGetListInputVo`,查询参数):
  244 +
  245 +```json
  246 +{
  247 + "skipCount": 0,
  248 + "maxResultCount": 10,
  249 + "keyword": "测试模板",
  250 + "locationId": "11111111-1111-1111-1111-111111111111",
  251 + "labelType": "PRICE",
  252 + "state": true
  253 +}
  254 +```
  255 +
  256 +### 4.2 详情
  257 +
  258 +方法:`GET /api/app/label-template/{id}`
  259 +
  260 +入参:
  261 +
  262 +- `id`:模板编码 `TemplateCode`(字符串)
  263 +
  264 +### 4.3 新增模板
  265 +
  266 +方法:`POST /api/app/label-template`
  267 +
  268 +入参(Body:`LabelTemplateCreateInputVo`):
  269 +
  270 +```json
  271 +{
  272 + "id": "TPL_TEST_001",
  273 + "name": "测试模板-价格签(4x6)",
  274 + "labelType": "PRICE",
  275 + "unit": "inch",
  276 + "width": 4,
  277 + "height": 6,
  278 + "appliedLocation": "ALL",
  279 + "showRuler": true,
  280 + "showGrid": true,
  281 + "state": true,
  282 + "elements": [
  283 + {
  284 + "id": "el-fixed-title",
  285 + "type": "TEXT_STATIC",
  286 + "x": 32,
  287 + "y": 24,
  288 + "width": 160,
  289 + "height": 24,
  290 + "rotation": "horizontal",
  291 + "border": "none",
  292 + "zIndex": 1,
  293 + "orderNum": 1,
  294 + "valueSourceType": "FIXED",
  295 + "isRequiredInput": false,
  296 + "config": {
  297 + "text": "商品名",
  298 + "fontFamily": "Arial",
  299 + "fontSize": 14,
  300 + "fontWeight": "bold",
  301 + "textAlign": "left"
  302 + }
  303 + }
  304 + ],
  305 + "appliedLocationIds": []
  306 +}
  307 +```
  308 +
  309 +说明:
  310 +- 当 `appliedLocation=SPECIFIED` 时,`appliedLocationIds` 必须至少选择一个门店。
  311 +
  312 +### 4.4 编辑模板
  313 +
  314 +方法:`PUT /api/app/label-template/{id}`
  315 +
  316 +入参:
  317 +- Path:`id` 是当前模板编码(TemplateCode)
  318 +- Body:字段同新增(`id/name/elements/...`)
  319 +
  320 +示例(编辑:同样字段,appliedLocation 切到 SPECIFIED):
  321 +
  322 +```json
  323 +{
  324 + "id": "TPL_TEST_001",
  325 + "name": "测试模板-价格签(4x6) v2",
  326 + "labelType": "PRICE",
  327 + "unit": "inch",
  328 + "width": 4,
  329 + "height": 6,
  330 + "appliedLocation": "SPECIFIED",
  331 + "showRuler": true,
  332 + "showGrid": true,
  333 + "state": true,
  334 + "elements": [],
  335 + "appliedLocationIds": ["11111111-1111-1111-1111-111111111111"]
  336 +}
  337 +```
  338 +
  339 +版本:
  340 +- `VersionNo` 会在编辑时自动 `+1`。
  341 +- `elements` 会按传入内容全量重建。
  342 +
  343 +### 4.5 删除(逻辑删除)
  344 +
  345 +方法:`DELETE /api/app/label-template/{id}`
  346 +
  347 +入参:
  348 +- `id`:模板编码 `TemplateCode`
  349 +
  350 +删除校验:
  351 +- 若该模板已被 `fl_label` 引用,则禁止删除。
  352 +
  353 +---
  354 +
  355 +## 接口 5:Labels(按产品展示多个标签)
  356 +
  357 +说明:
  358 +- 列表接口按 `ProductId` 查询,一个产品会对应多条标签记录。
  359 +- 标签详情/编辑/删除的 `id` 使用 `fl_label.LabelCode`。
  360 +
  361 +### 5.1 分页列表(按产品)
  362 +
  363 +方法:`GET /api/app/label`
  364 +
  365 +入参(`LabelGetListInputVo`,查询参数):
  366 +
  367 +```json
  368 +{
  369 + "skipCount": 0,
  370 + "maxResultCount": 10,
  371 + "sorting": "",
  372 + "keyword": "早餐",
  373 + "locationId": "11111111-1111-1111-1111-111111111111",
  374 + "productId": "22222222-2222-2222-2222-222222222222",
  375 + "labelCategoryId": "33333333-3333-3333-3333-333333333333",
  376 + "labelTypeId": "44444444-4444-4444-4444-444444444444",
  377 + "templateCode": "TPL_TEST_001",
  378 + "state": true
  379 +}
  380 +```
  381 +
  382 +### 5.2 详情
  383 +
  384 +方法:`GET /api/app/label/{id}`
  385 +
  386 +入参:
  387 +- `id`:标签编码 `LabelCode`
  388 +
  389 +返回:
  390 +- `productIds`:该标签绑定的产品Id 列表
  391 +
  392 +### 5.3 新增标签
  393 +
  394 +方法:`POST /api/app/label`
  395 +
  396 +入参(Body:`LabelCreateInputVo`):
  397 +
  398 +```json
  399 +{
  400 + "labelCode": "LBL_TEST_001",
  401 + "labelName": "早餐标签",
  402 + "templateCode": "TPL_TEST_001",
  403 + "locationId": "11111111-1111-1111-1111-111111111111",
  404 + "labelCategoryId": "33333333-3333-3333-3333-333333333333",
  405 + "labelTypeId": "44444444-4444-4444-4444-444444444444",
  406 + "productIds": ["22222222-2222-2222-2222-222222222222"],
  407 + "labelInfoJson": { "note": "测试标签1" },
  408 + "state": true
  409 +}
  410 +```
  411 +
  412 +校验:
  413 +- `productIds` 至少 1 个
  414 +- `templateCode/locationId/labelCategoryId/labelTypeId` 不能为空
  415 +
  416 +### 5.4 编辑标签
  417 +
  418 +方法:`PUT /api/app/label/{id}`
  419 +
  420 +入参:
  421 +- Path:`id` 为当前标签编码 `LabelCode`
  422 +- Body:字段同创建(`LabelUpdateInputVo`)
  423 +
  424 +```json
  425 +{
  426 + "labelName": "早餐标签 v2",
  427 + "templateCode": "TPL_TEST_001",
  428 + "locationId": "11111111-1111-1111-1111-111111111111",
  429 + "labelCategoryId": "33333333-3333-3333-3333-333333333333",
  430 + "labelTypeId": "44444444-4444-4444-4444-444444444444",
  431 + "productIds": ["22222222-2222-2222-2222-222222222222"],
  432 + "labelInfoJson": { "note": "测试标签1 v2" },
  433 + "state": true
  434 +}
  435 +```
  436 +
  437 +关联维护:
  438 +- `fl_label_product` 会按新 `productIds` 重建。
  439 +
  440 +### 5.5 删除标签(逻辑删除)
  441 +
  442 +方法:`DELETE /api/app/label/{id}`
  443 +
  444 +入参:
  445 +- `id`:标签编码 `LabelCode`
  446 +
  447 +删除行为:
  448 +- 逻辑删除 `fl_label`
  449 +- 删除该标签对应的 `fl_label_product` 关联
  450 +
  451 +---
  452 +## 接口 6:Products(产品)
  453 +
  454 +说明:
  455 +- 产品表:`fl_product`
  456 +- 删除为逻辑删除:`IsDeleted = true`
  457 +
  458 +### 6.1 分页列表
  459 +
  460 +方法:`GET /api/app/product`
  461 +
  462 +入参(`ProductGetListInputVo`,查询参数):
  463 +```json
  464 +{
  465 + "skipCount": 0,
  466 + "maxResultCount": 10,
  467 + "sorting": "",
  468 + "keyword": "Chicken",
  469 + "state": true
  470 +}
  471 +```
  472 +
  473 +### 6.2 详情
  474 +
  475 +方法:`GET /api/app/product/{id}`
  476 +
  477 +入参:
  478 +- `id`:产品Id(`fl_product.Id`)
  479 +
  480 +### 6.3 新增产品
  481 +
  482 +方法:`POST /api/app/product`
  483 +
  484 +入参(Body:`ProductCreateInputVo`):
  485 +```json
  486 +{
  487 + "productCode": "PRD_TEST_001",
  488 + "productName": "Chicken",
  489 + "categoryName": "Meat",
  490 + "productImageUrl": "https://example.com/img.png",
  491 + "state": true
  492 +}
  493 +```
  494 +
  495 +校验:
  496 +- `productCode/productName` 不能为空
  497 +- `productCode` 不能与未删除的数据重复
  498 +
  499 +### 6.4 编辑产品
  500 +
  501 +方法:`PUT /api/app/product/{id}`
  502 +
  503 +入参:
  504 +- Path:`id` 为当前产品Id(`fl_product.Id`)
  505 +- Body:字段同新增(`ProductUpdateInputVo`)
  506 +
  507 +### 6.5 删除(逻辑删除)
  508 +
  509 +方法:`DELETE /api/app/product/{id}`
  510 +
  511 +入参:
  512 +- `id`:产品Id
  513 +
  514 +---
  515 +## 接口 7:Product-Location(门店-产品关联)
  516 +
  517 +说明:
  518 +- 关联表:`fl_location_product`
  519 +- 关联按门店进行批量替换:
  520 + - `Create`:在门店下新增未存在的 product 关联
  521 + - `Update`:替换该门店下全部关联(先删后建)
  522 + - `Delete`:删除该门店下全部关联
  523 +
  524 +### 7.1 分页列表
  525 +
  526 +方法:`GET /api/app/product-location`
  527 +
  528 +入参(`ProductLocationGetListInputVo`,查询参数):
  529 +```json
  530 +{
  531 + "skipCount": 0,
  532 + "maxResultCount": 10,
  533 + "sorting": "",
  534 + "locationId": "11111111-1111-1111-1111-111111111111",
  535 + "productId": "22222222-2222-2222-2222-222222222222"
  536 +}
  537 +```
  538 +
  539 +### 7.2 获取门店下全部产品
  540 +
  541 +方法:`GET /api/app/product-location/{id}`
  542 +
  543 +入参:
  544 +- `id`:门店Id(`location.Id`,string 表示)
  545 +
  546 +返回:
  547 +- 门店Id + 该门店关联的产品列表
  548 +
  549 +### 7.3 新增/建立门店关联
  550 +
  551 +方法:`POST /api/app/product-location`
  552 +
  553 +入参(Body:`ProductLocationCreateInputVo`):
  554 +```json
  555 +{
  556 + "locationId": "11111111-1111-1111-1111-111111111111",
  557 + "productIds": ["22222222-2222-2222-2222-222222222222"]
  558 +}
  559 +```
  560 +
  561 +校验:
  562 +- `locationId` 对应门店必须存在
  563 +- `productIds` 必须都存在于 `fl_product` 且未删除
  564 +
  565 +### 7.4 编辑/替换门店关联
  566 +
  567 +方法:`PUT /api/app/product-location/{id}`
  568 +
  569 +入参:
  570 +- Path:`id` 为门店Id
  571 +- Body:`ProductLocationUpdateInputVo`
  572 +```json
  573 +{
  574 + "productIds": ["22222222-2222-2222-2222-222222222222"]
  575 +}
  576 +```
  577 +
  578 +### 7.5 删除门店关联(按门店删除全部)
  579 +
  580 +方法:`DELETE /api/app/product-location/{id}`
  581 +
  582 +入参:
  583 +- `id`:门店Id
  584 +
... ...