This commit is contained in:
2026-08-08 11:33:09 +08:00
parent cb0792963c
commit bc14ec7bd2
12 changed files with 59 additions and 4 deletions
+2 -1
View File
@@ -1,3 +1,4 @@
VITE_API_BASE_URL=/api VITE_API_BASE_URL=/api
API_PROXY_TARGET=http://8.163.30.128:9000 API_PROXY_TARGET=http://8.163.30.128:9000
VITE_CLIENT_WEB_URL=http://localhost:5176 # Local Vite fallback. Container deployments should use CLIENT_WEB_URL instead.
VITE_CLIENT_WEB_URL=http://localhost:5174
+12
View File
@@ -31,6 +31,18 @@
依赖方向保持为 `app -> modules/core/shared`、`modules -> core/shared`、`core -> shared`、`shared -> 无上层依赖`。模块间确需调用时只能引用对方根入口公开的 API 或契约,不得引用其他模块的页面、内部组件或内部目录。 依赖方向保持为 `app -> modules/core/shared`、`modules -> core/shared`、`core -> shared`、`shared -> 无上层依赖`。模块间确需调用时只能引用对方根入口公开的 API 或契约,不得引用其他模块的页面、内部组件或内部目录。
## 一期/二期/三期 Admin 快速索引
按阶段处理需求时,先核对仓库根目录对应阶段文档,再进入下列模块;阶段范围高于原型中的全量菜单。模块代码均从 `src/` 起算。
| 阶段 | Admin 范围与入口 | 当前边界 |
|---|---|---|
| 一期:头程订单闭环 | `modules/auth/` 登录;`customers/` 客户与企业审核;`iam/` 内部用户、角色、权限;`warehouses/` 平台收货仓;`master-data/` 快递公司、往来单位;`first-leg-orders/` 头程订单;`dashboard/` 工作台 | 功能基本完成,处于测试环境部署、样本补齐和验收收口;企业审核已合并客户列表,平台 RBAC 仅维护平台员工与平台权限资源 |
| 二期:到仓与头程计费 | `modules/inbounds/` 入库预报、收货、测量/OCR、审核、差异与费用快照;`warehouse-resources/pallets/` 托盘;`warehouse-resources/locations/` 库位;`fba-pricing/`、`dimension-config/` 计费规则与尺寸配置;`unified-orders/` 订单工作流编排 | 技术闭环完成,验收终点为 `WAIT_SPLIT + APPROVED + CONFIRMED`(等待三期分柜);海外仓阶梯费用仅后端契约已具备,前端联调仍需单独确认;不得把内部备注、文件或租户上下文泄露给 Client |
| 三期:国内出仓 | 当前无正式三期模块;规划复用 `first-leg-orders/`、`unified-orders/`、`inbounds/` 的订单与入库工作流页面,新增出仓能力时按稳定契约拆分模块 | 当前 NO-GO。先解除 `../../docs/三期/三期需求确认表.md` 的 P3-Q1/Q2/Q5/Q9/Q11/Q12/Q13 等阻断项;不得把原型中的分柜、装柜、报关、港务或完整 WMS 菜单提前展示 |
阶段文档入口(路径相对本文件所在目录):一期读 `../../docs/一期/一期开发进度表.md` 与 `../../docs/一期/一期头程订单闭环设计.md`;二期读 `../../docs/二期/README.md`、`../../docs/二期/二期需求优化版-v2-测量计费入库主流程.md` 及联调报告;三期读 `../../docs/三期/README.md`、`../../docs/三期/三期开发规划.md`、`../../docs/三期/三期需求确认表.md` 和页面清单。阶段状态与代码现状冲突时,以需求确认和验收证据为准,并在改动说明中记录。
## 业务与安全约束 ## 业务与安全约束
- 当前产品层级只有平台和客户(企业)两级;Admin 不把“租户”作为用户可见概念,不新增租户选择器、目标租户文案或依赖租户上下文才能使用的页面流程。多租户产品能力后续单独设计。 - 当前产品层级只有平台和客户(企业)两级;Admin 不把“租户”作为用户可见概念,不新增租户选择器、目标租户文案或依赖租户上下文才能使用的页面流程。多租户产品能力后续单独设计。
+2
View File
@@ -27,6 +27,8 @@ RUN pnpm build
FROM nginx:1.27-alpine FROM nginx:1.27-alpine
COPY nginx/default.conf.template /etc/nginx/templates/default.conf.template COPY nginx/default.conf.template /etc/nginx/templates/default.conf.template
COPY docker/runtime-config.sh /docker-entrypoint.d/40-runtime-config.sh
RUN chmod +x /docker-entrypoint.d/40-runtime-config.sh
COPY --from=build /app/dist /usr/share/nginx/html COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80 EXPOSE 80
+2
View File
@@ -48,6 +48,8 @@ VITE_API_BASE_URL=/api
API_PROXY_TARGET=http://8.163.30.128:9000 API_PROXY_TARGET=http://8.163.30.128:9000
``` ```
客户代登录打开的工作台地址是运行时配置,不需要为每个环境重新构建镜像。Docker 部署时设置容器环境变量 `CLIENT_WEB_URL`,例如 `http://8.163.30.128:9002`;容器启动时会生成 `/runtime-config.js`。本地 Vite 开发才使用 `VITE_CLIENT_WEB_URL` 作为兜底,当前 Client 默认端口为 `5174`。
认证请求使用 `/api/auth/**`,平台 IAM 请求使用 `/api/platform/v1/iam/**`。登录后从 JWT 读取平台身份和授权快照,再与 IAM 返回的启用权限资源取交集。企业审核调用 `/reviews`,打开审核详情后按租户调用 `/tenants/{tenantId}/qualifications`;营业执照通过 `/api/platform/v1/files/qualifications/{tenantId}/{fileName}` 携带平台 Token 临时读取,关闭预览后释放本地对象地址。客户端自主注册企业审核通过后继续使用注册账号作为管理员,不展示新凭据;Admin 代建企业并由后端创建管理员时,页面必须展示不可通过遮罩、Esc 或关闭按钮跳过的一次性凭据弹窗,运营人员确认保存后才清除账号密码。平台角色查询、新增、编辑、启停、删除和权限全量保存均已按 `RbacController` 接入真实 `/roles` 契约,角色返回的 `permissionIds` 用于授权回显;系统内置角色及仍被成员使用的角色继续由服务端保护。权限资源查询和新增调用 `/permissions`,尚未提供的权限编辑、删除和启停不会模拟成功。一期真实权限资源只创建 `MENU` 与 `BUTTON`,不创建 `PAGE` 或 `API` 类型。其余业务页面需在对应服务契约确认后逐项替换,生产逻辑不得静默回退到模拟数据。 认证请求使用 `/api/auth/**`,平台 IAM 请求使用 `/api/platform/v1/iam/**`。登录后从 JWT 读取平台身份和授权快照,再与 IAM 返回的启用权限资源取交集。企业审核调用 `/reviews`,打开审核详情后按租户调用 `/tenants/{tenantId}/qualifications`;营业执照通过 `/api/platform/v1/files/qualifications/{tenantId}/{fileName}` 携带平台 Token 临时读取,关闭预览后释放本地对象地址。客户端自主注册企业审核通过后继续使用注册账号作为管理员,不展示新凭据;Admin 代建企业并由后端创建管理员时,页面必须展示不可通过遮罩、Esc 或关闭按钮跳过的一次性凭据弹窗,运营人员确认保存后才清除账号密码。平台角色查询、新增、编辑、启停、删除和权限全量保存均已按 `RbacController` 接入真实 `/roles` 契约,角色返回的 `permissionIds` 用于授权回显;系统内置角色及仍被成员使用的角色继续由服务端保护。权限资源查询和新增调用 `/permissions`,尚未提供的权限编辑、删除和启停不会模拟成功。一期真实权限资源只创建 `MENU` 与 `BUTTON`,不创建 `PAGE` 或 `API` 类型。其余业务页面需在对应服务契约确认后逐项替换,生产逻辑不得静默回退到模拟数据。
仓库管理已接入真实平台 Warehouse API,统一维护国内收货仓、FBA 仓和海外仓,支持按租户筛选、详情、新增、编辑和启停,并在更新时保留后端返回的地址、能力和截单时间等完整字段。停用仓库保留历史数据,但不再提供给新业务选择;订单详情使用下单时保存的仓库编码、名称和地址快照,仓库主数据后续修改不改变历史展示。库存和出入库作业由对应业务模块管理,不在仓库主数据页面中模拟。 仓库管理已接入真实平台 Warehouse API,统一维护国内收货仓、FBA 仓和海外仓,支持按租户筛选、详情、新增、编辑和启停,并在更新时保留后端返回的地址、能力和截单时间等完整字段。停用仓库保留历史数据,但不再提供给新业务选择;订单详情使用下单时保存的仓库编码、名称和地址快照,仓库主数据后续修改不改变历史展示。库存和出入库作业由对应业务模块管理,不在仓库主数据页面中模拟。
+1 -2
View File
@@ -3,8 +3,6 @@ services:
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
args:
VITE_CLIENT_WEB_URL: "${VITE_CLIENT_WEB_URL:-http://8.163.30.128:9002}"
image: admin-web-test:local image: admin-web-test:local
container_name: admin-web-test container_name: admin-web-test
restart: unless-stopped restart: unless-stopped
@@ -13,6 +11,7 @@ services:
extra_hosts: extra_hosts:
- "host.docker.internal:host-gateway" - "host.docker.internal:host-gateway"
environment: environment:
CLIENT_WEB_URL: "${CLIENT_WEB_URL:-http://8.163.30.128:9002}"
# Gateway 与前端容器在同一台测试服务器,通过宿主机内部地址访问。 # Gateway 与前端容器在同一台测试服务器,通过宿主机内部地址访问。
# 测试环境固定走 Docker 宿主机网关,避免 Jenkins 全局环境变量或 # 测试环境固定走 Docker 宿主机网关,避免 Jenkins 全局环境变量或
# 工作区残留配置把上游覆盖成公网 IP,造成容器回连公网端口失败。 # 工作区残留配置把上游覆盖成公网 IP,造成容器回连公网端口失败。
+7
View File
@@ -0,0 +1,7 @@
#!/bin/sh
set -eu
runtime_file=/usr/share/nginx/html/runtime-config.js
client_web_url=${CLIENT_WEB_URL:-}
escaped_url=$(printf '%s' "$client_web_url" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\r/\\r/g; s/\n/\\n/g')
printf "window.__CHONGCHONG_RUNTIME_CONFIG__ = { clientWebUrl: \"%s\" }\n" "$escaped_url" > "$runtime_file"
+1
View File
@@ -6,6 +6,7 @@
<meta name="description" content="柏悦物流管理后台" /> <meta name="description" content="柏悦物流管理后台" />
<link rel="icon" type="image/png" href="/src/assets/brand/baiyue-favicon.png" /> <link rel="icon" type="image/png" href="/src/assets/brand/baiyue-favicon.png" />
<title>柏悦物流管理后台</title> <title>柏悦物流管理后台</title>
<script src="/runtime-config.js"></script>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
+7
View File
@@ -12,6 +12,13 @@ server {
return 200 "ok\n"; return 200 "ok\n";
} }
location = /runtime-config.js {
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
try_files $uri =404;
}
# 前端代码中的 API 前缀为 /api;保持原始路径转给 Gateway。 # 前端代码中的 API 前缀为 /api;保持原始路径转给 Gateway。
location /api/ { location /api/ {
client_max_body_size 12m; client_max_body_size 12m;
+1
View File
@@ -0,0 +1 @@
window.__CHONGCHONG_RUNTIME_CONFIG__ = window.__CHONGCHONG_RUNTIME_CONFIG__ || { clientWebUrl: '' }
+8
View File
@@ -13,6 +13,14 @@
- 服务端字段变化先核对 Controller、DTO 和 OpenAPI,再更新本应用的类型、API 适配层和测试;本包不反向定义服务端行为。 - 服务端字段变化先核对 Controller、DTO 和 OpenAPI,再更新本应用的类型、API 适配层和测试;本包不反向定义服务端行为。
- 避免建立宽泛的万能类型或引入重量级依赖。 - 避免建立宽泛的万能类型或引入重量级依赖。
## 一期/二期/三期共享模型索引
- 一期消费者主要是 `customers`、`iam`、`warehouses`、`master-data`、`first-leg-orders`;`CurrentUser`、`NavigationItem`、`ApiResponse`、平台收货仓快照及头程订单状态常量由 `src/index.ts` 统一导出。共享导出只描述前端数据形状,不替代 Java 服务端权限或状态机。
- 二期消费者主要是 `inbounds`、`warehouse-resources`、`fba-pricing`、`dimension-config` 与 `unified-orders`。测量、费用、托盘、库位等类型若只被单一模块使用,应留在模块内;确需跨模块复用时,先核对 `baiyue-firstleg` Controller/DTO/OpenAPI,再以兼容方式新增导出。
- 三期当前 NO-GO,暂无正式三期共享契约。不要为了复用原型中的分柜、装柜、报关或 PDA 字段提前扩展本包;待 `../../../docs/三期/三期需求确认表.md` 阻断项解除并锁定后端契约,再评估新增类型并同步全部 Admin 消费者。
阶段依据(路径相对本文件所在目录):一期 `../../../docs/一期/`,二期 `../../../docs/二期/`,三期 `../../../docs/三期/`;共享包不保存阶段进度,也不承载跨应用(Client)源码复用。
## 兼容性与验证 ## 兼容性与验证
- 修改导出名称、字段类型、枚举值或常量语义前,搜索当前应用内全部消费者。 - 修改导出名称、字段类型、枚举值或常量语义前,搜索当前应用内全部消费者。
+14
View File
@@ -0,0 +1,14 @@
export type AdminRuntimeConfig = {
clientWebUrl?: string
}
declare global {
interface Window {
__CHONGCHONG_RUNTIME_CONFIG__?: AdminRuntimeConfig
}
}
export function getClientWebUrl(): string {
const runtimeUrl = window.__CHONGCHONG_RUNTIME_CONFIG__?.clientWebUrl
return String(runtimeUrl || import.meta.env.VITE_CLIENT_WEB_URL || '').trim()
}
@@ -39,6 +39,7 @@ import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.v
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue' import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
import { showExportTaskCreatedDialog } from '@/shared/components/exportTaskCreatedDialog' import { showExportTaskCreatedDialog } from '@/shared/components/exportTaskCreatedDialog'
import { useAccessPreviewStore } from '@/core/access/accessPreview' import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { getClientWebUrl } from '@/core/config/runtimeConfig'
type CustomerFilters = QueryModel & { keyword: string; status: PlatformTenantStatus | '' } type CustomerFilters = QueryModel & { keyword: string; status: PlatformTenantStatus | '' }
type QualificationDraft = { type QualificationDraft = {
@@ -701,7 +702,7 @@ async function submitImpersonation() {
if (!impersonationTenant.value || impersonationSubmitting.value || !impersonationFormRef.value) return if (!impersonationTenant.value || impersonationSubmitting.value || !impersonationFormRef.value) return
const valid = await impersonationFormRef.value.validate().then(() => true).catch(() => false) const valid = await impersonationFormRef.value.validate().then(() => true).catch(() => false)
if (!valid) return if (!valid) return
const clientUrl = String(import.meta.env.VITE_CLIENT_WEB_URL || '').trim() const clientUrl = getClientWebUrl()
if (!clientUrl) { if (!clientUrl) {
impersonationError.value = getApiErrorDetails(new Error('未配置客户工作台地址'), '暂未配置客户工作台地址,无法安全跳转登录') impersonationError.value = getApiErrorDetails(new Error('未配置客户工作台地址'), '暂未配置客户工作台地址,无法安全跳转登录')
return return