yeg
2026-09-07 fca70eaab1dcd4b13f31562406d0d6f9312922f1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import { expect, test, type Page } from '@playwright/test'
import { Ticket04Stack } from './support/ticket04-stack'
 
const stack = new Ticket04Stack()
test.use({ actionTimeout: 20_000 })
test.beforeAll(async () => {
  await stack.start()
  stack.executeIsolatedSql(`
    INSERT INTO t_battery_group(group_id,group_name,model) VALUES(9201,'拼接打印组','型号');
    INSERT INTO t_test_type(test_type_id,test_type_name) VALUES(9201,'容量');
    INSERT INTO t_test(test_id,test_name,creator_id,principal_id,test_type_id,test_purpose,test_status,reception_status)
    VALUES(9201,'来源甲',1,1,9201,'隔离验收','COMPLETED','ENDED'),(9202,'来源乙',1,1,9201,'隔离验收','COMPLETED','ENDED');
    INSERT INTO t_test_battery(test_battery_id,test_id,group_id,battery_no,collector_id)
    VALUES(9201,9201,9201,'电池01',9201),(9202,9202,9201,'电池01',9202);
  `)
})
test.afterAll(async () => stack.stop())
 
async function login(page: Page, username = 'admin') {
  if (page.url().startsWith('http')) await page.evaluate(() => localStorage.clear())
  await page.goto('/login')
  await page.locator('input[autocomplete="username"]').fill(username)
  await page.locator('input[autocomplete="current-password"]').fill(username === 'admin' ? 'admin123' : 'Report123!')
  await page.getByRole('button', { name: '登录', exact: true }).click()
  await expect(page).toHaveURL(/\/dashboard$/)
}
async function api(page: Page, path: string, method = 'GET', data?: unknown, code = 200) {
  const token = await page.evaluate(() => localStorage.getItem('battery_detection_auth_token'))
  const body = await (await page.request.fetch(`${stack.backendUrl}/system/${path}`, { method, data, headers: { Authorization: `Bearer ${token}` } })).json()
  expect(body.code, `${path}: ${body.msg}`).toBe(code)
  return body
}
 
test('指定保存版本独立重开、删源打印、未保存及窗口失败反馈', async ({ page }) => {
  await login(page)
  const payload = { name: '拼接打印V1', description: '说明 <script>不可执行</script>', selections: [{ testId: 9201, testBatteryId: 9201 }, { testId: 9202, testBatteryId: 9202 }], settings: { batteryFields: [], specialFields: ['人工备注'], detailFieldValues: { 人工备注: { 电池01: '0' } }, metrics: ['finalVoltage1', 'energyEfficiency'], seriesColors: ['#123456'] } }
  const v1 = (await api(page, 'splice-report', 'POST', payload)).data
  const v2 = (await api(page, `splice-report/${v1.id}/versions`, 'POST', { ...payload, name: '拼接打印V2', baseVersionNo: 1, changeSummary: '更新说明' })).data
  await page.goto(`/report-splice/${v1.id}/edit?mode=view&version=1`)
  await expect(page.locator('.splice-snapshot h1')).toHaveText('拼接打印V1')
  await expect(page.locator('.splice-snapshot')).toContainText('来源甲')
  await page.evaluate(() => { window.open = () => null })
  await page.getByRole('button', { name: '打印 / 另存 PDF', exact: true }).click()
  await expect(page.getByText('打印窗口打开失败,请允许浏览器弹出窗口后重试', { exact: true })).toBeVisible()
  await api(page, 'test/9201', 'DELETE')
  await api(page, 'test/9202', 'DELETE')
  await page.context().addInitScript(() => { window.print = () => { document.body.dataset.printRequested = 'true' } })
  for (const saved of [v1, v2]) {
    expect((await api(page, `splice-report/${v1.id}/versions/${saved.versionNo}/print`)).data).toEqual(saved)
    await page.goto(`/report-splice/${v1.id}/edit?mode=view&version=${saved.versionNo}`)
    await expect(page.locator('.splice-snapshot h1')).toHaveText(saved.name)
    const popupPromise = page.waitForEvent('popup')
    await page.getByRole('button', { name: '打印 / 另存 PDF', exact: true }).click()
    const popup = await popupPromise
    await expect(popup.locator('body')).toHaveAttribute('data-print-requested', 'true')
    await expect(popup.locator('.splice-snapshot')).toContainText('来源甲')
    await expect(popup.locator('.splice-note')).toHaveText(saved.description)
    expect(await popup.locator('.splice-snapshot').innerText()).toEqual(await page.locator('.splice-snapshot').innerText())
    await popup.close()
  }
  await page.goto(`/report-splice/${v1.id}/edit`)
  await page.getByPlaceholder('请输入拼接报表名称').fill('未保存名称')
  await page.getByRole('button', { name: '打印 / 另存 PDF', exact: true }).click()
  await expect(page.getByText('有未保存修改,请先保存新版本后再打印', { exact: true })).toBeVisible()
  await page.goto('/report-splice/new')
  await page.getByRole('button', { name: '打印 / 另存 PDF', exact: true }).click()
  await expect(page.getByText('请先保存报表后再打印', { exact: true })).toBeVisible()
})
 
