472 lines
16 KiB
JavaScript
472 lines
16 KiB
JavaScript
// 浏览器测试的 API 边界替身;绝不向真实业务接口发请求。
|
|
const pdfObjects = [
|
|
'<< /Type /Catalog /Pages 2 0 R >>',
|
|
'<< /Type /Pages /Kids [3 0 R] /Count 1 >>',
|
|
'<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] /Resources << >> /Contents 4 0 R >>',
|
|
'<< /Length 0 >>\nstream\n\nendstream'
|
|
]
|
|
let pdfFixture = '%PDF-1.4\n'
|
|
const offsets = [0]
|
|
pdfObjects.forEach((obj, i) => {
|
|
offsets.push(Buffer.byteLength(pdfFixture))
|
|
pdfFixture += `${i + 1} 0 obj\n${obj}\nendobj\n`
|
|
})
|
|
const xref = Buffer.byteLength(pdfFixture)
|
|
pdfFixture += `xref\n0 5\n0000000000 65535 f \n${offsets
|
|
.slice(1)
|
|
.map((n) => `${String(n).padStart(10, '0')} 00000 n `)
|
|
.join('\n')}\ntrailer\n<< /Size 5 /Root 1 0 R >>\nstartxref\n${xref}\n%%EOF`
|
|
export function makePlan(index, overrides = {}) {
|
|
const id = `plan-${index}`
|
|
const toxic = {
|
|
id: `${id}-toxic`,
|
|
planId: id,
|
|
productId: 'toxic',
|
|
productNo: 'CP001',
|
|
productName: '测试易制毒产品',
|
|
brandName: '测试品牌',
|
|
aliasName: '测试别名',
|
|
model: '99%',
|
|
unit: 'ton',
|
|
quantity: 12,
|
|
actualQty: 11,
|
|
isToxic: 1,
|
|
isExplosive: 0,
|
|
purchaseCertUrl: '',
|
|
transportCertUrl: '',
|
|
legalUseCertUrl: ''
|
|
}
|
|
const explosive = {
|
|
...toxic,
|
|
id: `${id}-explosive`,
|
|
productId: 'explosive',
|
|
productNo: 'CP002',
|
|
productName: '测试易制爆产品',
|
|
isToxic: 0,
|
|
isExplosive: 1
|
|
}
|
|
const stopProducts = [toxic, explosive].map((p) => ({
|
|
...p,
|
|
id: `stop-${p.id}`,
|
|
planProductId: p.id,
|
|
planQty: 12,
|
|
inboundQty: 10,
|
|
outboundQty: 9,
|
|
contractId: 'contract-1',
|
|
contractNo: 'HT-001',
|
|
packageSpec: 'bulk',
|
|
remark: '产品备注'
|
|
}))
|
|
return {
|
|
id,
|
|
planNo: `YS${String(index).padStart(3, '0')}`,
|
|
planDate: '2026-10-17',
|
|
createDate: '2026-10-17',
|
|
createTime: '2026-10-17 09:15:00',
|
|
belongsCompany: 'shanxia',
|
|
planType: ['inbound', 'outbound', 'direct'][(index - 1) % 3],
|
|
followStatus: 'pending_arrange',
|
|
transportStatus: 'pending',
|
|
archiveStatus: 'pending',
|
|
completeStatus: 'pending',
|
|
hasFreight: 1,
|
|
freightAmount: 1234,
|
|
freightRemark: '测试运费说明',
|
|
totalAmount: 12345,
|
|
hazardCertReady: 0,
|
|
followUserName: '测试跟进人',
|
|
licensePlate: '鄂E12345',
|
|
trailerNo: '鄂E1234挂',
|
|
driverName: '测试司机',
|
|
escortName: '测试押运员',
|
|
personnelSummary: '司机:测试司机;押运:测试押运员',
|
|
deliveryTimes: ['2026-10-18 10:30:00', '2026-10-19 14:20:00'],
|
|
remark: '证书迁移测试备注',
|
|
products: [toxic, explosive],
|
|
documentAttachments: [{ url: '/fixtures/plan.pdf', label: '计划附件.pdf' }],
|
|
load: {
|
|
id: `${id}-load`,
|
|
customerName: '测试供应商',
|
|
address: '湖北省宜昌市装货地址',
|
|
loadTime: '2026-10-17 09:20:00',
|
|
contact: '张师傅 13800000000',
|
|
notes: '装货注意事项',
|
|
contracts: [{ contractId: 'contract-1', contractNo: 'HT-001' }],
|
|
products: stopProducts
|
|
},
|
|
deliveries: [1, 2].map((seq) => ({
|
|
id: `${id}-delivery-${seq}`,
|
|
seq,
|
|
customerName: `测试需方${seq}`,
|
|
address: `送货地址${seq}`,
|
|
deliveryTime: `2026-10-${17 + seq} 10:30:00`,
|
|
contact: '李师傅 13900000000',
|
|
notes: `送货注意事项${seq}`,
|
|
generateDibang: 0,
|
|
contracts: [{ contractId: 'contract-1', contractNo: 'HT-001' }],
|
|
products: stopProducts.map((p) => ({ ...p, id: `${p.id}-${seq}`, planQty: 6 }))
|
|
})),
|
|
...overrides
|
|
}
|
|
}
|
|
|
|
export async function installCertFixture(context) {
|
|
const state = {
|
|
plans: Array.from({ length: 24 }, (_, i) => makePlan(i + 1)),
|
|
codes: [
|
|
'mini:pl:plan-cert:list',
|
|
'pl:plan-cert:list',
|
|
'pl:plan-cert:upload',
|
|
'pl:plan-cert:ready'
|
|
],
|
|
calls: [],
|
|
unknown: [],
|
|
failList: false,
|
|
failSave: false,
|
|
failReadyIds: [],
|
|
uploadGate: null,
|
|
listDelay: null,
|
|
linkedListGate: null,
|
|
failPermissions: false
|
|
}
|
|
state.plans[3].archiveStatus = 'archived'
|
|
state.plans[4].followStatus = 'cancelled'
|
|
state.plans[5].hazardCertReady = 1
|
|
state.plans[6].transportStatus = 'arranged'
|
|
state.plans[6].followStatus = 'arranged'
|
|
state.plans[6].transportId = 'transport-7'
|
|
await context.addInitScript(() => {
|
|
window.__copiedText = ''
|
|
Object.defineProperty(navigator, 'clipboard', {
|
|
configurable: true,
|
|
value: {
|
|
writeText: async (text) => {
|
|
window.__copiedText = text
|
|
}
|
|
}
|
|
})
|
|
window.wx = {
|
|
miniProgram: {
|
|
navigateBack() {},
|
|
reLaunch() {},
|
|
getEnv(cb) {
|
|
cb({ miniprogram: true })
|
|
}
|
|
}
|
|
}
|
|
})
|
|
await context.route('**/fixtures/**', (route) =>
|
|
route.fulfill(
|
|
route.request().url().endsWith('.png')
|
|
? {
|
|
contentType: 'image/png',
|
|
body: Buffer.from(
|
|
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+cL1sAAAAASUVORK5CYII=',
|
|
'base64'
|
|
)
|
|
}
|
|
: { contentType: 'application/pdf', body: pdfFixture }
|
|
)
|
|
)
|
|
await context.route('https://api.qrserver.com/**', (route) =>
|
|
route.fulfill({
|
|
contentType: 'image/svg+xml',
|
|
body: '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200"><rect width="200" height="200" fill="white"/><text x="10" y="100">Mock QR preview</text></svg>'
|
|
})
|
|
)
|
|
await context.route('**/api/**', async (route) => {
|
|
const request = route.request()
|
|
const url = new URL(request.url())
|
|
if (!url.pathname.startsWith('/api/')) return route.continue()
|
|
const path = url.pathname.slice(4)
|
|
const method = request.method()
|
|
let body = {}
|
|
try {
|
|
body = request.postDataJSON() || {}
|
|
} catch {
|
|
body = {}
|
|
}
|
|
const params = Object.fromEntries(url.searchParams)
|
|
state.calls.push({ path, method, body, params })
|
|
let data
|
|
let error = ''
|
|
if (path === '/web/auth/mp-webview-exchange') {
|
|
data = {
|
|
token: 'fixture-token',
|
|
refreshToken: 'fixture-refresh',
|
|
userInfo: {
|
|
id: 'user-1',
|
|
userId: 'user-1',
|
|
userName: '测试用户',
|
|
role: 'admin',
|
|
roles: ['admin']
|
|
}
|
|
}
|
|
} else if (path === '/web/auth/menu-codes') {
|
|
data = { all: false, isSuperAdmin: false, menuCodes: state.codes }
|
|
if (state.failPermissions) error = '权限加载失败'
|
|
} else if (path === '/web/admin/dict-data/all') {
|
|
data = {
|
|
crm_product_unit: [{ value: 'ton', label: '吨' }],
|
|
pl_package_spec: [{ value: 'bulk', label: '散装' }]
|
|
}
|
|
} else if (path === '/transport/plan/') {
|
|
if (params.contractId === 'contract-1' && state.linkedListGate) await state.linkedListGate
|
|
if (state.listDelay) await state.listDelay(params)
|
|
if (state.failList) {
|
|
state.failList = false
|
|
error = '列表测试失败'
|
|
}
|
|
let rows = state.plans
|
|
if (params.followStatus)
|
|
rows = rows.filter((p) => params.followStatus.split(',').includes(p.followStatus))
|
|
if (params.planType) rows = rows.filter((p) => p.planType === params.planType)
|
|
if (params.hazardCertReady !== undefined)
|
|
rows = rows.filter((p) => p.hazardCertReady === Number(params.hazardCertReady))
|
|
if (params.q)
|
|
rows = rows.filter(
|
|
(p) =>
|
|
p.planNo.includes(params.q) || p.products.some((x) => x.productName.includes(params.q))
|
|
)
|
|
if (params.certUploadStatus === 'incomplete')
|
|
rows = rows.filter((p) =>
|
|
p.products.some((x) =>
|
|
x.isToxic
|
|
? !(x.purchaseCertUrl && x.transportCertUrl)
|
|
: x.isExplosive && !x.legalUseCertUrl
|
|
)
|
|
)
|
|
const page = Number(params.page || 1),
|
|
size = Number(params.pageSize || 20)
|
|
data = {
|
|
list: rows.slice((page - 1) * size, page * size),
|
|
total: rows.length,
|
|
page,
|
|
pageSize: size
|
|
}
|
|
} else if (path === '/transport/plan/column-facet-values') {
|
|
const values = {
|
|
planNo: ['YS001'],
|
|
licensePlate: ['鄂E12345'],
|
|
productName: ['测试易制毒产品'],
|
|
productSpec: ['99%'],
|
|
productUnit: ['ton'],
|
|
loadCustomer: ['测试供应商'],
|
|
deliveryCustomer: ['测试需方1'],
|
|
productContract: ['HT-001']
|
|
}
|
|
data = {
|
|
list: (values[body.field] || []).map((value) => ({ value, label: value, count: 1 }))
|
|
}
|
|
} else if (/^\/transport\/plan\/plan-\d+$/.test(path)) {
|
|
data = state.plans.find((p) => p.id === path.split('/').pop())
|
|
} else if (/\/plan-\d+\/product-certs$/.test(path)) {
|
|
const plan = state.plans.find((p) => p.id === path.split('/')[3])
|
|
if (state.failSave) {
|
|
state.failSave = false
|
|
error = '证书保存测试失败'
|
|
} else
|
|
for (const item of body.products)
|
|
Object.assign(
|
|
plan.products.find((p) => p.id === item.id),
|
|
item
|
|
)
|
|
data = plan
|
|
} else if (/\/plan-\d+\/hazard-cert-ready$/.test(path)) {
|
|
const plan = state.plans.find((p) => p.id === path.split('/')[3])
|
|
if (state.failReadyIds.includes(plan.id)) error = '齐备测试失败'
|
|
else plan.hazardCertReady = body.ready ? 1 : 0
|
|
data = plan
|
|
} else if (/\/plan-\d+\/arrange-info$/.test(path)) {
|
|
data = {
|
|
driverUrl: 'https://example.test/driver',
|
|
licensePlate: '鄂E12345',
|
|
siteRecords: [
|
|
{
|
|
siteLinkId: 'site-1',
|
|
siteUrl: 'https://example.test/site',
|
|
planProductId: 'plan-7-toxic',
|
|
productName: '测试易制毒产品',
|
|
customerName: '测试需方1'
|
|
}
|
|
],
|
|
inventoryRecords: [
|
|
{
|
|
dbInventoryId: 'inventory-7',
|
|
dbPurchasePlanId: 'purchase-7',
|
|
planProductId: 'plan-7-toxic',
|
|
productName: '测试易制毒产品',
|
|
businessType: '入库'
|
|
}
|
|
]
|
|
}
|
|
} else if (path === '/transport/inbound/by-plan/plan-7') {
|
|
data = {
|
|
id: 'io-7',
|
|
kind: 'inbound',
|
|
orderNo: 'RK-007',
|
|
status: 'in_progress',
|
|
siteFeedbackRemark: '入库现场备注',
|
|
siteFeedbackTime: '2026-10-17 16:20:00',
|
|
siteFeedbackImages: ['/fixtures/inbound.pdf'],
|
|
products: []
|
|
}
|
|
} else if (path === '/transport/order/transport-7') {
|
|
data = {
|
|
id: 'transport-7',
|
|
transportNo: 'TR007',
|
|
licensePlate: '鄂E12345',
|
|
driverName: '测试司机',
|
|
driverPhone: '13800000000',
|
|
escortName: '测试押运员',
|
|
freightAmount: 1234,
|
|
freightPrice: 50,
|
|
freightType: 'by_weight',
|
|
transportQty: 24,
|
|
hasInvoice: 1,
|
|
distanceKm: 100,
|
|
unit: 'ton',
|
|
transportStatus: 'arranged',
|
|
approvalStatus: 'approved',
|
|
createTime: '2026-10-17 11:00:00',
|
|
feedbackTime: '2026-10-17 16:00:00',
|
|
feedbackQty: 23,
|
|
feedbackRemark: '司机回传测试备注',
|
|
feedbackImages: '["/fixtures/driver.pdf"]',
|
|
driverStopFeedbacks: [
|
|
{
|
|
stopType: 'load',
|
|
stopId: 'plan-7-load',
|
|
label: '装货',
|
|
customerName: '测试供应商',
|
|
quantity: 23,
|
|
remark: '司机装货回传',
|
|
feedbackTime: '2026-10-17 16:00:00',
|
|
attachmentUrls: ['/fixtures/driver.png'],
|
|
products: []
|
|
}
|
|
],
|
|
siteStopFeedbacks: [
|
|
{
|
|
stopType: 'delivery',
|
|
stopId: 'plan-7-delivery-1',
|
|
label: '送货',
|
|
customerName: '测试需方1',
|
|
quantity: 12,
|
|
remark: '现场送货回传',
|
|
feedbackTime: '2026-10-18 10:00:00',
|
|
attachmentUrls: ['/fixtures/site.pdf'],
|
|
products: []
|
|
}
|
|
]
|
|
}
|
|
} else if (path === '/transport/freight/by-transport/transport-7') {
|
|
data = null
|
|
} else if (path === '/inventory/inventory-7/related') {
|
|
data = {
|
|
plan: null,
|
|
weighing: {
|
|
id: 'weighing-7',
|
|
vehicleNo: '鄂E12345',
|
|
netWeight: 11,
|
|
grossWeight: 30,
|
|
tareWeight: 19,
|
|
grossWeightTime: '2026-10-17 15:30:00',
|
|
tareWeightTime: '2026-10-17 16:30:00',
|
|
supplier: '测试供应商',
|
|
consignee: '测试需方1',
|
|
remarks: '测试过磅备注'
|
|
}
|
|
}
|
|
} else if (path === '/inventory/inventory-7') {
|
|
data = {
|
|
id: 'inventory-7',
|
|
date: '2026-10-17',
|
|
vehicleNo: '鄂E12345',
|
|
partnerCompany: '测试供应商',
|
|
product: '测试易制毒产品',
|
|
quantity: 12,
|
|
businessType: '入库'
|
|
}
|
|
} else if (/^\/web\/crm\/contract\/approval\/flow\/contract-[12]$/.test(path)) {
|
|
data = []
|
|
} else if (/^\/web\/crm\/contract\/contract-[12]$/.test(path)) {
|
|
data = {
|
|
id: path.split('/').pop(),
|
|
contractNo: path.endsWith('contract-1') ? 'HT-001' : 'HT-002',
|
|
contractName: '危化品证书测试合同',
|
|
opportunityId: 'opportunity-1',
|
|
quoteId: 'quote-1',
|
|
contractType: 'sales',
|
|
customerName: '测试需方1',
|
|
supplierName: '测试供应商',
|
|
status: 'approved',
|
|
startDate: '2026-01-01',
|
|
endDate: '2026-12-31',
|
|
remark: '合同说明',
|
|
amount: 2400,
|
|
partyA: '测试甲方',
|
|
partyB: '测试乙方',
|
|
partyAAccount: '测试账号001',
|
|
contractFile: '[{"ossId":"/fixtures/contract.pdf","name":"合同原件.pdf"}]',
|
|
attachmentFiles: '[{"ossId":"/fixtures/extra.pdf","name":"其他附件.pdf"}]',
|
|
products: [
|
|
{
|
|
id: 'contract-product-1',
|
|
productId: 'toxic',
|
|
productName: '测试别名',
|
|
unit: 'ton',
|
|
quantity: 24,
|
|
price: 100,
|
|
subtotal: 2400
|
|
}
|
|
],
|
|
attachments: '/fixtures/contract.pdf'
|
|
}
|
|
} else if (path === '/web/crm/opportunity/detail') {
|
|
data = { id: 'opportunity-1', opportunityName: '测试关联商机' }
|
|
} else if (path === '/web/crm/quote/detail') {
|
|
data = { id: 'quote-1', quoteNo: 'BJ-001' }
|
|
} else if (path === '/web/crm/products/detail') {
|
|
data = {
|
|
id: body.id,
|
|
productName: body.id === 'toxic' ? '测试易制毒产品' : '测试易制爆产品',
|
|
aliases: [{ aliasName: '测试别名' }],
|
|
isToxic: body.id === 'toxic' ? 1 : 0,
|
|
isExplosive: body.id === 'explosive' ? 1 : 0
|
|
}
|
|
} else if (path === '/web/admin/attachment/preview-url') {
|
|
data = { previewUrl: params.fileUrl || body.fileUrl }
|
|
} else if (path === '/web/admin/attachment/download-url') {
|
|
data = { downloadUrl: params.fileUrl || body.fileUrl }
|
|
} else if (path === '/web/admin/attachment/upload') {
|
|
if (state.uploadGate) await state.uploadGate
|
|
data = {
|
|
id: 'file-1',
|
|
fileName: '测试证书.pdf',
|
|
fileUrl: '/fixtures/cert.pdf',
|
|
fileType: 'application/pdf'
|
|
}
|
|
} else {
|
|
state.unknown.push({ path, method, body, params })
|
|
error = `未定义测试接口:${method} ${path}`
|
|
}
|
|
if (error)
|
|
return route.fulfill({
|
|
status: 400,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ code: 400, msg: error, detail: error })
|
|
})
|
|
// 运输 API 是 FastAPI 直出;Web API 使用 code=200 的信封。
|
|
await route.fulfill({
|
|
contentType: 'application/json',
|
|
body: JSON.stringify(
|
|
path.startsWith('/transport/') || path.startsWith('/inventory/')
|
|
? data
|
|
: { code: 200, msg: 'ok', data }
|
|
)
|
|
})
|
|
})
|
|
return state
|
|
}
|