Release/test 合并上生产 #2
@@ -31,17 +31,34 @@
|
||||
|
||||
依赖方向保持为 `app -> modules/core/shared`、`modules -> core/shared`、`core -> shared`、`shared -> 无上层依赖`。模块间确需调用时只能引用对方根入口公开的 API 或契约,不得引用其他模块的页面、内部组件或内部目录。
|
||||
|
||||
## 模块快速查询
|
||||
|
||||
进入模块后按 `routes.ts → pages/ → api/、types/、stores/ 或 composables/` 查询;没有对应目录时再查相邻代码,不先扫描全部 `src/`。
|
||||
|
||||
| 业务 | 模块入口 | 联查模块或服务端 |
|
||||
|---|---|---|
|
||||
| 客户、企业审核 | `modules/customers/` | `iam/`、`file/` |
|
||||
| 平台用户与 RBAC | `modules/iam/`、`core/access/` | `auth/`、`iam/` |
|
||||
| 平台仓库与仓储资源 | `modules/warehouses/`、`modules/warehouse-resources/` | `warehouse/` |
|
||||
| 订单列表与详情编排 | `modules/unified-orders/`、`modules/first-leg-orders/` | `order/`、`firstleg/` |
|
||||
| 入库、测量和计费 | `modules/inbounds/`、`modules/fba-pricing/`、`modules/dimension-config/`、`modules/fee-items/` | `firstleg/`、`file/`、`integration-service/` |
|
||||
| 货柜与智能排柜 | `modules/container-loading/` | `firstleg/loading/`、`firstleg/split/` |
|
||||
| 预装与装柜清单 | `modules/loading-lists/` | `firstleg/loading/`、`file/`、`warehouse/` |
|
||||
| 海运订舱与船期 | `modules/ocean-bookings/` | `firstleg/oceanbooking/`、`firstleg/portschedule/`、`integration-service/` |
|
||||
| 拖车预约 | `modules/trucking/` | `firstleg/outbound/dispatch/`、`firstleg/` |
|
||||
| 主数据与下载 | `modules/master-data/`、`modules/downloads/` | `warehouse/`、`file/` |
|
||||
|
||||
## 一期/二期/三期 Admin 快速索引
|
||||
|
||||
按阶段处理需求时,先核对仓库根目录对应阶段文档,再按“模块目录 → `routes.ts`/页面 → `api`/类型”定位;阶段范围高于原型中的全量菜单。模块代码均从 `src/` 起算。本表只描述 Admin 页面、路由和前端契约,不替代后端模块指引。
|
||||
|
||||
| 阶段 | Admin 范围与入口 | 当前边界 |
|
||||
| 阶段 | Admin 范围与入口 | 稳定边界 |
|
||||
|---|---|---|
|
||||
| 一期:头程订单闭环 | `modules/auth/` 登录;`customers/` 客户与企业审核;`iam/` 内部用户、角色、权限;`warehouses/` 平台收货仓;`master-data/` 快递公司、往来单位;`first-leg-orders/` 头程订单;`dashboard/` 工作台 | 功能基本完成,处于测试环境部署、样本补齐和验收收口;企业审核已合并客户列表,平台 RBAC 仅维护平台员工与平台权限资源 |
|
||||
| 一期:头程订单闭环 | `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/`、`ocean-bookings/`、`trucking/`,新增能力按稳定契约拆分模块 | 已确认业务规则可进入页面与接口实现;仅未稳定的接口、权限、数据模型和验收项阻断对应页面的发布。不得把原型中的完整 WMS、海外仓或未确认功能提前展示 |
|
||||
| 三期:国内出仓 | `unified-orders/` 订单编排;`container-loading/` 货柜与排柜;`loading-lists/` 预装/装柜清单;`ocean-bookings/` 订舱船期;`trucking/` 拖车 | 页面、权限和 API 以稳定服务端契约发布;完整 WMS、海外仓及原型未纳入能力不属于本范围 |
|
||||
|
||||
阶段文档入口(路径相对本文件所在目录):一期读 `../../docs/一期/一期开发进度表.md` 与 `../../docs/一期/一期头程订单闭环设计.md`;二期读 `../../docs/二期/README.md`、`../../docs/二期/二期需求优化版-v2-测量计费入库主流程.md` 及联调报告;三期读 `../../docs/三期/README.md`、`../../docs/三期/三期开发规划.md`、`../../docs/三期/三期需求确认表.md` 和页面清单。阶段状态与代码现状冲突时,以需求确认和验收证据为准,并在改动说明中记录。
|
||||
阶段文档入口(路径相对本文件所在目录):一期读 `../../docs/一期/README.md` 与闭环设计;二期读 `../../docs/二期/README.md` 和主流程;三期读 `../../docs/三期/README.md`、需求确认表、开工门禁、开发规划和页面清单。进度、排期或验收状态问题再读取相应进度表和验收报告。
|
||||
|
||||
## 业务与安全约束
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ async function loadRows() {
|
||||
rows.value = []; total.value = 0; loadError.value = getApiErrorDetails(error, '货柜列表加载失败,请稍后重试')
|
||||
} finally { if (sequence === listSequence) loading.value = false }
|
||||
}
|
||||
function search() { applied.value = { ...filters.value, sailingRange: [...filters.value.sailingRange] }; page.value = 1; void loadRows() }
|
||||
function search() { applied.value = { ...filters.value, sailingRange: [...filters.value.sailingRange] }; if (page.value === 1) void loadRows(); else page.value = 1 }
|
||||
async function openDetail(source: object) {
|
||||
const row = source as ContainerBookingResource
|
||||
detailTarget.value = row
|
||||
@@ -176,7 +176,7 @@ async function save() {
|
||||
}
|
||||
function goWorkbench(source: object) { const row = source as ContainerBookingResource; void router.push({ name: 'smart-loading-workbench', params: { containerId: row.id } }) }
|
||||
|
||||
watch([page, pageSize], ([nextPage], [previousPage]) => { if (nextPage !== previousPage || page.value > 0) void loadRows() })
|
||||
watch([page, pageSize], ([nextPage], [previousPage]) => { if (nextPage !== previousPage || pageSize.value > 0) void loadRows() })
|
||||
onMounted(() => { void Promise.all([loadWarehouses(), loadRows()]) })
|
||||
</script>
|
||||
|
||||
@@ -185,7 +185,7 @@ onMounted(() => { void Promise.all([loadWarehouses(), loadRows()]) })
|
||||
<section class="container-page">
|
||||
<ListPageLayout ariaLabel="货柜列表">
|
||||
<template #toolbar>
|
||||
<QueryToolbar v-model="filters" class="surface-panel" ariaLabel="货柜筛选" :fields="queryFields" preference-key="container-loading-query" @search="search" @reset="search" />
|
||||
<QueryToolbar v-model="filters" class="surface-panel" ariaLabel="货柜筛选" :fields="queryFields" preference-key="container-loading-query" @search="search" />
|
||||
</template>
|
||||
<template v-if="warehouseError || loadError" #state>
|
||||
<LoadErrorState v-if="warehouseError" title="装柜仓库加载失败" :message="warehouseError.message" compact @retry="loadWarehouses"><template #details><ApiErrorMeta :details="warehouseError" /></template></LoadErrorState>
|
||||
|
||||
@@ -63,7 +63,7 @@ onMounted(() => { void load() })
|
||||
<AdminPage>
|
||||
<div class="business-page overseas-fees-page">
|
||||
<ListPageLayout ariaLabel="海外仓阶梯费用列表">
|
||||
<template #toolbar><QueryToolbar v-model="filters" class="surface-panel" :fields="queryFields" ariaLabel="海外仓阶梯费用筛选" preference-key="overseas-storage-fees-query" @search="search" @reset="() => { filters.keyword = ''; search() }" /></template>
|
||||
<template #toolbar><QueryToolbar v-model="filters" class="surface-panel" :fields="queryFields" ariaLabel="海外仓阶梯费用筛选" preference-key="overseas-storage-fees-query" @search="search" /></template>
|
||||
<template v-if="error" #state><section class="surface-panel error-state" role="alert"><div><strong>{{ error.message }}</strong><ApiErrorMeta :details="error" /></div><el-button type="primary" @click="load">重新加载</el-button></section></template>
|
||||
<section v-if="!error" class="surface-panel table-panel">
|
||||
<DataTable v-model:page="page" v-model:page-size="size" :data="rows" :columns="columns" :loading="loading" :total="total" preference-key="overseas-storage-fees-table" row-key="id" empty-text="暂无海外仓阶梯费用配置" @update:page="load" @update:page-size="load">
|
||||
|
||||
@@ -397,7 +397,7 @@ watch(() => filters.value.customerId, (customerId) => {
|
||||
void loadWarehouseOptions()
|
||||
void loadInbounds()
|
||||
})
|
||||
watch(tab, () => { page.value = 1; void loadInbounds() })
|
||||
watch(tab, () => { if (page.value === 1) void loadInbounds(); else page.value = 1 })
|
||||
watch([page, pageSize], () => { void loadInbounds() })
|
||||
onMounted(() => { void loadCustomers(); void loadInbounds() })
|
||||
onBeforeUnmount(() => { disposed = true; loadRequestId += 1; detailRequestId += 1; customerRequestId += 1; warehouseRequestId += 1 })
|
||||
|
||||
@@ -40,7 +40,7 @@ const columns = [
|
||||
]
|
||||
async function load() { loading.value = true; error.value = null; try { const result = await listOceanBookings({ page: page.value, size: pageSize.value, keyword: String(query.keyword || '') || undefined, status: (query.status || undefined) as OceanBookingStatus | undefined } satisfies OceanBookingQuery); rows.value = result.records; total.value = result.total } catch (e) { error.value = getApiErrorDetails(e) } finally { loading.value = false } }
|
||||
function open(id?: string) { void router.push(id ? `/ocean-bookings/${id}` : '/ocean-bookings/new') }
|
||||
function search() { page.value = 1; void load() }
|
||||
function search() { if (page.value === 1) void load(); else page.value = 1 }
|
||||
watch([page, pageSize], () => void load()); onMounted(load)
|
||||
</script>
|
||||
<template>
|
||||
|
||||
@@ -51,15 +51,14 @@ function statusLabel(value: unknown) {
|
||||
: String(value || '—')
|
||||
}
|
||||
async function load() { loading.value = true; loadError.value = null; try { const result = await listTruckAppointments({ page: page.value, size: pageSize.value, ...applied.value } as TruckAppointmentQuery); rows.value = result.records; total.value = result.total } catch (error) { rows.value = []; total.value = 0; loadError.value = getApiErrorDetails(error, '拖车预约加载失败,请稍后重试') } finally { loading.value = false } }
|
||||
function search(model: QueryModel) { applied.value = { keyword: String(model.keyword || ''), status: (model.status || '') as TruckAppointmentStatus | '', containerBookingId: '', containerType: String(model.containerType || '') }; page.value = 1; void load() }
|
||||
function reset() { filters.value = { keyword: '', status: '', containerBookingId: '', containerType: '' }; search(filters.value) }
|
||||
function search(model: QueryModel) { applied.value = { keyword: String(model.keyword || ''), status: (model.status || '') as TruckAppointmentStatus | '', containerBookingId: '', containerType: String(model.containerType || '') }; if (page.value === 1) void load(); else page.value = 1 }
|
||||
onMounted(load)
|
||||
function openDetail(row: TruckAppointmentResource, edit = false) { router.push({ name: 'trucking-detail', params: { id: String(row.id) }, query: edit ? { edit: '1' } : undefined }) }
|
||||
</script>
|
||||
<template>
|
||||
<AdminPage as="div" class="trucking-page">
|
||||
<ListPageLayout aria-label="拖车预约列表">
|
||||
<template #toolbar><QueryToolbar v-model="filters" class="surface-panel" ariaLabel="拖车预约查询" :fields="fields" preference-key="trucking-query" @search="search" @reset="reset" /></template>
|
||||
<template #toolbar><QueryToolbar v-model="filters" class="surface-panel" ariaLabel="拖车预约查询" :fields="fields" preference-key="trucking-query" @search="search" /></template>
|
||||
<template v-if="loadError" #state><LoadErrorState title="拖车预约列表暂时无法加载" :message="loadError.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="loadError" /></template></LoadErrorState></template>
|
||||
<section v-if="!loadError" class="surface-panel table-panel" :aria-busy="loading">
|
||||
<DataTable v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :loading="loading" :total="total" preference-key="trucking-appointments" aria-label="拖车预约列表" empty-text="暂无符合筛选条件的拖车预约">
|
||||
|
||||
@@ -666,8 +666,8 @@ function search() {
|
||||
|
||||
function reset() {
|
||||
filters.value = emptyFilters()
|
||||
page.value = 1
|
||||
void loadOrders()
|
||||
if (page.value === 1) void loadOrders()
|
||||
else page.value = 1
|
||||
}
|
||||
|
||||
async function openDetail(row: UnifiedOrderResource) {
|
||||
|
||||
@@ -314,7 +314,7 @@ onMounted(() => { void load() })
|
||||
<section class="smart-loading__section">
|
||||
<div class="smart-loading__section-title"><h4>候选货物</h4><span>后端按入库完成时间排序</span></div>
|
||||
<ListPageLayout ariaLabel="智能排柜候选货物">
|
||||
<template #toolbar><QueryToolbar v-model="filters" ariaLabel="候选货物筛选" :fields="queryFields" @search="applyFilters" @reset="applyFilters" /></template>
|
||||
<template #toolbar><QueryToolbar v-model="filters" ariaLabel="候选货物筛选" :fields="queryFields" @search="applyFilters" /></template>
|
||||
<section class="surface-panel table-panel">
|
||||
<DataTable :data="filteredCandidates" :columns="candidateColumns" :loading="loading" empty-text="当前柜次仓库暂无可用候选货物">
|
||||
<template #actions="{ row }"><el-button link type="primary" :disabled="!plan || !canAssign || !editable || fittingCount(row as SmartLoadingCandidate) <= 0 || Boolean(action)" @click="openAssign(row as SmartLoadingCandidate)"><Plus :size="14" />加入</el-button></template>
|
||||
|
||||
Reference in New Issue
Block a user