From ecb291fd1960e19566da9b022f461e5ba4d5b45d Mon Sep 17 00:00:00 2001 From: 李曜臣 Date: Wed, 6 May 2026 19:40:03 +0800 Subject: [PATCH] 门店支持优化;标签,产品组件优化 --- 本次新增与优化接口汇总(1).md | 32 ++++++++++++++------------------ 标签模块接口对接说明.md | 36 ++++++++++++++++++++++++++++++------ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LocationSupport/LocationSupportCreateInputVo.cs | 2 -- 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LocationSupport/LocationSupportGetOutputDto.cs | 4 ---- 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LocationSupport/LocationSupportUpdateInputVo.cs | 2 -- 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/ILocationSupportAppService.cs | 11 +++++------ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/UsAppJwtClaims.cs | 13 +++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/CategoryAppearanceStorageHelper.cs | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLocationSupportDbEntity.cs | 2 -- 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelCategoryAppService.cs | 18 ++++-------------- 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LocationSupportAppService.cs | 120 ++++++++++++++++++++++++++++++++++++------------------------------------------------------------------------------------ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductCategoryAppService.cs | 19 +++++-------------- 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppAuthAppService.cs | 4 +++- 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppLabelingAppService.cs | 4 ++-- 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_location_support_alter_drop_locationid.sql | 8 ++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_location_support_create.sql | 7 ++----- 项目相关文档/产品模块Categories接口对接说明.md | 42 +++++++++++++++++++++++++++++++++--------- 项目相关文档/平台端Categories图片上传接口说明.md | 6 +++--- 项目相关文档/本次新增与优化接口汇总.md | 20 ++++++++++---------- 项目相关文档/标签模块接口对接说明.md | 36 ++++++++++++++++++++++++++++++------ 项目相关文档/美国版App登录接口说明.md | 65 +++++++++++++++++++++++++++++++++++++---------------------------- 21 files changed, 300 insertions(+), 216 deletions(-) create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/UsAppJwtClaims.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/CategoryAppearanceStorageHelper.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_location_support_alter_drop_locationid.sql diff --git a/本次新增与优化接口汇总(1).md b/本次新增与优化接口汇总(1).md index e651ed6..3da3a4d 100644 --- a/本次新增与优化接口汇总(1).md +++ b/本次新增与优化接口汇总(1).md @@ -39,9 +39,9 @@ - `fl_product_category`(主表)关键字段: - `DisplayText`:按钮展示文案(为空可回退 `CategoryName`) - - `ButtonAppearance`:`TEXT/COLOR/IMAGE` - - `ButtonTextColor` / `ButtonBgColor` / `ButtonImageUrl` - - `ButtonStyleJson`:样式扩展 JSON(可选) + - `ButtonAppearance`:**JSON 格式字符串**(如 `["TEXT","COLOR"]`);兼容历史单行 `TEXT`/`COLOR`/`IMAGE`(保存时规范为 JSON 数组) + - `CategoryPhotoUrl`:**JSON 格式字符串**(展示数据由前端解析);非 JSON 纯文本入库时由后端包成 JSON 字符串 + - **已删除列**(若库上仍存在需迁移):`ButtonTextColor`、`ButtonBgColor`、`ButtonImageUrl`、`ButtonStyleJson` - `AvailabilityType`:`ALL/SPECIFIED`(门店可用范围) - `fl_product_category_location`(关联表): - `(CategoryId, LocationId)` 唯一约束;用于 `AvailabilityType=SPECIFIED` 指定门店 @@ -56,7 +56,7 @@ - **路径**:`/api/app/product-category` - **列表行新增返回**: - `displayText` - - `buttonAppearance` + - `buttonAppearance`、`categoryPhotoUrl`(均为字符串,内容多为 JSON) - `availabilityType` #### 2.2.2 详情 @@ -65,7 +65,7 @@ - **路径**:`/api/app/product-category/{id}` - **新增返回字段**: - `displayText` - - `buttonAppearance/buttonTextColor/buttonBgColor/buttonImageUrl/buttonStyleJson` + - `buttonAppearance`、`categoryPhotoUrl` - `availabilityType` - `locationIds`(当 `availabilityType=SPECIFIED` 返回门店 Id 列表,否则为空数组) @@ -75,7 +75,7 @@ - **路径**:`/api/app/product-category` - **新增入参字段**: - `displayText` - - `buttonAppearance/buttonTextColor/buttonBgColor/buttonImageUrl/buttonStyleJson` + - `buttonAppearance`、`categoryPhotoUrl`(JSON 字符串约定,详见 `项目相关文档/产品模块Categories接口对接说明.md`) - `availabilityType` - `locationIds`(当 `availabilityType=SPECIFIED` 必填且至少 1 个) @@ -95,9 +95,7 @@ - `availabilityType` 仅允许 `ALL/SPECIFIED` - `SPECIFIED` 时 `locationIds` 至少 1 个 -- `buttonAppearance` 仅允许 `TEXT/COLOR/IMAGE` - - `IMAGE` 时必须有 `buttonImageUrl` - - `COLOR` 时必须有 `buttonBgColor` +- `buttonAppearance`:须为 **合法 JSON**,或为兼容的 **`TEXT`/`COLOR`/`IMAGE` 单行**;`categoryPhotoUrl` 非 JSON 纯文本时后端会序列化为 JSON 字符串存储 --- @@ -109,9 +107,9 @@ - `fl_label_category`(主表)关键字段: - `DisplayText`:按钮展示文案(为空可回退 `CategoryName`) - - `ButtonAppearance`:`TEXT/COLOR/IMAGE` - - `ButtonTextColor` / `ButtonBgColor` / `ButtonImageUrl` - - `ButtonStyleJson`:样式扩展 JSON(可选) + - `ButtonAppearance`:**JSON 格式字符串**(如 `["TEXT","COLOR"]`);兼容历史单行 `TEXT`/`COLOR`/`IMAGE` + - `CategoryPhotoUrl`:**JSON 格式字符串**;非 JSON 纯文本入库时由后端包成 JSON 字符串 + - **已删除列**(若库上仍存在需迁移):`ButtonTextColor`、`ButtonBgColor`、`ButtonImageUrl`、`ButtonStyleJson` - `AvailabilityType`:`ALL/SPECIFIED`(门店可用范围) - `fl_label_category_location`(关联表): - `(CategoryId, LocationId)` 唯一约束;用于 `AvailabilityType=SPECIFIED` 指定门店(`LocationId` 对应 `location` 表主键) @@ -126,7 +124,7 @@ - **路径**:`/api/app/label-category` - **列表行新增返回**: - `displayText` - - `buttonAppearance` + - `buttonAppearance`、`categoryPhotoUrl`(均为字符串,内容多为 JSON) - `availabilityType` #### 3.2.2 详情 @@ -135,7 +133,7 @@ - **路径**:`/api/app/label-category/{id}` - **新增返回字段**: - `displayText` - - `buttonAppearance/buttonTextColor/buttonBgColor/buttonImageUrl/buttonStyleJson` + - `buttonAppearance`、`categoryPhotoUrl` - `availabilityType` - `locationIds`(当 `availabilityType=SPECIFIED` 返回门店 Id 列表,否则为空数组) @@ -145,7 +143,7 @@ - **路径**:`/api/app/label-category` - **新增入参字段**: - `displayText` - - `buttonAppearance/buttonTextColor/buttonBgColor/buttonImageUrl/buttonStyleJson` + - `buttonAppearance`、`categoryPhotoUrl`(JSON 字符串约定,详见 `项目相关文档/标签模块接口对接说明.md`) - `availabilityType` - `locationIds`(当 `availabilityType=SPECIFIED` 必填且至少 1 个) @@ -165,7 +163,5 @@ - `availabilityType` 仅允许 `ALL/SPECIFIED` - `SPECIFIED` 时 `locationIds` 至少 1 个 -- `buttonAppearance` 仅允许 `TEXT/COLOR/IMAGE` - - `IMAGE` 时必须有 `buttonImageUrl` - - `COLOR` 时必须有 `buttonBgColor` +- `buttonAppearance`:须为 **合法 JSON**,或为兼容的 **`TEXT`/`COLOR`/`IMAGE` 单行**;`categoryPhotoUrl` 非 JSON 纯文本时后端会序列化为 JSON 字符串存储 diff --git a/标签模块接口对接说明.md b/标签模块接口对接说明.md index b8553a7..5a88ce2 100644 --- a/标签模块接口对接说明.md +++ b/标签模块接口对接说明.md @@ -51,6 +51,12 @@ Swagger 地址: } ``` +### 1.1.1 字段约定:`buttonAppearance` 与 `categoryPhotoUrl`(JSON 字符串) + +- **`buttonAppearance`**:库中存 **JSON 文本**(如 `["TEXT","COLOR"]`、仅图片 `["IMAGE"]` 等);兼容历史单行 `TEXT`/`COLOR`/`IMAGE`(保存时规范为 `["TEXT"]` 等)。未传或空白时后端默认 `["TEXT"]`。非法值(非 JSON 且非上述三者)会返回友好错误。 +- **`categoryPhotoUrl`**:同样为 **JSON 文本**(如 `["Prep","#10B981"]`);若传**非 JSON** 的纯文本(色值、`/picture/...` 等),后端会序列化为合法 JSON 字符串再存储。列表/详情/App 树**原样返回**字符串,由客户端解析。 +- 其它常用字段:`displayText`、`availabilityType`(`ALL`/`SPECIFIED`)、`locationIds`(指定门店时必填),与产品类别接口语义一致(见 `项目相关文档/产品模块Categories接口对接说明.md`)。 + ### 1.2 详情 方法:`GET /api/app/label-category/{id}` @@ -69,7 +75,11 @@ Swagger 地址: { "categoryCode": "CAT_PREP", "categoryName": "Prep", - "categoryPhotoUrl": "https://cdn.example.com/cat-prep.png", + "displayText": "Prep", + "buttonAppearance": "[\"TEXT\",\"COLOR\"]", + "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]", + "availabilityType": "ALL", + "locationIds": [], "state": true, "orderNum": 1 } @@ -85,7 +95,11 @@ Swagger 地址: { "categoryCode": "CAT_PREP", "categoryName": "Prep", - "categoryPhotoUrl": null, + "displayText": "Prep", + "buttonAppearance": "[\"TEXT\",\"COLOR\"]", + "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]", + "availabilityType": "ALL", + "locationIds": [], "state": true, "orderNum": 2 } @@ -706,7 +720,8 @@ ADD UNIQUE KEY `uk_fl_ltpd_template_product_label_type` (`TemplateId`, `ProductI |------|------|------| | `id` | string | `fl_label_category.Id` | | `categoryName` | string | 分类名称 | -| `categoryPhotoUrl` | string \| null | 分类图标/图 | +| `categoryPhotoUrl` | string \| null | 分类展示数据,**JSON 格式字符串**(与库中 `CategoryPhotoUrl` 一致,客户端解析) | +| `buttonAppearance` | string | 按钮外观,**JSON 格式字符串**(与库中 `ButtonAppearance` 一致;空时后端默认 `"TEXT"`) | | `orderNum` | number | 排序 | | `productCategories` | array | 第二级列表(见下表) | @@ -715,8 +730,12 @@ ADD UNIQUE KEY `uk_fl_ltpd_template_product_label_type` (`TemplateId`, `ProductI | 字段 | 类型 | 说明 | |------|------|------| | `categoryId` | string \| null | 产品分类Id;产品未归类或分类不存在时为空 | -| `categoryPhotoUrl` | string \| null | 产品分类图片地址;产品未归类或分类不存在时为空 | +| `categoryPhotoUrl` | string \| null | 产品分类展示数据,**JSON 格式字符串**;未归类或分类不存在时为空 | | `name` | string | 产品分类显示名;空源数据为 **`无`** | +| `displayText` | string \| null | 按钮展示文案 | +| `buttonAppearance` | string | **JSON 格式字符串**(与库中一致;空时默认 `"TEXT"`) | +| `availabilityType` | string | `ALL` / `SPECIFIED`(树已按当前门店过滤,仍返回供客户端展示) | +| `orderNum` | number | 排序 | | `itemCount` | number | 该分类下 **产品个数**(去重后的产品数) | | `products` | array | 第三级产品列表(见下表) | @@ -771,13 +790,18 @@ curl -X GET "http://localhost:19001/api/app/us-app-labeling/labeling-tree?locati { "id": "cat-prep-id", "categoryName": "Prep", - "categoryPhotoUrl": "/picture/...", + "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]", + "buttonAppearance": "[\"TEXT\",\"COLOR\"]", "orderNum": 1, "productCategories": [ { "categoryId": "pc-meat-id", - "categoryPhotoUrl": "/picture/product-category/20260325123010_xxx.png", + "categoryPhotoUrl": "[\"/picture/product-category/20260325123010_xxx.png\"]", "name": "Meat", + "displayText": "Meat", + "buttonAppearance": "[\"IMAGE\"]", + "availabilityType": "ALL", + "orderNum": 10, "itemCount": 1, "products": [ { diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LocationSupport/LocationSupportCreateInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LocationSupport/LocationSupportCreateInputVo.cs index c3b02c6..e2d70a1 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LocationSupport/LocationSupportCreateInputVo.cs +++ b/美国版/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; /// public class LocationSupportCreateInputVo { - public string LocationId { get; set; } = string.Empty; - public string SupportPhone { get; set; } = string.Empty; public string SupportEmail { get; set; } = string.Empty; diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LocationSupport/LocationSupportGetOutputDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LocationSupport/LocationSupportGetOutputDto.cs index e30ed66..0dd1801 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LocationSupport/LocationSupportGetOutputDto.cs +++ b/美国版/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 { public string Id { get; set; } = string.Empty; - public string LocationId { get; set; } = string.Empty; - - public string? LocationName { get; set; } - public string SupportPhone { get; set; } = string.Empty; public string SupportEmail { get; set; } = string.Empty; diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LocationSupport/LocationSupportUpdateInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LocationSupport/LocationSupportUpdateInputVo.cs index 2c131f3..6e40b87 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LocationSupport/LocationSupportUpdateInputVo.cs +++ b/美国版/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; /// public class LocationSupportUpdateInputVo { - public string LocationId { get; set; } = string.Empty; - public string SupportPhone { get; set; } = string.Empty; public string SupportEmail { get; set; } = string.Empty; diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/ILocationSupportAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/ILocationSupportAppService.cs index 4b403ad..73f3a9d 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/ILocationSupportAppService.cs +++ b/美国版/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; namespace FoodLabeling.Application.Contracts.IServices; /// -/// 门店 Support 联系方式管理(后台设置) +/// 全局 Support 联系方式(全平台共用;Web 可增改查,App 仅可查) /// public interface ILocationSupportAppService : IApplicationService { /// - /// 按门店查询 Support 联系方式 + /// 查询全局 Support 联系方式(已登录即可;App / Web 共用) /// - /// 门店Id - Task GetByLocationIdAsync(string locationId); + Task GetSupportAsync(); /// - /// 新增门店 Support 联系方式(每个门店仅允许一条) + /// 新增全局 Support 联系方式(系统仅允许一条;Web 管理端) /// /// 联系方式 Task CreateAsync(LocationSupportCreateInputVo input); /// - /// 编辑门店 Support 联系方式 + /// 编辑全局 Support 联系方式(Web 管理端) /// /// 联系方式主键 /// 联系方式 diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/UsAppJwtClaims.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/UsAppJwtClaims.cs new file mode 100644 index 0000000..2727138 --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/UsAppJwtClaims.cs @@ -0,0 +1,13 @@ +namespace FoodLabeling.Application.Contracts; + +/// +/// 美国版 App JWT 自定义声明(用于与 Web 管理端 Token 区分能力) +/// +public static class UsAppJwtClaims +{ + /// 声明类型:客户端种类 + public const string ClientKind = "client_kind"; + + /// 美国版移动端 App + public const string ClientKindUsApp = "us-app"; +} diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/CategoryAppearanceStorageHelper.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/CategoryAppearanceStorageHelper.cs new file mode 100644 index 0000000..7faa3ec --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/CategoryAppearanceStorageHelper.cs @@ -0,0 +1,65 @@ +using System.Text.Json; +using Volo.Abp; + +namespace FoodLabeling.Application.Helpers; + +/// +/// 将标签/产品类别的按钮外观与展示字段按「JSON 字符串」落库;兼容历史单行 TEXT/COLOR/IMAGE。 +/// +public static class CategoryAppearanceStorageHelper +{ + /// 未传按钮外观时的默认 JSON(与前端数组语义一致)。 + public const string DefaultButtonAppearanceJson = """["TEXT"]"""; + + /// + /// 规范化 / + /// 产品类别同名字段:落库为合法 JSON 文本,不做整串 ToUpper(避免破坏 JSON)。 + /// + public static string NormalizeButtonAppearanceForStorage(string? raw) + { + if (string.IsNullOrWhiteSpace(raw)) + { + return DefaultButtonAppearanceJson; + } + + var t = raw.Trim(); + var legacy = t.ToUpperInvariant(); + if (legacy is "TEXT" or "COLOR" or "IMAGE") + { + return JsonSerializer.Serialize(new[] { legacy }); + } + + try + { + using var _ = JsonDocument.Parse(t); + return t; + } + catch (JsonException) + { + throw new UserFriendlyException("按钮外观格式不正确,须为合法 JSON(或兼容旧的 TEXT/COLOR/IMAGE)"); + } + } + + /// + /// 规范化 / + /// 产品类别同名字段:已是 JSON 则原样落库;否则将整段文本序列化为 JSON 字符串(兼容历史单行色值/URL)。 + /// + public static string? NormalizeCategoryPhotoUrlForStorage(string? raw) + { + if (string.IsNullOrWhiteSpace(raw)) + { + return null; + } + + var t = raw.Trim(); + try + { + using var _ = JsonDocument.Parse(t); + return t; + } + catch (JsonException) + { + return JsonSerializer.Serialize(t); + } + } +} diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLocationSupportDbEntity.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLocationSupportDbEntity.cs index 296d2f5..746ba5a 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLocationSupportDbEntity.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLocationSupportDbEntity.cs @@ -21,8 +21,6 @@ public class FlLocationSupportDbEntity public DateTime? LastModificationTime { get; set; } - public string LocationId { get; set; } = string.Empty; - public string SupportPhone { get; set; } = string.Empty; public string SupportEmail { get; set; } = string.Empty; diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelCategoryAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelCategoryAppService.cs index 827d7cc..b4818b4 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelCategoryAppService.cs +++ b/美国版/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 } var displayText = input.DisplayText?.Trim(); - var appearance = (input.ButtonAppearance ?? "TEXT").Trim().ToUpperInvariant(); + var appearance = CategoryAppearanceStorageHelper.NormalizeButtonAppearanceForStorage(input.ButtonAppearance); var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant(); - ValidateButtonAppearance(appearance); var locationIds = NormalizeLocationIds(input.LocationIds); ValidateAvailabilityTypeAndLocations(availabilityType, locationIds); @@ -146,7 +145,7 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ CategoryCode = code, CategoryName = name, DisplayText = string.IsNullOrWhiteSpace(displayText) ? null : displayText, - CategoryPhotoUrl = input.CategoryPhotoUrl?.Trim(), + CategoryPhotoUrl = CategoryAppearanceStorageHelper.NormalizeCategoryPhotoUrlForStorage(input.CategoryPhotoUrl), State = input.State, ButtonAppearance = appearance, AvailabilityType = availabilityType, @@ -175,9 +174,8 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ } var displayText = input.DisplayText?.Trim(); - var appearance = (input.ButtonAppearance ?? "TEXT").Trim().ToUpperInvariant(); + var appearance = CategoryAppearanceStorageHelper.NormalizeButtonAppearanceForStorage(input.ButtonAppearance); var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant(); - ValidateButtonAppearance(appearance); var locationIds = NormalizeLocationIds(input.LocationIds); ValidateAvailabilityTypeAndLocations(availabilityType, locationIds); @@ -191,7 +189,7 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ entity.CategoryCode = code; entity.CategoryName = name; entity.DisplayText = string.IsNullOrWhiteSpace(displayText) ? null : displayText; - entity.CategoryPhotoUrl = input.CategoryPhotoUrl?.Trim(); + entity.CategoryPhotoUrl = CategoryAppearanceStorageHelper.NormalizeCategoryPhotoUrlForStorage(input.CategoryPhotoUrl); entity.State = input.State; entity.ButtonAppearance = appearance; entity.AvailabilityType = availabilityType; @@ -264,14 +262,6 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ .ToList() ?? new(); } - private static void ValidateButtonAppearance(string appearance) - { - if (appearance != "TEXT" && appearance != "COLOR" && appearance != "IMAGE") - { - throw new UserFriendlyException("按钮外观不合法(TEXT/COLOR/IMAGE)"); - } - } - private async Task SaveCategoryLocationsAsync( string categoryId, string availabilityType, diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LocationSupportAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LocationSupportAppService.cs index f5263f0..0421c09 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LocationSupportAppService.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LocationSupportAppService.cs @@ -1,7 +1,8 @@ +using FoodLabeling.Application.Contracts; using FoodLabeling.Application.Contracts.Dtos.LocationSupport; using FoodLabeling.Application.Contracts.IServices; using FoodLabeling.Application.Services.DbModels; -using FoodLabeling.Domain.Entities; +using Microsoft.AspNetCore.Authorization; using Volo.Abp; using Volo.Abp.Application.Services; using Volo.Abp.Guids; @@ -11,8 +12,9 @@ using Yi.Framework.SqlSugarCore.Abstractions; namespace FoodLabeling.Application.Services; /// -/// 门店 Support 联系方式(后台设置,App 展示) +/// 全局 Support 联系方式(全门店共用;Web 可增改查,App JWT 仅可读) /// +[Authorize] public class LocationSupportAppService : ApplicationService, ILocationSupportAppService { private readonly ISqlSugarDbContext _dbContext; @@ -25,46 +27,36 @@ public class LocationSupportAppService : ApplicationService, ILocationSupportApp } /// - public async Task GetByLocationIdAsync(string locationId) + public async Task GetSupportAsync() { - var lid = locationId?.Trim(); - if (string.IsNullOrWhiteSpace(lid)) - { - throw new UserFriendlyException("门店Id不能为空"); - } - - // 全门店共用一套 Support 联系方式,按任意门店 Id 查询都返回同一条配置。 - var entity = await _dbContext.SqlSugarClient.Queryable() - .FirstAsync(x => !x.IsDeleted); - if (entity is null) - { - return null; - } - - return await MapOutputAsync(entity); + var rows = await _dbContext.SqlSugarClient.Queryable() + .Where(x => !x.IsDeleted) + .ToListAsync(); + var entity = rows.FirstOrDefault(); + return MapOutput(entity); } /// [UnitOfWork] public async Task CreateAsync(LocationSupportCreateInputVo input) { + EnsureNotUsAppClient(); + if (input is null) { - throw new UserFriendlyException("入参不能为空"); + throw new UserFriendlyException("Request body is required."); } - var locationId = NormalizeLocationId(input.LocationId); - var phone = NormalizeRequired(input.SupportPhone, "Support 电话不能为空"); - var email = NormalizeRequired(input.SupportEmail, "Support 邮箱不能为空"); + var phone = NormalizeRequired(input.SupportPhone, "Support phone is required."); + var email = NormalizeRequired(input.SupportEmail, "Support email is required."); EnsureEmailFormat(email); - await EnsureLocationExistsAsync(locationId); - var existed = await _dbContext.SqlSugarClient.Queryable() .AnyAsync(x => !x.IsDeleted); if (existed) { - throw new UserFriendlyException("已存在全局 Support 联系方式,请使用编辑接口"); + throw new UserFriendlyException( + "Global support contact already exists. Use update instead."); } var now = Clock.Now; @@ -76,74 +68,60 @@ public class LocationSupportAppService : ApplicationService, ILocationSupportApp CreatorId = CurrentUser?.Id?.ToString(), LastModificationTime = now, LastModifierId = CurrentUser?.Id?.ToString(), - LocationId = locationId, SupportPhone = phone, SupportEmail = email }; await _dbContext.SqlSugarClient.Insertable(entity).ExecuteCommandAsync(); - return (await MapOutputAsync(entity))!; + return MapOutput(entity)!; } /// [UnitOfWork] public async Task UpdateAsync(string id, LocationSupportUpdateInputVo input) { + EnsureNotUsAppClient(); + var supportId = id?.Trim(); if (string.IsNullOrWhiteSpace(supportId)) { - throw new UserFriendlyException("联系方式Id不能为空"); + throw new UserFriendlyException("Support record id is required."); } if (input is null) { - throw new UserFriendlyException("入参不能为空"); + throw new UserFriendlyException("Request body is required."); } - var entity = await _dbContext.SqlSugarClient.Queryable() - .FirstAsync(x => !x.IsDeleted && x.Id == supportId); + var rows = await _dbContext.SqlSugarClient.Queryable() + .Where(x => !x.IsDeleted && x.Id == supportId) + .ToListAsync(); + var entity = rows.FirstOrDefault(); if (entity is null) { - throw new UserFriendlyException("联系方式记录不存在"); + throw new UserFriendlyException("Support record not found."); } - var locationId = NormalizeLocationId(input.LocationId); - var phone = NormalizeRequired(input.SupportPhone, "Support 电话不能为空"); - var email = NormalizeRequired(input.SupportEmail, "Support 邮箱不能为空"); + var phone = NormalizeRequired(input.SupportPhone, "Support phone is required."); + var email = NormalizeRequired(input.SupportEmail, "Support email is required."); EnsureEmailFormat(email); - await EnsureLocationExistsAsync(locationId); - var conflict = await _dbContext.SqlSugarClient.Queryable() - .AnyAsync(x => !x.IsDeleted && x.Id != supportId); - if (conflict) - { - throw new UserFriendlyException("系统仅允许一条全局 Support 联系方式"); - } - - entity.LocationId = locationId; entity.SupportPhone = phone; entity.SupportEmail = email; entity.LastModificationTime = Clock.Now; entity.LastModifierId = CurrentUser?.Id?.ToString(); await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync(); - return (await MapOutputAsync(entity))!; + return MapOutput(entity)!; } - private string NormalizeLocationId(string? locationId) + private void EnsureNotUsAppClient() { - var lid = locationId?.Trim(); - if (string.IsNullOrWhiteSpace(lid)) - { - throw new UserFriendlyException("门店Id不能为空"); - } - - if (!Guid.TryParse(lid, out _)) + if (CurrentUser.FindClaim(UsAppJwtClaims.ClientKind)?.Value == UsAppJwtClaims.ClientKindUsApp) { - throw new UserFriendlyException("门店Id格式不正确"); + throw new UserFriendlyException( + "The mobile app can only view support contacts. Please use the web console to edit."); } - - return lid; } private static string NormalizeRequired(string? value, string message) @@ -162,48 +140,22 @@ public class LocationSupportAppService : ApplicationService, ILocationSupportApp if (!email.Contains("@", StringComparison.Ordinal) || email.StartsWith("@", StringComparison.Ordinal) || email.EndsWith("@", StringComparison.Ordinal)) { - throw new UserFriendlyException("Support 邮箱格式不正确"); + throw new UserFriendlyException("Support email format is invalid."); } } - private async Task EnsureLocationExistsAsync(string locationId) - { - if (!Guid.TryParse(locationId, out var gid)) - { - throw new UserFriendlyException("门店Id格式不正确"); - } - - var exists = await _dbContext.SqlSugarClient.Queryable() - .AnyAsync(x => !x.IsDeleted && x.Id == gid); - if (!exists) - { - throw new UserFriendlyException("门店不存在"); - } - } - - private async Task MapOutputAsync(FlLocationSupportDbEntity? entity) + private static LocationSupportGetOutputDto? MapOutput(FlLocationSupportDbEntity? entity) { if (entity is null) { return null; } - string? locationName = null; - if (Guid.TryParse(entity.LocationId, out var lid)) - { - var loc = await _dbContext.SqlSugarClient.Queryable() - .FirstAsync(x => !x.IsDeleted && x.Id == lid); - locationName = loc?.LocationName?.Trim(); - } - return new LocationSupportGetOutputDto { Id = entity.Id, - LocationId = entity.LocationId, - LocationName = locationName, SupportPhone = entity.SupportPhone, SupportEmail = entity.SupportEmail }; } } - diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductCategoryAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductCategoryAppService.cs index 9af1412..a66c2f2 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductCategoryAppService.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductCategoryAppService.cs @@ -1,3 +1,4 @@ +using FoodLabeling.Application.Helpers; using FoodLabeling.Application.Contracts.Dtos.Common; using FoodLabeling.Application.Contracts.Dtos.ProductCategory; using FoodLabeling.Application.Contracts.IServices; @@ -128,9 +129,8 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp } var displayText = input.DisplayText?.Trim(); - var appearance = (input.ButtonAppearance ?? "TEXT").Trim().ToUpperInvariant(); + var appearance = CategoryAppearanceStorageHelper.NormalizeButtonAppearanceForStorage(input.ButtonAppearance); var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant(); - ValidateButtonAppearance(appearance); var locationIds = NormalizeLocationIds(input.LocationIds); ValidateAvailabilityTypeAndLocations(availabilityType, locationIds); @@ -155,7 +155,7 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp CategoryCode = code, CategoryName = name, DisplayText = string.IsNullOrWhiteSpace(displayText) ? null : displayText, - CategoryPhotoUrl = input.CategoryPhotoUrl?.Trim(), + CategoryPhotoUrl = CategoryAppearanceStorageHelper.NormalizeCategoryPhotoUrlForStorage(input.CategoryPhotoUrl), ButtonAppearance = appearance, State = input.State, AvailabilityType = availabilityType, @@ -187,9 +187,8 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp } var displayText = input.DisplayText?.Trim(); - var appearance = (input.ButtonAppearance ?? "TEXT").Trim().ToUpperInvariant(); + var appearance = CategoryAppearanceStorageHelper.NormalizeButtonAppearanceForStorage(input.ButtonAppearance); var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant(); - ValidateButtonAppearance(appearance); var locationIds = NormalizeLocationIds(input.LocationIds); ValidateAvailabilityTypeAndLocations(availabilityType, locationIds); @@ -203,7 +202,7 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp entity.CategoryCode = code; entity.CategoryName = name; entity.DisplayText = string.IsNullOrWhiteSpace(displayText) ? null : displayText; - entity.CategoryPhotoUrl = input.CategoryPhotoUrl?.Trim(); + entity.CategoryPhotoUrl = CategoryAppearanceStorageHelper.NormalizeCategoryPhotoUrlForStorage(input.CategoryPhotoUrl); entity.ButtonAppearance = appearance; entity.State = input.State; entity.AvailabilityType = availabilityType; @@ -280,14 +279,6 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp .ToList() ?? new(); } - private static void ValidateButtonAppearance(string appearance) - { - if (appearance != "TEXT" && appearance != "COLOR" && appearance != "IMAGE") - { - throw new UserFriendlyException("按钮外观不合法(TEXT/COLOR/IMAGE)"); - } - } - private async Task SaveCategoryLocationsAsync( string categoryId, string availabilityType, diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppAuthAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppAuthAppService.cs index 4afa3f5..deacda1 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppAuthAppService.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppAuthAppService.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Security.Claims; using System.Text; using System.Threading.Tasks; +using FoodLabeling.Application.Contracts; using FoodLabeling.Application.Contracts.Dtos.UsAppAuth; using FoodLabeling.Application.Contracts.IServices; using FoodLabeling.Application.Services.DbModels; @@ -432,7 +433,8 @@ public class UsAppAuthAppService : ApplicationService, IUsAppAuthAppService var claims = new List { new(AbpClaimTypes.UserId, user.Id.ToString()), - new(AbpClaimTypes.UserName, user.UserName) + new(AbpClaimTypes.UserName, user.UserName), + new(UsAppJwtClaims.ClientKind, UsAppJwtClaims.ClientKindUsApp) }; if (!string.IsNullOrWhiteSpace(user.Email)) diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppLabelingAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppLabelingAppService.cs index 7729567..03a6822 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppLabelingAppService.cs +++ b/美国版/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 { var l1Appearance = string.IsNullOrWhiteSpace(g1.Key.LabelCategoryButtonAppearance) ? "TEXT" - : g1.Key.LabelCategoryButtonAppearance.Trim().ToUpperInvariant(); + : g1.Key.LabelCategoryButtonAppearance.Trim(); var l1 = new UsAppLabelCategoryTreeNodeDto { Id = g1.Key.LabelCategoryId, @@ -178,7 +178,7 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ var productsGrouped = g2.GroupBy(x => x.ProductId).OrderBy(pg => pg.First().ProductName); var appearance = string.IsNullOrWhiteSpace(g2.Key.ButtonAppearance) ? "TEXT" - : g2.Key.ButtonAppearance.Trim().ToUpperInvariant(); + : g2.Key.ButtonAppearance.Trim(); var availability = string.IsNullOrWhiteSpace(g2.Key.AvailabilityType) ? "ALL" : g2.Key.AvailabilityType.Trim().ToUpperInvariant(); diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_location_support_alter_drop_locationid.sql b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_location_support_alter_drop_locationid.sql new file mode 100644 index 0000000..a0c37fd --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_location_support_alter_drop_locationid.sql @@ -0,0 +1,8 @@ +-- 从旧版「按门店」结构迁移为「全局一条」:删除 LocationId 及门店唯一索引 +-- 执行前请确认库中 `fl_location_support` 已存在;若不存在可跳过本脚本,直接使用 fl_location_support_create.sql + +-- 若存在按门店的唯一索引则删除(名称与历史脚本一致) +ALTER TABLE `fl_location_support` DROP INDEX `uk_fl_location_support_locationid`; + +-- 删除门店列(若列不存在会报错,需按环境调整) +ALTER TABLE `fl_location_support` DROP COLUMN `LocationId`; diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_location_support_create.sql b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_location_support_create.sql index 4d16b3c..2c5b93a 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_location_support_create.sql +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_location_support_create.sql @@ -1,4 +1,4 @@ --- 门店 Support 联系方式(每个门店仅一条) +-- 全局 Support 联系方式(全门店共用一条) CREATE TABLE IF NOT EXISTS `fl_location_support` ( `Id` varchar(50) NOT NULL COMMENT '主键', `IsDeleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除', @@ -6,11 +6,8 @@ CREATE TABLE IF NOT EXISTS `fl_location_support` ( `CreatorId` varchar(50) DEFAULT NULL COMMENT '创建人', `LastModifierId` varchar(50) DEFAULT NULL COMMENT '最后修改人', `LastModificationTime` datetime DEFAULT NULL COMMENT '最后修改时间', - `LocationId` varchar(50) NOT NULL COMMENT '门店Id(对应location.Id)', `SupportPhone` varchar(100) NOT NULL COMMENT 'Support 电话', `SupportEmail` varchar(200) NOT NULL COMMENT 'Support 邮箱', PRIMARY KEY (`Id`), - UNIQUE KEY `uk_fl_location_support_locationid` (`LocationId`), KEY `idx_fl_location_support_isdeleted` (`IsDeleted`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='门店 Support 联系方式'; - +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='全局 Support 联系方式'; diff --git a/项目相关文档/产品模块Categories接口对接说明.md b/项目相关文档/产品模块Categories接口对接说明.md index 37ad481..d548dee 100644 --- a/项目相关文档/产品模块Categories接口对接说明.md +++ b/项目相关文档/产品模块Categories接口对接说明.md @@ -8,7 +8,9 @@ - **接口前缀**:宿主统一前缀为 `/api/app` - **分类表**:`fl_product_category` - **关联字段**:`fl_product.category_id` → `fl_product_category.id` -- **图片字段**:`CategoryPhotoUrl`(前端字段:`categoryPhotoUrl`) +- **外观字段(字符串落库,内容为 JSON 文本)**: + - `ButtonAppearance`(`buttonAppearance`):如 `["TEXT","COLOR"]`、仅图片 `["IMAGE"]`、或合法 JSON 对象/数组;兼容历史单行 `TEXT`/`COLOR`/`IMAGE`(保存时会规范为 JSON 数组,如 `["TEXT"]`)。 + - `CategoryPhotoUrl`(`categoryPhotoUrl`):与外观配合的**展示数据**,同样为 **JSON 字符串**(如 `["Prep","#10B981"]`、图片 URL 数组等);若传入**非 JSON** 的纯文本(如旧数据中的 `#EC4899` 或 `/picture/...`),后端会序列化为合法 JSON 字符串再存储。列表/详情/App 树**原样返回**库中字符串,由前端解析。 > 说明:本文以 Swagger 为准(本地示例:`http://localhost:19001/swagger`,搜索 `ProductCategory`)。 @@ -57,7 +59,10 @@ Authorization: Bearer eyJhbGciOi... | `id` | string | 主键 | | `categoryCode` | string | 类别编码 | | `categoryName` | string | 类别名称 | -| `categoryPhotoUrl` | string \| null | 类别图片 URL(建议用 `/picture/...`) | +| `displayText` | string \| null | 按钮展示文案(空可回退 `categoryName`) | +| `categoryPhotoUrl` | string \| null | 分类展示数据,**JSON 格式字符串**(含义由前端与 `buttonAppearance` 约定) | +| `buttonAppearance` | string | 按钮外观,**JSON 格式字符串**(见上文「外观字段」) | +| `availabilityType` | string | `ALL` / `SPECIFIED`(门店可用范围) | | `state` | boolean | 是否启用 | | `orderNum` | number | 排序 | | `lastEdited` | string | 最后编辑时间 | @@ -75,7 +80,10 @@ Authorization: Bearer eyJhbGciOi... "id": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f", "categoryCode": "CAT_PREP", "categoryName": "Prep", - "categoryPhotoUrl": "/picture/category/20260325123010_xxx.png", + "displayText": "Prep", + "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]", + "buttonAppearance": "[\"TEXT\",\"COLOR\"]", + "availabilityType": "ALL", "state": true, "orderNum": 100, "lastEdited": "2026-03-25 12:30:10" @@ -108,7 +116,11 @@ Authorization: Bearer eyJhbGciOi... "id": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f", "categoryCode": "CAT_PREP", "categoryName": "Prep", - "categoryPhotoUrl": "/picture/category/20260325123010_xxx.png", + "displayText": "Prep", + "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]", + "buttonAppearance": "[\"TEXT\",\"COLOR\"]", + "availabilityType": "ALL", + "locationIds": [], "state": true, "orderNum": 100 } @@ -130,7 +142,11 @@ Authorization: Bearer eyJhbGciOi... |------|------|------|------| | `categoryCode` | string | 是 | 类别编码(唯一) | | `categoryName` | string | 是 | 类别名称(唯一) | -| `categoryPhotoUrl` | string \| null | 否 | 图片 URL(建议先上传图片拿到 `/picture/...` 再保存) | +| `displayText` | string \| null | 否 | 按钮展示文案 | +| `categoryPhotoUrl` | string \| null | 否 | **JSON 字符串**;与 `buttonAppearance` 配合(见概述)。纯路径等非 JSON 文本会被后端包成 JSON 字符串存储。 | +| `buttonAppearance` | string | 否 | **JSON 字符串**;未传或空白时后端默认 `["TEXT"]`。兼容传 `TEXT`/`COLOR`/`IMAGE` 单行(会规范为 `["TEXT"]` 等)。非法非 JSON 且非上述三者时报错。 | +| `availabilityType` | string | 否 | `ALL`(默认)或 `SPECIFIED` | +| `locationIds` | string[] | 条件 | `availabilityType=SPECIFIED` 时必填且至少 1 个门店 Id | | `state` | boolean | 否 | 是否启用(默认 true) | | `orderNum` | number | 否 | 排序(默认 0) | @@ -140,7 +156,11 @@ Authorization: Bearer eyJhbGciOi... { "categoryCode": "CAT_PREP", "categoryName": "Prep", - "categoryPhotoUrl": "/picture/category/20260325123010_xxx.png", + "displayText": "Prep", + "buttonAppearance": "[\"TEXT\",\"COLOR\"]", + "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]", + "availabilityType": "ALL", + "locationIds": [], "state": true, "orderNum": 100 } @@ -162,7 +182,11 @@ Authorization: Bearer eyJhbGciOi... { "categoryCode": "CAT_PREP", "categoryName": "Prep", - "categoryPhotoUrl": "/picture/category/20260325123010_xxx.png", + "displayText": "Prep", + "buttonAppearance": "[\"TEXT\",\"COLOR\"]", + "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]", + "availabilityType": "ALL", + "locationIds": [], "state": true, "orderNum": 100 } @@ -179,7 +203,7 @@ Authorization: Bearer eyJhbGciOi... ### 约束 -- 若该类别已被 `fl_label` 引用(`fl_label.LabelCategoryId = id`),删除会失败并返回友好提示:`该类别已被标签引用,无法删除`。 +- 若该类别已被 `fl_product` 引用(`fl_product.CategoryId = id`),删除会失败并返回友好提示:`该类别已被产品引用,无法删除`。 ### 请求示例 @@ -200,5 +224,5 @@ Authorization: Bearer eyJhbGciOi... 推荐前端流程: 1. 调用上传接口 `POST /api/app/picture/category/upload` 拿到响应 `url` -2. 新增/编辑类别时把 `categoryPhotoUrl` 设为该 `url` +2. 新增/编辑类别时:若采用 **JSON** 存展示数据,将 `url` 写入你方约定的 JSON 结构(例如 `["IMAGE","/picture/..."]`);若仍传**纯路径字符串**,后端会将其序列化为 JSON 字符串再入库(与仅图片场景兼容)。 diff --git a/项目相关文档/平台端Categories图片上传接口说明.md b/项目相关文档/平台端Categories图片上传接口说明.md index edeb7d7..5742322 100644 --- a/项目相关文档/平台端Categories图片上传接口说明.md +++ b/项目相关文档/平台端Categories图片上传接口说明.md @@ -54,7 +54,7 @@ curl -X POST "http://localhost:19001/api/app/picture/category/upload" ^ | 字段 | 类型 | 说明 | |------|------|------| -| `url` | string | 图片访问的相对路径,可直接保存到 `CategoryPhotoUrl`(例如:`/picture/category/xxx.png`) | +| `url` | string | 图片访问的相对路径;写入分类接口时,若 `categoryPhotoUrl` 采用 **JSON** 存展示数据,请将该 `url` 放入你方约定的 JSON 结构中。若仍传**纯路径字符串**,后端会序列化为 JSON 字符串再入库。 | | `fileName` | string | 服务器保存的文件名 | | `size` | number | 文件大小(字节) | @@ -96,7 +96,7 @@ curl -X POST "http://localhost:19001/api/app/picture/category/upload" ^ 推荐前端流程: 1. 调用本上传接口,拿到返回的 `url` -2. 再调用分类新增/编辑接口,把 `categoryPhotoUrl` 设置为该 `url` +2. 再调用分类新增/编辑接口:按平台与 **`buttonAppearance`(JSON 字符串)** 的约定组装 `categoryPhotoUrl`(JSON);或继续传纯 `url` 由后端自动包成 JSON 字符串。 -> 说明:分类 CRUD 已支持 `CategoryPhotoUrl` 字段;你只需要在页面表单里新增该字段即可。 +> 说明:详见 `项目相关文档/产品模块Categories接口对接说明.md`、`项目相关文档/标签模块接口对接说明.md` 中「JSON 字符串」约定。 diff --git a/项目相关文档/本次新增与优化接口汇总.md b/项目相关文档/本次新增与优化接口汇总.md index a2f79e6..5d7b960 100644 --- a/项目相关文档/本次新增与优化接口汇总.md +++ b/项目相关文档/本次新增与优化接口汇总.md @@ -42,8 +42,8 @@ - `fl_product_category`(主表)关键字段: - `DisplayText`:按钮展示文案(为空可回退 `CategoryName`) - - `ButtonAppearance`:`TEXT/COLOR/IMAGE`(前端按类型解析) - - `CategoryPhotoUrl`:与 `ButtonAppearance` 配合——`COLOR` 存颜色值、`IMAGE` 存图片 URL;`TEXT` 可空或不用 + - `ButtonAppearance`:**JSON 格式字符串**落库(如 `["TEXT","COLOR"]`、`["IMAGE"]`);兼容历史单行 `TEXT`/`COLOR`/`IMAGE`(保存时规范为 JSON 数组) + - `CategoryPhotoUrl`:**JSON 格式字符串**落库(展示数据由前端解析);非 JSON 纯文本(色值、URL 等)保存时会被后端包成 JSON 字符串 - **已删除列**(需在库上执行 `DROP COLUMN`):`ButtonTextColor`、`ButtonBgColor`、`ButtonImageUrl`、`ButtonStyleJson` - `AvailabilityType`:`ALL/SPECIFIED`(门店可用范围) - `fl_product_category_location`(关联表): @@ -69,7 +69,7 @@ - **路径**:`/api/app/product-category/{id}` - **新增返回字段**: - `displayText` - - `buttonAppearance`、`categoryPhotoUrl`(COLOR/IMAGE 的展示数据统一在此字段) + - `buttonAppearance`、`categoryPhotoUrl`(**JSON 格式字符串**,展示语义由前端解析) - `availabilityType` - `locationIds`(当 `availabilityType=SPECIFIED` 返回门店 Id 列表,否则为空数组) @@ -99,7 +99,7 @@ - `availabilityType` 仅允许 `ALL/SPECIFIED` - `SPECIFIED` 时 `locationIds` 至少 1 个 -- `buttonAppearance` 仅允许 `TEXT/COLOR/IMAGE`(接口层校验枚举;`categoryPhotoUrl` 是否必填由业务/前端约定,`COLOR/IMAGE` 时应写入该字段) +- `buttonAppearance`:须为 **合法 JSON**(任意对象/数组),或为兼容的 **`TEXT`/`COLOR`/`IMAGE` 单行**;其它字符串拒绝。`categoryPhotoUrl` 非空且非 JSON 时后端会序列化为 JSON 字符串存储;是否必填由业务/前端约定 --- @@ -111,8 +111,8 @@ - `fl_label_category`(主表)关键字段: - `DisplayText`:按钮展示文案(为空可回退 `CategoryName`) - - `ButtonAppearance`:`TEXT/COLOR/IMAGE`(前端按类型解析) - - `CategoryPhotoUrl`:与 `ButtonAppearance` 配合——`COLOR` 存颜色值、`IMAGE` 存图片 URL;`TEXT` 可空或不用 + - `ButtonAppearance`:**JSON 格式字符串**落库(如 `["TEXT","COLOR"]`、`["IMAGE"]`);兼容历史单行 `TEXT`/`COLOR`/`IMAGE`(保存时规范为 JSON 数组) + - `CategoryPhotoUrl`:**JSON 格式字符串**落库(展示数据由前端解析);非 JSON 纯文本(色值、URL 等)保存时会被后端包成 JSON 字符串 - **已删除列**(需在库上执行 `DROP COLUMN`):`ButtonTextColor`、`ButtonBgColor`、`ButtonImageUrl`、`ButtonStyleJson` - `AvailabilityType`:`ALL/SPECIFIED`(门店可用范围) - `fl_label_category_location`(关联表): @@ -138,7 +138,7 @@ - **路径**:`/api/app/label-category/{id}` - **新增返回字段**: - `displayText` - - `buttonAppearance`、`categoryPhotoUrl`(COLOR/IMAGE 的展示数据统一在此字段) + - `buttonAppearance`、`categoryPhotoUrl`(**JSON 格式字符串**,展示语义由前端解析) - `availabilityType` - `locationIds`(当 `availabilityType=SPECIFIED` 返回门店 Id 列表,否则为空数组) @@ -168,14 +168,14 @@ - `availabilityType` 仅允许 `ALL/SPECIFIED` - `SPECIFIED` 时 `locationIds` 至少 1 个 -- `buttonAppearance` 仅允许 `TEXT/COLOR/IMAGE`(接口层校验枚举;`categoryPhotoUrl` 是否必填由业务/前端约定,`COLOR/IMAGE` 时应写入该字段) +- `buttonAppearance`:须为 **合法 JSON**(任意对象/数组),或为兼容的 **`TEXT`/`COLOR`/`IMAGE` 单行**;其它字符串拒绝。`categoryPhotoUrl` 非空且非 JSON 时后端会序列化为 JSON 字符串存储;是否必填由业务/前端约定 --- ## 4. App 端 `GET /api/app/us-app-labeling/labeling-tree` -- **L1(标签分类)节点**:除原有 `categoryName`、`categoryPhotoUrl`、`orderNum` 等外,**返回 `buttonAppearance`**(缺省或空时后端按 `TEXT` 规范化为大写)。 -- **L2(产品分类)节点**:仅 `buttonAppearance` + `categoryPhotoUrl` 承载外观数据(已不再返回 `buttonTextColor`、`buttonBgColor`、`buttonImageUrl`、`buttonStyleJson`)。 +- **L1(标签分类)节点**:返回 `categoryPhotoUrl`、`buttonAppearance`(均为库中字符串,**多为 JSON**,与 CRUD 一致);缺省或空时 `buttonAppearance` 后端默认 **`"TEXT"`**(兼容旧数据,**不再**对整段做 `ToUpperInvariant` 以免破坏 JSON)。 +- **L2(产品分类)节点**:返回 `displayText`、`buttonAppearance`、`categoryPhotoUrl`、`availabilityType`、`orderNum` 等;外观数据由 **`buttonAppearance` + `categoryPhotoUrl`** 承载(已不再返回 `buttonTextColor`、`buttonBgColor`、`buttonImageUrl`、`buttonStyleJson`)。 ### 4.1 数据库迁移(两张主表) diff --git a/项目相关文档/标签模块接口对接说明.md b/项目相关文档/标签模块接口对接说明.md index d21d1dc..9c36c2f 100644 --- a/项目相关文档/标签模块接口对接说明.md +++ b/项目相关文档/标签模块接口对接说明.md @@ -51,6 +51,12 @@ Swagger 地址: } ``` +### 1.1.1 字段约定:`buttonAppearance` 与 `categoryPhotoUrl`(JSON 字符串) + +- **`buttonAppearance`**:库中存 **JSON 文本**(如 `["TEXT","COLOR"]`、仅图片 `["IMAGE"]` 等);兼容历史单行 `TEXT`/`COLOR`/`IMAGE`(保存时规范为 `["TEXT"]` 等)。未传或空白时后端默认 `["TEXT"]`。非法值(非 JSON 且非上述三者)会返回友好错误。 +- **`categoryPhotoUrl`**:同样为 **JSON 文本**(如 `["Prep","#10B981"]`);若传**非 JSON** 的纯文本(色值、`/picture/...` 等),后端会序列化为合法 JSON 字符串再存储。列表/详情/App 树**原样返回**字符串,由客户端解析。 +- 其它常用字段:`displayText`、`availabilityType`(`ALL`/`SPECIFIED`)、`locationIds`(指定门店时必填),与产品类别接口语义一致(见 `项目相关文档/产品模块Categories接口对接说明.md`)。 + ### 1.2 详情 方法:`GET /api/app/label-category/{id}` @@ -69,7 +75,11 @@ Swagger 地址: { "categoryCode": "CAT_PREP", "categoryName": "Prep", - "categoryPhotoUrl": "https://cdn.example.com/cat-prep.png", + "displayText": "Prep", + "buttonAppearance": "[\"TEXT\",\"COLOR\"]", + "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]", + "availabilityType": "ALL", + "locationIds": [], "state": true, "orderNum": 1 } @@ -85,7 +95,11 @@ Swagger 地址: { "categoryCode": "CAT_PREP", "categoryName": "Prep", - "categoryPhotoUrl": null, + "displayText": "Prep", + "buttonAppearance": "[\"TEXT\",\"COLOR\"]", + "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]", + "availabilityType": "ALL", + "locationIds": [], "state": true, "orderNum": 2 } @@ -706,7 +720,8 @@ ADD UNIQUE KEY `uk_fl_ltpd_template_product_label_type` (`TemplateId`, `ProductI |------|------|------| | `id` | string | `fl_label_category.Id` | | `categoryName` | string | 分类名称 | -| `categoryPhotoUrl` | string \| null | 分类图标/图 | +| `categoryPhotoUrl` | string \| null | 分类展示数据,**JSON 格式字符串**(与库中 `CategoryPhotoUrl` 一致,客户端解析) | +| `buttonAppearance` | string | 按钮外观,**JSON 格式字符串**(与库中 `ButtonAppearance` 一致;空时后端默认 `"TEXT"`) | | `orderNum` | number | 排序 | | `productCategories` | array | 第二级列表(见下表) | @@ -715,8 +730,12 @@ ADD UNIQUE KEY `uk_fl_ltpd_template_product_label_type` (`TemplateId`, `ProductI | 字段 | 类型 | 说明 | |------|------|------| | `categoryId` | string \| null | 产品分类Id;产品未归类或分类不存在时为空 | -| `categoryPhotoUrl` | string \| null | 产品分类图片地址;产品未归类或分类不存在时为空 | +| `categoryPhotoUrl` | string \| null | 产品分类展示数据,**JSON 格式字符串**;未归类或分类不存在时为空 | | `name` | string | 产品分类显示名;空源数据为 **`无`** | +| `displayText` | string \| null | 按钮展示文案 | +| `buttonAppearance` | string | **JSON 格式字符串**(与库中一致;空时默认 `"TEXT"`) | +| `availabilityType` | string | `ALL` / `SPECIFIED`(树已按当前门店过滤,仍返回供客户端展示) | +| `orderNum` | number | 排序 | | `itemCount` | number | 该分类下 **产品个数**(去重后的产品数) | | `products` | array | 第三级产品列表(见下表) | @@ -771,13 +790,18 @@ curl -X GET "http://localhost:19001/api/app/us-app-labeling/labeling-tree?locati { "id": "cat-prep-id", "categoryName": "Prep", - "categoryPhotoUrl": "/picture/...", + "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]", + "buttonAppearance": "[\"TEXT\",\"COLOR\"]", "orderNum": 1, "productCategories": [ { "categoryId": "pc-meat-id", - "categoryPhotoUrl": "/picture/product-category/20260325123010_xxx.png", + "categoryPhotoUrl": "[\"/picture/product-category/20260325123010_xxx.png\"]", "name": "Meat", + "displayText": "Meat", + "buttonAppearance": "[\"IMAGE\"]", + "availabilityType": "ALL", + "orderNum": 10, "itemCount": 1, "products": [ { diff --git a/项目相关文档/美国版App登录接口说明.md b/项目相关文档/美国版App登录接口说明.md index bb213c3..6859c10 100644 --- a/项目相关文档/美国版App登录接口说明.md +++ b/项目相关文档/美国版App登录接口说明.md @@ -285,54 +285,53 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... --- -## 接口 6:门店 Support 联系方式(App 展示) +## 接口 6:全局 Support 联系方式(App 只读 + Web 可读) -用于 App「Support」页面读取联系方式(电话、邮箱)。 -后台由 `LocationSupportAppService` 维护,规则已调整为 **全门店共用一条全局联系方式**。 +用于 App「Support」页与 Web 展示**全平台共用**的一条电话与邮箱。 +实现:`LocationSupportAppService`,表 `fl_location_support` **不再包含门店 Id**。 -### HTTP +### HTTP(查询) - **方法**:`GET` -- **路径**:`/api/app/location-support/by-location-id?locationId={locationId}`(以 Swagger 中 `LocationSupport` 为准) -- **鉴权**:需要登录(`Authorization: Bearer {token}`) +- **路径**(约定式 API,以 Swagger 中 `LocationSupport` → `GetSupport` 为准):一般为 **`/api/app/location-support/support`** +- **鉴权**:需要登录(`Authorization: Bearer {token}`)。**App 登录 Token 与 Web Token 均可调用本接口。** ### 请求参数 -| 参数名 | 位置 | 类型 | 必填 | 说明 | -|--------|------|------|------|------| -| `locationId` | Query | string | 是 | 门店主键(Guid 字符串)。当前用于入参兼容,返回值按全局联系方式配置 | +无 Query / Body。 ### 响应体(LocationSupportGetOutputDto) | 字段(JSON) | 类型 | 说明 | |--------------|------|------| -| `id` | string | 联系方式主键 | -| `locationId` | string | 配置记录中的门店主键(仅作兼容字段) | -| `locationName` | string \| null | 配置记录中的门店名称(仅作兼容字段) | +| `id` | string | 记录主键(Web 编辑 `PUT` 路径中的 `{id}`) | | `supportPhone` | string | Support 电话 | | `supportEmail` | string | Support 邮箱 | -> 若门店尚未配置联系方式,接口返回 `null`。 +> 若尚未在后台配置,接口返回 `null`。 ### 响应示例 ```json { "id": "3a2f4fda-1a93-4a35-9b98-95dca7bb5d2a", - "locationId": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f", - "locationName": "Downtown Store", "supportPhone": "1-800-SUPPORT", "supportEmail": "support@medvantage.com" } ``` +### App 与 Web 权限说明 + +- App 登录签发 JWT 时会写入声明 **`client_kind` = `us-app`**(与 Web 管理端 Token 区分)。 +- **App 仅允许调用本节的 `GET`(查询)**;若使用 App Token 调用新增/编辑,将返回业务错误(英文):`The mobile app can only view support contacts. Please use the web console to edit.` + --- -## 后台维护接口:Location Support(新增/编辑) +## 后台维护接口:Location Support(Web:新增 / 编辑) -仅后台管理端使用,用于配置 App Support 页面展示内容(全局唯一)。 +仅 **Web 管理端 Token**(无 `client_kind=us-app`)可调用,用于维护全局 Support 联系方式。 -### 接口 A:新增 Support 联系方式(全局) +### 接口 A:新增(全局一条) - **方法**:`POST` - **路径**:`/api/app/location-support` @@ -342,36 +341,46 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ```json { - "locationId": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f", "supportPhone": "1-800-SUPPORT", "supportEmail": "support@medvantage.com" } ``` 约束: -- 系统内仅允许存在一条未删除记录;若已存在,再次新增会报错:`已存在全局 Support 联系方式,请使用编辑接口` -### 接口 B:编辑 Support 联系方式(全局) +- 系统内仅允许存在一条未删除记录;若已存在,再次新增会报错:`Global support contact already exists. Use update instead.` + +### 接口 B:编辑 - **方法**:`PUT` - **路径**:`/api/app/location-support/{id}` - **Content-Type**:`application/json` -请求体(LocationSupportUpdateInputVo)与新增一致: +请求体(LocationSupportUpdateInputVo): ```json { - "locationId": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f", "supportPhone": "1-800-SUPPORT", "supportEmail": "support@medvantage.com" } ``` -常见错误: -- `门店Id不能为空` / `门店Id格式不正确` -- `Support 电话不能为空` -- `Support 邮箱不能为空` / `Support 邮箱格式不正确` -- `系统仅允许一条全局 Support 联系方式` +常见错误(英文): + +- `The mobile app can only view support contacts. Please use the web console to edit.` +- `Support phone is required.` / `Support email is required.` / `Support email format is invalid.` +- `Global support contact already exists. Use update instead.` +- `Support record not found.` / `Support record id is required.` + +### 数据库迁移(删除 `LocationId`) + +若线上表仍为旧结构(含 `LocationId`),请在库中执行脚本: + +- `美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_location_support_alter_drop_locationid.sql` + +新建库请使用: + +- `美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_location_support_create.sql` --- -- libgit2 0.21.4