优化
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
# 海外业务待补契约
|
||||
|
||||
当前仓库尚未提供海外区域 API Base、海外 Token 选择器或海外采购/本土运输正式端点。模块因此只冻结 TypeScript 请求/响应模型和页面结构,不创建临时 URL,不使用示例数据,也不模拟提交成功。
|
||||
|
||||
后端接入前需冻结:
|
||||
|
||||
- 海外 Gateway Base 与统一认证层的海外 Token 选择方式;
|
||||
- 采购订单、卡车订单、散板订单的列表、详情、创建与更新端点;
|
||||
- 分页信封、长整型 ID 字符串透传、金额/币种精度和时间格式;
|
||||
- 状态机、允许编辑状态、乐观锁版本、幂等键及错误码;
|
||||
- 客户、采购仓库等只读候选接口与权限;
|
||||
- 对应 `list/create/edit` 权限资源在海外服务端的鉴权落点。
|
||||
|
||||
接入时应在统一双区域 HTTP/认证层完成 Base 与 Token 选择,业务页面不得自行读写 Token 或创建第二套登录。
|
||||
@@ -0,0 +1,20 @@
|
||||
# 四期海外业务原型核对矩阵
|
||||
|
||||
原型来源:`docs/悦/货代SAAS平台后台/prototype-src/modules/07-overseas-business.html`;按钮函数来源:同原型目录 `prototype-src/app.js`。
|
||||
|
||||
| 页面 | 操作 | 原型证据 | 原型承载方式 | 冻结结论 |
|
||||
|---|---|---|---|---|
|
||||
| 海外采购订单 | 列表/筛选 | `page-overseas-purchase` 2–12 行;海外面板 9–11 行;`renderOverseasPurchaseList()` 13531–13534 行 | 独立列表页,订单编号/采购品类/商品名称筛选 | 仅实现海外面板;国内页与 `domestic-*` 全部排除;操作列第一列 |
|
||||
| 海外采购订单 | 详情 | `page-overseas-purchase-detail` 204–206 行;`showOverseasPurchaseDetail()` 13538–13540 行 | 独立详情页,商品/库存/订单/快递/进销存五分区 | 建立正式详情路由,详情复用 `purchase:list` |
|
||||
| 海外采购订单 | 编辑 | `page-overseas-purchase-edit` 143–150 行;`editOverseasPurchase()`、`saveOverseasPurchaseEdit()` 13536–13537 行 | 独立编辑页 | 建立正式编辑路由,使用独立 `purchase:edit` 权限 |
|
||||
| 海外采购订单 | 新增 | 14–15 行明确“历史新增表单仅保留为不可达模板” | 不可达 | 不建立新增入口或路由 |
|
||||
| 本土卡车 | 列表 | `page-overseas-truck` 58–80 行;`renderTruckOrderList()` 13550–13566 行 | 独立列表页 | 正式列表;操作列按根规则移到第一列 |
|
||||
| 本土卡车 | 新增 | `page-overseas-truck-add` 83–130 行;`saveTruckOrder()` 13567–13579 行 | 独立页面 | 建立正式新增路由;端点未冻结前提交禁用 |
|
||||
| 本土卡车 | 编辑 | `page-overseas-truck-edit` 313–362 行;`editTruckOrder()`、`saveTruckOrderEdit()` 13630–13674 行 | 独立页面 | 建立正式编辑路由;端点未冻结前提交禁用 |
|
||||
| 本土卡车 | 详情 | 列表渲染函数 13563 行仅提示“详情开发中” | 原型未定义 | 不建立详情路由 |
|
||||
| 本土散板 | 列表 | `page-overseas-looseboard` 222–244 行;`renderLooseboardList()` 13589–13605 行 | 独立列表页 | 正式列表;操作列按根规则移到第一列 |
|
||||
| 本土散板 | 新增 | `page-overseas-looseboard-add` 247–310 行;`saveLooseboardOrder()` 13606–13622 行 | 独立页面 | 建立正式新增路由;端点未冻结前提交禁用 |
|
||||
| 本土散板 | 编辑 | `page-overseas-looseboard-edit` 365–430 行;`editLooseboardOrder()`、`saveLooseboardOrderEdit()` 13676–13726 行 | 独立页面 | 建立正式编辑路由;端点未冻结前提交禁用 |
|
||||
| 本土散板 | 详情 | 列表渲染函数 13602 行仅提示“详情开发中” | 原型未定义 | 不建立详情路由 |
|
||||
|
||||
删除、审核/确认和文件预览在三类正式页面中均未定义,本批不自行补充承载方式。
|
||||
@@ -0,0 +1,31 @@
|
||||
import type {
|
||||
LocalLooseboardOrder,
|
||||
LocalTransportOrder,
|
||||
LocalTransportQuery,
|
||||
OverseasBusinessPage,
|
||||
OverseasPurchaseOrder,
|
||||
OverseasPurchaseQuery,
|
||||
SaveLocalLooseboardOrderRequest,
|
||||
SaveLocalTransportOrderRequest,
|
||||
UpdateOverseasPurchaseRequest,
|
||||
} from '../types'
|
||||
|
||||
/**
|
||||
* 海外区域 API 的前端边界。
|
||||
*
|
||||
* 当前仓库尚未冻结海外 API Base、Token 选择和端点路径,因此这里只保存
|
||||
* 请求/响应形状,不创建 Axios 实例、不拼接临时 URL,也不提供模拟实现。
|
||||
*/
|
||||
export interface OverseasBusinessApi {
|
||||
listPurchases(query: OverseasPurchaseQuery): Promise<OverseasBusinessPage<OverseasPurchaseOrder>>
|
||||
getPurchase(purchaseId: string): Promise<OverseasPurchaseOrder>
|
||||
updatePurchase(purchaseId: string, payload: UpdateOverseasPurchaseRequest): Promise<OverseasPurchaseOrder>
|
||||
listTrucks(query: LocalTransportQuery): Promise<OverseasBusinessPage<LocalTransportOrder>>
|
||||
getTruck(orderId: string): Promise<LocalTransportOrder>
|
||||
createTruck(payload: SaveLocalTransportOrderRequest): Promise<LocalTransportOrder>
|
||||
updateTruck(orderId: string, payload: SaveLocalTransportOrderRequest): Promise<LocalTransportOrder>
|
||||
listLooseboards(query: LocalTransportQuery): Promise<OverseasBusinessPage<LocalLooseboardOrder>>
|
||||
getLooseboard(orderId: string): Promise<LocalLooseboardOrder>
|
||||
createLooseboard(payload: SaveLocalLooseboardOrderRequest): Promise<LocalLooseboardOrder>
|
||||
updateLooseboard(orderId: string, payload: SaveLocalLooseboardOrderRequest): Promise<LocalLooseboardOrder>
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ domain: string; compact?: boolean }>(), { compact: false })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-alert class="contract-pending" type="warning" :closable="false" show-icon>
|
||||
<template #title>{{ domain }}海外区域接口待接入</template>
|
||||
<p v-if="!compact">页面已按四期原型冻结字段与承载方式;海外 API Base、Token 选择和正式端点补齐前,不展示演示数据,也不会模拟查询或写入成功。</p>
|
||||
</el-alert>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.contract-pending p { margin: 5px 0 0; color: var(--el-text-color-regular); font-size: 13px; line-height: 1.55; }
|
||||
</style>
|
||||
@@ -0,0 +1,53 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const read = (path: string) => readFileSync(resolve(process.cwd(), `src/modules/overseas-business/${path}`), 'utf8')
|
||||
|
||||
describe('overseas business pending contract pages', () => {
|
||||
it('keeps operation columns first and renders no sample records', () => {
|
||||
const purchase = read('pages/PurchaseOrdersView.vue')
|
||||
const transport = read('pages/TransportOrdersView.vue')
|
||||
const purchaseColumns = purchase.slice(purchase.indexOf('const columns'))
|
||||
const transportColumns = transport.slice(transport.indexOf('const baseColumns'))
|
||||
expect(purchaseColumns.indexOf("key: 'actions'")).toBeLessThan(purchaseColumns.indexOf("key: 'orderNo'"))
|
||||
expect(transportColumns.indexOf("key: 'actions'")).toBeLessThan(transportColumns.indexOf("key: 'orderNo'"))
|
||||
expect(purchase).toContain('const rows = ref<OverseasPurchaseOrder[]>([])')
|
||||
expect(transport).toContain('const rows = ref<Array<LocalTransportOrder | LocalLooseboardOrder>>([])')
|
||||
expect(`${purchase}${transport}`).not.toMatch(/OPO\d|TK\d|LB\d/)
|
||||
})
|
||||
|
||||
it('gives every formal list a stable query preference key without sharing transport preferences', () => {
|
||||
const purchase = read('pages/PurchaseOrdersView.vue')
|
||||
const transport = read('pages/TransportOrdersView.vue')
|
||||
const purchaseToolbar = purchase.match(/<QueryToolbar[^>]*\/>/)?.[0]
|
||||
const transportToolbar = transport.match(/<QueryToolbar[^>]*\/>/)?.[0]
|
||||
|
||||
expect(purchaseToolbar).toContain('preference-key="overseas-business-purchases-query"')
|
||||
expect(transportToolbar).toContain(':preference-key="queryPreferenceKey"')
|
||||
expect(transport).toContain("'overseas-business-truck-query'")
|
||||
expect(transport).toContain("'overseas-business-looseboard-query'")
|
||||
})
|
||||
|
||||
it('does not invent an overseas endpoint or success fallback', () => {
|
||||
const apiContract = read('api/contracts.ts')
|
||||
const allPages = ['PurchaseOrdersView.vue', 'PurchaseOrderDetailView.vue', 'PurchaseOrderEditView.vue', 'TransportOrdersView.vue', 'TransportOrderFormView.vue']
|
||||
.map((file) => read(`pages/${file}`)).join('\n')
|
||||
expect(apiContract).not.toContain("from '@/shared/api/http'")
|
||||
expect(apiContract).not.toMatch(/\/platform\/v1|\/overseas\/v1|axios\.create/)
|
||||
expect(allPages).not.toMatch(/ElMessage\.success|模拟成功|提交成功/)
|
||||
expect(allPages).toContain('海外区域写端点待冻结')
|
||||
expect(allPages).toContain('海外区域更新端点待冻结')
|
||||
})
|
||||
|
||||
it('keeps prototype-backed forms as routes with validation and unsaved-work registration', () => {
|
||||
const purchaseEdit = read('pages/PurchaseOrderEditView.vue')
|
||||
const transportForm = read('pages/TransportOrderFormView.vue')
|
||||
expect(purchaseEdit).toContain('useUnsavedWorkRegistration')
|
||||
expect(purchaseEdit).toContain('出库量不能大于入库量')
|
||||
expect(transportForm).toContain('useUnsavedWorkRegistration')
|
||||
expect(transportForm).toContain("message: '请输入起运地'")
|
||||
expect(transportForm).toContain("message: '请输入目的地'")
|
||||
expect(transportForm).toContain('disabled><Save')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,3 @@
|
||||
export { overseasBusinessPermissions } from './permissions'
|
||||
export { overseasBusinessRoutes } from './routes'
|
||||
export type * from './types'
|
||||
@@ -0,0 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ArrowLeft } from '@lucide/vue'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'
|
||||
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
|
||||
import DetailPageLayout from '@/shared/components/DetailPageLayout.vue'
|
||||
import ContractPendingNotice from '../components/ContractPendingNotice.vue'
|
||||
import type { InventoryTransaction } from '../types'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const purchaseId = computed(() => typeof route.params.purchaseId === 'string' ? route.params.purchaseId : '')
|
||||
const fields = ['客户名称', '采购仓库', '采购品类', '商品名称', '商品规格', '商品链接']
|
||||
const stockFields = ['入库量', '出库量', '库存量', '库存状态']
|
||||
const orderFields = ['订单编号', '采购数量', '预算单价', '预算总价', '订单状态', '联系人', '联系电话', '收货地址', '订单备注']
|
||||
const courierFields = ['国内承运商', '国内运单号', '海外承运商', '海外运单号', '预计到达时间']
|
||||
const transactionColumns: DataTableColumn[] = [
|
||||
{ key: 'occurredAt', prop: 'occurredAt', label: '时间', minWidth: 155 },
|
||||
{ key: 'documentType', prop: 'documentType', label: '单据类型', minWidth: 120 },
|
||||
{ key: 'documentNo', prop: 'documentNo', label: '关联单号', minWidth: 160 },
|
||||
{ key: 'inboundQuantity', prop: 'inboundQuantity', label: '入库量', width: 95, align: 'right' },
|
||||
{ key: 'outboundQuantity', prop: 'outboundQuantity', label: '出库量', width: 95, align: 'right' },
|
||||
{ key: 'balanceQuantity', prop: 'balanceQuantity', label: '结存量', width: 95, align: 'right' },
|
||||
{ key: 'remark', prop: 'remark', label: '备注', minWidth: 180 },
|
||||
]
|
||||
const transactions: InventoryTransaction[] = []
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminPage><DetailPageLayout aria-label="海外采购订单详情">
|
||||
<template #header><div class="detail-header"><div><h2>海外采购订单详情</h2><span>订单标识:{{ purchaseId || '—' }}</span></div><el-button @click="router.push({ name: 'overseas-business-purchases' })"><ArrowLeft :size="16" />返回</el-button></div></template>
|
||||
<ContractPendingNotice domain="海外采购订单详情" />
|
||||
<div class="detail-surface surface-panel">
|
||||
<div class="detail-grid">
|
||||
<section><h3>商品信息</h3><dl><div v-for="label in fields" :key="label"><dt>{{ label }}</dt><dd>—</dd></div></dl></section>
|
||||
<section><h3>库存信息</h3><dl><div v-for="label in stockFields" :key="label"><dt>{{ label }}</dt><dd>—</dd></div></dl></section>
|
||||
<section><h3>订单信息</h3><dl><div v-for="label in orderFields" :key="label"><dt>{{ label }}</dt><dd>—</dd></div></dl></section>
|
||||
<section><h3>快递信息</h3><dl><div v-for="label in courierFields" :key="label"><dt>{{ label }}</dt><dd>—</dd></div></dl></section>
|
||||
<section class="full"><h3>进销存明细</h3><DataTable :data="transactions" :columns="transactionColumns" row-key="id" empty-text="进销存明细接口待接入" /></section>
|
||||
</div>
|
||||
</div>
|
||||
</DetailPageLayout></AdminPage>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.detail-header { display:flex; align-items:center; justify-content:space-between; gap:16px; padding:14px 18px; border:1px solid var(--cc-border); border-radius:8px; background:#fff; }.detail-header h2 { margin:0; font-size:16px; }.detail-header span { display:block; margin-top:4px; color:var(--cc-muted); font-size:12px; }.detail-surface { padding:20px; }.detail-grid { display:grid; grid-template-columns:1fr 1fr; gap:0 24px; }.detail-grid section { padding:0 0 18px; border-bottom:1px solid var(--cc-border); }.detail-grid section:nth-last-child(-n+2) { border-bottom:0; }.detail-grid h3 { margin:0 0 12px; font-size:14px; }.detail-grid .full { grid-column:1 / -1; padding-top:18px; }.detail-grid dl { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:12px 18px; margin:0; }.detail-grid dl div { min-width:0; }.detail-grid dt { color:var(--cc-muted); font-size:12px; }.detail-grid dd { margin:4px 0 0; color:var(--cc-text); font-size:13px; }
|
||||
</style>
|
||||
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ArrowLeft, Save } from '@lucide/vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'
|
||||
import DetailPageLayout from '@/shared/components/DetailPageLayout.vue'
|
||||
import FormSection from '@/shared/components/FormSection.vue'
|
||||
import { useUnsavedWorkRegistration } from '@/core/unsaved-work/useUnsavedWorkRegistration'
|
||||
import ContractPendingNotice from '../components/ContractPendingNotice.vue'
|
||||
import { overseasBusinessPermissions } from '../permissions'
|
||||
import type { OverseasPurchaseStatus, UpdateOverseasPurchaseRequest } from '../types'
|
||||
|
||||
interface PurchaseEditModel extends Omit<UpdateOverseasPurchaseRequest, 'expectedVersion'> { orderNo: string }
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const purchaseId = computed(() => typeof route.params.purchaseId === 'string' ? route.params.purchaseId : '')
|
||||
const formRef = ref<FormInstance>()
|
||||
const form = reactive<PurchaseEditModel>({ orderNo: '', category: '', productName: '', specification: null, productUrl: null, inboundQuantity: 0, outboundQuantity: 0, status: 'PENDING_REVIEW', domesticCarrier: null, domesticTrackingNo: null, overseasCarrier: null, overseasTrackingNo: null, estimatedArrivalAt: null, remark: null })
|
||||
const validateOutbound = (_rule: unknown, value: number, callback: (error?: Error) => void) => value > form.inboundQuantity ? callback(new Error('出库量不能大于入库量')) : callback()
|
||||
const rules: FormRules<PurchaseEditModel> = {
|
||||
category: [{ required: true, message: '请输入采购品类', trigger: 'blur' }],
|
||||
productName: [{ required: true, message: '请输入商品名称', trigger: 'blur' }],
|
||||
inboundQuantity: [{ required: true, type: 'number', min: 0, message: '入库量不能小于 0', trigger: 'change' }],
|
||||
outboundQuantity: [{ required: true, type: 'number', min: 0, message: '出库量不能小于 0', trigger: 'change' }, { validator: validateOutbound, trigger: 'change' }],
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'change' }],
|
||||
}
|
||||
const statusOptions: Array<{ value: OverseasPurchaseStatus; label: string }> = [
|
||||
{ value: 'PENDING_REVIEW', label: '待审核' }, { value: 'CONFIRMED', label: '已确认' }, { value: 'PURCHASING', label: '采购中' }, { value: 'COMPLETED', label: '已完成' },
|
||||
]
|
||||
useUnsavedWorkRegistration({ key: 'overseas-purchase-editor', label: computed(() => '编辑海外采购订单'), isOpen: computed(() => true), model: form, requiredPermission: computed(() => overseasBusinessPermissions.purchaseEdit) })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminPage width="comfortable"><DetailPageLayout aria-label="编辑海外采购订单">
|
||||
<template #header><div class="form-header"><div><h2>编辑海外采购订单</h2><span>订单标识:{{ purchaseId || '—' }}</span></div><el-button @click="router.push({ name: 'overseas-business-purchase-detail', params: { purchaseId } })"><ArrowLeft :size="16" />返回</el-button></div></template>
|
||||
<ContractPendingNotice domain="海外采购订单编辑" />
|
||||
<section class="surface-panel form-panel"><el-form ref="formRef" :model="form" :rules="rules" label-position="top">
|
||||
<FormSection title="商品信息"><div class="form-grid three"><el-form-item label="订单编号"><el-input v-model="form.orderNo" disabled placeholder="接口接入后回填" /></el-form-item><el-form-item label="采购品类" prop="category"><el-input v-model="form.category" maxlength="100" /></el-form-item><el-form-item label="商品名称" prop="productName"><el-input v-model="form.productName" maxlength="160" /></el-form-item><el-form-item class="span-2" label="商品规格" prop="specification"><el-input v-model="form.specification" maxlength="255" /></el-form-item><el-form-item class="span-3" label="商品链接" prop="productUrl"><el-input v-model="form.productUrl" maxlength="1000" /></el-form-item></div></FormSection>
|
||||
<FormSection title="库存与快递信息"><div class="form-grid three"><el-form-item label="入库量" prop="inboundQuantity"><el-input-number v-model="form.inboundQuantity" :min="0" controls-position="right" /></el-form-item><el-form-item label="出库量" prop="outboundQuantity"><el-input-number v-model="form.outboundQuantity" :min="0" controls-position="right" /></el-form-item><el-form-item label="状态" prop="status"><el-select v-model="form.status"><el-option v-for="option in statusOptions" :key="option.value" :label="option.label" :value="option.value" /></el-select></el-form-item><el-form-item label="国内承运商"><el-input v-model="form.domesticCarrier" maxlength="120" /></el-form-item><el-form-item label="国内运单号"><el-input v-model="form.domesticTrackingNo" maxlength="120" /></el-form-item><el-form-item label="海外承运商"><el-input v-model="form.overseasCarrier" maxlength="120" /></el-form-item><el-form-item label="海外运单号"><el-input v-model="form.overseasTrackingNo" maxlength="120" /></el-form-item><el-form-item label="预计到达时间"><el-date-picker v-model="form.estimatedArrivalAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" /></el-form-item><el-form-item class="span-3" label="备注"><el-input v-model="form.remark" type="textarea" :rows="3" maxlength="500" show-word-limit /></el-form-item></div></FormSection>
|
||||
</el-form><footer class="form-actions"><el-tooltip content="海外区域更新端点待冻结"><span><el-button type="primary" disabled><Save :size="16" />保存</el-button></span></el-tooltip><el-button @click="router.push({ name: 'overseas-business-purchase-detail', params: { purchaseId } })">取消</el-button></footer></section>
|
||||
</DetailPageLayout></AdminPage>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.form-header { display:flex; align-items:center; justify-content:space-between; gap:16px; padding:14px 18px; border:1px solid var(--cc-border); border-radius:8px; background:#fff; }.form-header h2 { margin:0; font-size:16px; }.form-header span { display:block; margin-top:4px; color:var(--cc-muted); font-size:12px; }.form-panel { padding:20px 24px; }.form-grid { display:grid; gap:0 20px; }.form-grid.three { grid-template-columns:repeat(3,minmax(0,1fr)); }.span-2 { grid-column:span 2; }.span-3 { grid-column:1 / -1; }.form-grid :deep(.el-select), .form-grid :deep(.el-date-editor), .form-grid :deep(.el-input-number) { width:100%; }.form-actions { display:flex; justify-content:flex-end; gap:12px; padding-top:16px; border-top:1px solid var(--cc-border); }
|
||||
</style>
|
||||
@@ -0,0 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Eye, Pencil } from '@lucide/vue'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'
|
||||
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
|
||||
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
|
||||
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
|
||||
import ContractPendingNotice from '../components/ContractPendingNotice.vue'
|
||||
import type { OverseasPurchaseOrder } from '../types'
|
||||
|
||||
const rows = ref<OverseasPurchaseOrder[]>([])
|
||||
const page = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const filters = ref<QueryModel>({ orderNo: '', category: '', productName: '' })
|
||||
const fields: QueryField[] = [
|
||||
{ key: 'orderNo', type: 'input', label: '订单编号', placeholder: '请输入订单编号', width: 210 },
|
||||
{ key: 'category', type: 'input', label: '采购品类', placeholder: '请输入采购品类', width: 190 },
|
||||
{ key: 'productName', type: 'input', label: '商品名称', placeholder: '请输入商品名称', width: 210 },
|
||||
]
|
||||
const columns: DataTableColumn[] = [
|
||||
{ key: 'actions', label: '操作', width: 176, fixed: 'left', slotName: 'actions' },
|
||||
{ key: 'orderNo', prop: 'orderNo', label: '订单编号', minWidth: 170 },
|
||||
{ key: 'customerName', prop: 'customerName', label: '客户名称', minWidth: 180 },
|
||||
{ key: 'purchaseWarehouseName', prop: 'purchaseWarehouseName', label: '采购仓库', minWidth: 160 },
|
||||
{ key: 'category', prop: 'category', label: '采购品类', minWidth: 130 },
|
||||
{ key: 'productName', prop: 'productName', label: '商品名称', minWidth: 160 },
|
||||
{ key: 'specification', prop: 'specification', label: '商品规格', minWidth: 180 },
|
||||
{ key: 'unitPrice', prop: 'unitPrice', label: '采购单价', width: 120, align: 'right' },
|
||||
{ key: 'inboundQuantity', prop: 'inboundQuantity', label: '入库量', width: 95, align: 'right' },
|
||||
{ key: 'outboundQuantity', prop: 'outboundQuantity', label: '出库量', width: 95, align: 'right' },
|
||||
{ key: 'stockQuantity', prop: 'stockQuantity', label: '库存量', width: 95, align: 'right' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminPage><ListPageLayout aria-label="海外采购订单列表">
|
||||
<template #toolbar><QueryToolbar v-model="filters" class="surface-panel" :fields="fields" ariaLabel="海外采购订单筛选" preference-key="overseas-business-purchases-query" @search="page = 1" /></template>
|
||||
<template #state><ContractPendingNotice domain="海外采购订单" /></template>
|
||||
<section class="surface-panel table-panel"><DataTable v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :total="0" preference-key="overseas-business-purchases" row-key="id" empty-text="海外采购订单查询契约待接入">
|
||||
<template #actions><div class="table-actions"><el-button size="small" type="primary" plain disabled><Eye :size="15" />详情</el-button><el-button size="small" disabled><Pencil :size="15" />编辑</el-button></div></template>
|
||||
<template #pagination-left>共 0 条海外采购订单</template>
|
||||
</DataTable></section>
|
||||
</ListPageLayout></AdminPage>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.table-panel { overflow: hidden; }.table-actions { display: flex; gap: 6px; }.table-actions :deep(.el-button) { margin-left: 0; }
|
||||
</style>
|
||||
@@ -0,0 +1,62 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ArrowLeft, Save } from '@lucide/vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'
|
||||
import DetailPageLayout from '@/shared/components/DetailPageLayout.vue'
|
||||
import FormSection from '@/shared/components/FormSection.vue'
|
||||
import { useUnsavedWorkRegistration } from '@/core/unsaved-work/useUnsavedWorkRegistration'
|
||||
import ContractPendingNotice from '../components/ContractPendingNotice.vue'
|
||||
import { overseasBusinessPermissions } from '../permissions'
|
||||
import type { LocalTransportStatus, SaveLocalLooseboardOrderRequest } from '../types'
|
||||
|
||||
const props = defineProps<{ domain: 'truck' | 'looseboard' }>()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const formRef = ref<FormInstance>()
|
||||
const orderId = computed(() => typeof route.params.orderId === 'string' ? route.params.orderId : '')
|
||||
const editing = computed(() => Boolean(orderId.value))
|
||||
const isLooseboard = computed(() => props.domain === 'looseboard')
|
||||
const domainLabel = computed(() => isLooseboard.value ? '本土散板' : '本土卡车')
|
||||
const listRouteName = computed(() => isLooseboard.value ? 'overseas-business-looseboards' : 'overseas-business-trucks')
|
||||
const requiredPermission = computed(() => {
|
||||
if (isLooseboard.value) return editing.value ? overseasBusinessPermissions.looseboardEdit : overseasBusinessPermissions.looseboardCreate
|
||||
return editing.value ? overseasBusinessPermissions.truckEdit : overseasBusinessPermissions.truckCreate
|
||||
})
|
||||
interface TransportFormModel extends SaveLocalLooseboardOrderRequest { status: LocalTransportStatus }
|
||||
const form = reactive<TransportFormModel>({ transportMode: '', palletRequired: '', transportType: '', origin: '', destination: '', originAddress: null, destinationAddress: null, contactName: null, contactPhone: null, containerType: '20GP', pieces: 0, grossWeightKg: null, volumeCbm: null, budgetAmount: null, currency: 'CNY', requiredDate: null, status: 'PENDING_ACCEPTANCE' })
|
||||
const rules: FormRules<TransportFormModel> = {
|
||||
transportMode: [{ validator: (_rule, value, callback) => !isLooseboard.value || value ? callback() : callback(new Error('请选择运输方式')), trigger: 'change' }],
|
||||
palletRequired: [{ validator: (_rule, value, callback) => !isLooseboard.value || value ? callback() : callback(new Error('请选择是否打盘')), trigger: 'change' }],
|
||||
transportType: [{ required: true, message: '请选择运输类型', trigger: 'change' }],
|
||||
origin: [{ required: true, message: '请输入起运地', trigger: 'blur' }],
|
||||
destination: [{ required: true, message: '请输入目的地', trigger: 'blur' }],
|
||||
pieces: [{ required: true, type: 'number', min: 0, message: '件数不能小于 0', trigger: 'change' }],
|
||||
grossWeightKg: [{ pattern: /^\d+(\.\d{1,3})?$/, message: '总重量最多保留 3 位小数', trigger: 'blur' }],
|
||||
volumeCbm: [{ pattern: /^\d+(\.\d{1,3})?$/, message: '总体积最多保留 3 位小数', trigger: 'blur' }],
|
||||
budgetAmount: [{ pattern: /^\d+(\.\d{1,2})?$/, message: '预算总价最多保留 2 位小数', trigger: 'blur' }],
|
||||
}
|
||||
const transportTypes = [{ value: 'PORT_PICKUP', label: '⚓ 港口提柜' }, { value: 'WAREHOUSE_TO_WAREHOUSE', label: '🏭 仓到仓' }, { value: 'WAREHOUSE_TO_DOOR', label: '🚛 仓到门' }]
|
||||
const transportModes = [{ value: 'SHARED_TRUCK', label: '🚐 拼车' }, { value: 'PRIORITY_CONSOLIDATION', label: '📦 优先拼载' }]
|
||||
const statusOptions: Array<{ value: LocalTransportStatus; label: string }> = [{ value: 'PENDING_ACCEPTANCE', label: '待接单' }, { value: 'DISPATCHED', label: '已派车' }, { value: 'IN_TRANSIT', label: '运输中' }, { value: 'COMPLETED', label: '已完成' }]
|
||||
useUnsavedWorkRegistration({ key: computed(() => `overseas-${props.domain}-editor`).value, label: computed(() => `${editing.value ? '编辑' : '新增'}${domainLabel.value}订单`), isOpen: computed(() => true), model: form, requiredPermission })
|
||||
function back() { void router.push({ name: listRouteName.value }) }
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminPage width="comfortable"><DetailPageLayout :aria-label="`${editing ? '编辑' : '新增'}${domainLabel}订单`">
|
||||
<template #header><div class="form-header"><div><h2>{{ editing ? '编辑' : '新增' }}{{ domainLabel }}订单</h2><span v-if="editing">订单标识:{{ orderId }}</span></div><el-button @click="back"><ArrowLeft :size="16" />返回列表</el-button></div></template>
|
||||
<ContractPendingNotice :domain="`${domainLabel}订单${editing ? '编辑' : '新增'}`" />
|
||||
<section class="surface-panel form-panel"><el-form ref="formRef" :model="form" :rules="rules" label-position="top">
|
||||
<FormSection v-if="isLooseboard" title="散板方式"><div class="choice-block"><el-form-item label="运输方式" prop="transportMode"><el-radio-group v-model="form.transportMode"><el-radio-button v-for="item in transportModes" :key="item.value" :value="item.value">{{ item.label }}</el-radio-button></el-radio-group></el-form-item><el-form-item label="是否打盘" prop="palletRequired"><el-radio-group v-model="form.palletRequired"><el-radio-button value="YES">是</el-radio-button><el-radio-button value="NO">否</el-radio-button></el-radio-group></el-form-item></div></FormSection>
|
||||
<FormSection title="运输信息"><el-form-item label="运输类型" prop="transportType"><el-radio-group v-model="form.transportType"><el-radio-button v-for="item in transportTypes" :key="item.value" :value="item.value">{{ item.label }}</el-radio-button></el-radio-group></el-form-item><div class="form-grid two"><el-form-item label="起运地" prop="origin"><el-input v-model="form.origin" maxlength="160" placeholder="城市 / 港口 / 州" /></el-form-item><el-form-item label="目的地" prop="destination"><el-input v-model="form.destination" maxlength="160" placeholder="城市 / 州" /></el-form-item><el-form-item label="详细地址"><el-input v-model="form.originAddress" maxlength="255" placeholder="街道 / 门牌" /></el-form-item><el-form-item label="详细地址 / 码头 / 仓号"><el-input v-model="form.destinationAddress" maxlength="255" placeholder="地址 / 码头 / 仓号" /></el-form-item><el-form-item label="联系人"><el-input v-model="form.contactName" maxlength="80" placeholder="联系人姓名" /></el-form-item><el-form-item label="联系方式"><el-input v-model="form.contactPhone" maxlength="40" placeholder="联系电话" /></el-form-item></div></FormSection>
|
||||
<FormSection title="📦 货物信息"><div class="form-grid four"><el-form-item label="柜型"><el-select v-model="form.containerType"><el-option v-for="value in ['20GP','40GP','40HQ','45HQ']" :key="value" :label="value" :value="value" /></el-select></el-form-item><el-form-item label="件数" prop="pieces"><el-input-number v-model="form.pieces" :min="0" controls-position="right" /></el-form-item><el-form-item label="总重量 (kg)" prop="grossWeightKg"><el-input v-model="form.grossWeightKg" inputmode="decimal" /></el-form-item><el-form-item label="总体积 (m³)" prop="volumeCbm"><el-input v-model="form.volumeCbm" inputmode="decimal" /></el-form-item></div></FormSection>
|
||||
<FormSection title="费用与时效"><div class="form-grid two"><el-form-item label="预算总价(¥)" prop="budgetAmount"><el-input v-model="form.budgetAmount" inputmode="decimal" /></el-form-item><el-form-item label="要求时间"><el-date-picker v-model="form.requiredDate" type="date" value-format="YYYY-MM-DD" /></el-form-item><el-form-item v-if="editing" label="状态"><el-select v-model="form.status"><el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select></el-form-item></div></FormSection>
|
||||
</el-form><footer class="form-actions"><el-button @click="back">取消</el-button><el-tooltip content="海外区域写端点待冻结"><span><el-button type="primary" disabled><Save :size="16" />{{ editing ? '保存修改' : '提交订单' }}</el-button></span></el-tooltip></footer></section>
|
||||
</DetailPageLayout></AdminPage>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.form-header { display:flex; align-items:center; justify-content:space-between; gap:16px; padding:14px 18px; border:1px solid var(--cc-border); border-radius:8px; background:#fff; }.form-header h2 { margin:0; font-size:16px; }.form-header span { display:block; margin-top:4px; color:var(--cc-muted); font-size:12px; }.form-panel { max-width:760px; padding:20px 24px; }.form-grid { display:grid; gap:0 24px; }.form-grid.two { grid-template-columns:repeat(2,minmax(0,1fr)); }.form-grid.four { grid-template-columns:repeat(4,minmax(0,1fr)); gap:0 16px; }.form-grid :deep(.el-select), .form-grid :deep(.el-date-editor), .form-grid :deep(.el-input-number) { width:100%; }.choice-block { display:grid; grid-template-columns:1fr 1fr; gap:24px; }.form-actions { display:flex; justify-content:flex-end; gap:12px; padding-top:16px; border-top:1px solid var(--cc-border); }
|
||||
</style>
|
||||
@@ -0,0 +1,71 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Pencil, Plus } from '@lucide/vue'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'
|
||||
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
|
||||
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
|
||||
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import ContractPendingNotice from '../components/ContractPendingNotice.vue'
|
||||
import { overseasBusinessPermissions } from '../permissions'
|
||||
import type { LocalLooseboardOrder, LocalTransportOrder, LocalTransportStatus } from '../types'
|
||||
|
||||
const props = defineProps<{ domain: 'truck' | 'looseboard' }>()
|
||||
const router = useRouter()
|
||||
const access = useAccessPreviewStore()
|
||||
const isLooseboard = computed(() => props.domain === 'looseboard')
|
||||
const domainLabel = computed(() => isLooseboard.value ? '本土散板' : '本土卡车')
|
||||
const queryPreferenceKey = computed(() => isLooseboard.value
|
||||
? 'overseas-business-looseboard-query'
|
||||
: 'overseas-business-truck-query')
|
||||
const createPermission = computed(() => isLooseboard.value ? overseasBusinessPermissions.looseboardCreate : overseasBusinessPermissions.truckCreate)
|
||||
const rows = ref<Array<LocalTransportOrder | LocalLooseboardOrder>>([])
|
||||
const page = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const filters = ref<QueryModel>({ status: '', routeKeyword: '' })
|
||||
const statusOptions: Array<{ value: LocalTransportStatus; label: string }> = [
|
||||
{ value: 'PENDING_ACCEPTANCE', label: '待接单' }, { value: 'DISPATCHED', label: '已派车' }, { value: 'IN_TRANSIT', label: '运输中' }, { value: 'COMPLETED', label: '已完成' },
|
||||
]
|
||||
const fields: QueryField[] = [
|
||||
{ key: 'status', type: 'select', label: '状态', placeholder: '全部状态', width: 160, options: statusOptions },
|
||||
{ key: 'routeKeyword', type: 'input', label: '运输路线', placeholder: '起运地/目的地', width: 240 },
|
||||
]
|
||||
const baseColumns: DataTableColumn[] = [
|
||||
{ key: 'actions', label: '操作', width: 110, fixed: 'left', slotName: 'actions' },
|
||||
{ key: 'orderNo', prop: 'orderNo', label: '订单编号', minWidth: 170 },
|
||||
{ key: 'transportType', prop: 'transportType', label: '运输类型', minWidth: 120 },
|
||||
{ key: 'origin', prop: 'origin', label: '起运地', minWidth: 145 },
|
||||
{ key: 'destination', prop: 'destination', label: '目的地', minWidth: 145 },
|
||||
{ key: 'contactName', prop: 'contactName', label: '联系人', minWidth: 110 },
|
||||
{ key: 'contactPhone', prop: 'contactPhone', label: '联系方式', minWidth: 145 },
|
||||
{ key: 'containerType', prop: 'containerType', label: '柜型', width: 90 },
|
||||
{ key: 'grossWeightKg', prop: 'grossWeightKg', label: '总重量(kg)', width: 115, align: 'right' },
|
||||
{ key: 'budgetAmount', prop: 'budgetAmount', label: '预算总价', width: 115, align: 'right' },
|
||||
{ key: 'requiredDate', prop: 'requiredDate', label: '要求时间', width: 120 },
|
||||
{ key: 'status', prop: 'status', label: '状态', width: 105 },
|
||||
]
|
||||
const columns = computed<DataTableColumn[]>(() => isLooseboard.value ? [
|
||||
baseColumns[0]!, baseColumns[1]!,
|
||||
{ key: 'transportMode', prop: 'transportMode', label: '运输方式', minWidth: 120 },
|
||||
{ key: 'palletRequired', prop: 'palletRequired', label: '是否打盘', width: 100 },
|
||||
...baseColumns.slice(2),
|
||||
] : baseColumns)
|
||||
function openCreate() { void router.push({ name: isLooseboard.value ? 'overseas-business-looseboard-new' : 'overseas-business-truck-new' }) }
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminPage><ListPageLayout :aria-label="`${domainLabel}订单列表`">
|
||||
<template #toolbar><QueryToolbar v-model="filters" class="surface-panel" :fields="fields" :ariaLabel="`${domainLabel}订单筛选`" :preference-key="queryPreferenceKey" @search="page = 1" /></template>
|
||||
<template #state><ContractPendingNotice :domain="`${domainLabel}订单`" /></template>
|
||||
<section class="surface-panel table-panel"><DataTable v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :total="0" :preference-key="`overseas-business-${domain}`" row-key="id" :empty-text="`${domainLabel}订单查询契约待接入`">
|
||||
<template #toolbar-actions><el-tooltip v-if="access.can(createPermission)" content="海外区域创建端点待冻结"><span><el-button type="primary" disabled @click="openCreate"><Plus :size="16" />新增订单</el-button></span></el-tooltip></template>
|
||||
<template #actions><el-button size="small" disabled><Pencil :size="15" />编辑</el-button></template>
|
||||
<template #pagination-left>共 0 条{{ domainLabel }}订单</template>
|
||||
</DataTable></section>
|
||||
</ListPageLayout></AdminPage>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.table-panel { overflow:hidden; }
|
||||
</style>
|
||||
@@ -0,0 +1,10 @@
|
||||
export const overseasBusinessPermissions = {
|
||||
purchaseList: 'platform:overseas-business:purchase:list',
|
||||
purchaseEdit: 'platform:overseas-business:purchase:edit',
|
||||
truckList: 'platform:overseas-business:truck:list',
|
||||
truckCreate: 'platform:overseas-business:truck:create',
|
||||
truckEdit: 'platform:overseas-business:truck:edit',
|
||||
looseboardList: 'platform:overseas-business:looseboard:list',
|
||||
looseboardCreate: 'platform:overseas-business:looseboard:create',
|
||||
looseboardEdit: 'platform:overseas-business:looseboard:edit',
|
||||
} as const
|
||||
@@ -0,0 +1,27 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { overseasBusinessPermissions as permission } from './permissions'
|
||||
import { overseasBusinessRoutes } from './routes'
|
||||
|
||||
describe('overseas business routes', () => {
|
||||
it('keeps the frozen paths and permissions', () => {
|
||||
expect(overseasBusinessRoutes.map((route) => [route.path, route.meta?.permission])).toEqual([
|
||||
['overseas-business/purchases', permission.purchaseList],
|
||||
['overseas-business/purchases/:purchaseId', permission.purchaseList],
|
||||
['overseas-business/purchases/:purchaseId/edit', permission.purchaseEdit],
|
||||
['overseas-business/trucks', permission.truckList],
|
||||
['overseas-business/trucks/new', permission.truckCreate],
|
||||
['overseas-business/trucks/:orderId/edit', permission.truckEdit],
|
||||
['overseas-business/looseboards', permission.looseboardList],
|
||||
['overseas-business/looseboards/new', permission.looseboardCreate],
|
||||
['overseas-business/looseboards/:orderId/edit', permission.looseboardEdit],
|
||||
])
|
||||
})
|
||||
|
||||
it('does not invent excluded create or detail routes', () => {
|
||||
const paths = overseasBusinessRoutes.map((route) => route.path)
|
||||
expect(paths).not.toContain('overseas-business/purchases/new')
|
||||
expect(paths).not.toContain('overseas-business/trucks/:orderId')
|
||||
expect(paths).not.toContain('overseas-business/looseboards/:orderId')
|
||||
expect(JSON.stringify(overseasBusinessRoutes)).not.toContain('permissionAnyOf')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,65 @@
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { overseasBusinessPermissions as permission } from './permissions'
|
||||
|
||||
export const overseasBusinessRoutes = [
|
||||
{
|
||||
path: 'overseas-business/purchases',
|
||||
name: 'overseas-business-purchases',
|
||||
component: () => import('./pages/PurchaseOrdersView.vue'),
|
||||
meta: { title: '海外采购订单', description: '查询海外采购订单与库存变化', permission: permission.purchaseList },
|
||||
},
|
||||
{
|
||||
path: 'overseas-business/purchases/:purchaseId',
|
||||
name: 'overseas-business-purchase-detail',
|
||||
component: () => import('./pages/PurchaseOrderDetailView.vue'),
|
||||
meta: { title: '海外采购订单详情', description: '查看商品、库存、订单、快递与进销存明细', permission: permission.purchaseList, showPageHeading: false },
|
||||
},
|
||||
{
|
||||
path: 'overseas-business/purchases/:purchaseId/edit',
|
||||
name: 'overseas-business-purchase-edit',
|
||||
component: () => import('./pages/PurchaseOrderEditView.vue'),
|
||||
meta: { title: '编辑海外采购订单', description: '维护采购商品、库存和快递信息', permission: permission.purchaseEdit, showPageHeading: false },
|
||||
},
|
||||
{
|
||||
path: 'overseas-business/trucks',
|
||||
name: 'overseas-business-trucks',
|
||||
component: () => import('./pages/TransportOrdersView.vue'),
|
||||
props: { domain: 'truck' },
|
||||
meta: { title: '本土卡车', description: '管理海外本土卡车订单', permission: permission.truckList },
|
||||
},
|
||||
{
|
||||
path: 'overseas-business/trucks/new',
|
||||
name: 'overseas-business-truck-new',
|
||||
component: () => import('./pages/TransportOrderFormView.vue'),
|
||||
props: { domain: 'truck' },
|
||||
meta: { title: '新增本土卡车订单', description: '创建海外本土卡车订单', permission: permission.truckCreate, showPageHeading: false },
|
||||
},
|
||||
{
|
||||
path: 'overseas-business/trucks/:orderId/edit',
|
||||
name: 'overseas-business-truck-edit',
|
||||
component: () => import('./pages/TransportOrderFormView.vue'),
|
||||
props: { domain: 'truck' },
|
||||
meta: { title: '编辑本土卡车订单', description: '维护海外本土卡车订单', permission: permission.truckEdit, showPageHeading: false },
|
||||
},
|
||||
{
|
||||
path: 'overseas-business/looseboards',
|
||||
name: 'overseas-business-looseboards',
|
||||
component: () => import('./pages/TransportOrdersView.vue'),
|
||||
props: { domain: 'looseboard' },
|
||||
meta: { title: '本土散板', description: '管理海外本土散板订单', permission: permission.looseboardList },
|
||||
},
|
||||
{
|
||||
path: 'overseas-business/looseboards/new',
|
||||
name: 'overseas-business-looseboard-new',
|
||||
component: () => import('./pages/TransportOrderFormView.vue'),
|
||||
props: { domain: 'looseboard' },
|
||||
meta: { title: '新增本土散板订单', description: '创建海外本土散板订单', permission: permission.looseboardCreate, showPageHeading: false },
|
||||
},
|
||||
{
|
||||
path: 'overseas-business/looseboards/:orderId/edit',
|
||||
name: 'overseas-business-looseboard-edit',
|
||||
component: () => import('./pages/TransportOrderFormView.vue'),
|
||||
props: { domain: 'looseboard' },
|
||||
meta: { title: '编辑本土散板订单', description: '维护海外本土散板订单', permission: permission.looseboardEdit, showPageHeading: false },
|
||||
},
|
||||
] satisfies RouteRecordRaw[]
|
||||
@@ -0,0 +1,137 @@
|
||||
export interface OverseasBusinessPage<T> {
|
||||
records: T[]
|
||||
page: number
|
||||
size: number
|
||||
total: number
|
||||
pages: number
|
||||
}
|
||||
|
||||
export interface OverseasPurchaseQuery {
|
||||
page?: number
|
||||
size?: number
|
||||
orderNo?: string
|
||||
category?: string
|
||||
productName?: string
|
||||
}
|
||||
|
||||
export type OverseasPurchaseStatus = 'PENDING_REVIEW' | 'CONFIRMED' | 'PURCHASING' | 'COMPLETED'
|
||||
|
||||
export interface InventoryTransaction {
|
||||
id: string
|
||||
occurredAt: string
|
||||
documentType: string
|
||||
documentNo: string
|
||||
inboundQuantity: number
|
||||
outboundQuantity: number
|
||||
balanceQuantity: number
|
||||
remark: string | null
|
||||
}
|
||||
|
||||
export interface OverseasPurchaseOrder {
|
||||
id: string
|
||||
orderNo: string
|
||||
customerId: string
|
||||
customerName: string
|
||||
purchaseWarehouseId: string
|
||||
purchaseWarehouseName: string
|
||||
category: string
|
||||
productName: string
|
||||
specification: string | null
|
||||
productUrl: string | null
|
||||
purchaseQuantity: number
|
||||
unitPrice: string | null
|
||||
currency: string
|
||||
inboundQuantity: number
|
||||
outboundQuantity: number
|
||||
stockQuantity: number
|
||||
receivingAddress: string | null
|
||||
domesticCarrier: string | null
|
||||
domesticTrackingNo: string | null
|
||||
overseasCarrier: string | null
|
||||
overseasTrackingNo: string | null
|
||||
estimatedArrivalAt: string | null
|
||||
contactName: string | null
|
||||
contactPhone: string | null
|
||||
status: OverseasPurchaseStatus
|
||||
remark: string | null
|
||||
inventoryTransactions: InventoryTransaction[]
|
||||
version: number
|
||||
}
|
||||
|
||||
export interface UpdateOverseasPurchaseRequest {
|
||||
category: string
|
||||
productName: string
|
||||
specification: string | null
|
||||
productUrl: string | null
|
||||
inboundQuantity: number
|
||||
outboundQuantity: number
|
||||
status: OverseasPurchaseStatus
|
||||
domesticCarrier: string | null
|
||||
domesticTrackingNo: string | null
|
||||
overseasCarrier: string | null
|
||||
overseasTrackingNo: string | null
|
||||
estimatedArrivalAt: string | null
|
||||
remark: string | null
|
||||
expectedVersion: number
|
||||
}
|
||||
|
||||
export type LocalTransportType = 'PORT_PICKUP' | 'WAREHOUSE_TO_WAREHOUSE' | 'WAREHOUSE_TO_DOOR'
|
||||
export type LocalTransportStatus = 'PENDING_ACCEPTANCE' | 'DISPATCHED' | 'IN_TRANSIT' | 'COMPLETED'
|
||||
export type LooseboardTransportMode = 'SHARED_TRUCK' | 'PRIORITY_CONSOLIDATION'
|
||||
export type PalletRequired = 'YES' | 'NO'
|
||||
|
||||
export interface LocalTransportOrder {
|
||||
id: string
|
||||
orderNo: string
|
||||
transportType: LocalTransportType
|
||||
origin: string
|
||||
destination: string
|
||||
originAddress: string | null
|
||||
destinationAddress: string | null
|
||||
contactName: string | null
|
||||
contactPhone: string | null
|
||||
containerType: string | null
|
||||
pieces: number
|
||||
grossWeightKg: string | null
|
||||
volumeCbm: string | null
|
||||
budgetAmount: string | null
|
||||
currency: string
|
||||
requiredDate: string | null
|
||||
status: LocalTransportStatus
|
||||
version: number
|
||||
}
|
||||
|
||||
export interface LocalLooseboardOrder extends LocalTransportOrder {
|
||||
transportMode: LooseboardTransportMode
|
||||
palletRequired: PalletRequired
|
||||
}
|
||||
|
||||
export interface LocalTransportQuery {
|
||||
page?: number
|
||||
size?: number
|
||||
status?: LocalTransportStatus | ''
|
||||
routeKeyword?: string
|
||||
}
|
||||
|
||||
export interface SaveLocalTransportOrderRequest {
|
||||
transportType: LocalTransportType | ''
|
||||
origin: string
|
||||
destination: string
|
||||
originAddress: string | null
|
||||
destinationAddress: string | null
|
||||
contactName: string | null
|
||||
contactPhone: string | null
|
||||
containerType: string | null
|
||||
pieces: number
|
||||
grossWeightKg: string | null
|
||||
volumeCbm: string | null
|
||||
budgetAmount: string | null
|
||||
currency: string
|
||||
requiredDate: string | null
|
||||
expectedVersion?: number
|
||||
}
|
||||
|
||||
export interface SaveLocalLooseboardOrderRequest extends SaveLocalTransportOrderRequest {
|
||||
transportMode: LooseboardTransportMode | ''
|
||||
palletRequired: PalletRequired | ''
|
||||
}
|
||||
Reference in New Issue
Block a user