feat(pl): 完善双端现场日历并支持待派车和直销计划,满足车辆未定及现场查看需求。
This commit is contained in:
1 parent
fcbf8a9b65
commit
142b8fdf48
31 files changed
+1180
-60
No files matched your search
@@ -162,10 +162,11 @@ test('现场复制隐藏起止地和备注,单据类型放在每条记录末
|
||||
assert.deepEqual(
|
||||
manualCopyText('2026-10-26', [record, { ...record, id: 'site-2', quantity: '' }]).split('\n'),
|
||||
[
|
||||
'2026-10-26 派车计划',
|
||||
' ·皖H12345',
|
||||
` 液碱 32.50吨(${documentType})`,
|
||||
` 液碱(${documentType})`
|
||||
'2026-10-26 派车计划:',
|
||||
'',
|
||||
'液碱2车',
|
||||
` ·皖H12345 液碱 32.50吨(${documentType})`,
|
||||
` ·皖H12345 液碱(${documentType})`
|
||||
]
|
||||
)
|
||||
assert.equal(
|
||||
@@ -181,7 +182,10 @@ test('现场复制隐藏起止地和备注,单据类型放在每条记录末
|
||||
documentType,
|
||||
remark: '内部备注'
|
||||
}
|
||||
assert.equal(manualCopyText('2026-10-26', [record]), '2026-10-26 派车计划\n ·皖H12345\n 液碱')
|
||||
assert.equal(
|
||||
manualCopyText('2026-10-26', [record]),
|
||||
'2026-10-26 派车计划:\n\n液碱1车\n ·皖H12345 液碱'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -192,3 +196,64 @@ test('单据类型选填且最多50个字符', () => {
|
||||
/单据类型.*50/
|
||||
)
|
||||
})
|
||||
|
||||
test('现场按产品分组统计,交错产品集中展示且符合指定复制样例', () => {
|
||||
const site = (
|
||||
id: string,
|
||||
productId: string,
|
||||
productName: string,
|
||||
licensePlate: string,
|
||||
quantity: string
|
||||
) => ({
|
||||
...row(id, '2026-11-01', licensePlate),
|
||||
calendarKind: 'site' as const,
|
||||
productId,
|
||||
productName,
|
||||
licensePlate,
|
||||
quantity,
|
||||
documentType: '出库计划',
|
||||
remark: '内部备注'
|
||||
})
|
||||
const records = [
|
||||
site('1', 'p98', '98% 工业硫酸', '皖H10291', '32'),
|
||||
site('2', 'pure', '精制硫酸', '皖H57837', '32'),
|
||||
site('4', 'p50', '50% 工业硫酸', '皖HA3690', '28'),
|
||||
site('3', 'pure', '精制硫酸', '皖HA3190', '32')
|
||||
]
|
||||
assert.equal(
|
||||
manualCopyText('2026-11-01', records),
|
||||
[
|
||||
'2026-11-01 派车计划:',
|
||||
'',
|
||||
'98% 工业硫酸1车',
|
||||
' ·皖H10291 98% 工业硫酸 32吨(出库计划)',
|
||||
'精制硫酸2车',
|
||||
' ·皖H57837 精制硫酸 32吨(出库计划)',
|
||||
' ·皖HA3190 精制硫酸 32吨(出库计划)',
|
||||
'50% 工业硫酸1车',
|
||||
' ·皖HA3690 50% 工业硫酸 28吨(出库计划)'
|
||||
].join('\n')
|
||||
)
|
||||
assert.deepEqual(
|
||||
records.map((r) => r.id),
|
||||
['1', '2', '4', '3']
|
||||
)
|
||||
})
|
||||
|
||||
test('现场同车重复记录逐条计车,同名不同产品ID保持独立且组内按创建顺序', () => {
|
||||
const first = { ...row('1'), calendarKind: 'site' as const, createTime: '2026-10-26 01:00:00' }
|
||||
const later = { ...first, id: '2', createTime: '2026-10-26 02:00:00', quantity: '20' }
|
||||
const different = { ...first, id: '3', productId: 'p2' }
|
||||
assert.equal(
|
||||
manualCopyText('2026-10-26', [later, different, first]),
|
||||
[
|
||||
'2026-10-26 派车计划:',
|
||||
'',
|
||||
'液碱2车',
|
||||
' ·皖H12345 液碱',
|
||||
' ·皖H12345 液碱 20吨',
|
||||
'液碱1车',
|
||||
' ·皖H12345 液碱'
|
||||
].join('\n')
|
||||
)
|
||||
})
|
||||
@@ -3,6 +3,7 @@ import { chromium } from 'playwright'
|
||||
const browser = await chromium.launch({ headless: true, channel: 'chrome' })
|
||||
try {
|
||||
for (const { mobile, kind, title } of [
|
||||
{ mobile: true, kind: 'site', title: '现场日历' },
|
||||
{ mobile: false, kind: 'acid', title: '酸派车日历' },
|
||||
{ mobile: false, kind: 'alkali', title: '碱派车日历' },
|
||||
{ mobile: false, kind: 'other', title: '其他日历' },
|
||||
@@ -101,7 +102,12 @@ try {
|
||||
}
|
||||
const body = req.postDataJSON(),
|
||||
id = req.method() === 'PUT' ? path.split('/').pop() : String(writes)
|
||||
const row = { ...body, id, licensePlate: '皖H12345', productName: '液碱' }
|
||||
const row = {
|
||||
...body,
|
||||
id,
|
||||
licensePlate: body.vehicleId === '__pending_vehicle__' ? '待派车' : '皖H12345',
|
||||
productName: '液碱'
|
||||
}
|
||||
rows = rows.filter((r) => r.id !== id).concat(row)
|
||||
return route.fulfill({ json: row })
|
||||
}
|
||||
@@ -173,8 +179,12 @@ try {
|
||||
const editor = page.locator('[data-testid="manual-editor"]:visible')
|
||||
if (mobile) {
|
||||
await editor.getByPlaceholder('选择车辆').click()
|
||||
assert.equal(
|
||||
await page.locator('.mp-manual-picker-list button').first().innerText(),
|
||||
'待派车'
|
||||
)
|
||||
await page
|
||||
.getByRole('button', { name: '皖H12345', exact: true })
|
||||
.getByRole('button', { name: '待派车', exact: true })
|
||||
.click({ timeout: 8000 })
|
||||
.catch(async (e) => {
|
||||
console.log('PICKER', await page.locator('.mp-manual-sheet:visible').allTextContents())
|
||||
@@ -185,14 +195,21 @@ try {
|
||||
await page.getByRole('button', { name: '液碱', exact: true }).click()
|
||||
} else {
|
||||
await editor.getByRole('combobox', { name: '车牌号', exact: true }).click()
|
||||
await page.getByRole('option', { name: '皖H12345', exact: true }).click()
|
||||
assert.equal(
|
||||
await page.locator('.el-select-dropdown:visible [role=option]').first().innerText(),
|
||||
'待派车'
|
||||
)
|
||||
await page.getByRole('option', { name: '待派车', exact: true }).click()
|
||||
await editor.getByRole('combobox', { name: '产品名称', exact: true }).click()
|
||||
await page.getByRole('option', { name: '液碱', exact: true }).click()
|
||||
}
|
||||
if (kind === 'site') {
|
||||
if (kind === 'site' && mobile) {
|
||||
await editor.getByLabel('选择单据类型', { exact: true }).click()
|
||||
await page.getByRole('button', { name: '直销计划', exact: true }).click()
|
||||
} else if (kind === 'site') {
|
||||
await editor.getByRole('combobox', { name: '单据类型', exact: true }).click()
|
||||
await page.getByRole('option', { name: '出库计划', exact: true }).waitFor()
|
||||
await page.getByRole('option', { name: '入库计划', exact: true }).click()
|
||||
await page.getByRole('option', { name: '直销计划', exact: true }).click()
|
||||
} else {
|
||||
assert.equal(await editor.getByRole('combobox', { name: '单据类型', exact: true }).count(), 0)
|
||||
}
|
||||
@@ -200,16 +217,33 @@ try {
|
||||
await editor.getByRole('button', { name: '保存', exact: true }).click()
|
||||
await editor.waitFor({ state: 'hidden' })
|
||||
assert.equal(rows.length, 1)
|
||||
assert.equal(rows[0].vehicleId, '__pending_vehicle__')
|
||||
assert.equal(rows[0].licensePlate, '待派车')
|
||||
assert.equal(rows[0].origin, '')
|
||||
assert.equal(rows[0].destination, '')
|
||||
assert.equal(rows[0].calendarKind, kind)
|
||||
if (kind === 'site') assert.equal(rows[0].documentType, '入库计划')
|
||||
if (kind === 'site') assert.equal(rows[0].documentType, '直销计划')
|
||||
await page.locator('[data-testid="manual-record"]').first().click()
|
||||
if (kind === 'site')
|
||||
await page.locator('.el-dialog:visible').getByText('入库计划', { exact: true }).waitFor()
|
||||
await page
|
||||
.locator(mobile ? '.mp-manual-detail' : '.el-dialog:visible')
|
||||
.getByText('直销计划', { exact: true })
|
||||
.waitFor()
|
||||
await page.getByRole('button', { name: '编辑', exact: true }).click()
|
||||
if (kind === 'site') {
|
||||
assert.match(await editor.innerText(), /入库计划/)
|
||||
if (mobile) {
|
||||
assert.equal(await editor.getByPlaceholder('选择车辆').inputValue(), '待派车')
|
||||
await editor.getByPlaceholder('选择车辆').click()
|
||||
await page.getByRole('button', { name: '皖H12345', exact: true }).click()
|
||||
} else {
|
||||
assert.match(await editor.innerText(), /待派车/)
|
||||
await editor.getByRole('combobox', { name: '车牌号', exact: true }).click()
|
||||
await page.getByRole('option', { name: '皖H12345', exact: true }).click()
|
||||
}
|
||||
if (kind === 'site' && mobile) {
|
||||
assert.equal(await editor.getByPlaceholder('选择或输入单据类型').inputValue(), '直销计划')
|
||||
await editor.getByPlaceholder('选择或输入单据类型').fill('现场调拨')
|
||||
} else if (kind === 'site') {
|
||||
assert.match(await editor.innerText(), /直销计划/)
|
||||
await editor.getByRole('combobox', { name: '单据类型', exact: true }).fill('现场调拨')
|
||||
await page.getByRole('option', { name: '现场调拨', exact: true }).click()
|
||||
}
|
||||
@@ -232,8 +266,9 @@ try {
|
||||
assert.match(await card.innerText(), /山峡 → 云栋/)
|
||||
if (kind === 'site') {
|
||||
assert.equal(rows[0].documentType, '现场调拨')
|
||||
assert.equal(await card.locator('.route').innerText(), '山峡 → 云栋(现场调拨)')
|
||||
assert.equal(await card.locator('.route').innerText(), '山峡 → 云栋')
|
||||
assert.equal(await card.locator('.el-tag').count(), 0)
|
||||
assert.doesNotMatch(await card.innerText(), /现场调拨/)
|
||||
}
|
||||
await page
|
||||
.locator('[data-date="2026-10-26"]')
|
||||
@@ -241,7 +276,7 @@ try {
|
||||
.click()
|
||||
const copiedText = await page.evaluate(() => navigator.clipboard.readText())
|
||||
if (kind === 'site') {
|
||||
assert.equal(copiedText, '2026-10-26 派车计划\n ·皖H12345\n 液碱(现场调拨)')
|
||||
assert.equal(copiedText, '2026-10-26 派车计划:\n\n液碱1车\n ·皖H12345 液碱(现场调拨)')
|
||||
} else {
|
||||
assert.match(copiedText, /皖H12345.*液碱.*山峡 → 云栋/s)
|
||||
}
|
||||
@@ -252,10 +287,7 @@ try {
|
||||
await editor.getByRole('button', { name: '保存', exact: true }).click()
|
||||
await editor.waitFor({ state: 'hidden' })
|
||||
if (kind === 'site') assert.equal(rows[0].documentType, '现场调拨')
|
||||
await page
|
||||
.getByText(kind === 'site' ? '山峡 → 安庆(现场调拨)' : '山峡 → 安庆', { exact: true })
|
||||
.first()
|
||||
.waitFor()
|
||||
await page.getByText('山峡 → 安庆', { exact: true }).first().waitFor()
|
||||
await page.locator('[data-testid="manual-record"]').first().click()
|
||||
await page.getByRole('button', { name: '删除', exact: true }).click()
|
||||
await page.getByRole('button', { name: mobile ? '确认删除' : '确定', exact: true }).click()
|
||||
@@ -305,14 +337,24 @@ try {
|
||||
})
|
||||
await page.getByRole('tab', { name: title, exact: true }).click()
|
||||
await page.locator('.pair-trip').nth(1).waitFor()
|
||||
assert.match(await page.locator('.pair-trip').nth(0).innerText(), /山峡 → 云栋(出入库计划)/)
|
||||
assert.match(await page.locator('.pair-trip').nth(1).innerText(), /山峡 → 云栋(入库计划)/)
|
||||
assert.match(await page.locator('.pair-trip').nth(0).innerText(), /山峡 → 云栋/)
|
||||
assert.match(await page.locator('.pair-trip').nth(1).innerText(), /山峡 → 云栋/)
|
||||
assert.equal(await page.locator('.manual-day-content .el-tag').count(), 0)
|
||||
assert.doesNotMatch(
|
||||
await page.locator('.manual-day-content').innerText(),
|
||||
/出入库计划|入库计划/
|
||||
)
|
||||
await page.locator('[data-testid="manual-record"]').first().click()
|
||||
await page.getByText('自动同步,请在来源派车日历修改或删除', { exact: true }).waitFor()
|
||||
assert.equal(await page.getByRole('button', { name: '编辑', exact: true }).count(), 0)
|
||||
assert.equal(await page.getByRole('button', { name: '删除', exact: true }).count(), 0)
|
||||
await page.locator('.el-dialog:visible .el-dialog__headerbtn').click()
|
||||
await page
|
||||
.locator(
|
||||
mobile
|
||||
? '.van-popup:visible .van-popup__close-icon'
|
||||
: '.el-dialog:visible .el-dialog__headerbtn'
|
||||
)
|
||||
.click()
|
||||
await page.getByRole('tab', { name: '实际派车信息', exact: true }).click()
|
||||
allowManage = false
|
||||
delete rows[0].sourceRecordId
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { chromium } from 'playwright'
|
||||
const browser = await chromium.launch({ headless: true, channel: 'chrome' })
|
||||
try {
|
||||
for (const allPermissions of [false, true]) {
|
||||
const context = await browser.newContext({
|
||||
permissions: ['clipboard-read', 'clipboard-write'],
|
||||
viewport: { width: 375, height: 812 }
|
||||
})
|
||||
const page = await context.newPage()
|
||||
// 固定起始日期但保持时钟前进;冻结 Date.now 会触发 Vue 嵌套点击事件的时间戳保护。
|
||||
await page.clock.install({ time: new Date('2026-10-26T04:00:00Z') })
|
||||
page.setDefaultTimeout(20000)
|
||||
page.on('console', (msg) => {
|
||||
if (msg.type() === 'error') console.log('CONSOLE', msg.text())
|
||||
})
|
||||
page.on('requestfailed', (r) => console.log('FAILED', r.url(), r.failure()))
|
||||
const errors = []
|
||||
page.on('pageerror', (e) => errors.push(e.message))
|
||||
const rows = [
|
||||
{
|
||||
id: 'site1',
|
||||
dispatchDate: '2026-10-26',
|
||||
calendarKind: 'site',
|
||||
vehicleId: 'v1',
|
||||
licensePlate: '皖H12345',
|
||||
productId: 'p1',
|
||||
productName: '液碱',
|
||||
origin: '山峡',
|
||||
destination: '武汉',
|
||||
quantity: '32',
|
||||
documentType: '出库计划',
|
||||
remark: ''
|
||||
},
|
||||
{
|
||||
id: 'site2',
|
||||
dispatchDate: '2026-10-26',
|
||||
calendarKind: 'site',
|
||||
sourceRecordId: 'source2',
|
||||
vehicleId: 'v2',
|
||||
licensePlate: '皖H54321',
|
||||
productId: 'p1',
|
||||
productName: '液碱',
|
||||
origin: '武汉',
|
||||
destination: '云栋',
|
||||
quantity: '28',
|
||||
documentType: '入库计划',
|
||||
remark: ''
|
||||
}
|
||||
]
|
||||
const forbiddenRequests = []
|
||||
let lastMonth = ''
|
||||
await context.addInitScript(() => {
|
||||
localStorage.setItem(
|
||||
'sys-v3.0.2-user',
|
||||
JSON.stringify({
|
||||
isLogin: true,
|
||||
accessToken: 'fixture',
|
||||
info: {
|
||||
id: 'fixture',
|
||||
userId: 'fixture',
|
||||
userName: '测试',
|
||||
role: 'admin',
|
||||
roles: ['admin']
|
||||
}
|
||||
})
|
||||
)
|
||||
localStorage.setItem('sys-version', '3.0.2')
|
||||
})
|
||||
await context.route('**/api/**', async (route) => {
|
||||
const req = route.request(),
|
||||
url = new URL(req.url()),
|
||||
path = url.pathname.slice(4)
|
||||
if (!url.pathname.startsWith('/api/')) return route.continue()
|
||||
const user = {
|
||||
id: 'fixture',
|
||||
userId: 'fixture',
|
||||
userName: '测试',
|
||||
role: 'admin',
|
||||
roles: ['admin']
|
||||
}
|
||||
if (path === '/web/auth/mp-webview-exchange')
|
||||
return route.fulfill({
|
||||
json: {
|
||||
code: 200,
|
||||
data: { token: 'fixture', refreshToken: 'refresh', expiresIn: 3600, userInfo: user }
|
||||
}
|
||||
})
|
||||
if (path === '/web/auth/current-user')
|
||||
return route.fulfill({ json: { code: 200, data: user } })
|
||||
if (path === '/web/auth/menu-codes')
|
||||
return route.fulfill({
|
||||
json: {
|
||||
code: 200,
|
||||
data: {
|
||||
all: allPermissions,
|
||||
menuCodes: ['mini:pl:site-dispatch-calendar:list']
|
||||
}
|
||||
}
|
||||
})
|
||||
if (path === '/transport/site-dispatch-calendar' && req.method() === 'GET') {
|
||||
lastMonth = url.searchParams.get('month')
|
||||
assert.equal(url.searchParams.has('calendarKind'), false)
|
||||
const list = rows.filter((row) => row.dispatchDate.startsWith(lastMonth))
|
||||
return route.fulfill({ json: { list, total: list.length } })
|
||||
}
|
||||
if (path.startsWith('/transport/')) {
|
||||
forbiddenRequests.push(req.method() + ' ' + path)
|
||||
return route.fulfill({ status: 403, json: { detail: 'forbidden' } })
|
||||
}
|
||||
return route.fulfill({
|
||||
json: {
|
||||
code: 200,
|
||||
data: path.includes('favorites') || path.includes('upgrade-logs') ? [] : {}
|
||||
}
|
||||
})
|
||||
})
|
||||
await page.goto('http://localhost:3006/#/m/pl/site-dispatch-calendar?mpTicket=readonly-test')
|
||||
await page.getByText('现场派车日历', { exact: true }).waitFor()
|
||||
await page.locator('[data-testid="manual-record"]').nth(1).waitFor()
|
||||
assert.equal(await page.getByRole('tab').count(), 0)
|
||||
assert.equal(await page.getByRole('button', { name: /新增/ }).count(), 0)
|
||||
assert.equal(await page.locator('[data-testid="manual-editor"]').count(), 0)
|
||||
for (let index = 0; index < 2; index++) {
|
||||
await page.locator('[data-testid="manual-record"]').nth(index).click()
|
||||
await page.getByText('派车计划详情', { exact: true }).waitFor()
|
||||
assert.equal(await page.getByRole('button', { name: '编辑', exact: true }).count(), 0)
|
||||
assert.equal(await page.getByRole('button', { name: '删除', exact: true }).count(), 0)
|
||||
await page.locator('.van-popup:visible .van-popup__close-icon').click()
|
||||
}
|
||||
await page
|
||||
.locator('[data-date="2026-10-26"]')
|
||||
.getByRole('button', { name: '复制', exact: true })
|
||||
.click()
|
||||
assert.equal(
|
||||
await page.evaluate(() => navigator.clipboard.readText()),
|
||||
'2026-10-26 派车计划:\n\n液碱2车\n ·皖H12345 液碱 32吨(出库计划)\n ·皖H54321 液碱 28吨(入库计划)'
|
||||
)
|
||||
await page.getByLabel('车辆筛选', { exact: true }).click()
|
||||
let vehiclePicker = page.locator('.mp-calendar-vehicle-picker:visible')
|
||||
await vehiclePicker.getByText('皖H54321', { exact: true }).click()
|
||||
await vehiclePicker.getByRole('button', { name: '确认', exact: true }).click()
|
||||
await page
|
||||
.locator('[data-testid="manual-record"]')
|
||||
.filter({ hasText: '皖H12345' })
|
||||
.waitFor({ state: 'hidden' })
|
||||
assert.equal(await page.locator('[data-testid="manual-record"]').count(), 1)
|
||||
await page
|
||||
.locator('[data-date="2026-10-26"]')
|
||||
.getByRole('button', { name: '复制', exact: true })
|
||||
.click()
|
||||
assert.equal(
|
||||
await page.evaluate(() => navigator.clipboard.readText()),
|
||||
'2026-10-26 派车计划:\n\n液碱1车\n ·皖H54321 液碱 28吨(入库计划)'
|
||||
)
|
||||
await page.getByLabel('车辆筛选', { exact: true }).click()
|
||||
vehiclePicker = page.locator('.mp-calendar-vehicle-picker:visible')
|
||||
await vehiclePicker.getByText('全部车辆', { exact: true }).click()
|
||||
await vehiclePicker.getByRole('button', { name: '确认', exact: true }).click()
|
||||
await page.getByLabel('查看月份', { exact: true }).click()
|
||||
const popup = page.locator('.mp-calendar-date-picker:visible')
|
||||
await popup.locator('.van-picker-column').nth(1).getByText('09', { exact: true }).click()
|
||||
await popup.getByRole('button', { name: '确认', exact: true }).click()
|
||||
await page.getByText('本月暂无现场派车计划', { exact: true }).waitFor()
|
||||
assert.equal(lastMonth, '2026-09')
|
||||
await page.getByRole('button', { name: '今天', exact: true }).click()
|
||||
await page.locator('[data-testid="manual-record"]').nth(1).waitFor()
|
||||
for (const width of [320, 375, 414]) {
|
||||
await page.setViewportSize({ width, height: 812 })
|
||||
assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1))
|
||||
}
|
||||
await page.screenshot({ path: `/tmp/site-readonly-${allPermissions ? 'admin' : 'staff'}.png` })
|
||||
assert.deepEqual(forbiddenRequests, [])
|
||||
assert.deepEqual(errors, [])
|
||||
console.log(
|
||||
`readonly site ${allPermissions ? 'all permissions' : 'site permission only'} passed`
|
||||
)
|
||||
await context.close()
|
||||
}
|
||||
} finally {
|
||||
await browser.close()
|
||||
}
|
||||
Reference in new issue
Block a user