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