Commit ecb291fd1960e19566da9b022f461e5ba4d5b45d
1 parent
87313aec
门店支持优化;标签,产品组件优化
Showing
21 changed files
with
300 additions
and
216 deletions
本次新增与优化接口汇总(1).md
| ... | ... | @@ -39,9 +39,9 @@ |
| 39 | 39 | |
| 40 | 40 | - `fl_product_category`(主表)关键字段: |
| 41 | 41 | - `DisplayText`:按钮展示文案(为空可回退 `CategoryName`) |
| 42 | - - `ButtonAppearance`:`TEXT/COLOR/IMAGE` | |
| 43 | - - `ButtonTextColor` / `ButtonBgColor` / `ButtonImageUrl` | |
| 44 | - - `ButtonStyleJson`:样式扩展 JSON(可选) | |
| 42 | + - `ButtonAppearance`:**JSON 格式字符串**(如 `["TEXT","COLOR"]`);兼容历史单行 `TEXT`/`COLOR`/`IMAGE`(保存时规范为 JSON 数组) | |
| 43 | + - `CategoryPhotoUrl`:**JSON 格式字符串**(展示数据由前端解析);非 JSON 纯文本入库时由后端包成 JSON 字符串 | |
| 44 | + - **已删除列**(若库上仍存在需迁移):`ButtonTextColor`、`ButtonBgColor`、`ButtonImageUrl`、`ButtonStyleJson` | |
| 45 | 45 | - `AvailabilityType`:`ALL/SPECIFIED`(门店可用范围) |
| 46 | 46 | - `fl_product_category_location`(关联表): |
| 47 | 47 | - `(CategoryId, LocationId)` 唯一约束;用于 `AvailabilityType=SPECIFIED` 指定门店 |
| ... | ... | @@ -56,7 +56,7 @@ |
| 56 | 56 | - **路径**:`/api/app/product-category` |
| 57 | 57 | - **列表行新增返回**: |
| 58 | 58 | - `displayText` |
| 59 | - - `buttonAppearance` | |
| 59 | + - `buttonAppearance`、`categoryPhotoUrl`(均为字符串,内容多为 JSON) | |
| 60 | 60 | - `availabilityType` |
| 61 | 61 | |
| 62 | 62 | #### 2.2.2 详情 |
| ... | ... | @@ -65,7 +65,7 @@ |
| 65 | 65 | - **路径**:`/api/app/product-category/{id}` |
| 66 | 66 | - **新增返回字段**: |
| 67 | 67 | - `displayText` |
| 68 | - - `buttonAppearance/buttonTextColor/buttonBgColor/buttonImageUrl/buttonStyleJson` | |
| 68 | + - `buttonAppearance`、`categoryPhotoUrl` | |
| 69 | 69 | - `availabilityType` |
| 70 | 70 | - `locationIds`(当 `availabilityType=SPECIFIED` 返回门店 Id 列表,否则为空数组) |
| 71 | 71 | |
| ... | ... | @@ -75,7 +75,7 @@ |
| 75 | 75 | - **路径**:`/api/app/product-category` |
| 76 | 76 | - **新增入参字段**: |
| 77 | 77 | - `displayText` |
| 78 | - - `buttonAppearance/buttonTextColor/buttonBgColor/buttonImageUrl/buttonStyleJson` | |
| 78 | + - `buttonAppearance`、`categoryPhotoUrl`(JSON 字符串约定,详见 `项目相关文档/产品模块Categories接口对接说明.md`) | |
| 79 | 79 | - `availabilityType` |
| 80 | 80 | - `locationIds`(当 `availabilityType=SPECIFIED` 必填且至少 1 个) |
| 81 | 81 | |
| ... | ... | @@ -95,9 +95,7 @@ |
| 95 | 95 | |
| 96 | 96 | - `availabilityType` 仅允许 `ALL/SPECIFIED` |
| 97 | 97 | - `SPECIFIED` 时 `locationIds` 至少 1 个 |
| 98 | -- `buttonAppearance` 仅允许 `TEXT/COLOR/IMAGE` | |
| 99 | - - `IMAGE` 时必须有 `buttonImageUrl` | |
| 100 | - - `COLOR` 时必须有 `buttonBgColor` | |
| 98 | +- `buttonAppearance`:须为 **合法 JSON**,或为兼容的 **`TEXT`/`COLOR`/`IMAGE` 单行**;`categoryPhotoUrl` 非 JSON 纯文本时后端会序列化为 JSON 字符串存储 | |
| 101 | 99 | |
| 102 | 100 | --- |
| 103 | 101 | |
| ... | ... | @@ -109,9 +107,9 @@ |
| 109 | 107 | |
| 110 | 108 | - `fl_label_category`(主表)关键字段: |
| 111 | 109 | - `DisplayText`:按钮展示文案(为空可回退 `CategoryName`) |
| 112 | - - `ButtonAppearance`:`TEXT/COLOR/IMAGE` | |
| 113 | - - `ButtonTextColor` / `ButtonBgColor` / `ButtonImageUrl` | |
| 114 | - - `ButtonStyleJson`:样式扩展 JSON(可选) | |
| 110 | + - `ButtonAppearance`:**JSON 格式字符串**(如 `["TEXT","COLOR"]`);兼容历史单行 `TEXT`/`COLOR`/`IMAGE` | |
| 111 | + - `CategoryPhotoUrl`:**JSON 格式字符串**;非 JSON 纯文本入库时由后端包成 JSON 字符串 | |
| 112 | + - **已删除列**(若库上仍存在需迁移):`ButtonTextColor`、`ButtonBgColor`、`ButtonImageUrl`、`ButtonStyleJson` | |
| 115 | 113 | - `AvailabilityType`:`ALL/SPECIFIED`(门店可用范围) |
| 116 | 114 | - `fl_label_category_location`(关联表): |
| 117 | 115 | - `(CategoryId, LocationId)` 唯一约束;用于 `AvailabilityType=SPECIFIED` 指定门店(`LocationId` 对应 `location` 表主键) |
| ... | ... | @@ -126,7 +124,7 @@ |
| 126 | 124 | - **路径**:`/api/app/label-category` |
| 127 | 125 | - **列表行新增返回**: |
| 128 | 126 | - `displayText` |
| 129 | - - `buttonAppearance` | |
| 127 | + - `buttonAppearance`、`categoryPhotoUrl`(均为字符串,内容多为 JSON) | |
| 130 | 128 | - `availabilityType` |
| 131 | 129 | |
| 132 | 130 | #### 3.2.2 详情 |
| ... | ... | @@ -135,7 +133,7 @@ |
| 135 | 133 | - **路径**:`/api/app/label-category/{id}` |
| 136 | 134 | - **新增返回字段**: |
| 137 | 135 | - `displayText` |
| 138 | - - `buttonAppearance/buttonTextColor/buttonBgColor/buttonImageUrl/buttonStyleJson` | |
| 136 | + - `buttonAppearance`、`categoryPhotoUrl` | |
| 139 | 137 | - `availabilityType` |
| 140 | 138 | - `locationIds`(当 `availabilityType=SPECIFIED` 返回门店 Id 列表,否则为空数组) |
| 141 | 139 | |
| ... | ... | @@ -145,7 +143,7 @@ |
| 145 | 143 | - **路径**:`/api/app/label-category` |
| 146 | 144 | - **新增入参字段**: |
| 147 | 145 | - `displayText` |
| 148 | - - `buttonAppearance/buttonTextColor/buttonBgColor/buttonImageUrl/buttonStyleJson` | |
| 146 | + - `buttonAppearance`、`categoryPhotoUrl`(JSON 字符串约定,详见 `项目相关文档/标签模块接口对接说明.md`) | |
| 149 | 147 | - `availabilityType` |
| 150 | 148 | - `locationIds`(当 `availabilityType=SPECIFIED` 必填且至少 1 个) |
| 151 | 149 | |
| ... | ... | @@ -165,7 +163,5 @@ |
| 165 | 163 | |
| 166 | 164 | - `availabilityType` 仅允许 `ALL/SPECIFIED` |
| 167 | 165 | - `SPECIFIED` 时 `locationIds` 至少 1 个 |
| 168 | -- `buttonAppearance` 仅允许 `TEXT/COLOR/IMAGE` | |
| 169 | - - `IMAGE` 时必须有 `buttonImageUrl` | |
| 170 | - - `COLOR` 时必须有 `buttonBgColor` | |
| 166 | +- `buttonAppearance`:须为 **合法 JSON**,或为兼容的 **`TEXT`/`COLOR`/`IMAGE` 单行**;`categoryPhotoUrl` 非 JSON 纯文本时后端会序列化为 JSON 字符串存储 | |
| 171 | 167 | ... | ... |
标签模块接口对接说明.md
| ... | ... | @@ -51,6 +51,12 @@ Swagger 地址: |
| 51 | 51 | } |
| 52 | 52 | ``` |
| 53 | 53 | |
| 54 | +### 1.1.1 字段约定:`buttonAppearance` 与 `categoryPhotoUrl`(JSON 字符串) | |
| 55 | + | |
| 56 | +- **`buttonAppearance`**:库中存 **JSON 文本**(如 `["TEXT","COLOR"]`、仅图片 `["IMAGE"]` 等);兼容历史单行 `TEXT`/`COLOR`/`IMAGE`(保存时规范为 `["TEXT"]` 等)。未传或空白时后端默认 `["TEXT"]`。非法值(非 JSON 且非上述三者)会返回友好错误。 | |
| 57 | +- **`categoryPhotoUrl`**:同样为 **JSON 文本**(如 `["Prep","#10B981"]`);若传**非 JSON** 的纯文本(色值、`/picture/...` 等),后端会序列化为合法 JSON 字符串再存储。列表/详情/App 树**原样返回**字符串,由客户端解析。 | |
| 58 | +- 其它常用字段:`displayText`、`availabilityType`(`ALL`/`SPECIFIED`)、`locationIds`(指定门店时必填),与产品类别接口语义一致(见 `项目相关文档/产品模块Categories接口对接说明.md`)。 | |
| 59 | + | |
| 54 | 60 | ### 1.2 详情 |
| 55 | 61 | |
| 56 | 62 | 方法:`GET /api/app/label-category/{id}` |
| ... | ... | @@ -69,7 +75,11 @@ Swagger 地址: |
| 69 | 75 | { |
| 70 | 76 | "categoryCode": "CAT_PREP", |
| 71 | 77 | "categoryName": "Prep", |
| 72 | - "categoryPhotoUrl": "https://cdn.example.com/cat-prep.png", | |
| 78 | + "displayText": "Prep", | |
| 79 | + "buttonAppearance": "[\"TEXT\",\"COLOR\"]", | |
| 80 | + "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]", | |
| 81 | + "availabilityType": "ALL", | |
| 82 | + "locationIds": [], | |
| 73 | 83 | "state": true, |
| 74 | 84 | "orderNum": 1 |
| 75 | 85 | } |
| ... | ... | @@ -85,7 +95,11 @@ Swagger 地址: |
| 85 | 95 | { |
| 86 | 96 | "categoryCode": "CAT_PREP", |
| 87 | 97 | "categoryName": "Prep", |
| 88 | - "categoryPhotoUrl": null, | |
| 98 | + "displayText": "Prep", | |
| 99 | + "buttonAppearance": "[\"TEXT\",\"COLOR\"]", | |
| 100 | + "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]", | |
| 101 | + "availabilityType": "ALL", | |
| 102 | + "locationIds": [], | |
| 89 | 103 | "state": true, |
| 90 | 104 | "orderNum": 2 |
| 91 | 105 | } |
| ... | ... | @@ -706,7 +720,8 @@ ADD UNIQUE KEY `uk_fl_ltpd_template_product_label_type` (`TemplateId`, `ProductI |
| 706 | 720 | |------|------|------| |
| 707 | 721 | | `id` | string | `fl_label_category.Id` | |
| 708 | 722 | | `categoryName` | string | 分类名称 | |
| 709 | -| `categoryPhotoUrl` | string \| null | 分类图标/图 | | |
| 723 | +| `categoryPhotoUrl` | string \| null | 分类展示数据,**JSON 格式字符串**(与库中 `CategoryPhotoUrl` 一致,客户端解析) | | |
| 724 | +| `buttonAppearance` | string | 按钮外观,**JSON 格式字符串**(与库中 `ButtonAppearance` 一致;空时后端默认 `"TEXT"`) | | |
| 710 | 725 | | `orderNum` | number | 排序 | |
| 711 | 726 | | `productCategories` | array | 第二级列表(见下表) | |
| 712 | 727 | |
| ... | ... | @@ -715,8 +730,12 @@ ADD UNIQUE KEY `uk_fl_ltpd_template_product_label_type` (`TemplateId`, `ProductI |
| 715 | 730 | | 字段 | 类型 | 说明 | |
| 716 | 731 | |------|------|------| |
| 717 | 732 | | `categoryId` | string \| null | 产品分类Id;产品未归类或分类不存在时为空 | |
| 718 | -| `categoryPhotoUrl` | string \| null | 产品分类图片地址;产品未归类或分类不存在时为空 | | |
| 733 | +| `categoryPhotoUrl` | string \| null | 产品分类展示数据,**JSON 格式字符串**;未归类或分类不存在时为空 | | |
| 719 | 734 | | `name` | string | 产品分类显示名;空源数据为 **`无`** | |
| 735 | +| `displayText` | string \| null | 按钮展示文案 | | |
| 736 | +| `buttonAppearance` | string | **JSON 格式字符串**(与库中一致;空时默认 `"TEXT"`) | | |
| 737 | +| `availabilityType` | string | `ALL` / `SPECIFIED`(树已按当前门店过滤,仍返回供客户端展示) | | |
| 738 | +| `orderNum` | number | 排序 | | |
| 720 | 739 | | `itemCount` | number | 该分类下 **产品个数**(去重后的产品数) | |
| 721 | 740 | | `products` | array | 第三级产品列表(见下表) | |
| 722 | 741 | |
| ... | ... | @@ -771,13 +790,18 @@ curl -X GET "http://localhost:19001/api/app/us-app-labeling/labeling-tree?locati |
| 771 | 790 | { |
| 772 | 791 | "id": "cat-prep-id", |
| 773 | 792 | "categoryName": "Prep", |
| 774 | - "categoryPhotoUrl": "/picture/...", | |
| 793 | + "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]", | |
| 794 | + "buttonAppearance": "[\"TEXT\",\"COLOR\"]", | |
| 775 | 795 | "orderNum": 1, |
| 776 | 796 | "productCategories": [ |
| 777 | 797 | { |
| 778 | 798 | "categoryId": "pc-meat-id", |
| 779 | - "categoryPhotoUrl": "/picture/product-category/20260325123010_xxx.png", | |
| 799 | + "categoryPhotoUrl": "[\"/picture/product-category/20260325123010_xxx.png\"]", | |
| 780 | 800 | "name": "Meat", |
| 801 | + "displayText": "Meat", | |
| 802 | + "buttonAppearance": "[\"IMAGE\"]", | |
| 803 | + "availabilityType": "ALL", | |
| 804 | + "orderNum": 10, | |
| 781 | 805 | "itemCount": 1, |
| 782 | 806 | "products": [ |
| 783 | 807 | { | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LocationSupport/LocationSupportCreateInputVo.cs
| ... | ... | @@ -5,8 +5,6 @@ namespace FoodLabeling.Application.Contracts.Dtos.LocationSupport; |
| 5 | 5 | /// </summary> |
| 6 | 6 | public class LocationSupportCreateInputVo |
| 7 | 7 | { |
| 8 | - public string LocationId { get; set; } = string.Empty; | |
| 9 | - | |
| 10 | 8 | public string SupportPhone { get; set; } = string.Empty; |
| 11 | 9 | |
| 12 | 10 | public string SupportEmail { get; set; } = string.Empty; | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LocationSupport/LocationSupportGetOutputDto.cs
| ... | ... | @@ -7,10 +7,6 @@ public class LocationSupportGetOutputDto |
| 7 | 7 | { |
| 8 | 8 | public string Id { get; set; } = string.Empty; |
| 9 | 9 | |
| 10 | - public string LocationId { get; set; } = string.Empty; | |
| 11 | - | |
| 12 | - public string? LocationName { get; set; } | |
| 13 | - | |
| 14 | 10 | public string SupportPhone { get; set; } = string.Empty; |
| 15 | 11 | |
| 16 | 12 | public string SupportEmail { get; set; } = string.Empty; | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LocationSupport/LocationSupportUpdateInputVo.cs
| ... | ... | @@ -5,8 +5,6 @@ namespace FoodLabeling.Application.Contracts.Dtos.LocationSupport; |
| 5 | 5 | /// </summary> |
| 6 | 6 | public class LocationSupportUpdateInputVo |
| 7 | 7 | { |
| 8 | - public string LocationId { get; set; } = string.Empty; | |
| 9 | - | |
| 10 | 8 | public string SupportPhone { get; set; } = string.Empty; |
| 11 | 9 | |
| 12 | 10 | public string SupportEmail { get; set; } = string.Empty; | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/ILocationSupportAppService.cs
| ... | ... | @@ -4,24 +4,23 @@ using Volo.Abp.Application.Services; |
| 4 | 4 | namespace FoodLabeling.Application.Contracts.IServices; |
| 5 | 5 | |
| 6 | 6 | /// <summary> |
| 7 | -/// 门店 Support 联系方式管理(后台设置) | |
| 7 | +/// 全局 Support 联系方式(全平台共用;Web 可增改查,App 仅可查) | |
| 8 | 8 | /// </summary> |
| 9 | 9 | public interface ILocationSupportAppService : IApplicationService |
| 10 | 10 | { |
| 11 | 11 | /// <summary> |
| 12 | - /// 按门店查询 Support 联系方式 | |
| 12 | + /// 查询全局 Support 联系方式(已登录即可;App / Web 共用) | |
| 13 | 13 | /// </summary> |
| 14 | - /// <param name="locationId">门店Id</param> | |
| 15 | - Task<LocationSupportGetOutputDto?> GetByLocationIdAsync(string locationId); | |
| 14 | + Task<LocationSupportGetOutputDto?> GetSupportAsync(); | |
| 16 | 15 | |
| 17 | 16 | /// <summary> |
| 18 | - /// 新增门店 Support 联系方式(每个门店仅允许一条) | |
| 17 | + /// 新增全局 Support 联系方式(系统仅允许一条;Web 管理端) | |
| 19 | 18 | /// </summary> |
| 20 | 19 | /// <param name="input">联系方式</param> |
| 21 | 20 | Task<LocationSupportGetOutputDto> CreateAsync(LocationSupportCreateInputVo input); |
| 22 | 21 | |
| 23 | 22 | /// <summary> |
| 24 | - /// 编辑门店 Support 联系方式 | |
| 23 | + /// 编辑全局 Support 联系方式(Web 管理端) | |
| 25 | 24 | /// </summary> |
| 26 | 25 | /// <param name="id">联系方式主键</param> |
| 27 | 26 | /// <param name="input">联系方式</param> | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/UsAppJwtClaims.cs
0 → 100644
| 1 | +namespace FoodLabeling.Application.Contracts; | |
| 2 | + | |
| 3 | +/// <summary> | |
| 4 | +/// 美国版 App JWT 自定义声明(用于与 Web 管理端 Token 区分能力) | |
| 5 | +/// </summary> | |
| 6 | +public static class UsAppJwtClaims | |
| 7 | +{ | |
| 8 | + /// <summary>声明类型:客户端种类</summary> | |
| 9 | + public const string ClientKind = "client_kind"; | |
| 10 | + | |
| 11 | + /// <summary>美国版移动端 App</summary> | |
| 12 | + public const string ClientKindUsApp = "us-app"; | |
| 13 | +} | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/CategoryAppearanceStorageHelper.cs
0 → 100644
| 1 | +using System.Text.Json; | |
| 2 | +using Volo.Abp; | |
| 3 | + | |
| 4 | +namespace FoodLabeling.Application.Helpers; | |
| 5 | + | |
| 6 | +/// <summary> | |
| 7 | +/// 将标签/产品类别的按钮外观与展示字段按「JSON 字符串」落库;兼容历史单行 TEXT/COLOR/IMAGE。 | |
| 8 | +/// </summary> | |
| 9 | +public static class CategoryAppearanceStorageHelper | |
| 10 | +{ | |
| 11 | + /// <summary>未传按钮外观时的默认 JSON(与前端数组语义一致)。</summary> | |
| 12 | + public const string DefaultButtonAppearanceJson = """["TEXT"]"""; | |
| 13 | + | |
| 14 | + /// <summary> | |
| 15 | + /// 规范化 <see cref="FoodLabeling.Application.Services.DbModels.FlLabelCategoryDbEntity.ButtonAppearance"/> / | |
| 16 | + /// 产品类别同名字段:落库为合法 JSON 文本,不做整串 ToUpper(避免破坏 JSON)。 | |
| 17 | + /// </summary> | |
| 18 | + public static string NormalizeButtonAppearanceForStorage(string? raw) | |
| 19 | + { | |
| 20 | + if (string.IsNullOrWhiteSpace(raw)) | |
| 21 | + { | |
| 22 | + return DefaultButtonAppearanceJson; | |
| 23 | + } | |
| 24 | + | |
| 25 | + var t = raw.Trim(); | |
| 26 | + var legacy = t.ToUpperInvariant(); | |
| 27 | + if (legacy is "TEXT" or "COLOR" or "IMAGE") | |
| 28 | + { | |
| 29 | + return JsonSerializer.Serialize(new[] { legacy }); | |
| 30 | + } | |
| 31 | + | |
| 32 | + try | |
| 33 | + { | |
| 34 | + using var _ = JsonDocument.Parse(t); | |
| 35 | + return t; | |
| 36 | + } | |
| 37 | + catch (JsonException) | |
| 38 | + { | |
| 39 | + throw new UserFriendlyException("按钮外观格式不正确,须为合法 JSON(或兼容旧的 TEXT/COLOR/IMAGE)"); | |
| 40 | + } | |
| 41 | + } | |
| 42 | + | |
| 43 | + /// <summary> | |
| 44 | + /// 规范化 <see cref="FoodLabeling.Application.Services.DbModels.FlLabelCategoryDbEntity.CategoryPhotoUrl"/> / | |
| 45 | + /// 产品类别同名字段:已是 JSON 则原样落库;否则将整段文本序列化为 JSON 字符串(兼容历史单行色值/URL)。 | |
| 46 | + /// </summary> | |
| 47 | + public static string? NormalizeCategoryPhotoUrlForStorage(string? raw) | |
| 48 | + { | |
| 49 | + if (string.IsNullOrWhiteSpace(raw)) | |
| 50 | + { | |
| 51 | + return null; | |
| 52 | + } | |
| 53 | + | |
| 54 | + var t = raw.Trim(); | |
| 55 | + try | |
| 56 | + { | |
| 57 | + using var _ = JsonDocument.Parse(t); | |
| 58 | + return t; | |
| 59 | + } | |
| 60 | + catch (JsonException) | |
| 61 | + { | |
| 62 | + return JsonSerializer.Serialize(t); | |
| 63 | + } | |
| 64 | + } | |
| 65 | +} | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLocationSupportDbEntity.cs
| ... | ... | @@ -21,8 +21,6 @@ public class FlLocationSupportDbEntity |
| 21 | 21 | |
| 22 | 22 | public DateTime? LastModificationTime { get; set; } |
| 23 | 23 | |
| 24 | - public string LocationId { get; set; } = string.Empty; | |
| 25 | - | |
| 26 | 24 | public string SupportPhone { get; set; } = string.Empty; |
| 27 | 25 | |
| 28 | 26 | public string SupportEmail { get; set; } = string.Empty; | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelCategoryAppService.cs
| ... | ... | @@ -125,9 +125,8 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ |
| 125 | 125 | } |
| 126 | 126 | |
| 127 | 127 | var displayText = input.DisplayText?.Trim(); |
| 128 | - var appearance = (input.ButtonAppearance ?? "TEXT").Trim().ToUpperInvariant(); | |
| 128 | + var appearance = CategoryAppearanceStorageHelper.NormalizeButtonAppearanceForStorage(input.ButtonAppearance); | |
| 129 | 129 | var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant(); |
| 130 | - ValidateButtonAppearance(appearance); | |
| 131 | 130 | var locationIds = NormalizeLocationIds(input.LocationIds); |
| 132 | 131 | ValidateAvailabilityTypeAndLocations(availabilityType, locationIds); |
| 133 | 132 | |
| ... | ... | @@ -146,7 +145,7 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ |
| 146 | 145 | CategoryCode = code, |
| 147 | 146 | CategoryName = name, |
| 148 | 147 | DisplayText = string.IsNullOrWhiteSpace(displayText) ? null : displayText, |
| 149 | - CategoryPhotoUrl = input.CategoryPhotoUrl?.Trim(), | |
| 148 | + CategoryPhotoUrl = CategoryAppearanceStorageHelper.NormalizeCategoryPhotoUrlForStorage(input.CategoryPhotoUrl), | |
| 150 | 149 | State = input.State, |
| 151 | 150 | ButtonAppearance = appearance, |
| 152 | 151 | AvailabilityType = availabilityType, |
| ... | ... | @@ -175,9 +174,8 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ |
| 175 | 174 | } |
| 176 | 175 | |
| 177 | 176 | var displayText = input.DisplayText?.Trim(); |
| 178 | - var appearance = (input.ButtonAppearance ?? "TEXT").Trim().ToUpperInvariant(); | |
| 177 | + var appearance = CategoryAppearanceStorageHelper.NormalizeButtonAppearanceForStorage(input.ButtonAppearance); | |
| 179 | 178 | var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant(); |
| 180 | - ValidateButtonAppearance(appearance); | |
| 181 | 179 | var locationIds = NormalizeLocationIds(input.LocationIds); |
| 182 | 180 | ValidateAvailabilityTypeAndLocations(availabilityType, locationIds); |
| 183 | 181 | |
| ... | ... | @@ -191,7 +189,7 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ |
| 191 | 189 | entity.CategoryCode = code; |
| 192 | 190 | entity.CategoryName = name; |
| 193 | 191 | entity.DisplayText = string.IsNullOrWhiteSpace(displayText) ? null : displayText; |
| 194 | - entity.CategoryPhotoUrl = input.CategoryPhotoUrl?.Trim(); | |
| 192 | + entity.CategoryPhotoUrl = CategoryAppearanceStorageHelper.NormalizeCategoryPhotoUrlForStorage(input.CategoryPhotoUrl); | |
| 195 | 193 | entity.State = input.State; |
| 196 | 194 | entity.ButtonAppearance = appearance; |
| 197 | 195 | entity.AvailabilityType = availabilityType; |
| ... | ... | @@ -264,14 +262,6 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ |
| 264 | 262 | .ToList() ?? new(); |
| 265 | 263 | } |
| 266 | 264 | |
| 267 | - private static void ValidateButtonAppearance(string appearance) | |
| 268 | - { | |
| 269 | - if (appearance != "TEXT" && appearance != "COLOR" && appearance != "IMAGE") | |
| 270 | - { | |
| 271 | - throw new UserFriendlyException("按钮外观不合法(TEXT/COLOR/IMAGE)"); | |
| 272 | - } | |
| 273 | - } | |
| 274 | - | |
| 275 | 265 | private async Task SaveCategoryLocationsAsync( |
| 276 | 266 | string categoryId, |
| 277 | 267 | string availabilityType, | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LocationSupportAppService.cs
| 1 | +using FoodLabeling.Application.Contracts; | |
| 1 | 2 | using FoodLabeling.Application.Contracts.Dtos.LocationSupport; |
| 2 | 3 | using FoodLabeling.Application.Contracts.IServices; |
| 3 | 4 | using FoodLabeling.Application.Services.DbModels; |
| 4 | -using FoodLabeling.Domain.Entities; | |
| 5 | +using Microsoft.AspNetCore.Authorization; | |
| 5 | 6 | using Volo.Abp; |
| 6 | 7 | using Volo.Abp.Application.Services; |
| 7 | 8 | using Volo.Abp.Guids; |
| ... | ... | @@ -11,8 +12,9 @@ using Yi.Framework.SqlSugarCore.Abstractions; |
| 11 | 12 | namespace FoodLabeling.Application.Services; |
| 12 | 13 | |
| 13 | 14 | /// <summary> |
| 14 | -/// 门店 Support 联系方式(后台设置,App 展示) | |
| 15 | +/// 全局 Support 联系方式(全门店共用;Web 可增改查,App JWT 仅可读) | |
| 15 | 16 | /// </summary> |
| 17 | +[Authorize] | |
| 16 | 18 | public class LocationSupportAppService : ApplicationService, ILocationSupportAppService |
| 17 | 19 | { |
| 18 | 20 | private readonly ISqlSugarDbContext _dbContext; |
| ... | ... | @@ -25,46 +27,36 @@ public class LocationSupportAppService : ApplicationService, ILocationSupportApp |
| 25 | 27 | } |
| 26 | 28 | |
| 27 | 29 | /// <inheritdoc /> |
| 28 | - public async Task<LocationSupportGetOutputDto?> GetByLocationIdAsync(string locationId) | |
| 30 | + public async Task<LocationSupportGetOutputDto?> GetSupportAsync() | |
| 29 | 31 | { |
| 30 | - var lid = locationId?.Trim(); | |
| 31 | - if (string.IsNullOrWhiteSpace(lid)) | |
| 32 | - { | |
| 33 | - throw new UserFriendlyException("门店Id不能为空"); | |
| 34 | - } | |
| 35 | - | |
| 36 | - // 全门店共用一套 Support 联系方式,按任意门店 Id 查询都返回同一条配置。 | |
| 37 | - var entity = await _dbContext.SqlSugarClient.Queryable<FlLocationSupportDbEntity>() | |
| 38 | - .FirstAsync(x => !x.IsDeleted); | |
| 39 | - if (entity is null) | |
| 40 | - { | |
| 41 | - return null; | |
| 42 | - } | |
| 43 | - | |
| 44 | - return await MapOutputAsync(entity); | |
| 32 | + var rows = await _dbContext.SqlSugarClient.Queryable<FlLocationSupportDbEntity>() | |
| 33 | + .Where(x => !x.IsDeleted) | |
| 34 | + .ToListAsync(); | |
| 35 | + var entity = rows.FirstOrDefault(); | |
| 36 | + return MapOutput(entity); | |
| 45 | 37 | } |
| 46 | 38 | |
| 47 | 39 | /// <inheritdoc /> |
| 48 | 40 | [UnitOfWork] |
| 49 | 41 | public async Task<LocationSupportGetOutputDto> CreateAsync(LocationSupportCreateInputVo input) |
| 50 | 42 | { |
| 43 | + EnsureNotUsAppClient(); | |
| 44 | + | |
| 51 | 45 | if (input is null) |
| 52 | 46 | { |
| 53 | - throw new UserFriendlyException("入参不能为空"); | |
| 47 | + throw new UserFriendlyException("Request body is required."); | |
| 54 | 48 | } |
| 55 | 49 | |
| 56 | - var locationId = NormalizeLocationId(input.LocationId); | |
| 57 | - var phone = NormalizeRequired(input.SupportPhone, "Support 电话不能为空"); | |
| 58 | - var email = NormalizeRequired(input.SupportEmail, "Support 邮箱不能为空"); | |
| 50 | + var phone = NormalizeRequired(input.SupportPhone, "Support phone is required."); | |
| 51 | + var email = NormalizeRequired(input.SupportEmail, "Support email is required."); | |
| 59 | 52 | EnsureEmailFormat(email); |
| 60 | 53 | |
| 61 | - await EnsureLocationExistsAsync(locationId); | |
| 62 | - | |
| 63 | 54 | var existed = await _dbContext.SqlSugarClient.Queryable<FlLocationSupportDbEntity>() |
| 64 | 55 | .AnyAsync(x => !x.IsDeleted); |
| 65 | 56 | if (existed) |
| 66 | 57 | { |
| 67 | - throw new UserFriendlyException("已存在全局 Support 联系方式,请使用编辑接口"); | |
| 58 | + throw new UserFriendlyException( | |
| 59 | + "Global support contact already exists. Use update instead."); | |
| 68 | 60 | } |
| 69 | 61 | |
| 70 | 62 | var now = Clock.Now; |
| ... | ... | @@ -76,74 +68,60 @@ public class LocationSupportAppService : ApplicationService, ILocationSupportApp |
| 76 | 68 | CreatorId = CurrentUser?.Id?.ToString(), |
| 77 | 69 | LastModificationTime = now, |
| 78 | 70 | LastModifierId = CurrentUser?.Id?.ToString(), |
| 79 | - LocationId = locationId, | |
| 80 | 71 | SupportPhone = phone, |
| 81 | 72 | SupportEmail = email |
| 82 | 73 | }; |
| 83 | 74 | |
| 84 | 75 | await _dbContext.SqlSugarClient.Insertable(entity).ExecuteCommandAsync(); |
| 85 | - return (await MapOutputAsync(entity))!; | |
| 76 | + return MapOutput(entity)!; | |
| 86 | 77 | } |
| 87 | 78 | |
| 88 | 79 | /// <inheritdoc /> |
| 89 | 80 | [UnitOfWork] |
| 90 | 81 | public async Task<LocationSupportGetOutputDto> UpdateAsync(string id, LocationSupportUpdateInputVo input) |
| 91 | 82 | { |
| 83 | + EnsureNotUsAppClient(); | |
| 84 | + | |
| 92 | 85 | var supportId = id?.Trim(); |
| 93 | 86 | if (string.IsNullOrWhiteSpace(supportId)) |
| 94 | 87 | { |
| 95 | - throw new UserFriendlyException("联系方式Id不能为空"); | |
| 88 | + throw new UserFriendlyException("Support record id is required."); | |
| 96 | 89 | } |
| 97 | 90 | |
| 98 | 91 | if (input is null) |
| 99 | 92 | { |
| 100 | - throw new UserFriendlyException("入参不能为空"); | |
| 93 | + throw new UserFriendlyException("Request body is required."); | |
| 101 | 94 | } |
| 102 | 95 | |
| 103 | - var entity = await _dbContext.SqlSugarClient.Queryable<FlLocationSupportDbEntity>() | |
| 104 | - .FirstAsync(x => !x.IsDeleted && x.Id == supportId); | |
| 96 | + var rows = await _dbContext.SqlSugarClient.Queryable<FlLocationSupportDbEntity>() | |
| 97 | + .Where(x => !x.IsDeleted && x.Id == supportId) | |
| 98 | + .ToListAsync(); | |
| 99 | + var entity = rows.FirstOrDefault(); | |
| 105 | 100 | if (entity is null) |
| 106 | 101 | { |
| 107 | - throw new UserFriendlyException("联系方式记录不存在"); | |
| 102 | + throw new UserFriendlyException("Support record not found."); | |
| 108 | 103 | } |
| 109 | 104 | |
| 110 | - var locationId = NormalizeLocationId(input.LocationId); | |
| 111 | - var phone = NormalizeRequired(input.SupportPhone, "Support 电话不能为空"); | |
| 112 | - var email = NormalizeRequired(input.SupportEmail, "Support 邮箱不能为空"); | |
| 105 | + var phone = NormalizeRequired(input.SupportPhone, "Support phone is required."); | |
| 106 | + var email = NormalizeRequired(input.SupportEmail, "Support email is required."); | |
| 113 | 107 | EnsureEmailFormat(email); |
| 114 | - await EnsureLocationExistsAsync(locationId); | |
| 115 | 108 | |
| 116 | - var conflict = await _dbContext.SqlSugarClient.Queryable<FlLocationSupportDbEntity>() | |
| 117 | - .AnyAsync(x => !x.IsDeleted && x.Id != supportId); | |
| 118 | - if (conflict) | |
| 119 | - { | |
| 120 | - throw new UserFriendlyException("系统仅允许一条全局 Support 联系方式"); | |
| 121 | - } | |
| 122 | - | |
| 123 | - entity.LocationId = locationId; | |
| 124 | 109 | entity.SupportPhone = phone; |
| 125 | 110 | entity.SupportEmail = email; |
| 126 | 111 | entity.LastModificationTime = Clock.Now; |
| 127 | 112 | entity.LastModifierId = CurrentUser?.Id?.ToString(); |
| 128 | 113 | |
| 129 | 114 | await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync(); |
| 130 | - return (await MapOutputAsync(entity))!; | |
| 115 | + return MapOutput(entity)!; | |
| 131 | 116 | } |
| 132 | 117 | |
| 133 | - private string NormalizeLocationId(string? locationId) | |
| 118 | + private void EnsureNotUsAppClient() | |
| 134 | 119 | { |
| 135 | - var lid = locationId?.Trim(); | |
| 136 | - if (string.IsNullOrWhiteSpace(lid)) | |
| 137 | - { | |
| 138 | - throw new UserFriendlyException("门店Id不能为空"); | |
| 139 | - } | |
| 140 | - | |
| 141 | - if (!Guid.TryParse(lid, out _)) | |
| 120 | + if (CurrentUser.FindClaim(UsAppJwtClaims.ClientKind)?.Value == UsAppJwtClaims.ClientKindUsApp) | |
| 142 | 121 | { |
| 143 | - throw new UserFriendlyException("门店Id格式不正确"); | |
| 122 | + throw new UserFriendlyException( | |
| 123 | + "The mobile app can only view support contacts. Please use the web console to edit."); | |
| 144 | 124 | } |
| 145 | - | |
| 146 | - return lid; | |
| 147 | 125 | } |
| 148 | 126 | |
| 149 | 127 | private static string NormalizeRequired(string? value, string message) |
| ... | ... | @@ -162,48 +140,22 @@ public class LocationSupportAppService : ApplicationService, ILocationSupportApp |
| 162 | 140 | if (!email.Contains("@", StringComparison.Ordinal) || email.StartsWith("@", StringComparison.Ordinal) || |
| 163 | 141 | email.EndsWith("@", StringComparison.Ordinal)) |
| 164 | 142 | { |
| 165 | - throw new UserFriendlyException("Support 邮箱格式不正确"); | |
| 143 | + throw new UserFriendlyException("Support email format is invalid."); | |
| 166 | 144 | } |
| 167 | 145 | } |
| 168 | 146 | |
| 169 | - private async Task EnsureLocationExistsAsync(string locationId) | |
| 170 | - { | |
| 171 | - if (!Guid.TryParse(locationId, out var gid)) | |
| 172 | - { | |
| 173 | - throw new UserFriendlyException("门店Id格式不正确"); | |
| 174 | - } | |
| 175 | - | |
| 176 | - var exists = await _dbContext.SqlSugarClient.Queryable<LocationAggregateRoot>() | |
| 177 | - .AnyAsync(x => !x.IsDeleted && x.Id == gid); | |
| 178 | - if (!exists) | |
| 179 | - { | |
| 180 | - throw new UserFriendlyException("门店不存在"); | |
| 181 | - } | |
| 182 | - } | |
| 183 | - | |
| 184 | - private async Task<LocationSupportGetOutputDto?> MapOutputAsync(FlLocationSupportDbEntity? entity) | |
| 147 | + private static LocationSupportGetOutputDto? MapOutput(FlLocationSupportDbEntity? entity) | |
| 185 | 148 | { |
| 186 | 149 | if (entity is null) |
| 187 | 150 | { |
| 188 | 151 | return null; |
| 189 | 152 | } |
| 190 | 153 | |
| 191 | - string? locationName = null; | |
| 192 | - if (Guid.TryParse(entity.LocationId, out var lid)) | |
| 193 | - { | |
| 194 | - var loc = await _dbContext.SqlSugarClient.Queryable<LocationAggregateRoot>() | |
| 195 | - .FirstAsync(x => !x.IsDeleted && x.Id == lid); | |
| 196 | - locationName = loc?.LocationName?.Trim(); | |
| 197 | - } | |
| 198 | - | |
| 199 | 154 | return new LocationSupportGetOutputDto |
| 200 | 155 | { |
| 201 | 156 | Id = entity.Id, |
| 202 | - LocationId = entity.LocationId, | |
| 203 | - LocationName = locationName, | |
| 204 | 157 | SupportPhone = entity.SupportPhone, |
| 205 | 158 | SupportEmail = entity.SupportEmail |
| 206 | 159 | }; |
| 207 | 160 | } |
| 208 | 161 | } |
| 209 | - | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductCategoryAppService.cs
| 1 | +using FoodLabeling.Application.Helpers; | |
| 1 | 2 | using FoodLabeling.Application.Contracts.Dtos.Common; |
| 2 | 3 | using FoodLabeling.Application.Contracts.Dtos.ProductCategory; |
| 3 | 4 | using FoodLabeling.Application.Contracts.IServices; |
| ... | ... | @@ -128,9 +129,8 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp |
| 128 | 129 | } |
| 129 | 130 | |
| 130 | 131 | var displayText = input.DisplayText?.Trim(); |
| 131 | - var appearance = (input.ButtonAppearance ?? "TEXT").Trim().ToUpperInvariant(); | |
| 132 | + var appearance = CategoryAppearanceStorageHelper.NormalizeButtonAppearanceForStorage(input.ButtonAppearance); | |
| 132 | 133 | var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant(); |
| 133 | - ValidateButtonAppearance(appearance); | |
| 134 | 134 | var locationIds = NormalizeLocationIds(input.LocationIds); |
| 135 | 135 | ValidateAvailabilityTypeAndLocations(availabilityType, locationIds); |
| 136 | 136 | |
| ... | ... | @@ -155,7 +155,7 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp |
| 155 | 155 | CategoryCode = code, |
| 156 | 156 | CategoryName = name, |
| 157 | 157 | DisplayText = string.IsNullOrWhiteSpace(displayText) ? null : displayText, |
| 158 | - CategoryPhotoUrl = input.CategoryPhotoUrl?.Trim(), | |
| 158 | + CategoryPhotoUrl = CategoryAppearanceStorageHelper.NormalizeCategoryPhotoUrlForStorage(input.CategoryPhotoUrl), | |
| 159 | 159 | ButtonAppearance = appearance, |
| 160 | 160 | State = input.State, |
| 161 | 161 | AvailabilityType = availabilityType, |
| ... | ... | @@ -187,9 +187,8 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp |
| 187 | 187 | } |
| 188 | 188 | |
| 189 | 189 | var displayText = input.DisplayText?.Trim(); |
| 190 | - var appearance = (input.ButtonAppearance ?? "TEXT").Trim().ToUpperInvariant(); | |
| 190 | + var appearance = CategoryAppearanceStorageHelper.NormalizeButtonAppearanceForStorage(input.ButtonAppearance); | |
| 191 | 191 | var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant(); |
| 192 | - ValidateButtonAppearance(appearance); | |
| 193 | 192 | var locationIds = NormalizeLocationIds(input.LocationIds); |
| 194 | 193 | ValidateAvailabilityTypeAndLocations(availabilityType, locationIds); |
| 195 | 194 | |
| ... | ... | @@ -203,7 +202,7 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp |
| 203 | 202 | entity.CategoryCode = code; |
| 204 | 203 | entity.CategoryName = name; |
| 205 | 204 | entity.DisplayText = string.IsNullOrWhiteSpace(displayText) ? null : displayText; |
| 206 | - entity.CategoryPhotoUrl = input.CategoryPhotoUrl?.Trim(); | |
| 205 | + entity.CategoryPhotoUrl = CategoryAppearanceStorageHelper.NormalizeCategoryPhotoUrlForStorage(input.CategoryPhotoUrl); | |
| 207 | 206 | entity.ButtonAppearance = appearance; |
| 208 | 207 | entity.State = input.State; |
| 209 | 208 | entity.AvailabilityType = availabilityType; |
| ... | ... | @@ -280,14 +279,6 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp |
| 280 | 279 | .ToList() ?? new(); |
| 281 | 280 | } |
| 282 | 281 | |
| 283 | - private static void ValidateButtonAppearance(string appearance) | |
| 284 | - { | |
| 285 | - if (appearance != "TEXT" && appearance != "COLOR" && appearance != "IMAGE") | |
| 286 | - { | |
| 287 | - throw new UserFriendlyException("按钮外观不合法(TEXT/COLOR/IMAGE)"); | |
| 288 | - } | |
| 289 | - } | |
| 290 | - | |
| 291 | 282 | private async Task SaveCategoryLocationsAsync( |
| 292 | 283 | string categoryId, |
| 293 | 284 | string availabilityType, | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppAuthAppService.cs
| ... | ... | @@ -6,6 +6,7 @@ using System.Linq; |
| 6 | 6 | using System.Security.Claims; |
| 7 | 7 | using System.Text; |
| 8 | 8 | using System.Threading.Tasks; |
| 9 | +using FoodLabeling.Application.Contracts; | |
| 9 | 10 | using FoodLabeling.Application.Contracts.Dtos.UsAppAuth; |
| 10 | 11 | using FoodLabeling.Application.Contracts.IServices; |
| 11 | 12 | using FoodLabeling.Application.Services.DbModels; |
| ... | ... | @@ -432,7 +433,8 @@ public class UsAppAuthAppService : ApplicationService, IUsAppAuthAppService |
| 432 | 433 | var claims = new List<Claim> |
| 433 | 434 | { |
| 434 | 435 | new(AbpClaimTypes.UserId, user.Id.ToString()), |
| 435 | - new(AbpClaimTypes.UserName, user.UserName) | |
| 436 | + new(AbpClaimTypes.UserName, user.UserName), | |
| 437 | + new(UsAppJwtClaims.ClientKind, UsAppJwtClaims.ClientKindUsApp) | |
| 436 | 438 | }; |
| 437 | 439 | |
| 438 | 440 | if (!string.IsNullOrWhiteSpace(user.Email)) | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppLabelingAppService.cs
| ... | ... | @@ -129,7 +129,7 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ |
| 129 | 129 | { |
| 130 | 130 | var l1Appearance = string.IsNullOrWhiteSpace(g1.Key.LabelCategoryButtonAppearance) |
| 131 | 131 | ? "TEXT" |
| 132 | - : g1.Key.LabelCategoryButtonAppearance.Trim().ToUpperInvariant(); | |
| 132 | + : g1.Key.LabelCategoryButtonAppearance.Trim(); | |
| 133 | 133 | var l1 = new UsAppLabelCategoryTreeNodeDto |
| 134 | 134 | { |
| 135 | 135 | Id = g1.Key.LabelCategoryId, |
| ... | ... | @@ -178,7 +178,7 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ |
| 178 | 178 | var productsGrouped = g2.GroupBy(x => x.ProductId).OrderBy(pg => pg.First().ProductName); |
| 179 | 179 | var appearance = string.IsNullOrWhiteSpace(g2.Key.ButtonAppearance) |
| 180 | 180 | ? "TEXT" |
| 181 | - : g2.Key.ButtonAppearance.Trim().ToUpperInvariant(); | |
| 181 | + : g2.Key.ButtonAppearance.Trim(); | |
| 182 | 182 | var availability = string.IsNullOrWhiteSpace(g2.Key.AvailabilityType) |
| 183 | 183 | ? "ALL" |
| 184 | 184 | : g2.Key.AvailabilityType.Trim().ToUpperInvariant(); | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_location_support_alter_drop_locationid.sql
0 → 100644
| 1 | +-- 从旧版「按门店」结构迁移为「全局一条」:删除 LocationId 及门店唯一索引 | |
| 2 | +-- 执行前请确认库中 `fl_location_support` 已存在;若不存在可跳过本脚本,直接使用 fl_location_support_create.sql | |
| 3 | + | |
| 4 | +-- 若存在按门店的唯一索引则删除(名称与历史脚本一致) | |
| 5 | +ALTER TABLE `fl_location_support` DROP INDEX `uk_fl_location_support_locationid`; | |
| 6 | + | |
| 7 | +-- 删除门店列(若列不存在会报错,需按环境调整) | |
| 8 | +ALTER TABLE `fl_location_support` DROP COLUMN `LocationId`; | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_location_support_create.sql
| 1 | --- 门店 Support 联系方式(每个门店仅一条) | |
| 1 | +-- 全局 Support 联系方式(全门店共用一条) | |
| 2 | 2 | CREATE TABLE IF NOT EXISTS `fl_location_support` ( |
| 3 | 3 | `Id` varchar(50) NOT NULL COMMENT '主键', |
| 4 | 4 | `IsDeleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除', |
| ... | ... | @@ -6,11 +6,8 @@ CREATE TABLE IF NOT EXISTS `fl_location_support` ( |
| 6 | 6 | `CreatorId` varchar(50) DEFAULT NULL COMMENT '创建人', |
| 7 | 7 | `LastModifierId` varchar(50) DEFAULT NULL COMMENT '最后修改人', |
| 8 | 8 | `LastModificationTime` datetime DEFAULT NULL COMMENT '最后修改时间', |
| 9 | - `LocationId` varchar(50) NOT NULL COMMENT '门店Id(对应location.Id)', | |
| 10 | 9 | `SupportPhone` varchar(100) NOT NULL COMMENT 'Support 电话', |
| 11 | 10 | `SupportEmail` varchar(200) NOT NULL COMMENT 'Support 邮箱', |
| 12 | 11 | PRIMARY KEY (`Id`), |
| 13 | - UNIQUE KEY `uk_fl_location_support_locationid` (`LocationId`), | |
| 14 | 12 | KEY `idx_fl_location_support_isdeleted` (`IsDeleted`) |
| 15 | -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='门店 Support 联系方式'; | |
| 16 | - | |
| 13 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='全局 Support 联系方式'; | ... | ... |
项目相关文档/产品模块Categories接口对接说明.md
| ... | ... | @@ -8,7 +8,9 @@ |
| 8 | 8 | - **接口前缀**:宿主统一前缀为 `/api/app` |
| 9 | 9 | - **分类表**:`fl_product_category` |
| 10 | 10 | - **关联字段**:`fl_product.category_id` → `fl_product_category.id` |
| 11 | -- **图片字段**:`CategoryPhotoUrl`(前端字段:`categoryPhotoUrl`) | |
| 11 | +- **外观字段(字符串落库,内容为 JSON 文本)**: | |
| 12 | + - `ButtonAppearance`(`buttonAppearance`):如 `["TEXT","COLOR"]`、仅图片 `["IMAGE"]`、或合法 JSON 对象/数组;兼容历史单行 `TEXT`/`COLOR`/`IMAGE`(保存时会规范为 JSON 数组,如 `["TEXT"]`)。 | |
| 13 | + - `CategoryPhotoUrl`(`categoryPhotoUrl`):与外观配合的**展示数据**,同样为 **JSON 字符串**(如 `["Prep","#10B981"]`、图片 URL 数组等);若传入**非 JSON** 的纯文本(如旧数据中的 `#EC4899` 或 `/picture/...`),后端会序列化为合法 JSON 字符串再存储。列表/详情/App 树**原样返回**库中字符串,由前端解析。 | |
| 12 | 14 | |
| 13 | 15 | > 说明:本文以 Swagger 为准(本地示例:`http://localhost:19001/swagger`,搜索 `ProductCategory`)。 |
| 14 | 16 | |
| ... | ... | @@ -57,7 +59,10 @@ Authorization: Bearer eyJhbGciOi... |
| 57 | 59 | | `id` | string | 主键 | |
| 58 | 60 | | `categoryCode` | string | 类别编码 | |
| 59 | 61 | | `categoryName` | string | 类别名称 | |
| 60 | -| `categoryPhotoUrl` | string \| null | 类别图片 URL(建议用 `/picture/...`) | | |
| 62 | +| `displayText` | string \| null | 按钮展示文案(空可回退 `categoryName`) | | |
| 63 | +| `categoryPhotoUrl` | string \| null | 分类展示数据,**JSON 格式字符串**(含义由前端与 `buttonAppearance` 约定) | | |
| 64 | +| `buttonAppearance` | string | 按钮外观,**JSON 格式字符串**(见上文「外观字段」) | | |
| 65 | +| `availabilityType` | string | `ALL` / `SPECIFIED`(门店可用范围) | | |
| 61 | 66 | | `state` | boolean | 是否启用 | |
| 62 | 67 | | `orderNum` | number | 排序 | |
| 63 | 68 | | `lastEdited` | string | 最后编辑时间 | |
| ... | ... | @@ -75,7 +80,10 @@ Authorization: Bearer eyJhbGciOi... |
| 75 | 80 | "id": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f", |
| 76 | 81 | "categoryCode": "CAT_PREP", |
| 77 | 82 | "categoryName": "Prep", |
| 78 | - "categoryPhotoUrl": "/picture/category/20260325123010_xxx.png", | |
| 83 | + "displayText": "Prep", | |
| 84 | + "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]", | |
| 85 | + "buttonAppearance": "[\"TEXT\",\"COLOR\"]", | |
| 86 | + "availabilityType": "ALL", | |
| 79 | 87 | "state": true, |
| 80 | 88 | "orderNum": 100, |
| 81 | 89 | "lastEdited": "2026-03-25 12:30:10" |
| ... | ... | @@ -108,7 +116,11 @@ Authorization: Bearer eyJhbGciOi... |
| 108 | 116 | "id": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f", |
| 109 | 117 | "categoryCode": "CAT_PREP", |
| 110 | 118 | "categoryName": "Prep", |
| 111 | - "categoryPhotoUrl": "/picture/category/20260325123010_xxx.png", | |
| 119 | + "displayText": "Prep", | |
| 120 | + "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]", | |
| 121 | + "buttonAppearance": "[\"TEXT\",\"COLOR\"]", | |
| 122 | + "availabilityType": "ALL", | |
| 123 | + "locationIds": [], | |
| 112 | 124 | "state": true, |
| 113 | 125 | "orderNum": 100 |
| 114 | 126 | } |
| ... | ... | @@ -130,7 +142,11 @@ Authorization: Bearer eyJhbGciOi... |
| 130 | 142 | |------|------|------|------| |
| 131 | 143 | | `categoryCode` | string | 是 | 类别编码(唯一) | |
| 132 | 144 | | `categoryName` | string | 是 | 类别名称(唯一) | |
| 133 | -| `categoryPhotoUrl` | string \| null | 否 | 图片 URL(建议先上传图片拿到 `/picture/...` 再保存) | | |
| 145 | +| `displayText` | string \| null | 否 | 按钮展示文案 | | |
| 146 | +| `categoryPhotoUrl` | string \| null | 否 | **JSON 字符串**;与 `buttonAppearance` 配合(见概述)。纯路径等非 JSON 文本会被后端包成 JSON 字符串存储。 | | |
| 147 | +| `buttonAppearance` | string | 否 | **JSON 字符串**;未传或空白时后端默认 `["TEXT"]`。兼容传 `TEXT`/`COLOR`/`IMAGE` 单行(会规范为 `["TEXT"]` 等)。非法非 JSON 且非上述三者时报错。 | | |
| 148 | +| `availabilityType` | string | 否 | `ALL`(默认)或 `SPECIFIED` | | |
| 149 | +| `locationIds` | string[] | 条件 | `availabilityType=SPECIFIED` 时必填且至少 1 个门店 Id | | |
| 134 | 150 | | `state` | boolean | 否 | 是否启用(默认 true) | |
| 135 | 151 | | `orderNum` | number | 否 | 排序(默认 0) | |
| 136 | 152 | |
| ... | ... | @@ -140,7 +156,11 @@ Authorization: Bearer eyJhbGciOi... |
| 140 | 156 | { |
| 141 | 157 | "categoryCode": "CAT_PREP", |
| 142 | 158 | "categoryName": "Prep", |
| 143 | - "categoryPhotoUrl": "/picture/category/20260325123010_xxx.png", | |
| 159 | + "displayText": "Prep", | |
| 160 | + "buttonAppearance": "[\"TEXT\",\"COLOR\"]", | |
| 161 | + "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]", | |
| 162 | + "availabilityType": "ALL", | |
| 163 | + "locationIds": [], | |
| 144 | 164 | "state": true, |
| 145 | 165 | "orderNum": 100 |
| 146 | 166 | } |
| ... | ... | @@ -162,7 +182,11 @@ Authorization: Bearer eyJhbGciOi... |
| 162 | 182 | { |
| 163 | 183 | "categoryCode": "CAT_PREP", |
| 164 | 184 | "categoryName": "Prep", |
| 165 | - "categoryPhotoUrl": "/picture/category/20260325123010_xxx.png", | |
| 185 | + "displayText": "Prep", | |
| 186 | + "buttonAppearance": "[\"TEXT\",\"COLOR\"]", | |
| 187 | + "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]", | |
| 188 | + "availabilityType": "ALL", | |
| 189 | + "locationIds": [], | |
| 166 | 190 | "state": true, |
| 167 | 191 | "orderNum": 100 |
| 168 | 192 | } |
| ... | ... | @@ -179,7 +203,7 @@ Authorization: Bearer eyJhbGciOi... |
| 179 | 203 | |
| 180 | 204 | ### 约束 |
| 181 | 205 | |
| 182 | -- 若该类别已被 `fl_label` 引用(`fl_label.LabelCategoryId = id`),删除会失败并返回友好提示:`该类别已被标签引用,无法删除`。 | |
| 206 | +- 若该类别已被 `fl_product` 引用(`fl_product.CategoryId = id`),删除会失败并返回友好提示:`该类别已被产品引用,无法删除`。 | |
| 183 | 207 | |
| 184 | 208 | ### 请求示例 |
| 185 | 209 | |
| ... | ... | @@ -200,5 +224,5 @@ Authorization: Bearer eyJhbGciOi... |
| 200 | 224 | 推荐前端流程: |
| 201 | 225 | |
| 202 | 226 | 1. 调用上传接口 `POST /api/app/picture/category/upload` 拿到响应 `url` |
| 203 | -2. 新增/编辑类别时把 `categoryPhotoUrl` 设为该 `url` | |
| 227 | +2. 新增/编辑类别时:若采用 **JSON** 存展示数据,将 `url` 写入你方约定的 JSON 结构(例如 `["IMAGE","/picture/..."]`);若仍传**纯路径字符串**,后端会将其序列化为 JSON 字符串再入库(与仅图片场景兼容)。 | |
| 204 | 228 | ... | ... |
项目相关文档/平台端Categories图片上传接口说明.md
| ... | ... | @@ -54,7 +54,7 @@ curl -X POST "http://localhost:19001/api/app/picture/category/upload" ^ |
| 54 | 54 | |
| 55 | 55 | | 字段 | 类型 | 说明 | |
| 56 | 56 | |------|------|------| |
| 57 | -| `url` | string | 图片访问的相对路径,可直接保存到 `CategoryPhotoUrl`(例如:`/picture/category/xxx.png`) | | |
| 57 | +| `url` | string | 图片访问的相对路径;写入分类接口时,若 `categoryPhotoUrl` 采用 **JSON** 存展示数据,请将该 `url` 放入你方约定的 JSON 结构中。若仍传**纯路径字符串**,后端会序列化为 JSON 字符串再入库。 | | |
| 58 | 58 | | `fileName` | string | 服务器保存的文件名 | |
| 59 | 59 | | `size` | number | 文件大小(字节) | |
| 60 | 60 | |
| ... | ... | @@ -96,7 +96,7 @@ curl -X POST "http://localhost:19001/api/app/picture/category/upload" ^ |
| 96 | 96 | 推荐前端流程: |
| 97 | 97 | |
| 98 | 98 | 1. 调用本上传接口,拿到返回的 `url` |
| 99 | -2. 再调用分类新增/编辑接口,把 `categoryPhotoUrl` 设置为该 `url` | |
| 99 | +2. 再调用分类新增/编辑接口:按平台与 **`buttonAppearance`(JSON 字符串)** 的约定组装 `categoryPhotoUrl`(JSON);或继续传纯 `url` 由后端自动包成 JSON 字符串。 | |
| 100 | 100 | |
| 101 | -> 说明:分类 CRUD 已支持 `CategoryPhotoUrl` 字段;你只需要在页面表单里新增该字段即可。 | |
| 101 | +> 说明:详见 `项目相关文档/产品模块Categories接口对接说明.md`、`项目相关文档/标签模块接口对接说明.md` 中「JSON 字符串」约定。 | |
| 102 | 102 | ... | ... |
项目相关文档/本次新增与优化接口汇总.md
| ... | ... | @@ -42,8 +42,8 @@ |
| 42 | 42 | |
| 43 | 43 | - `fl_product_category`(主表)关键字段: |
| 44 | 44 | - `DisplayText`:按钮展示文案(为空可回退 `CategoryName`) |
| 45 | - - `ButtonAppearance`:`TEXT/COLOR/IMAGE`(前端按类型解析) | |
| 46 | - - `CategoryPhotoUrl`:与 `ButtonAppearance` 配合——`COLOR` 存颜色值、`IMAGE` 存图片 URL;`TEXT` 可空或不用 | |
| 45 | + - `ButtonAppearance`:**JSON 格式字符串**落库(如 `["TEXT","COLOR"]`、`["IMAGE"]`);兼容历史单行 `TEXT`/`COLOR`/`IMAGE`(保存时规范为 JSON 数组) | |
| 46 | + - `CategoryPhotoUrl`:**JSON 格式字符串**落库(展示数据由前端解析);非 JSON 纯文本(色值、URL 等)保存时会被后端包成 JSON 字符串 | |
| 47 | 47 | - **已删除列**(需在库上执行 `DROP COLUMN`):`ButtonTextColor`、`ButtonBgColor`、`ButtonImageUrl`、`ButtonStyleJson` |
| 48 | 48 | - `AvailabilityType`:`ALL/SPECIFIED`(门店可用范围) |
| 49 | 49 | - `fl_product_category_location`(关联表): |
| ... | ... | @@ -69,7 +69,7 @@ |
| 69 | 69 | - **路径**:`/api/app/product-category/{id}` |
| 70 | 70 | - **新增返回字段**: |
| 71 | 71 | - `displayText` |
| 72 | - - `buttonAppearance`、`categoryPhotoUrl`(COLOR/IMAGE 的展示数据统一在此字段) | |
| 72 | + - `buttonAppearance`、`categoryPhotoUrl`(**JSON 格式字符串**,展示语义由前端解析) | |
| 73 | 73 | - `availabilityType` |
| 74 | 74 | - `locationIds`(当 `availabilityType=SPECIFIED` 返回门店 Id 列表,否则为空数组) |
| 75 | 75 | |
| ... | ... | @@ -99,7 +99,7 @@ |
| 99 | 99 | |
| 100 | 100 | - `availabilityType` 仅允许 `ALL/SPECIFIED` |
| 101 | 101 | - `SPECIFIED` 时 `locationIds` 至少 1 个 |
| 102 | -- `buttonAppearance` 仅允许 `TEXT/COLOR/IMAGE`(接口层校验枚举;`categoryPhotoUrl` 是否必填由业务/前端约定,`COLOR/IMAGE` 时应写入该字段) | |
| 102 | +- `buttonAppearance`:须为 **合法 JSON**(任意对象/数组),或为兼容的 **`TEXT`/`COLOR`/`IMAGE` 单行**;其它字符串拒绝。`categoryPhotoUrl` 非空且非 JSON 时后端会序列化为 JSON 字符串存储;是否必填由业务/前端约定 | |
| 103 | 103 | |
| 104 | 104 | --- |
| 105 | 105 | |
| ... | ... | @@ -111,8 +111,8 @@ |
| 111 | 111 | |
| 112 | 112 | - `fl_label_category`(主表)关键字段: |
| 113 | 113 | - `DisplayText`:按钮展示文案(为空可回退 `CategoryName`) |
| 114 | - - `ButtonAppearance`:`TEXT/COLOR/IMAGE`(前端按类型解析) | |
| 115 | - - `CategoryPhotoUrl`:与 `ButtonAppearance` 配合——`COLOR` 存颜色值、`IMAGE` 存图片 URL;`TEXT` 可空或不用 | |
| 114 | + - `ButtonAppearance`:**JSON 格式字符串**落库(如 `["TEXT","COLOR"]`、`["IMAGE"]`);兼容历史单行 `TEXT`/`COLOR`/`IMAGE`(保存时规范为 JSON 数组) | |
| 115 | + - `CategoryPhotoUrl`:**JSON 格式字符串**落库(展示数据由前端解析);非 JSON 纯文本(色值、URL 等)保存时会被后端包成 JSON 字符串 | |
| 116 | 116 | - **已删除列**(需在库上执行 `DROP COLUMN`):`ButtonTextColor`、`ButtonBgColor`、`ButtonImageUrl`、`ButtonStyleJson` |
| 117 | 117 | - `AvailabilityType`:`ALL/SPECIFIED`(门店可用范围) |
| 118 | 118 | - `fl_label_category_location`(关联表): |
| ... | ... | @@ -138,7 +138,7 @@ |
| 138 | 138 | - **路径**:`/api/app/label-category/{id}` |
| 139 | 139 | - **新增返回字段**: |
| 140 | 140 | - `displayText` |
| 141 | - - `buttonAppearance`、`categoryPhotoUrl`(COLOR/IMAGE 的展示数据统一在此字段) | |
| 141 | + - `buttonAppearance`、`categoryPhotoUrl`(**JSON 格式字符串**,展示语义由前端解析) | |
| 142 | 142 | - `availabilityType` |
| 143 | 143 | - `locationIds`(当 `availabilityType=SPECIFIED` 返回门店 Id 列表,否则为空数组) |
| 144 | 144 | |
| ... | ... | @@ -168,14 +168,14 @@ |
| 168 | 168 | |
| 169 | 169 | - `availabilityType` 仅允许 `ALL/SPECIFIED` |
| 170 | 170 | - `SPECIFIED` 时 `locationIds` 至少 1 个 |
| 171 | -- `buttonAppearance` 仅允许 `TEXT/COLOR/IMAGE`(接口层校验枚举;`categoryPhotoUrl` 是否必填由业务/前端约定,`COLOR/IMAGE` 时应写入该字段) | |
| 171 | +- `buttonAppearance`:须为 **合法 JSON**(任意对象/数组),或为兼容的 **`TEXT`/`COLOR`/`IMAGE` 单行**;其它字符串拒绝。`categoryPhotoUrl` 非空且非 JSON 时后端会序列化为 JSON 字符串存储;是否必填由业务/前端约定 | |
| 172 | 172 | |
| 173 | 173 | --- |
| 174 | 174 | |
| 175 | 175 | ## 4. App 端 `GET /api/app/us-app-labeling/labeling-tree` |
| 176 | 176 | |
| 177 | -- **L1(标签分类)节点**:除原有 `categoryName`、`categoryPhotoUrl`、`orderNum` 等外,**返回 `buttonAppearance`**(缺省或空时后端按 `TEXT` 规范化为大写)。 | |
| 178 | -- **L2(产品分类)节点**:仅 `buttonAppearance` + `categoryPhotoUrl` 承载外观数据(已不再返回 `buttonTextColor`、`buttonBgColor`、`buttonImageUrl`、`buttonStyleJson`)。 | |
| 177 | +- **L1(标签分类)节点**:返回 `categoryPhotoUrl`、`buttonAppearance`(均为库中字符串,**多为 JSON**,与 CRUD 一致);缺省或空时 `buttonAppearance` 后端默认 **`"TEXT"`**(兼容旧数据,**不再**对整段做 `ToUpperInvariant` 以免破坏 JSON)。 | |
| 178 | +- **L2(产品分类)节点**:返回 `displayText`、`buttonAppearance`、`categoryPhotoUrl`、`availabilityType`、`orderNum` 等;外观数据由 **`buttonAppearance` + `categoryPhotoUrl`** 承载(已不再返回 `buttonTextColor`、`buttonBgColor`、`buttonImageUrl`、`buttonStyleJson`)。 | |
| 179 | 179 | |
| 180 | 180 | ### 4.1 数据库迁移(两张主表) |
| 181 | 181 | ... | ... |
项目相关文档/标签模块接口对接说明.md
| ... | ... | @@ -51,6 +51,12 @@ Swagger 地址: |
| 51 | 51 | } |
| 52 | 52 | ``` |
| 53 | 53 | |
| 54 | +### 1.1.1 字段约定:`buttonAppearance` 与 `categoryPhotoUrl`(JSON 字符串) | |
| 55 | + | |
| 56 | +- **`buttonAppearance`**:库中存 **JSON 文本**(如 `["TEXT","COLOR"]`、仅图片 `["IMAGE"]` 等);兼容历史单行 `TEXT`/`COLOR`/`IMAGE`(保存时规范为 `["TEXT"]` 等)。未传或空白时后端默认 `["TEXT"]`。非法值(非 JSON 且非上述三者)会返回友好错误。 | |
| 57 | +- **`categoryPhotoUrl`**:同样为 **JSON 文本**(如 `["Prep","#10B981"]`);若传**非 JSON** 的纯文本(色值、`/picture/...` 等),后端会序列化为合法 JSON 字符串再存储。列表/详情/App 树**原样返回**字符串,由客户端解析。 | |
| 58 | +- 其它常用字段:`displayText`、`availabilityType`(`ALL`/`SPECIFIED`)、`locationIds`(指定门店时必填),与产品类别接口语义一致(见 `项目相关文档/产品模块Categories接口对接说明.md`)。 | |
| 59 | + | |
| 54 | 60 | ### 1.2 详情 |
| 55 | 61 | |
| 56 | 62 | 方法:`GET /api/app/label-category/{id}` |
| ... | ... | @@ -69,7 +75,11 @@ Swagger 地址: |
| 69 | 75 | { |
| 70 | 76 | "categoryCode": "CAT_PREP", |
| 71 | 77 | "categoryName": "Prep", |
| 72 | - "categoryPhotoUrl": "https://cdn.example.com/cat-prep.png", | |
| 78 | + "displayText": "Prep", | |
| 79 | + "buttonAppearance": "[\"TEXT\",\"COLOR\"]", | |
| 80 | + "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]", | |
| 81 | + "availabilityType": "ALL", | |
| 82 | + "locationIds": [], | |
| 73 | 83 | "state": true, |
| 74 | 84 | "orderNum": 1 |
| 75 | 85 | } |
| ... | ... | @@ -85,7 +95,11 @@ Swagger 地址: |
| 85 | 95 | { |
| 86 | 96 | "categoryCode": "CAT_PREP", |
| 87 | 97 | "categoryName": "Prep", |
| 88 | - "categoryPhotoUrl": null, | |
| 98 | + "displayText": "Prep", | |
| 99 | + "buttonAppearance": "[\"TEXT\",\"COLOR\"]", | |
| 100 | + "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]", | |
| 101 | + "availabilityType": "ALL", | |
| 102 | + "locationIds": [], | |
| 89 | 103 | "state": true, |
| 90 | 104 | "orderNum": 2 |
| 91 | 105 | } |
| ... | ... | @@ -706,7 +720,8 @@ ADD UNIQUE KEY `uk_fl_ltpd_template_product_label_type` (`TemplateId`, `ProductI |
| 706 | 720 | |------|------|------| |
| 707 | 721 | | `id` | string | `fl_label_category.Id` | |
| 708 | 722 | | `categoryName` | string | 分类名称 | |
| 709 | -| `categoryPhotoUrl` | string \| null | 分类图标/图 | | |
| 723 | +| `categoryPhotoUrl` | string \| null | 分类展示数据,**JSON 格式字符串**(与库中 `CategoryPhotoUrl` 一致,客户端解析) | | |
| 724 | +| `buttonAppearance` | string | 按钮外观,**JSON 格式字符串**(与库中 `ButtonAppearance` 一致;空时后端默认 `"TEXT"`) | | |
| 710 | 725 | | `orderNum` | number | 排序 | |
| 711 | 726 | | `productCategories` | array | 第二级列表(见下表) | |
| 712 | 727 | |
| ... | ... | @@ -715,8 +730,12 @@ ADD UNIQUE KEY `uk_fl_ltpd_template_product_label_type` (`TemplateId`, `ProductI |
| 715 | 730 | | 字段 | 类型 | 说明 | |
| 716 | 731 | |------|------|------| |
| 717 | 732 | | `categoryId` | string \| null | 产品分类Id;产品未归类或分类不存在时为空 | |
| 718 | -| `categoryPhotoUrl` | string \| null | 产品分类图片地址;产品未归类或分类不存在时为空 | | |
| 733 | +| `categoryPhotoUrl` | string \| null | 产品分类展示数据,**JSON 格式字符串**;未归类或分类不存在时为空 | | |
| 719 | 734 | | `name` | string | 产品分类显示名;空源数据为 **`无`** | |
| 735 | +| `displayText` | string \| null | 按钮展示文案 | | |
| 736 | +| `buttonAppearance` | string | **JSON 格式字符串**(与库中一致;空时默认 `"TEXT"`) | | |
| 737 | +| `availabilityType` | string | `ALL` / `SPECIFIED`(树已按当前门店过滤,仍返回供客户端展示) | | |
| 738 | +| `orderNum` | number | 排序 | | |
| 720 | 739 | | `itemCount` | number | 该分类下 **产品个数**(去重后的产品数) | |
| 721 | 740 | | `products` | array | 第三级产品列表(见下表) | |
| 722 | 741 | |
| ... | ... | @@ -771,13 +790,18 @@ curl -X GET "http://localhost:19001/api/app/us-app-labeling/labeling-tree?locati |
| 771 | 790 | { |
| 772 | 791 | "id": "cat-prep-id", |
| 773 | 792 | "categoryName": "Prep", |
| 774 | - "categoryPhotoUrl": "/picture/...", | |
| 793 | + "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]", | |
| 794 | + "buttonAppearance": "[\"TEXT\",\"COLOR\"]", | |
| 775 | 795 | "orderNum": 1, |
| 776 | 796 | "productCategories": [ |
| 777 | 797 | { |
| 778 | 798 | "categoryId": "pc-meat-id", |
| 779 | - "categoryPhotoUrl": "/picture/product-category/20260325123010_xxx.png", | |
| 799 | + "categoryPhotoUrl": "[\"/picture/product-category/20260325123010_xxx.png\"]", | |
| 780 | 800 | "name": "Meat", |
| 801 | + "displayText": "Meat", | |
| 802 | + "buttonAppearance": "[\"IMAGE\"]", | |
| 803 | + "availabilityType": "ALL", | |
| 804 | + "orderNum": 10, | |
| 781 | 805 | "itemCount": 1, |
| 782 | 806 | "products": [ |
| 783 | 807 | { | ... | ... |
项目相关文档/美国版App登录接口说明.md
| ... | ... | @@ -285,54 +285,53 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... |
| 285 | 285 | |
| 286 | 286 | --- |
| 287 | 287 | |
| 288 | -## 接口 6:门店 Support 联系方式(App 展示) | |
| 288 | +## 接口 6:全局 Support 联系方式(App 只读 + Web 可读) | |
| 289 | 289 | |
| 290 | -用于 App「Support」页面读取联系方式(电话、邮箱)。 | |
| 291 | -后台由 `LocationSupportAppService` 维护,规则已调整为 **全门店共用一条全局联系方式**。 | |
| 290 | +用于 App「Support」页与 Web 展示**全平台共用**的一条电话与邮箱。 | |
| 291 | +实现:`LocationSupportAppService`,表 `fl_location_support` **不再包含门店 Id**。 | |
| 292 | 292 | |
| 293 | -### HTTP | |
| 293 | +### HTTP(查询) | |
| 294 | 294 | |
| 295 | 295 | - **方法**:`GET` |
| 296 | -- **路径**:`/api/app/location-support/by-location-id?locationId={locationId}`(以 Swagger 中 `LocationSupport` 为准) | |
| 297 | -- **鉴权**:需要登录(`Authorization: Bearer {token}`) | |
| 296 | +- **路径**(约定式 API,以 Swagger 中 `LocationSupport` → `GetSupport` 为准):一般为 **`/api/app/location-support/support`** | |
| 297 | +- **鉴权**:需要登录(`Authorization: Bearer {token}`)。**App 登录 Token 与 Web Token 均可调用本接口。** | |
| 298 | 298 | |
| 299 | 299 | ### 请求参数 |
| 300 | 300 | |
| 301 | -| 参数名 | 位置 | 类型 | 必填 | 说明 | | |
| 302 | -|--------|------|------|------|------| | |
| 303 | -| `locationId` | Query | string | 是 | 门店主键(Guid 字符串)。当前用于入参兼容,返回值按全局联系方式配置 | | |
| 301 | +无 Query / Body。 | |
| 304 | 302 | |
| 305 | 303 | ### 响应体(LocationSupportGetOutputDto) |
| 306 | 304 | |
| 307 | 305 | | 字段(JSON) | 类型 | 说明 | |
| 308 | 306 | |--------------|------|------| |
| 309 | -| `id` | string | 联系方式主键 | | |
| 310 | -| `locationId` | string | 配置记录中的门店主键(仅作兼容字段) | | |
| 311 | -| `locationName` | string \| null | 配置记录中的门店名称(仅作兼容字段) | | |
| 307 | +| `id` | string | 记录主键(Web 编辑 `PUT` 路径中的 `{id}`) | | |
| 312 | 308 | | `supportPhone` | string | Support 电话 | |
| 313 | 309 | | `supportEmail` | string | Support 邮箱 | |
| 314 | 310 | |
| 315 | -> 若门店尚未配置联系方式,接口返回 `null`。 | |
| 311 | +> 若尚未在后台配置,接口返回 `null`。 | |
| 316 | 312 | |
| 317 | 313 | ### 响应示例 |
| 318 | 314 | |
| 319 | 315 | ```json |
| 320 | 316 | { |
| 321 | 317 | "id": "3a2f4fda-1a93-4a35-9b98-95dca7bb5d2a", |
| 322 | - "locationId": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f", | |
| 323 | - "locationName": "Downtown Store", | |
| 324 | 318 | "supportPhone": "1-800-SUPPORT", |
| 325 | 319 | "supportEmail": "support@medvantage.com" |
| 326 | 320 | } |
| 327 | 321 | ``` |
| 328 | 322 | |
| 323 | +### App 与 Web 权限说明 | |
| 324 | + | |
| 325 | +- App 登录签发 JWT 时会写入声明 **`client_kind` = `us-app`**(与 Web 管理端 Token 区分)。 | |
| 326 | +- **App 仅允许调用本节的 `GET`(查询)**;若使用 App Token 调用新增/编辑,将返回业务错误(英文):`The mobile app can only view support contacts. Please use the web console to edit.` | |
| 327 | + | |
| 329 | 328 | --- |
| 330 | 329 | |
| 331 | -## 后台维护接口:Location Support(新增/编辑) | |
| 330 | +## 后台维护接口:Location Support(Web:新增 / 编辑) | |
| 332 | 331 | |
| 333 | -仅后台管理端使用,用于配置 App Support 页面展示内容(全局唯一)。 | |
| 332 | +仅 **Web 管理端 Token**(无 `client_kind=us-app`)可调用,用于维护全局 Support 联系方式。 | |
| 334 | 333 | |
| 335 | -### 接口 A:新增 Support 联系方式(全局) | |
| 334 | +### 接口 A:新增(全局一条) | |
| 336 | 335 | |
| 337 | 336 | - **方法**:`POST` |
| 338 | 337 | - **路径**:`/api/app/location-support` |
| ... | ... | @@ -342,36 +341,46 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... |
| 342 | 341 | |
| 343 | 342 | ```json |
| 344 | 343 | { |
| 345 | - "locationId": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f", | |
| 346 | 344 | "supportPhone": "1-800-SUPPORT", |
| 347 | 345 | "supportEmail": "support@medvantage.com" |
| 348 | 346 | } |
| 349 | 347 | ``` |
| 350 | 348 | |
| 351 | 349 | 约束: |
| 352 | -- 系统内仅允许存在一条未删除记录;若已存在,再次新增会报错:`已存在全局 Support 联系方式,请使用编辑接口` | |
| 353 | 350 | |
| 354 | -### 接口 B:编辑 Support 联系方式(全局) | |
| 351 | +- 系统内仅允许存在一条未删除记录;若已存在,再次新增会报错:`Global support contact already exists. Use update instead.` | |
| 352 | + | |
| 353 | +### 接口 B:编辑 | |
| 355 | 354 | |
| 356 | 355 | - **方法**:`PUT` |
| 357 | 356 | - **路径**:`/api/app/location-support/{id}` |
| 358 | 357 | - **Content-Type**:`application/json` |
| 359 | 358 | |
| 360 | -请求体(LocationSupportUpdateInputVo)与新增一致: | |
| 359 | +请求体(LocationSupportUpdateInputVo): | |
| 361 | 360 | |
| 362 | 361 | ```json |
| 363 | 362 | { |
| 364 | - "locationId": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f", | |
| 365 | 363 | "supportPhone": "1-800-SUPPORT", |
| 366 | 364 | "supportEmail": "support@medvantage.com" |
| 367 | 365 | } |
| 368 | 366 | ``` |
| 369 | 367 | |
| 370 | -常见错误: | |
| 371 | -- `门店Id不能为空` / `门店Id格式不正确` | |
| 372 | -- `Support 电话不能为空` | |
| 373 | -- `Support 邮箱不能为空` / `Support 邮箱格式不正确` | |
| 374 | -- `系统仅允许一条全局 Support 联系方式` | |
| 368 | +常见错误(英文): | |
| 369 | + | |
| 370 | +- `The mobile app can only view support contacts. Please use the web console to edit.` | |
| 371 | +- `Support phone is required.` / `Support email is required.` / `Support email format is invalid.` | |
| 372 | +- `Global support contact already exists. Use update instead.` | |
| 373 | +- `Support record not found.` / `Support record id is required.` | |
| 374 | + | |
| 375 | +### 数据库迁移(删除 `LocationId`) | |
| 376 | + | |
| 377 | +若线上表仍为旧结构(含 `LocationId`),请在库中执行脚本: | |
| 378 | + | |
| 379 | +- `美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_location_support_alter_drop_locationid.sql` | |
| 380 | + | |
| 381 | +新建库请使用: | |
| 382 | + | |
| 383 | +- `美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_location_support_create.sql` | |
| 375 | 384 | |
| 376 | 385 | --- |
| 377 | 386 | ... | ... |