From da0185d1aa1d5ee66ca3c8f8f57fb2bce8d86def Mon Sep 17 00:00:00 2001 From: czl231 <3286836406@qq.com> Date: Sat, 22 Aug 2026 19:49:28 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=85=8D=E9=80=81=E7=82=B9?= =?UTF-8?q?=E8=B5=84=E6=96=99=E5=8F=AA=E8=AF=BB=E5=8D=A1=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/internal/logic/delivery/base.go | 41 ++++- .../internal/logic/delivery/profile_test.go | 33 ++++ docs/操作日志_配送点资料只读卡片_20260822.md | 52 ++++++ docs/项目文档_配送点资料只读卡片_v1.0.md | 67 +++++++ frontend/delivery_admin/package.json | 1 + .../scripts/check-delivery-profile-page.mjs | 19 ++ frontend/delivery_admin/src/api/http.ts | 15 +- .../src/views/shared/DeliveryProfilePage.vue | 169 ++++++++++++++++++ .../src/views/shared/ResourcePage.vue | 8 +- 9 files changed, 401 insertions(+), 4 deletions(-) create mode 100644 backend/api/internal/logic/delivery/profile_test.go create mode 100644 docs/操作日志_配送点资料只读卡片_20260822.md create mode 100644 docs/项目文档_配送点资料只读卡片_v1.0.md create mode 100644 frontend/delivery_admin/scripts/check-delivery-profile-page.mjs create mode 100644 frontend/delivery_admin/src/views/shared/DeliveryProfilePage.vue diff --git a/backend/api/internal/logic/delivery/base.go b/backend/api/internal/logic/delivery/base.go index 7c95c9b..b77cde7 100644 --- a/backend/api/internal/logic/delivery/base.go +++ b/backend/api/internal/logic/delivery/base.go @@ -3,6 +3,7 @@ package delivery import ( "net/url" "reflect" + "time" "git.apinb.com/bsm-sdk/core/infra" "git.apinb.com/heqiapp/platforms/backend/api/internal/config" @@ -13,6 +14,21 @@ import ( "gorm.io/gorm" ) +// deliveryProfileView 表示配送点管理员可查看的本点只读资料。 +type deliveryProfileView struct { + ID uint64 `json:"id"` + Identity string `json:"identity"` + DeliveryCode string `json:"delivery_code"` + Name string `json:"name"` + Principal string `json:"principal"` + Address string `json:"address"` + GasBasicIdentity string `json:"gas_basic_identity"` + GasBasicName string `json:"gas_basic_name"` + Status int `json:"status"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + func currentScope(ctx *gin.Context) (models.DeliveryBasic, models.GasBasic, bool) { _, point, station, ok := CurrentDeliveryAccount(ctx) return point, station, ok @@ -40,12 +56,33 @@ func InvitationQRCode(ctx *gin.Context) { infra.Response.Success(ctx, gin.H{"register_url": registerURL.String()}) } +// ListProfile 返回 JWT 数据范围内唯一配送点的只读资料。 func ListProfile(ctx *gin.Context) { - _, point, _, ok := CurrentDeliveryAccount(ctx) + _, point, station, ok := CurrentDeliveryAccount(ctx) if !ok { return } - respondList(ctx, []models.DeliveryBasic{point}, 1) + infra.Response.Success(ctx, gin.H{ + "total": 1, + "list": []deliveryProfileView{buildDeliveryProfileView(point, station)}, + }) +} + +// buildDeliveryProfileView 组合配送点与所属气站的公开展示字段。 +func buildDeliveryProfileView(point models.DeliveryBasic, station models.GasBasic) deliveryProfileView { + return deliveryProfileView{ + ID: point.ID, + Identity: point.Identity, + DeliveryCode: point.DeliveryCode, + Name: point.Name, + Principal: point.Principal, + Address: point.Address, + GasBasicIdentity: station.Identity, + GasBasicName: station.Name, + Status: point.Status, + CreatedAt: point.CreatedAt, + UpdatedAt: point.UpdatedAt, + } } func respondList(ctx *gin.Context, list any, total int64) { diff --git a/backend/api/internal/logic/delivery/profile_test.go b/backend/api/internal/logic/delivery/profile_test.go new file mode 100644 index 0000000..b9ea5a7 --- /dev/null +++ b/backend/api/internal/logic/delivery/profile_test.go @@ -0,0 +1,33 @@ +// 功能描述:验证配送点资料响应仅暴露本点只读展示所需字段。版本:v1.0.0。 +package delivery + +import ( + "testing" + "time" + + "git.apinb.com/heqiapp/platforms/backend/api/internal/models" +) + +// TestBuildDeliveryProfileView 验证资料响应包含所属气站名称且保持公开标识。 +func TestBuildDeliveryProfileView(t *testing.T) { + now := time.Date(2026, time.August, 22, 10, 30, 0, 0, time.UTC) + point := models.DeliveryBasic{ + Entity: models.Entity{ID: 19, Identity: "delivery-identity", Status: 1, CreatedAt: now, UpdatedAt: now}, + DeliveryCode: "D-001", + Name: "测试配送点", + Principal: "负责人", + Address: "测试地址", + } + station := models.GasBasic{ + Entity: models.Entity{ID: 7, Identity: "gas-identity"}, + Name: "测试气站", + } + + view := buildDeliveryProfileView(point, station) + if view.ID != point.ID || view.Identity != point.Identity || view.GasBasicIdentity != station.Identity { + t.Fatalf("资料标识映射错误:%#v", view) + } + if view.GasBasicName != station.Name || view.Name != point.Name || view.Status != point.Status { + t.Fatalf("资料展示字段映射错误:%#v", view) + } +} diff --git a/docs/操作日志_配送点资料只读卡片_20260822.md b/docs/操作日志_配送点资料只读卡片_20260822.md new file mode 100644 index 0000000..23f6aa8 --- /dev/null +++ b/docs/操作日志_配送点资料只读卡片_20260822.md @@ -0,0 +1,52 @@ +# 操作日志:配送点资料只读卡片 + +操作时间:2026-08-22 19:42:41 +操作类型:扩展 +影响模块:配送点后台本点资料、配送点 API 资料响应、HTTP 错误处理 + +## 操作前状态 + +- 本点资料套用通用列表页面,只显示 ID、唯一标识和“详情”按钮。 +- 点击详情请求不存在的 `/delivery_profile/:identity`,纯文本 404 被当作 JSON 解析并显示底层异常。 +- 页面未直接展示需求规定的配送点资料字段。 + +## 具体操作 + +1. 根据需求文档确认本点资料是 JWT 范围内的单一只读视图。 +2. 新增专用资料卡并在资源页面入口中进行兼容分发。 +3. 扩展现有 `/delivery_profile` 响应,补充所属气站名称。 +4. 增强 HTTP 客户端的非 JSON 响应处理。 +5. 增加后端单元测试和前端专项静态检查。 +6. 执行构建、测试和浏览器核心路径回归。 + +## 操作后状态 + +- 页面直接展示本点资料,不再发起不存在的详情请求。 +- 刷新成功且不再出现 JSON 解析异常。 +- 后端保持 JWT 数据范围、现有接口路径和列表响应结构。 +- HTTP 404 或非 JSON 响应转换为可读错误。 + +## 代码变更 + +- `backend/api/internal/logic/delivery/base.go`:新增资料视图与字段组装。 +- `backend/api/internal/logic/delivery/profile_test.go`:新增资料字段映射测试。 +- `frontend/delivery_admin/src/views/shared/DeliveryProfilePage.vue`:新增专用资料卡。 +- `frontend/delivery_admin/src/views/shared/ResourcePage.vue`:扩展本点资料页面分发。 +- `frontend/delivery_admin/src/api/http.ts`:增强响应解析。 +- `frontend/delivery_admin/scripts/check-delivery-profile-page.mjs`:新增专项检查。 +- `frontend/delivery_admin/package.json`:新增 `profile:check` 命令。 + +## 验证结果 + +- `go test ./internal/logic/delivery`:通过。 +- `npm run profile:check`:通过。 +- `npm run build`:通过,共转换 2606 个模块。 +- 浏览器回归:资料卡正常展示;查询、分页、详情入口已移除;刷新正常;控制台无错误或警告。 +- 后端运行态:已备份旧二进制、部署新版本并恢复 `12426` 端口监听;重新登录后确认所属气站名称正确显示为“薛海”,刷新正常且控制台无错误或警告。 + +## 风险评估 + +- 影响范围仅限配送点后台的本点资料页面和通用 HTTP 错误文案。 +- API 仅新增响应字段,旧客户端可忽略,兼容性风险低。 +- HTTP 客户端仍保持 `request` 签名;成功 JSON 响应行为不变。 +- 缓解措施:已增加专项检查、单元测试、完整构建和浏览器回归;后端重启后需补验气站名称。 diff --git a/docs/项目文档_配送点资料只读卡片_v1.0.md b/docs/项目文档_配送点资料只读卡片_v1.0.md new file mode 100644 index 0000000..61ad179 --- /dev/null +++ b/docs/项目文档_配送点资料只读卡片_v1.0.md @@ -0,0 +1,67 @@ +# 项目文档:配送点资料只读卡片 v1.0 + +## 1. 项目概述 + +- 项目:和气配送点管理系统。 +- 功能:将“本点资料”从通用资源列表改为当前配送点专用只读资料卡。 +- 技术栈:Vue 3、TypeScript、Arco Design、Go、Gin。 +- 运行要求:前端 Node.js 20.19.0 及以上;后端沿用项目现有 Go 运行环境。 + +## 2. 目录结构 + +```text +platforms/ +├── backend/api/internal/logic/delivery/ +│ ├── base.go # 配送点资料响应组装 +│ └── profile_test.go # 配送点资料字段映射测试 +├── frontend/delivery_admin/ +│ ├── scripts/check-delivery-profile-page.mjs # 资料卡静态契约检查 +│ └── src/ +│ ├── api/http.ts # 配送点后台 HTTP 客户端 +│ └── views/shared/ +│ ├── DeliveryProfilePage.vue # 本点资料专用只读卡片 +│ └── ResourcePage.vue # 资源页面分发入口 +└── docs/ + ├── 项目文档_配送点资料只读卡片_v1.0.md + └── 操作日志_配送点资料只读卡片_20260822.md +``` + +## 3. 核心文件说明 + +### `backend/api/internal/logic/delivery/base.go` + +- `ListProfile` 继续依据 JWT 中的配送点范围返回唯一记录。 +- `buildDeliveryProfileView` 显式组合配送点公开字段和所属气站名称。 +- API 路径和 `{ total, list }` 响应结构保持不变,不增加按客户端 identity 查询本点资料的接口。 + +### `frontend/delivery_admin/src/views/shared/DeliveryProfilePage.vue` + +- 直接展示配送点编码、名称、负责人、地址、所属气站和状态。 +- 配送点及气站完整 identity 可复制。 +- 提供刷新、空数据、页面级错误和重新加载状态。 +- 不提供查询、分页、详情或编辑入口。 + +### `frontend/delivery_admin/src/api/http.ts` + +- `request` 公共签名保持不变。 +- 对纯文本 404、网关错误和其他非 JSON 响应生成可读错误,避免暴露 `JSON.parse` 异常。 + +## 4. 变更记录 + +- 新增本点资料专用只读卡片。 +- 扩展 `/delivery_profile` 响应,新增 `gas_basic_name`。 +- 移除本点资料页面的通用列表交互。 +- 增加非 JSON HTTP 响应保护、后端字段映射测试和前端专项检查。 +- 未新增依赖,未修改现有 API 路径和公共请求函数签名。 + +## 5. 维护指南 + +- 新增资料字段时,先在 `deliveryProfileView` 中声明公开字段,再同步前端 `DeliveryProfile` 类型和资料卡。 +- 不得把 `/delivery_profile` 改为允许客户端选择任意配送点;数据范围必须来自 JWT。 +- 修改资料卡后运行 `npm run profile:check` 和 `npm run build`。 +- 修改资料响应后运行 `go test ./internal/logic/delivery`。 +- 本地后端代码更新后必须重启 API 服务,再执行浏览器刷新回归。 + +## 6. 已知事项 + +- API 服务已更新并重启,重新登录后的浏览器回归确认所属气站名称、资料刷新和错误状态均正常。 diff --git a/frontend/delivery_admin/package.json b/frontend/delivery_admin/package.json index bcf2914..7ee3b66 100644 --- a/frontend/delivery_admin/package.json +++ b/frontend/delivery_admin/package.json @@ -13,6 +13,7 @@ "type:check": "vue-tsc -p tsconfig.build.json --noEmit --skipLibCheck", "contract:sync": "node scripts/sync-backend-contract.mjs", "contract:check": "node scripts/check-backend-contract.mjs", + "profile:check": "node scripts/check-delivery-profile-page.mjs", "audit:platform": "node scripts/check-backend-contract.mjs", "lint": "biome lint .", "lint:fix": "biome lint --write .", diff --git a/frontend/delivery_admin/scripts/check-delivery-profile-page.mjs b/frontend/delivery_admin/scripts/check-delivery-profile-page.mjs new file mode 100644 index 0000000..0f02580 --- /dev/null +++ b/frontend/delivery_admin/scripts/check-delivery-profile-page.mjs @@ -0,0 +1,19 @@ +/** 功能描述:静态检查本点资料专用卡片和非 JSON 错误保护。版本:v1.0.0。 */ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +const root = resolve(import.meta.dirname, '..'); +const profilePage = readFileSync(resolve(root, 'src/views/shared/DeliveryProfilePage.vue'), 'utf8'); +const resourcePage = readFileSync(resolve(root, 'src/views/shared/ResourcePage.vue'), 'utf8'); +const httpClient = readFileSync(resolve(root, 'src/api/http.ts'), 'utf8'); + +assert.match(resourcePage, /definition\.name === 'delivery_profile'/, '本点资料未使用专用页面'); +for (const field of ['配送点编码', '负责人', '配送点地址', '所属气站', '配送点标识']) { + assert.ok(profilePage.includes(field), `资料卡缺少字段:${field}`); +} +assert.doesNotMatch(profilePage, /详情|分页|关键字/, '资料卡不应保留通用列表交互'); +assert.match(httpClient, /response\.text\(\)/, 'HTTP 客户端未兼容非 JSON 响应'); +assert.match(httpClient, /HTTP \$\{response\.status\}/, 'HTTP 客户端未提供状态码错误'); + +console.log('配送点资料页检查通过:专用只读卡片与非 JSON 错误保护已启用。'); diff --git a/frontend/delivery_admin/src/api/http.ts b/frontend/delivery_admin/src/api/http.ts index 5fdc1ef..bcac257 100644 --- a/frontend/delivery_admin/src/api/http.ts +++ b/frontend/delivery_admin/src/api/http.ts @@ -5,6 +5,11 @@ const apiBaseURL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:12426/ export type PageResult = { total: number; list: T[] }; +/** + * 发送配送点后台请求。 + * 参数:path 为 API 相对路径,init 为标准 Fetch 请求选项。 + * 返回值:统一响应中的 details;网络、协议或业务失败时抛出可读错误。 + */ export async function request(path: string, init?: RequestInit): Promise { const token = getToken(); let response: Response; @@ -20,7 +25,15 @@ export async function request(path: string, init?: RequestInit): Promise { } catch { throw new Error('无法连接服务器,请确认服务已启动'); } - const payload = (await response.json()) as { code?: number; message?: string; details?: T }; + const responseText = await response.text(); + let payload: { code?: number; message?: string; details?: T }; + try { + payload = responseText ? JSON.parse(responseText) : {}; + } catch { + // 网关、代理和不存在的路由可能返回纯文本,不能把底层 JSON 异常暴露给用户。 + if (!response.ok) throw new Error(`请求失败(HTTP ${response.status})`); + throw new Error('服务器返回了无法识别的数据格式'); + } if (!response.ok || payload.code !== 0) throw new Error(payload.message || '请求失败'); return payload.details as T; } diff --git a/frontend/delivery_admin/src/views/shared/DeliveryProfilePage.vue b/frontend/delivery_admin/src/views/shared/DeliveryProfilePage.vue new file mode 100644 index 0000000..9c4289b --- /dev/null +++ b/frontend/delivery_admin/src/views/shared/DeliveryProfilePage.vue @@ -0,0 +1,169 @@ + + + + + + diff --git a/frontend/delivery_admin/src/views/shared/ResourcePage.vue b/frontend/delivery_admin/src/views/shared/ResourcePage.vue index d6d761d..9812f8a 100644 --- a/frontend/delivery_admin/src/views/shared/ResourcePage.vue +++ b/frontend/delivery_admin/src/views/shared/ResourcePage.vue @@ -1,6 +1,11 @@ +