Files
shanxia2.0/web-admin/scripts/mp-pl-plan-cert-fixture.mjs
2026-10-19 18:28:02 +08:00

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
}