Commit ecb291fd1960e19566da9b022f461e5ba4d5b45d
1 parent
87313aec
门店支持优化;标签,产品组件优化
Showing
21 changed files
with
300 additions
and
216 deletions
本次新增与优化接口汇总(1).md
| @@ -39,9 +39,9 @@ | @@ -39,9 +39,9 @@ | ||
| 39 | 39 | ||
| 40 | - `fl_product_category`(主表)关键字段: | 40 | - `fl_product_category`(主表)关键字段: |
| 41 | - `DisplayText`:按钮展示文案(为空可回退 `CategoryName`) | 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 | - `AvailabilityType`:`ALL/SPECIFIED`(门店可用范围) | 45 | - `AvailabilityType`:`ALL/SPECIFIED`(门店可用范围) |
| 46 | - `fl_product_category_location`(关联表): | 46 | - `fl_product_category_location`(关联表): |
| 47 | - `(CategoryId, LocationId)` 唯一约束;用于 `AvailabilityType=SPECIFIED` 指定门店 | 47 | - `(CategoryId, LocationId)` 唯一约束;用于 `AvailabilityType=SPECIFIED` 指定门店 |
| @@ -56,7 +56,7 @@ | @@ -56,7 +56,7 @@ | ||
| 56 | - **路径**:`/api/app/product-category` | 56 | - **路径**:`/api/app/product-category` |
| 57 | - **列表行新增返回**: | 57 | - **列表行新增返回**: |
| 58 | - `displayText` | 58 | - `displayText` |
| 59 | - - `buttonAppearance` | 59 | + - `buttonAppearance`、`categoryPhotoUrl`(均为字符串,内容多为 JSON) |
| 60 | - `availabilityType` | 60 | - `availabilityType` |
| 61 | 61 | ||
| 62 | #### 2.2.2 详情 | 62 | #### 2.2.2 详情 |
| @@ -65,7 +65,7 @@ | @@ -65,7 +65,7 @@ | ||
| 65 | - **路径**:`/api/app/product-category/{id}` | 65 | - **路径**:`/api/app/product-category/{id}` |
| 66 | - **新增返回字段**: | 66 | - **新增返回字段**: |
| 67 | - `displayText` | 67 | - `displayText` |
| 68 | - - `buttonAppearance/buttonTextColor/buttonBgColor/buttonImageUrl/buttonStyleJson` | 68 | + - `buttonAppearance`、`categoryPhotoUrl` |
| 69 | - `availabilityType` | 69 | - `availabilityType` |
| 70 | - `locationIds`(当 `availabilityType=SPECIFIED` 返回门店 Id 列表,否则为空数组) | 70 | - `locationIds`(当 `availabilityType=SPECIFIED` 返回门店 Id 列表,否则为空数组) |
| 71 | 71 | ||
| @@ -75,7 +75,7 @@ | @@ -75,7 +75,7 @@ | ||
| 75 | - **路径**:`/api/app/product-category` | 75 | - **路径**:`/api/app/product-category` |
| 76 | - **新增入参字段**: | 76 | - **新增入参字段**: |
| 77 | - `displayText` | 77 | - `displayText` |
| 78 | - - `buttonAppearance/buttonTextColor/buttonBgColor/buttonImageUrl/buttonStyleJson` | 78 | + - `buttonAppearance`、`categoryPhotoUrl`(JSON 字符串约定,详见 `项目相关文档/产品模块Categories接口对接说明.md`) |
| 79 | - `availabilityType` | 79 | - `availabilityType` |
| 80 | - `locationIds`(当 `availabilityType=SPECIFIED` 必填且至少 1 个) | 80 | - `locationIds`(当 `availabilityType=SPECIFIED` 必填且至少 1 个) |
| 81 | 81 | ||
| @@ -95,9 +95,7 @@ | @@ -95,9 +95,7 @@ | ||
| 95 | 95 | ||
| 96 | - `availabilityType` 仅允许 `ALL/SPECIFIED` | 96 | - `availabilityType` 仅允许 `ALL/SPECIFIED` |
| 97 | - `SPECIFIED` 时 `locationIds` 至少 1 个 | 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,9 +107,9 @@ | ||
| 109 | 107 | ||
| 110 | - `fl_label_category`(主表)关键字段: | 108 | - `fl_label_category`(主表)关键字段: |
| 111 | - `DisplayText`:按钮展示文案(为空可回退 `CategoryName`) | 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 | - `AvailabilityType`:`ALL/SPECIFIED`(门店可用范围) | 113 | - `AvailabilityType`:`ALL/SPECIFIED`(门店可用范围) |
| 116 | - `fl_label_category_location`(关联表): | 114 | - `fl_label_category_location`(关联表): |
| 117 | - `(CategoryId, LocationId)` 唯一约束;用于 `AvailabilityType=SPECIFIED` 指定门店(`LocationId` 对应 `location` 表主键) | 115 | - `(CategoryId, LocationId)` 唯一约束;用于 `AvailabilityType=SPECIFIED` 指定门店(`LocationId` 对应 `location` 表主键) |
| @@ -126,7 +124,7 @@ | @@ -126,7 +124,7 @@ | ||
| 126 | - **路径**:`/api/app/label-category` | 124 | - **路径**:`/api/app/label-category` |
| 127 | - **列表行新增返回**: | 125 | - **列表行新增返回**: |
| 128 | - `displayText` | 126 | - `displayText` |
| 129 | - - `buttonAppearance` | 127 | + - `buttonAppearance`、`categoryPhotoUrl`(均为字符串,内容多为 JSON) |
| 130 | - `availabilityType` | 128 | - `availabilityType` |
| 131 | 129 | ||
| 132 | #### 3.2.2 详情 | 130 | #### 3.2.2 详情 |
| @@ -135,7 +133,7 @@ | @@ -135,7 +133,7 @@ | ||
| 135 | - **路径**:`/api/app/label-category/{id}` | 133 | - **路径**:`/api/app/label-category/{id}` |
| 136 | - **新增返回字段**: | 134 | - **新增返回字段**: |
| 137 | - `displayText` | 135 | - `displayText` |
| 138 | - - `buttonAppearance/buttonTextColor/buttonBgColor/buttonImageUrl/buttonStyleJson` | 136 | + - `buttonAppearance`、`categoryPhotoUrl` |
| 139 | - `availabilityType` | 137 | - `availabilityType` |
| 140 | - `locationIds`(当 `availabilityType=SPECIFIED` 返回门店 Id 列表,否则为空数组) | 138 | - `locationIds`(当 `availabilityType=SPECIFIED` 返回门店 Id 列表,否则为空数组) |
| 141 | 139 | ||
| @@ -145,7 +143,7 @@ | @@ -145,7 +143,7 @@ | ||
| 145 | - **路径**:`/api/app/label-category` | 143 | - **路径**:`/api/app/label-category` |
| 146 | - **新增入参字段**: | 144 | - **新增入参字段**: |
| 147 | - `displayText` | 145 | - `displayText` |
| 148 | - - `buttonAppearance/buttonTextColor/buttonBgColor/buttonImageUrl/buttonStyleJson` | 146 | + - `buttonAppearance`、`categoryPhotoUrl`(JSON 字符串约定,详见 `项目相关文档/标签模块接口对接说明.md`) |
| 149 | - `availabilityType` | 147 | - `availabilityType` |
| 150 | - `locationIds`(当 `availabilityType=SPECIFIED` 必填且至少 1 个) | 148 | - `locationIds`(当 `availabilityType=SPECIFIED` 必填且至少 1 个) |
| 151 | 149 | ||
| @@ -165,7 +163,5 @@ | @@ -165,7 +163,5 @@ | ||
| 165 | 163 | ||
| 166 | - `availabilityType` 仅允许 `ALL/SPECIFIED` | 164 | - `availabilityType` 仅允许 `ALL/SPECIFIED` |
| 167 | - `SPECIFIED` 时 `locationIds` 至少 1 个 | 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,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 | ### 1.2 详情 | 60 | ### 1.2 详情 |
| 55 | 61 | ||
| 56 | 方法:`GET /api/app/label-category/{id}` | 62 | 方法:`GET /api/app/label-category/{id}` |
| @@ -69,7 +75,11 @@ Swagger 地址: | @@ -69,7 +75,11 @@ Swagger 地址: | ||
| 69 | { | 75 | { |
| 70 | "categoryCode": "CAT_PREP", | 76 | "categoryCode": "CAT_PREP", |
| 71 | "categoryName": "Prep", | 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 | "state": true, | 83 | "state": true, |
| 74 | "orderNum": 1 | 84 | "orderNum": 1 |
| 75 | } | 85 | } |
| @@ -85,7 +95,11 @@ Swagger 地址: | @@ -85,7 +95,11 @@ Swagger 地址: | ||
| 85 | { | 95 | { |
| 86 | "categoryCode": "CAT_PREP", | 96 | "categoryCode": "CAT_PREP", |
| 87 | "categoryName": "Prep", | 97 | "categoryName": "Prep", |
| 88 | - "categoryPhotoUrl": null, | 98 | + "displayText": "Prep", |
| 99 | + "buttonAppearance": "[\"TEXT\",\"COLOR\"]", | ||
| 100 | + "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]", | ||
| 101 | + "availabilityType": "ALL", | ||
| 102 | + "locationIds": [], | ||
| 89 | "state": true, | 103 | "state": true, |
| 90 | "orderNum": 2 | 104 | "orderNum": 2 |
| 91 | } | 105 | } |
| @@ -706,7 +720,8 @@ ADD UNIQUE KEY `uk_fl_ltpd_template_product_label_type` (`TemplateId`, `ProductI | @@ -706,7 +720,8 @@ ADD UNIQUE KEY `uk_fl_ltpd_template_product_label_type` (`TemplateId`, `ProductI | ||
| 706 | |------|------|------| | 720 | |------|------|------| |
| 707 | | `id` | string | `fl_label_category.Id` | | 721 | | `id` | string | `fl_label_category.Id` | |
| 708 | | `categoryName` | string | 分类名称 | | 722 | | `categoryName` | string | 分类名称 | |
| 709 | -| `categoryPhotoUrl` | string \| null | 分类图标/图 | | 723 | +| `categoryPhotoUrl` | string \| null | 分类展示数据,**JSON 格式字符串**(与库中 `CategoryPhotoUrl` 一致,客户端解析) | |
| 724 | +| `buttonAppearance` | string | 按钮外观,**JSON 格式字符串**(与库中 `ButtonAppearance` 一致;空时后端默认 `"TEXT"`) | | ||
| 710 | | `orderNum` | number | 排序 | | 725 | | `orderNum` | number | 排序 | |
| 711 | | `productCategories` | array | 第二级列表(见下表) | | 726 | | `productCategories` | array | 第二级列表(见下表) | |
| 712 | 727 | ||
| @@ -715,8 +730,12 @@ ADD UNIQUE KEY `uk_fl_ltpd_template_product_label_type` (`TemplateId`, `ProductI | @@ -715,8 +730,12 @@ ADD UNIQUE KEY `uk_fl_ltpd_template_product_label_type` (`TemplateId`, `ProductI | ||
| 715 | | 字段 | 类型 | 说明 | | 730 | | 字段 | 类型 | 说明 | |
| 716 | |------|------|------| | 731 | |------|------|------| |
| 717 | | `categoryId` | string \| null | 产品分类Id;产品未归类或分类不存在时为空 | | 732 | | `categoryId` | string \| null | 产品分类Id;产品未归类或分类不存在时为空 | |
| 718 | -| `categoryPhotoUrl` | string \| null | 产品分类图片地址;产品未归类或分类不存在时为空 | | 733 | +| `categoryPhotoUrl` | string \| null | 产品分类展示数据,**JSON 格式字符串**;未归类或分类不存在时为空 | |
| 719 | | `name` | string | 产品分类显示名;空源数据为 **`无`** | | 734 | | `name` | string | 产品分类显示名;空源数据为 **`无`** | |
| 735 | +| `displayText` | string \| null | 按钮展示文案 | | ||
| 736 | +| `buttonAppearance` | string | **JSON 格式字符串**(与库中一致;空时默认 `"TEXT"`) | | ||
| 737 | +| `availabilityType` | string | `ALL` / `SPECIFIED`(树已按当前门店过滤,仍返回供客户端展示) | | ||
| 738 | +| `orderNum` | number | 排序 | | ||
| 720 | | `itemCount` | number | 该分类下 **产品个数**(去重后的产品数) | | 739 | | `itemCount` | number | 该分类下 **产品个数**(去重后的产品数) | |
| 721 | | `products` | array | 第三级产品列表(见下表) | | 740 | | `products` | array | 第三级产品列表(见下表) | |
| 722 | 741 | ||
| @@ -771,13 +790,18 @@ curl -X GET "http://localhost:19001/api/app/us-app-labeling/labeling-tree?locati | @@ -771,13 +790,18 @@ curl -X GET "http://localhost:19001/api/app/us-app-labeling/labeling-tree?locati | ||
| 771 | { | 790 | { |
| 772 | "id": "cat-prep-id", | 791 | "id": "cat-prep-id", |
| 773 | "categoryName": "Prep", | 792 | "categoryName": "Prep", |
| 774 | - "categoryPhotoUrl": "/picture/...", | 793 | + "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]", |
| 794 | + "buttonAppearance": "[\"TEXT\",\"COLOR\"]", | ||
| 775 | "orderNum": 1, | 795 | "orderNum": 1, |
| 776 | "productCategories": [ | 796 | "productCategories": [ |
| 777 | { | 797 | { |
| 778 | "categoryId": "pc-meat-id", | 798 | "categoryId": "pc-meat-id", |
| 779 | - "categoryPhotoUrl": "/picture/product-category/20260325123010_xxx.png", | 799 | + "categoryPhotoUrl": "[\"/picture/product-category/20260325123010_xxx.png\"]", |
| 780 | "name": "Meat", | 800 | "name": "Meat", |
| 801 | + "displayText": "Meat", | ||
| 802 | + "buttonAppearance": "[\"IMAGE\"]", | ||
| 803 | + "availabilityType": "ALL", | ||
| 804 | + "orderNum": 10, | ||
| 781 | "itemCount": 1, | 805 | "itemCount": 1, |
| 782 | "products": [ | 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,8 +5,6 @@ namespace FoodLabeling.Application.Contracts.Dtos.LocationSupport; | ||
| 5 | /// </summary> | 5 | /// </summary> |
| 6 | public class LocationSupportCreateInputVo | 6 | public class LocationSupportCreateInputVo |
| 7 | { | 7 | { |
| 8 | - public string LocationId { get; set; } = string.Empty; | ||
| 9 | - | ||
| 10 | public string SupportPhone { get; set; } = string.Empty; | 8 | public string SupportPhone { get; set; } = string.Empty; |
| 11 | 9 | ||
| 12 | public string SupportEmail { get; set; } = string.Empty; | 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,10 +7,6 @@ public class LocationSupportGetOutputDto | ||
| 7 | { | 7 | { |
| 8 | public string Id { get; set; } = string.Empty; | 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 | public string SupportPhone { get; set; } = string.Empty; | 10 | public string SupportPhone { get; set; } = string.Empty; |
| 15 | 11 | ||
| 16 | public string SupportEmail { get; set; } = string.Empty; | 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,8 +5,6 @@ namespace FoodLabeling.Application.Contracts.Dtos.LocationSupport; | ||
| 5 | /// </summary> | 5 | /// </summary> |
| 6 | public class LocationSupportUpdateInputVo | 6 | public class LocationSupportUpdateInputVo |
| 7 | { | 7 | { |
| 8 | - public string LocationId { get; set; } = string.Empty; | ||
| 9 | - | ||
| 10 | public string SupportPhone { get; set; } = string.Empty; | 8 | public string SupportPhone { get; set; } = string.Empty; |
| 11 | 9 | ||
| 12 | public string SupportEmail { get; set; } = string.Empty; | 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,24 +4,23 @@ using Volo.Abp.Application.Services; | ||
| 4 | namespace FoodLabeling.Application.Contracts.IServices; | 4 | namespace FoodLabeling.Application.Contracts.IServices; |
| 5 | 5 | ||
| 6 | /// <summary> | 6 | /// <summary> |
| 7 | -/// 门店 Support 联系方式管理(后台设置) | 7 | +/// 全局 Support 联系方式(全平台共用;Web 可增改查,App 仅可查) |
| 8 | /// </summary> | 8 | /// </summary> |
| 9 | public interface ILocationSupportAppService : IApplicationService | 9 | public interface ILocationSupportAppService : IApplicationService |
| 10 | { | 10 | { |
| 11 | /// <summary> | 11 | /// <summary> |
| 12 | - /// 按门店查询 Support 联系方式 | 12 | + /// 查询全局 Support 联系方式(已登录即可;App / Web 共用) |
| 13 | /// </summary> | 13 | /// </summary> |
| 14 | - /// <param name="locationId">门店Id</param> | ||
| 15 | - Task<LocationSupportGetOutputDto?> GetByLocationIdAsync(string locationId); | 14 | + Task<LocationSupportGetOutputDto?> GetSupportAsync(); |
| 16 | 15 | ||
| 17 | /// <summary> | 16 | /// <summary> |
| 18 | - /// 新增门店 Support 联系方式(每个门店仅允许一条) | 17 | + /// 新增全局 Support 联系方式(系统仅允许一条;Web 管理端) |
| 19 | /// </summary> | 18 | /// </summary> |
| 20 | /// <param name="input">联系方式</param> | 19 | /// <param name="input">联系方式</param> |
| 21 | Task<LocationSupportGetOutputDto> CreateAsync(LocationSupportCreateInputVo input); | 20 | Task<LocationSupportGetOutputDto> CreateAsync(LocationSupportCreateInputVo input); |
| 22 | 21 | ||
| 23 | /// <summary> | 22 | /// <summary> |
| 24 | - /// 编辑门店 Support 联系方式 | 23 | + /// 编辑全局 Support 联系方式(Web 管理端) |
| 25 | /// </summary> | 24 | /// </summary> |
| 26 | /// <param name="id">联系方式主键</param> | 25 | /// <param name="id">联系方式主键</param> |
| 27 | /// <param name="input">联系方式</param> | 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,8 +21,6 @@ public class FlLocationSupportDbEntity | ||
| 21 | 21 | ||
| 22 | public DateTime? LastModificationTime { get; set; } | 22 | public DateTime? LastModificationTime { get; set; } |
| 23 | 23 | ||
| 24 | - public string LocationId { get; set; } = string.Empty; | ||
| 25 | - | ||
| 26 | public string SupportPhone { get; set; } = string.Empty; | 24 | public string SupportPhone { get; set; } = string.Empty; |
| 27 | 25 | ||
| 28 | public string SupportEmail { get; set; } = string.Empty; | 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,9 +125,8 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ | ||
| 125 | } | 125 | } |
| 126 | 126 | ||
| 127 | var displayText = input.DisplayText?.Trim(); | 127 | var displayText = input.DisplayText?.Trim(); |
| 128 | - var appearance = (input.ButtonAppearance ?? "TEXT").Trim().ToUpperInvariant(); | 128 | + var appearance = CategoryAppearanceStorageHelper.NormalizeButtonAppearanceForStorage(input.ButtonAppearance); |
| 129 | var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant(); | 129 | var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant(); |
| 130 | - ValidateButtonAppearance(appearance); | ||
| 131 | var locationIds = NormalizeLocationIds(input.LocationIds); | 130 | var locationIds = NormalizeLocationIds(input.LocationIds); |
| 132 | ValidateAvailabilityTypeAndLocations(availabilityType, locationIds); | 131 | ValidateAvailabilityTypeAndLocations(availabilityType, locationIds); |
| 133 | 132 | ||
| @@ -146,7 +145,7 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ | @@ -146,7 +145,7 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ | ||
| 146 | CategoryCode = code, | 145 | CategoryCode = code, |
| 147 | CategoryName = name, | 146 | CategoryName = name, |
| 148 | DisplayText = string.IsNullOrWhiteSpace(displayText) ? null : displayText, | 147 | DisplayText = string.IsNullOrWhiteSpace(displayText) ? null : displayText, |
| 149 | - CategoryPhotoUrl = input.CategoryPhotoUrl?.Trim(), | 148 | + CategoryPhotoUrl = CategoryAppearanceStorageHelper.NormalizeCategoryPhotoUrlForStorage(input.CategoryPhotoUrl), |
| 150 | State = input.State, | 149 | State = input.State, |
| 151 | ButtonAppearance = appearance, | 150 | ButtonAppearance = appearance, |
| 152 | AvailabilityType = availabilityType, | 151 | AvailabilityType = availabilityType, |
| @@ -175,9 +174,8 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ | @@ -175,9 +174,8 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ | ||
| 175 | } | 174 | } |
| 176 | 175 | ||
| 177 | var displayText = input.DisplayText?.Trim(); | 176 | var displayText = input.DisplayText?.Trim(); |
| 178 | - var appearance = (input.ButtonAppearance ?? "TEXT").Trim().ToUpperInvariant(); | 177 | + var appearance = CategoryAppearanceStorageHelper.NormalizeButtonAppearanceForStorage(input.ButtonAppearance); |
| 179 | var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant(); | 178 | var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant(); |
| 180 | - ValidateButtonAppearance(appearance); | ||
| 181 | var locationIds = NormalizeLocationIds(input.LocationIds); | 179 | var locationIds = NormalizeLocationIds(input.LocationIds); |
| 182 | ValidateAvailabilityTypeAndLocations(availabilityType, locationIds); | 180 | ValidateAvailabilityTypeAndLocations(availabilityType, locationIds); |
| 183 | 181 | ||
| @@ -191,7 +189,7 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ | @@ -191,7 +189,7 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ | ||
| 191 | entity.CategoryCode = code; | 189 | entity.CategoryCode = code; |
| 192 | entity.CategoryName = name; | 190 | entity.CategoryName = name; |
| 193 | entity.DisplayText = string.IsNullOrWhiteSpace(displayText) ? null : displayText; | 191 | entity.DisplayText = string.IsNullOrWhiteSpace(displayText) ? null : displayText; |
| 194 | - entity.CategoryPhotoUrl = input.CategoryPhotoUrl?.Trim(); | 192 | + entity.CategoryPhotoUrl = CategoryAppearanceStorageHelper.NormalizeCategoryPhotoUrlForStorage(input.CategoryPhotoUrl); |
| 195 | entity.State = input.State; | 193 | entity.State = input.State; |
| 196 | entity.ButtonAppearance = appearance; | 194 | entity.ButtonAppearance = appearance; |
| 197 | entity.AvailabilityType = availabilityType; | 195 | entity.AvailabilityType = availabilityType; |
| @@ -264,14 +262,6 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ | @@ -264,14 +262,6 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ | ||
| 264 | .ToList() ?? new(); | 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 | private async Task SaveCategoryLocationsAsync( | 265 | private async Task SaveCategoryLocationsAsync( |
| 276 | string categoryId, | 266 | string categoryId, |
| 277 | string availabilityType, | 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 | using FoodLabeling.Application.Contracts.Dtos.LocationSupport; | 2 | using FoodLabeling.Application.Contracts.Dtos.LocationSupport; |
| 2 | using FoodLabeling.Application.Contracts.IServices; | 3 | using FoodLabeling.Application.Contracts.IServices; |
| 3 | using FoodLabeling.Application.Services.DbModels; | 4 | using FoodLabeling.Application.Services.DbModels; |
| 4 | -using FoodLabeling.Domain.Entities; | 5 | +using Microsoft.AspNetCore.Authorization; |
| 5 | using Volo.Abp; | 6 | using Volo.Abp; |
| 6 | using Volo.Abp.Application.Services; | 7 | using Volo.Abp.Application.Services; |
| 7 | using Volo.Abp.Guids; | 8 | using Volo.Abp.Guids; |
| @@ -11,8 +12,9 @@ using Yi.Framework.SqlSugarCore.Abstractions; | @@ -11,8 +12,9 @@ using Yi.Framework.SqlSugarCore.Abstractions; | ||
| 11 | namespace FoodLabeling.Application.Services; | 12 | namespace FoodLabeling.Application.Services; |
| 12 | 13 | ||
| 13 | /// <summary> | 14 | /// <summary> |
| 14 | -/// 门店 Support 联系方式(后台设置,App 展示) | 15 | +/// 全局 Support 联系方式(全门店共用;Web 可增改查,App JWT 仅可读) |
| 15 | /// </summary> | 16 | /// </summary> |
| 17 | +[Authorize] | ||
| 16 | public class LocationSupportAppService : ApplicationService, ILocationSupportAppService | 18 | public class LocationSupportAppService : ApplicationService, ILocationSupportAppService |
| 17 | { | 19 | { |
| 18 | private readonly ISqlSugarDbContext _dbContext; | 20 | private readonly ISqlSugarDbContext _dbContext; |
| @@ -25,46 +27,36 @@ public class LocationSupportAppService : ApplicationService, ILocationSupportApp | @@ -25,46 +27,36 @@ public class LocationSupportAppService : ApplicationService, ILocationSupportApp | ||
| 25 | } | 27 | } |
| 26 | 28 | ||
| 27 | /// <inheritdoc /> | 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 | /// <inheritdoc /> | 39 | /// <inheritdoc /> |
| 48 | [UnitOfWork] | 40 | [UnitOfWork] |
| 49 | public async Task<LocationSupportGetOutputDto> CreateAsync(LocationSupportCreateInputVo input) | 41 | public async Task<LocationSupportGetOutputDto> CreateAsync(LocationSupportCreateInputVo input) |
| 50 | { | 42 | { |
| 43 | + EnsureNotUsAppClient(); | ||
| 44 | + | ||
| 51 | if (input is null) | 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 | EnsureEmailFormat(email); | 52 | EnsureEmailFormat(email); |
| 60 | 53 | ||
| 61 | - await EnsureLocationExistsAsync(locationId); | ||
| 62 | - | ||
| 63 | var existed = await _dbContext.SqlSugarClient.Queryable<FlLocationSupportDbEntity>() | 54 | var existed = await _dbContext.SqlSugarClient.Queryable<FlLocationSupportDbEntity>() |
| 64 | .AnyAsync(x => !x.IsDeleted); | 55 | .AnyAsync(x => !x.IsDeleted); |
| 65 | if (existed) | 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 | var now = Clock.Now; | 62 | var now = Clock.Now; |
| @@ -76,74 +68,60 @@ public class LocationSupportAppService : ApplicationService, ILocationSupportApp | @@ -76,74 +68,60 @@ public class LocationSupportAppService : ApplicationService, ILocationSupportApp | ||
| 76 | CreatorId = CurrentUser?.Id?.ToString(), | 68 | CreatorId = CurrentUser?.Id?.ToString(), |
| 77 | LastModificationTime = now, | 69 | LastModificationTime = now, |
| 78 | LastModifierId = CurrentUser?.Id?.ToString(), | 70 | LastModifierId = CurrentUser?.Id?.ToString(), |
| 79 | - LocationId = locationId, | ||
| 80 | SupportPhone = phone, | 71 | SupportPhone = phone, |
| 81 | SupportEmail = email | 72 | SupportEmail = email |
| 82 | }; | 73 | }; |
| 83 | 74 | ||
| 84 | await _dbContext.SqlSugarClient.Insertable(entity).ExecuteCommandAsync(); | 75 | await _dbContext.SqlSugarClient.Insertable(entity).ExecuteCommandAsync(); |
| 85 | - return (await MapOutputAsync(entity))!; | 76 | + return MapOutput(entity)!; |
| 86 | } | 77 | } |
| 87 | 78 | ||
| 88 | /// <inheritdoc /> | 79 | /// <inheritdoc /> |
| 89 | [UnitOfWork] | 80 | [UnitOfWork] |
| 90 | public async Task<LocationSupportGetOutputDto> UpdateAsync(string id, LocationSupportUpdateInputVo input) | 81 | public async Task<LocationSupportGetOutputDto> UpdateAsync(string id, LocationSupportUpdateInputVo input) |
| 91 | { | 82 | { |
| 83 | + EnsureNotUsAppClient(); | ||
| 84 | + | ||
| 92 | var supportId = id?.Trim(); | 85 | var supportId = id?.Trim(); |
| 93 | if (string.IsNullOrWhiteSpace(supportId)) | 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 | if (input is null) | 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 | if (entity is null) | 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 | EnsureEmailFormat(email); | 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 | entity.SupportPhone = phone; | 109 | entity.SupportPhone = phone; |
| 125 | entity.SupportEmail = email; | 110 | entity.SupportEmail = email; |
| 126 | entity.LastModificationTime = Clock.Now; | 111 | entity.LastModificationTime = Clock.Now; |
| 127 | entity.LastModifierId = CurrentUser?.Id?.ToString(); | 112 | entity.LastModifierId = CurrentUser?.Id?.ToString(); |
| 128 | 113 | ||
| 129 | await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync(); | 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 | private static string NormalizeRequired(string? value, string message) | 127 | private static string NormalizeRequired(string? value, string message) |
| @@ -162,48 +140,22 @@ public class LocationSupportAppService : ApplicationService, ILocationSupportApp | @@ -162,48 +140,22 @@ public class LocationSupportAppService : ApplicationService, ILocationSupportApp | ||
| 162 | if (!email.Contains("@", StringComparison.Ordinal) || email.StartsWith("@", StringComparison.Ordinal) || | 140 | if (!email.Contains("@", StringComparison.Ordinal) || email.StartsWith("@", StringComparison.Ordinal) || |
| 163 | email.EndsWith("@", StringComparison.Ordinal)) | 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 | if (entity is null) | 149 | if (entity is null) |
| 187 | { | 150 | { |
| 188 | return null; | 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 | return new LocationSupportGetOutputDto | 154 | return new LocationSupportGetOutputDto |
| 200 | { | 155 | { |
| 201 | Id = entity.Id, | 156 | Id = entity.Id, |
| 202 | - LocationId = entity.LocationId, | ||
| 203 | - LocationName = locationName, | ||
| 204 | SupportPhone = entity.SupportPhone, | 157 | SupportPhone = entity.SupportPhone, |
| 205 | SupportEmail = entity.SupportEmail | 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 | using FoodLabeling.Application.Contracts.Dtos.Common; | 2 | using FoodLabeling.Application.Contracts.Dtos.Common; |
| 2 | using FoodLabeling.Application.Contracts.Dtos.ProductCategory; | 3 | using FoodLabeling.Application.Contracts.Dtos.ProductCategory; |
| 3 | using FoodLabeling.Application.Contracts.IServices; | 4 | using FoodLabeling.Application.Contracts.IServices; |
| @@ -128,9 +129,8 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp | @@ -128,9 +129,8 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp | ||
| 128 | } | 129 | } |
| 129 | 130 | ||
| 130 | var displayText = input.DisplayText?.Trim(); | 131 | var displayText = input.DisplayText?.Trim(); |
| 131 | - var appearance = (input.ButtonAppearance ?? "TEXT").Trim().ToUpperInvariant(); | 132 | + var appearance = CategoryAppearanceStorageHelper.NormalizeButtonAppearanceForStorage(input.ButtonAppearance); |
| 132 | var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant(); | 133 | var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant(); |
| 133 | - ValidateButtonAppearance(appearance); | ||
| 134 | var locationIds = NormalizeLocationIds(input.LocationIds); | 134 | var locationIds = NormalizeLocationIds(input.LocationIds); |
| 135 | ValidateAvailabilityTypeAndLocations(availabilityType, locationIds); | 135 | ValidateAvailabilityTypeAndLocations(availabilityType, locationIds); |
| 136 | 136 | ||
| @@ -155,7 +155,7 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp | @@ -155,7 +155,7 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp | ||
| 155 | CategoryCode = code, | 155 | CategoryCode = code, |
| 156 | CategoryName = name, | 156 | CategoryName = name, |
| 157 | DisplayText = string.IsNullOrWhiteSpace(displayText) ? null : displayText, | 157 | DisplayText = string.IsNullOrWhiteSpace(displayText) ? null : displayText, |
| 158 | - CategoryPhotoUrl = input.CategoryPhotoUrl?.Trim(), | 158 | + CategoryPhotoUrl = CategoryAppearanceStorageHelper.NormalizeCategoryPhotoUrlForStorage(input.CategoryPhotoUrl), |
| 159 | ButtonAppearance = appearance, | 159 | ButtonAppearance = appearance, |
| 160 | State = input.State, | 160 | State = input.State, |
| 161 | AvailabilityType = availabilityType, | 161 | AvailabilityType = availabilityType, |
| @@ -187,9 +187,8 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp | @@ -187,9 +187,8 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp | ||
| 187 | } | 187 | } |
| 188 | 188 | ||
| 189 | var displayText = input.DisplayText?.Trim(); | 189 | var displayText = input.DisplayText?.Trim(); |
| 190 | - var appearance = (input.ButtonAppearance ?? "TEXT").Trim().ToUpperInvariant(); | 190 | + var appearance = CategoryAppearanceStorageHelper.NormalizeButtonAppearanceForStorage(input.ButtonAppearance); |
| 191 | var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant(); | 191 | var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant(); |
| 192 | - ValidateButtonAppearance(appearance); | ||
| 193 | var locationIds = NormalizeLocationIds(input.LocationIds); | 192 | var locationIds = NormalizeLocationIds(input.LocationIds); |
| 194 | ValidateAvailabilityTypeAndLocations(availabilityType, locationIds); | 193 | ValidateAvailabilityTypeAndLocations(availabilityType, locationIds); |
| 195 | 194 | ||
| @@ -203,7 +202,7 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp | @@ -203,7 +202,7 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp | ||
| 203 | entity.CategoryCode = code; | 202 | entity.CategoryCode = code; |
| 204 | entity.CategoryName = name; | 203 | entity.CategoryName = name; |
| 205 | entity.DisplayText = string.IsNullOrWhiteSpace(displayText) ? null : displayText; | 204 | entity.DisplayText = string.IsNullOrWhiteSpace(displayText) ? null : displayText; |
| 206 | - entity.CategoryPhotoUrl = input.CategoryPhotoUrl?.Trim(); | 205 | + entity.CategoryPhotoUrl = CategoryAppearanceStorageHelper.NormalizeCategoryPhotoUrlForStorage(input.CategoryPhotoUrl); |
| 207 | entity.ButtonAppearance = appearance; | 206 | entity.ButtonAppearance = appearance; |
| 208 | entity.State = input.State; | 207 | entity.State = input.State; |
| 209 | entity.AvailabilityType = availabilityType; | 208 | entity.AvailabilityType = availabilityType; |
| @@ -280,14 +279,6 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp | @@ -280,14 +279,6 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp | ||
| 280 | .ToList() ?? new(); | 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 | private async Task SaveCategoryLocationsAsync( | 282 | private async Task SaveCategoryLocationsAsync( |
| 292 | string categoryId, | 283 | string categoryId, |
| 293 | string availabilityType, | 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 +6,7 @@ using System.Linq; | ||
| 6 | using System.Security.Claims; | 6 | using System.Security.Claims; |
| 7 | using System.Text; | 7 | using System.Text; |
| 8 | using System.Threading.Tasks; | 8 | using System.Threading.Tasks; |
| 9 | +using FoodLabeling.Application.Contracts; | ||
| 9 | using FoodLabeling.Application.Contracts.Dtos.UsAppAuth; | 10 | using FoodLabeling.Application.Contracts.Dtos.UsAppAuth; |
| 10 | using FoodLabeling.Application.Contracts.IServices; | 11 | using FoodLabeling.Application.Contracts.IServices; |
| 11 | using FoodLabeling.Application.Services.DbModels; | 12 | using FoodLabeling.Application.Services.DbModels; |
| @@ -432,7 +433,8 @@ public class UsAppAuthAppService : ApplicationService, IUsAppAuthAppService | @@ -432,7 +433,8 @@ public class UsAppAuthAppService : ApplicationService, IUsAppAuthAppService | ||
| 432 | var claims = new List<Claim> | 433 | var claims = new List<Claim> |
| 433 | { | 434 | { |
| 434 | new(AbpClaimTypes.UserId, user.Id.ToString()), | 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 | if (!string.IsNullOrWhiteSpace(user.Email)) | 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,7 +129,7 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | ||
| 129 | { | 129 | { |
| 130 | var l1Appearance = string.IsNullOrWhiteSpace(g1.Key.LabelCategoryButtonAppearance) | 130 | var l1Appearance = string.IsNullOrWhiteSpace(g1.Key.LabelCategoryButtonAppearance) |
| 131 | ? "TEXT" | 131 | ? "TEXT" |
| 132 | - : g1.Key.LabelCategoryButtonAppearance.Trim().ToUpperInvariant(); | 132 | + : g1.Key.LabelCategoryButtonAppearance.Trim(); |
| 133 | var l1 = new UsAppLabelCategoryTreeNodeDto | 133 | var l1 = new UsAppLabelCategoryTreeNodeDto |
| 134 | { | 134 | { |
| 135 | Id = g1.Key.LabelCategoryId, | 135 | Id = g1.Key.LabelCategoryId, |
| @@ -178,7 +178,7 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | @@ -178,7 +178,7 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | ||
| 178 | var productsGrouped = g2.GroupBy(x => x.ProductId).OrderBy(pg => pg.First().ProductName); | 178 | var productsGrouped = g2.GroupBy(x => x.ProductId).OrderBy(pg => pg.First().ProductName); |
| 179 | var appearance = string.IsNullOrWhiteSpace(g2.Key.ButtonAppearance) | 179 | var appearance = string.IsNullOrWhiteSpace(g2.Key.ButtonAppearance) |
| 180 | ? "TEXT" | 180 | ? "TEXT" |
| 181 | - : g2.Key.ButtonAppearance.Trim().ToUpperInvariant(); | 181 | + : g2.Key.ButtonAppearance.Trim(); |
| 182 | var availability = string.IsNullOrWhiteSpace(g2.Key.AvailabilityType) | 182 | var availability = string.IsNullOrWhiteSpace(g2.Key.AvailabilityType) |
| 183 | ? "ALL" | 183 | ? "ALL" |
| 184 | : g2.Key.AvailabilityType.Trim().ToUpperInvariant(); | 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 | CREATE TABLE IF NOT EXISTS `fl_location_support` ( | 2 | CREATE TABLE IF NOT EXISTS `fl_location_support` ( |
| 3 | `Id` varchar(50) NOT NULL COMMENT '主键', | 3 | `Id` varchar(50) NOT NULL COMMENT '主键', |
| 4 | `IsDeleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除', | 4 | `IsDeleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除', |
| @@ -6,11 +6,8 @@ CREATE TABLE IF NOT EXISTS `fl_location_support` ( | @@ -6,11 +6,8 @@ CREATE TABLE IF NOT EXISTS `fl_location_support` ( | ||
| 6 | `CreatorId` varchar(50) DEFAULT NULL COMMENT '创建人', | 6 | `CreatorId` varchar(50) DEFAULT NULL COMMENT '创建人', |
| 7 | `LastModifierId` varchar(50) DEFAULT NULL COMMENT '最后修改人', | 7 | `LastModifierId` varchar(50) DEFAULT NULL COMMENT '最后修改人', |
| 8 | `LastModificationTime` datetime DEFAULT NULL COMMENT '最后修改时间', | 8 | `LastModificationTime` datetime DEFAULT NULL COMMENT '最后修改时间', |
| 9 | - `LocationId` varchar(50) NOT NULL COMMENT '门店Id(对应location.Id)', | ||
| 10 | `SupportPhone` varchar(100) NOT NULL COMMENT 'Support 电话', | 9 | `SupportPhone` varchar(100) NOT NULL COMMENT 'Support 电话', |
| 11 | `SupportEmail` varchar(200) NOT NULL COMMENT 'Support 邮箱', | 10 | `SupportEmail` varchar(200) NOT NULL COMMENT 'Support 邮箱', |
| 12 | PRIMARY KEY (`Id`), | 11 | PRIMARY KEY (`Id`), |
| 13 | - UNIQUE KEY `uk_fl_location_support_locationid` (`LocationId`), | ||
| 14 | KEY `idx_fl_location_support_isdeleted` (`IsDeleted`) | 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,7 +8,9 @@ | ||
| 8 | - **接口前缀**:宿主统一前缀为 `/api/app` | 8 | - **接口前缀**:宿主统一前缀为 `/api/app` |
| 9 | - **分类表**:`fl_product_category` | 9 | - **分类表**:`fl_product_category` |
| 10 | - **关联字段**:`fl_product.category_id` → `fl_product_category.id` | 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 | > 说明:本文以 Swagger 为准(本地示例:`http://localhost:19001/swagger`,搜索 `ProductCategory`)。 | 15 | > 说明:本文以 Swagger 为准(本地示例:`http://localhost:19001/swagger`,搜索 `ProductCategory`)。 |
| 14 | 16 | ||
| @@ -57,7 +59,10 @@ Authorization: Bearer eyJhbGciOi... | @@ -57,7 +59,10 @@ Authorization: Bearer eyJhbGciOi... | ||
| 57 | | `id` | string | 主键 | | 59 | | `id` | string | 主键 | |
| 58 | | `categoryCode` | string | 类别编码 | | 60 | | `categoryCode` | string | 类别编码 | |
| 59 | | `categoryName` | string | 类别名称 | | 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 | | `state` | boolean | 是否启用 | | 66 | | `state` | boolean | 是否启用 | |
| 62 | | `orderNum` | number | 排序 | | 67 | | `orderNum` | number | 排序 | |
| 63 | | `lastEdited` | string | 最后编辑时间 | | 68 | | `lastEdited` | string | 最后编辑时间 | |
| @@ -75,7 +80,10 @@ Authorization: Bearer eyJhbGciOi... | @@ -75,7 +80,10 @@ Authorization: Bearer eyJhbGciOi... | ||
| 75 | "id": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f", | 80 | "id": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f", |
| 76 | "categoryCode": "CAT_PREP", | 81 | "categoryCode": "CAT_PREP", |
| 77 | "categoryName": "Prep", | 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 | "state": true, | 87 | "state": true, |
| 80 | "orderNum": 100, | 88 | "orderNum": 100, |
| 81 | "lastEdited": "2026-03-25 12:30:10" | 89 | "lastEdited": "2026-03-25 12:30:10" |
| @@ -108,7 +116,11 @@ Authorization: Bearer eyJhbGciOi... | @@ -108,7 +116,11 @@ Authorization: Bearer eyJhbGciOi... | ||
| 108 | "id": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f", | 116 | "id": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f", |
| 109 | "categoryCode": "CAT_PREP", | 117 | "categoryCode": "CAT_PREP", |
| 110 | "categoryName": "Prep", | 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 | "state": true, | 124 | "state": true, |
| 113 | "orderNum": 100 | 125 | "orderNum": 100 |
| 114 | } | 126 | } |
| @@ -130,7 +142,11 @@ Authorization: Bearer eyJhbGciOi... | @@ -130,7 +142,11 @@ Authorization: Bearer eyJhbGciOi... | ||
| 130 | |------|------|------|------| | 142 | |------|------|------|------| |
| 131 | | `categoryCode` | string | 是 | 类别编码(唯一) | | 143 | | `categoryCode` | string | 是 | 类别编码(唯一) | |
| 132 | | `categoryName` | string | 是 | 类别名称(唯一) | | 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 | | `state` | boolean | 否 | 是否启用(默认 true) | | 150 | | `state` | boolean | 否 | 是否启用(默认 true) | |
| 135 | | `orderNum` | number | 否 | 排序(默认 0) | | 151 | | `orderNum` | number | 否 | 排序(默认 0) | |
| 136 | 152 | ||
| @@ -140,7 +156,11 @@ Authorization: Bearer eyJhbGciOi... | @@ -140,7 +156,11 @@ Authorization: Bearer eyJhbGciOi... | ||
| 140 | { | 156 | { |
| 141 | "categoryCode": "CAT_PREP", | 157 | "categoryCode": "CAT_PREP", |
| 142 | "categoryName": "Prep", | 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 | "state": true, | 164 | "state": true, |
| 145 | "orderNum": 100 | 165 | "orderNum": 100 |
| 146 | } | 166 | } |
| @@ -162,7 +182,11 @@ Authorization: Bearer eyJhbGciOi... | @@ -162,7 +182,11 @@ Authorization: Bearer eyJhbGciOi... | ||
| 162 | { | 182 | { |
| 163 | "categoryCode": "CAT_PREP", | 183 | "categoryCode": "CAT_PREP", |
| 164 | "categoryName": "Prep", | 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 | "state": true, | 190 | "state": true, |
| 167 | "orderNum": 100 | 191 | "orderNum": 100 |
| 168 | } | 192 | } |
| @@ -179,7 +203,7 @@ Authorization: Bearer eyJhbGciOi... | @@ -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,5 +224,5 @@ Authorization: Bearer eyJhbGciOi... | ||
| 200 | 推荐前端流程: | 224 | 推荐前端流程: |
| 201 | 225 | ||
| 202 | 1. 调用上传接口 `POST /api/app/picture/category/upload` 拿到响应 `url` | 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,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 | | `fileName` | string | 服务器保存的文件名 | | 58 | | `fileName` | string | 服务器保存的文件名 | |
| 59 | | `size` | number | 文件大小(字节) | | 59 | | `size` | number | 文件大小(字节) | |
| 60 | 60 | ||
| @@ -96,7 +96,7 @@ curl -X POST "http://localhost:19001/api/app/picture/category/upload" ^ | @@ -96,7 +96,7 @@ curl -X POST "http://localhost:19001/api/app/picture/category/upload" ^ | ||
| 96 | 推荐前端流程: | 96 | 推荐前端流程: |
| 97 | 97 | ||
| 98 | 1. 调用本上传接口,拿到返回的 `url` | 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,8 +42,8 @@ | ||
| 42 | 42 | ||
| 43 | - `fl_product_category`(主表)关键字段: | 43 | - `fl_product_category`(主表)关键字段: |
| 44 | - `DisplayText`:按钮展示文案(为空可回退 `CategoryName`) | 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 | - **已删除列**(需在库上执行 `DROP COLUMN`):`ButtonTextColor`、`ButtonBgColor`、`ButtonImageUrl`、`ButtonStyleJson` | 47 | - **已删除列**(需在库上执行 `DROP COLUMN`):`ButtonTextColor`、`ButtonBgColor`、`ButtonImageUrl`、`ButtonStyleJson` |
| 48 | - `AvailabilityType`:`ALL/SPECIFIED`(门店可用范围) | 48 | - `AvailabilityType`:`ALL/SPECIFIED`(门店可用范围) |
| 49 | - `fl_product_category_location`(关联表): | 49 | - `fl_product_category_location`(关联表): |
| @@ -69,7 +69,7 @@ | @@ -69,7 +69,7 @@ | ||
| 69 | - **路径**:`/api/app/product-category/{id}` | 69 | - **路径**:`/api/app/product-category/{id}` |
| 70 | - **新增返回字段**: | 70 | - **新增返回字段**: |
| 71 | - `displayText` | 71 | - `displayText` |
| 72 | - - `buttonAppearance`、`categoryPhotoUrl`(COLOR/IMAGE 的展示数据统一在此字段) | 72 | + - `buttonAppearance`、`categoryPhotoUrl`(**JSON 格式字符串**,展示语义由前端解析) |
| 73 | - `availabilityType` | 73 | - `availabilityType` |
| 74 | - `locationIds`(当 `availabilityType=SPECIFIED` 返回门店 Id 列表,否则为空数组) | 74 | - `locationIds`(当 `availabilityType=SPECIFIED` 返回门店 Id 列表,否则为空数组) |
| 75 | 75 | ||
| @@ -99,7 +99,7 @@ | @@ -99,7 +99,7 @@ | ||
| 99 | 99 | ||
| 100 | - `availabilityType` 仅允许 `ALL/SPECIFIED` | 100 | - `availabilityType` 仅允许 `ALL/SPECIFIED` |
| 101 | - `SPECIFIED` 时 `locationIds` 至少 1 个 | 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,8 +111,8 @@ | ||
| 111 | 111 | ||
| 112 | - `fl_label_category`(主表)关键字段: | 112 | - `fl_label_category`(主表)关键字段: |
| 113 | - `DisplayText`:按钮展示文案(为空可回退 `CategoryName`) | 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 | - **已删除列**(需在库上执行 `DROP COLUMN`):`ButtonTextColor`、`ButtonBgColor`、`ButtonImageUrl`、`ButtonStyleJson` | 116 | - **已删除列**(需在库上执行 `DROP COLUMN`):`ButtonTextColor`、`ButtonBgColor`、`ButtonImageUrl`、`ButtonStyleJson` |
| 117 | - `AvailabilityType`:`ALL/SPECIFIED`(门店可用范围) | 117 | - `AvailabilityType`:`ALL/SPECIFIED`(门店可用范围) |
| 118 | - `fl_label_category_location`(关联表): | 118 | - `fl_label_category_location`(关联表): |
| @@ -138,7 +138,7 @@ | @@ -138,7 +138,7 @@ | ||
| 138 | - **路径**:`/api/app/label-category/{id}` | 138 | - **路径**:`/api/app/label-category/{id}` |
| 139 | - **新增返回字段**: | 139 | - **新增返回字段**: |
| 140 | - `displayText` | 140 | - `displayText` |
| 141 | - - `buttonAppearance`、`categoryPhotoUrl`(COLOR/IMAGE 的展示数据统一在此字段) | 141 | + - `buttonAppearance`、`categoryPhotoUrl`(**JSON 格式字符串**,展示语义由前端解析) |
| 142 | - `availabilityType` | 142 | - `availabilityType` |
| 143 | - `locationIds`(当 `availabilityType=SPECIFIED` 返回门店 Id 列表,否则为空数组) | 143 | - `locationIds`(当 `availabilityType=SPECIFIED` 返回门店 Id 列表,否则为空数组) |
| 144 | 144 | ||
| @@ -168,14 +168,14 @@ | @@ -168,14 +168,14 @@ | ||
| 168 | 168 | ||
| 169 | - `availabilityType` 仅允许 `ALL/SPECIFIED` | 169 | - `availabilityType` 仅允许 `ALL/SPECIFIED` |
| 170 | - `SPECIFIED` 时 `locationIds` 至少 1 个 | 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 | ## 4. App 端 `GET /api/app/us-app-labeling/labeling-tree` | 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 | ### 4.1 数据库迁移(两张主表) | 180 | ### 4.1 数据库迁移(两张主表) |
| 181 | 181 |
项目相关文档/标签模块接口对接说明.md
| @@ -51,6 +51,12 @@ Swagger 地址: | @@ -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 | ### 1.2 详情 | 60 | ### 1.2 详情 |
| 55 | 61 | ||
| 56 | 方法:`GET /api/app/label-category/{id}` | 62 | 方法:`GET /api/app/label-category/{id}` |
| @@ -69,7 +75,11 @@ Swagger 地址: | @@ -69,7 +75,11 @@ Swagger 地址: | ||
| 69 | { | 75 | { |
| 70 | "categoryCode": "CAT_PREP", | 76 | "categoryCode": "CAT_PREP", |
| 71 | "categoryName": "Prep", | 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 | "state": true, | 83 | "state": true, |
| 74 | "orderNum": 1 | 84 | "orderNum": 1 |
| 75 | } | 85 | } |
| @@ -85,7 +95,11 @@ Swagger 地址: | @@ -85,7 +95,11 @@ Swagger 地址: | ||
| 85 | { | 95 | { |
| 86 | "categoryCode": "CAT_PREP", | 96 | "categoryCode": "CAT_PREP", |
| 87 | "categoryName": "Prep", | 97 | "categoryName": "Prep", |
| 88 | - "categoryPhotoUrl": null, | 98 | + "displayText": "Prep", |
| 99 | + "buttonAppearance": "[\"TEXT\",\"COLOR\"]", | ||
| 100 | + "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]", | ||
| 101 | + "availabilityType": "ALL", | ||
| 102 | + "locationIds": [], | ||
| 89 | "state": true, | 103 | "state": true, |
| 90 | "orderNum": 2 | 104 | "orderNum": 2 |
| 91 | } | 105 | } |
| @@ -706,7 +720,8 @@ ADD UNIQUE KEY `uk_fl_ltpd_template_product_label_type` (`TemplateId`, `ProductI | @@ -706,7 +720,8 @@ ADD UNIQUE KEY `uk_fl_ltpd_template_product_label_type` (`TemplateId`, `ProductI | ||
| 706 | |------|------|------| | 720 | |------|------|------| |
| 707 | | `id` | string | `fl_label_category.Id` | | 721 | | `id` | string | `fl_label_category.Id` | |
| 708 | | `categoryName` | string | 分类名称 | | 722 | | `categoryName` | string | 分类名称 | |
| 709 | -| `categoryPhotoUrl` | string \| null | 分类图标/图 | | 723 | +| `categoryPhotoUrl` | string \| null | 分类展示数据,**JSON 格式字符串**(与库中 `CategoryPhotoUrl` 一致,客户端解析) | |
| 724 | +| `buttonAppearance` | string | 按钮外观,**JSON 格式字符串**(与库中 `ButtonAppearance` 一致;空时后端默认 `"TEXT"`) | | ||
| 710 | | `orderNum` | number | 排序 | | 725 | | `orderNum` | number | 排序 | |
| 711 | | `productCategories` | array | 第二级列表(见下表) | | 726 | | `productCategories` | array | 第二级列表(见下表) | |
| 712 | 727 | ||
| @@ -715,8 +730,12 @@ ADD UNIQUE KEY `uk_fl_ltpd_template_product_label_type` (`TemplateId`, `ProductI | @@ -715,8 +730,12 @@ ADD UNIQUE KEY `uk_fl_ltpd_template_product_label_type` (`TemplateId`, `ProductI | ||
| 715 | | 字段 | 类型 | 说明 | | 730 | | 字段 | 类型 | 说明 | |
| 716 | |------|------|------| | 731 | |------|------|------| |
| 717 | | `categoryId` | string \| null | 产品分类Id;产品未归类或分类不存在时为空 | | 732 | | `categoryId` | string \| null | 产品分类Id;产品未归类或分类不存在时为空 | |
| 718 | -| `categoryPhotoUrl` | string \| null | 产品分类图片地址;产品未归类或分类不存在时为空 | | 733 | +| `categoryPhotoUrl` | string \| null | 产品分类展示数据,**JSON 格式字符串**;未归类或分类不存在时为空 | |
| 719 | | `name` | string | 产品分类显示名;空源数据为 **`无`** | | 734 | | `name` | string | 产品分类显示名;空源数据为 **`无`** | |
| 735 | +| `displayText` | string \| null | 按钮展示文案 | | ||
| 736 | +| `buttonAppearance` | string | **JSON 格式字符串**(与库中一致;空时默认 `"TEXT"`) | | ||
| 737 | +| `availabilityType` | string | `ALL` / `SPECIFIED`(树已按当前门店过滤,仍返回供客户端展示) | | ||
| 738 | +| `orderNum` | number | 排序 | | ||
| 720 | | `itemCount` | number | 该分类下 **产品个数**(去重后的产品数) | | 739 | | `itemCount` | number | 该分类下 **产品个数**(去重后的产品数) | |
| 721 | | `products` | array | 第三级产品列表(见下表) | | 740 | | `products` | array | 第三级产品列表(见下表) | |
| 722 | 741 | ||
| @@ -771,13 +790,18 @@ curl -X GET "http://localhost:19001/api/app/us-app-labeling/labeling-tree?locati | @@ -771,13 +790,18 @@ curl -X GET "http://localhost:19001/api/app/us-app-labeling/labeling-tree?locati | ||
| 771 | { | 790 | { |
| 772 | "id": "cat-prep-id", | 791 | "id": "cat-prep-id", |
| 773 | "categoryName": "Prep", | 792 | "categoryName": "Prep", |
| 774 | - "categoryPhotoUrl": "/picture/...", | 793 | + "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]", |
| 794 | + "buttonAppearance": "[\"TEXT\",\"COLOR\"]", | ||
| 775 | "orderNum": 1, | 795 | "orderNum": 1, |
| 776 | "productCategories": [ | 796 | "productCategories": [ |
| 777 | { | 797 | { |
| 778 | "categoryId": "pc-meat-id", | 798 | "categoryId": "pc-meat-id", |
| 779 | - "categoryPhotoUrl": "/picture/product-category/20260325123010_xxx.png", | 799 | + "categoryPhotoUrl": "[\"/picture/product-category/20260325123010_xxx.png\"]", |
| 780 | "name": "Meat", | 800 | "name": "Meat", |
| 801 | + "displayText": "Meat", | ||
| 802 | + "buttonAppearance": "[\"IMAGE\"]", | ||
| 803 | + "availabilityType": "ALL", | ||
| 804 | + "orderNum": 10, | ||
| 781 | "itemCount": 1, | 805 | "itemCount": 1, |
| 782 | "products": [ | 806 | "products": [ |
| 783 | { | 807 | { |
项目相关文档/美国版App登录接口说明.md
| @@ -285,54 +285,53 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... | @@ -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 | - **方法**:`GET` | 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 | ### 响应体(LocationSupportGetOutputDto) | 303 | ### 响应体(LocationSupportGetOutputDto) |
| 306 | 304 | ||
| 307 | | 字段(JSON) | 类型 | 说明 | | 305 | | 字段(JSON) | 类型 | 说明 | |
| 308 | |--------------|------|------| | 306 | |--------------|------|------| |
| 309 | -| `id` | string | 联系方式主键 | | ||
| 310 | -| `locationId` | string | 配置记录中的门店主键(仅作兼容字段) | | ||
| 311 | -| `locationName` | string \| null | 配置记录中的门店名称(仅作兼容字段) | | 307 | +| `id` | string | 记录主键(Web 编辑 `PUT` 路径中的 `{id}`) | |
| 312 | | `supportPhone` | string | Support 电话 | | 308 | | `supportPhone` | string | Support 电话 | |
| 313 | | `supportEmail` | string | Support 邮箱 | | 309 | | `supportEmail` | string | Support 邮箱 | |
| 314 | 310 | ||
| 315 | -> 若门店尚未配置联系方式,接口返回 `null`。 | 311 | +> 若尚未在后台配置,接口返回 `null`。 |
| 316 | 312 | ||
| 317 | ### 响应示例 | 313 | ### 响应示例 |
| 318 | 314 | ||
| 319 | ```json | 315 | ```json |
| 320 | { | 316 | { |
| 321 | "id": "3a2f4fda-1a93-4a35-9b98-95dca7bb5d2a", | 317 | "id": "3a2f4fda-1a93-4a35-9b98-95dca7bb5d2a", |
| 322 | - "locationId": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f", | ||
| 323 | - "locationName": "Downtown Store", | ||
| 324 | "supportPhone": "1-800-SUPPORT", | 318 | "supportPhone": "1-800-SUPPORT", |
| 325 | "supportEmail": "support@medvantage.com" | 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 | - **方法**:`POST` | 336 | - **方法**:`POST` |
| 338 | - **路径**:`/api/app/location-support` | 337 | - **路径**:`/api/app/location-support` |
| @@ -342,36 +341,46 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... | @@ -342,36 +341,46 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... | ||
| 342 | 341 | ||
| 343 | ```json | 342 | ```json |
| 344 | { | 343 | { |
| 345 | - "locationId": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f", | ||
| 346 | "supportPhone": "1-800-SUPPORT", | 344 | "supportPhone": "1-800-SUPPORT", |
| 347 | "supportEmail": "support@medvantage.com" | 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 | - **方法**:`PUT` | 355 | - **方法**:`PUT` |
| 357 | - **路径**:`/api/app/location-support/{id}` | 356 | - **路径**:`/api/app/location-support/{id}` |
| 358 | - **Content-Type**:`application/json` | 357 | - **Content-Type**:`application/json` |
| 359 | 358 | ||
| 360 | -请求体(LocationSupportUpdateInputVo)与新增一致: | 359 | +请求体(LocationSupportUpdateInputVo): |
| 361 | 360 | ||
| 362 | ```json | 361 | ```json |
| 363 | { | 362 | { |
| 364 | - "locationId": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f", | ||
| 365 | "supportPhone": "1-800-SUPPORT", | 363 | "supportPhone": "1-800-SUPPORT", |
| 366 | "supportEmail": "support@medvantage.com" | 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 |