test('打印权限独立校验,查看用户不能直接获取打印版本', async ({ page }) => {
  await login(page)
  const saved = (await api(page, 'splice-report/list')).rows[0]
  const permissions = (await api(page, 'menu/permissionList')).data.flatMap((module: any) => module.pages).find((item: any) => item.pageName === '拼接报表').permissions
  for (const [username, action] of [['splice_reader', '查看'], ['splice_exporter', '导出']]) {
    const menuIds = permissions.filter((item: any) => item.menuName === action).map((item: any) => item.menuId)
    expect(menuIds).toHaveLength(1)
    await api(page, 'role', 'POST', { roleName: username, roleKey: 'common', roleSort: 1, status: '0', menuIds })
    const role = (await api(page, `role/list?roleName=${username}`)).rows[0]
    await api(page, 'user', 'POST', { userName: username, nickName: username, password: 'Report123!', status: '0', roleIds: [role.roleId] })
  }
  await login(page, 'splice_reader')
  await api(page, `splice-report/${saved.id}/versions/1/print`, 'GET', undefined, 403)
  await page.goto(`/report-splice/${saved.id}/edit?mode=view`)
  await expect(page.locator('.splice-snapshot h1')).toHaveText('拼接打印V2')
  await expect(page.getByRole('button', { name: '打印 / 另存 PDF', exact: true })).toHaveCount(0)
  await page.getByRole('button', { name: '历史版本', exact: true }).click()
  await page.locator('.el-dialog tr').filter({ hasText: '首次保存' }).getByRole('button', { name: '查看', exact: true }).click()
  await expect(page.locator('.splice-snapshot h1')).toHaveText('拼接打印V1')
  await login(page, 'splice_exporter')
  await api(page, `splice-report/${saved.id}/versions/1/print`)
  await api(page, `splice-report/${saved.id}`, 'GET', undefined, 403)
  await api(page, `splice-report/${saved.id}/versions/999/print`, 'GET', undefined, 500)
})
 
test('长分段真实统计、单位缺失和桌面窄屏多页打印', async ({ page }) => {
  await login(page)
  const cycleNos = [1, 3, ...Array.from({ length: 48 }, (_, index) => index + 5)]
  stack.executeIsolatedSql(`
    INSERT INTO t_test(test_id,test_name,creator_id,principal_id,test_type_id,test_purpose,test_status,reception_status)
    VALUES(9211,'长分段甲',1,1,9201,'隔离验收','COMPLETED','ENDED'),(9212,'长分段乙',1,1,9201,'隔离验收','COMPLETED','ENDED');
    INSERT INTO t_test_battery(test_battery_id,test_id,group_id,battery_no,collector_id)
    VALUES(9211,9211,9201,'电池长名称01',9211),(9212,9212,9201,'电池长名称01',9212);
  `)
  for (let start = 0; start < cycleNos.length; start += 10) {
    const batch = cycleNos.slice(start, start + 10)
    stack.executeIsolatedSql(`
    INSERT INTO t_test_cycle_record(test_id,test_battery_id,binding_id,adapter_code,source_identity,source_record_id,dedupe_key,adapter_timezone_id,device_time_utc,device_time_raw,server_received_at_utc,cycle_no,charge_energy_wh,discharge_energy_wh,content_sha256)
    VALUES ${[9211, 9212].flatMap(id => batch.map(no => `(${id},${id},${id},'splice','splice:${id}','${no}','${no}','Asia/Hong_Kong','2026-09-01 00:00:00','2026-09-01 08:00:00','2026-09-01 00:00:01',${no},${no === 3 ? '0' : '0.1361'},0.1694,UNHEX(SHA2('${id}:${no}',256)))`)).join(',')};
    INSERT INTO t_test_step_record(test_id,test_battery_id,binding_id,adapter_code,source_identity,source_record_id,dedupe_key,adapter_timezone_id,device_time_utc,device_time_raw,server_received_at_utc,cycle_no,step_no,voltage_v,content_sha256)
    VALUES ${[9211, 9212].flatMap(id => batch.map(no => `(${id},${id},${id},'splice','splice:${id}','${no}','${no}','Asia/Hong_Kong','2026-09-01 00:00:00','2026-09-01 08:00:00','2026-09-01 00:00:01',${no},1,${no === 3 ? 'NULL' : no === 1 ? '0' : '-0.2'},UNHEX(SHA2('${id}:${no}',256)))`)).join(',')};
  `)
  }
  const saved = (await api(page, 'splice-report', 'POST', { name: '长分段拼接打印', description: '长说明'.repeat(150), selections: [{ testId: 9211, testBatteryId: 9211 }, { testId: 9212, testBatteryId: 9212 }], settings: { batteryFields: ['字段 (mAh)'], specialFields: ['备注'], detailFieldValues: { '字段 (mAh)': { 电池长名称01: '8512.2' }, 备注: { 电池长名称01: '' } }, metrics: ['finalVoltage1', 'energyEfficiency'], seriesColors: ['#123456'] } })).data
  expect(saved.segments[0].statistics['1'].finalVoltage1).toBe(0)
  expect(saved.segments[0].statistics['1'].energyEfficiency).toBeCloseTo(124.46730345)
  expect(saved.segments[0].statistics['3'].finalVoltage1 ?? null).toBeNull()
  await page.goto(`/report-splice/${saved.id}/edit?mode=view`)
  await expect(page.locator('.splice-chart')).toHaveCount(6)
  await expect(page.locator('.splice-statistics').first()).toContainText('124.4673')
  await expect(page.locator('.splice-statistics').first()).toContainText('缺失')
  for (const width of [1440, 390]) {
    await page.setViewportSize({ width, height: 900 })
    await expect.poll(() => page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
    await expect(page.getByRole('button', { name: '打印 / 另存 PDF', exact: true })).toBeInViewport()
    await page.locator('.splice-chart').first().scrollIntoViewIfNeeded()
    await page.screenshot({ path: `tests/e2e/.artifacts/splice-saved-${width}.png` })
  }
  await page.context().addInitScript(() => { window.print = () => { document.body.dataset.printRequested = 'true' } })
  const popupPromise = page.waitForEvent('popup')
  await page.getByRole('button', { name: '打印 / 另存 PDF', exact: true }).click()
  const popup = await popupPromise
  await expect(popup.locator('body')).toHaveAttribute('data-print-requested', 'true')
  await expect(popup.locator('.splice-chart')).toHaveCount(6)
  await expect(popup.locator('.splice-chart svg path[stroke="#123456"]').first()).toBeAttached()
  expect(await popup.locator('.splice-snapshot').innerText()).toEqual(await page.locator('.splice-snapshot').innerText())
  await popup.emulateMedia({ media: 'print' })
  await popup.screenshot({ path: 'tests/e2e/.artifacts/splice-print-long.png', fullPage: true })
  await popup.pdf({ path: 'tests/e2e/.artifacts/splice-print-long.pdf', preferCSSPageSize: true, printBackground: true })
  await popup.close()
  expect((await api(page, `splice-report/${saved.id}/versions`)).data).toHaveLength(1)
})