调整部分前端布局和后端增删改逻辑
This commit is contained in:
@@ -11,6 +11,25 @@ import * as testBatchContext from '../src/utils/testBatchContext.js'
|
||||
const frontendRoot = fileURLToPath(new URL('..', import.meta.url))
|
||||
const execFileAsync = promisify(execFile)
|
||||
|
||||
test('Web 端统一使用 Sylvan 横版标识、折叠图标和浏览器图标', async () => {
|
||||
const [logoComponent, toolBuilder, indexHtml, fullLogo, markLogo] = await Promise.all([
|
||||
readFile(path.join(frontendRoot, 'src/layout/components/Sidebar/Logo.vue'), 'utf8'),
|
||||
readFile(path.join(frontendRoot, 'src/views/tool/build/index.vue'), 'utf8'),
|
||||
readFile(path.join(frontendRoot, 'index.html'), 'utf8'),
|
||||
readFile(path.join(frontendRoot, 'src/assets/logo/sylvan-logo.svg'), 'utf8'),
|
||||
readFile(path.join(frontendRoot, 'src/assets/logo/sylvan-mark.svg'), 'utf8')
|
||||
])
|
||||
|
||||
assert.match(logoComponent, /@\/assets\/logo\/sylvan-logo\.svg/)
|
||||
assert.match(logoComponent, /@\/assets\/logo\/sylvan-mark\.svg/)
|
||||
assert.match(toolBuilder, /@\/assets\/logo\/sylvan-mark\.svg/)
|
||||
assert.match(indexHtml, /href="\/sylvan-mark\.svg"/)
|
||||
assert.match(fullLogo, /viewBox="0 0 516\.1 194\.52"/)
|
||||
assert.match(markLogo, /viewBox="0 0 140 140"/)
|
||||
assert.doesNotMatch(`${logoComponent}\n${toolBuilder}`, /assets\/logo\/logo\.png/)
|
||||
assert.doesNotMatch(`${fullLogo}\n${markLogo}`, /<script\b|<foreignObject\b|javascript:|\b(?:xlink:)?href=|\bonload=|\bonerror=/i)
|
||||
})
|
||||
|
||||
const expectedRequests = {
|
||||
'src/api/master/strain.js': [
|
||||
{ url: "'/master/strain/list'", method: 'get' },
|
||||
@@ -885,6 +904,23 @@ function extractButtonBlocks(source, clickHandler) {
|
||||
.filter((button) => button.includes(`@click="${clickHandler}`))
|
||||
}
|
||||
|
||||
function assertButtonInSingleToolbar(source, relativePath, toolbarClass, clickHandler) {
|
||||
const buttonIndex = source.indexOf(`@click="${clickHandler}`)
|
||||
assert.ok(buttonIndex >= 0, `${relativePath} 缺少 ${clickHandler} 按钮`)
|
||||
|
||||
const beforeButton = source.slice(0, buttonIndex)
|
||||
const rowStart = beforeButton.lastIndexOf('<el-row')
|
||||
const rowEnd = beforeButton.lastIndexOf('</el-row>')
|
||||
assert.ok(rowStart > rowEnd, `${relativePath} 的 ${clickHandler} 按钮必须位于 el-row 工具栏内`)
|
||||
|
||||
const rowTagEnd = source.indexOf('>', rowStart)
|
||||
const rowTag = source.slice(rowStart, rowTagEnd + 1)
|
||||
assert.match(rowTag, new RegExp(`class="[^"]*\\b${toolbarClass}\\b`), `${relativePath} 必须使用 ${toolbarClass}`)
|
||||
|
||||
const toolbarRows = [...source.matchAll(new RegExp(`<el-row\\b[^>]*class="[^"]*\\b${toolbarClass}\\b[^"]*"`, 'g'))]
|
||||
assert.equal(toolbarRows.length, 1, `${relativePath} 的操作按钮必须合并到同一个 ${toolbarClass}`)
|
||||
}
|
||||
|
||||
function assertButtonsUsePermission(source, clickHandler, permission) {
|
||||
const buttons = extractButtonBlocks(source, clickHandler)
|
||||
assert.ok(buttons.length > 0, `缺少 ${clickHandler} 按钮`)
|
||||
@@ -1176,7 +1212,7 @@ test('真实权限门禁解析拒绝注释常量、hasPermiOr 和动态数组假
|
||||
|
||||
const missing = expectedPermissions.filter((permission) => permission !== 'master:strain:query')
|
||||
assert.throws(
|
||||
() => assertExactPermissionLayers({ mysql: missing, postgresql: missing, controller: missing, vue: missing }),
|
||||
() => assertExactPermissionLayers({ mysql: missing, controller: missing, vue: missing }),
|
||||
/权限集合不精确/
|
||||
)
|
||||
})
|
||||
@@ -1487,6 +1523,38 @@ test('六个报表 API 只提供 GET /list,不提供导出接口', async () =>
|
||||
}
|
||||
})
|
||||
|
||||
test('托盘追溯报表将来源和流转状态显示为中文', async () => {
|
||||
const source = await readFile(path.join(frontendRoot, 'src/views/report/palletTrace/index.vue'), 'utf8')
|
||||
const sourceLabels = [
|
||||
['INBOUND', '入库单'],
|
||||
['FILLING', '装填记录'],
|
||||
['SHIPMENT', '发货单'],
|
||||
['USAGE', '使用记录'],
|
||||
['RETURN', '退回单']
|
||||
]
|
||||
const flowStateLabels = [
|
||||
['UNSTOCKED', '未入库'],
|
||||
['EMPTY_IN_WAREHOUSE', '仓库空托盘'],
|
||||
['FILLED_IN_WAREHOUSE', '仓库有料'],
|
||||
['AT_SITE_UNUSED', '现场未使用'],
|
||||
['AT_SITE_PARTIAL', '现场部分使用'],
|
||||
['AT_SITE_EMPTY', '现场空托盘'],
|
||||
['RETURNING', '退回中']
|
||||
]
|
||||
|
||||
for (const [value, label] of sourceLabels) {
|
||||
assert.match(source, new RegExp(`value: '${value}', label: '${label}'`))
|
||||
}
|
||||
for (const [value, label] of flowStateLabels) {
|
||||
assert.match(source, new RegExp(`value: '${value}', label: '${label}'`))
|
||||
}
|
||||
assert.match(source, /sourceLabel\(scope\.row\.sourceType,\s*scope\.row\.sourceId\)/)
|
||||
assert.match(source, /flowStateLabel\(scope\.row\.beforeState\)/)
|
||||
assert.match(source, /flowStateLabel\(scope\.row\.afterState\)/)
|
||||
assert.match(source, /function sourceLabel\([\s\S]*?sourceTypeOptions\.find\(/)
|
||||
assert.match(source, /function flowStateLabel\([\s\S]*?flowStateOptions\.find\(/)
|
||||
})
|
||||
|
||||
test('六个只读报表页面逐页满足 API、权限、筛选、列和异步分页契约', async (t) => {
|
||||
for (const [resource, contract] of Object.entries(reportPageContracts)) {
|
||||
await t.test(contract.relativePath, async () => {
|
||||
@@ -1506,6 +1574,53 @@ test('六个只读报表页面逐页满足 API、权限、筛选、列和异步
|
||||
assert.doesNotMatch(source, /report:[a-z]+:(?:add|edit|remove|export|print)/)
|
||||
assert.doesNotMatch(source, /\bhandle(?:Add|Edit|Delete|Remove|Export|Print|Download)\b|proxy\.download\s*\(/)
|
||||
|
||||
const queryFormTag = source.match(/<el-form\b[^>]*ref="queryRef"[^>]*>/)?.[0] || ''
|
||||
assert.match(queryFormTag, /v-show="showSearch"/, `${resource} 搜索区必须支持显隐`)
|
||||
assert.match(queryFormTag, /label-width="88px"/, `${resource} 筛选标签宽度必须统一`)
|
||||
assert.match(source, /const showSearch = ref\(true\);?/, `${resource} 搜索区必须默认展开`)
|
||||
const reportFilterFooter = source.match(/<div\s+class="report-filter__footer">[\s\S]*?<\/div>/)?.[0] || ''
|
||||
assert.ok(reportFilterFooter, `${resource} 日期和搜索操作必须位于统一底部操作栏`)
|
||||
assert.match(reportFilterFooter, /<el-form-item\b[^>]*class="report-filter__date"/, `${resource} 底部操作栏必须包含日期范围`)
|
||||
assert.match(reportFilterFooter, /<el-form-item\b[^>]*class="report-filter__actions"/, `${resource} 底部操作栏必须包含搜索操作`)
|
||||
assert.ok(
|
||||
reportFilterFooter.indexOf('report-filter__date') < reportFilterFooter.indexOf('report-filter__actions'),
|
||||
`${resource} 日期范围必须位于搜索操作之前`
|
||||
)
|
||||
|
||||
const reportToolbar = source.match(/<el-row\b[^>]*class="[^"]*\breport-toolbar\b[^"]*"[^>]*>[\s\S]*?<\/el-row>/)?.[0] || ''
|
||||
assert.ok(reportToolbar, `${resource} 缺少报表操作栏`)
|
||||
assert.match(reportToolbar, /<right-toolbar\s+v-model:showSearch="showSearch"\s+@queryTable="getList"\s*\/>/, `${resource} 必须提供搜索显隐和刷新入口`)
|
||||
assert.match(source, /\.report-filter\s*\{[^}]*display:\s*grid;[^}]*grid-template-columns:\s*repeat\(auto-fit,\s*minmax\(260px,\s*1fr\)\)/, `${resource} 筛选区必须使用响应式等宽网格`)
|
||||
assert.match(
|
||||
source,
|
||||
/\.report-filter__footer\s*\{[^}]*grid-column:\s*1\s*\/\s*-1;[^}]*display:\s*flex;[^}]*align-items:\s*flex-end;[^}]*gap:\s*16px;/,
|
||||
`${resource} 日期和操作项必须位于独占整行的底部操作栏`
|
||||
)
|
||||
assert.doesNotMatch(source, /\.report-filter__footer\s*\{[^}]*display:\s*contents;/, `${resource} 底部操作栏不得使用 contents 分散到筛选网格`)
|
||||
assert.doesNotMatch(source, /\.report-filter__date\s*\{[^}]*grid-column:\s*-3\s*\/\s*-1;/, `${resource} 日期范围不得单独跨最后两列`)
|
||||
assert.match(
|
||||
source,
|
||||
/\.report-filter\s+\.report-filter__date\s*\{[^}]*flex:\s*0\s+1\s+420px;[^}]*width:\s*420px;[^}]*min-width:\s*0;[^}]*max-width:\s*100%;/,
|
||||
`${resource} 桌面日期范围必须固定基础宽度并覆盖通用表单项宽度`
|
||||
)
|
||||
assert.match(source, /\.report-filter\s+\.report-filter__actions\s*\{[^}]*flex:\s*0\s+0\s+auto;[^}]*width:\s*auto;/, `${resource} 操作按钮必须按自身宽度紧邻日期范围`)
|
||||
assert.match(source, /\.report-filter :deep\(\.el-form-item__content\)\s*\{[^}]*min-width:\s*0;/, `${resource} 筛选内容必须允许收缩`)
|
||||
assert.match(
|
||||
source,
|
||||
/\.report-filter :deep\(\.el-input\),\s*\.report-filter :deep\(\.el-input-number\),\s*\.report-filter :deep\(\.el-select\),\s*\.report-filter :deep\(\.el-date-editor\)\s*\{[^}]*width:\s*100%\s*!important;[^}]*min-width:\s*0;/,
|
||||
`${resource} 筛选控件必须等宽且不得越界`
|
||||
)
|
||||
assert.match(
|
||||
source,
|
||||
/@media\s*\(max-width:\s*900px\)[\s\S]*?\.report-filter__footer\s*\{[^}]*grid-column:\s*1;[^}]*flex-direction:\s*column;[^}]*align-items:\s*stretch;/,
|
||||
`${resource} 窄屏底部操作栏必须恢复纵向布局`
|
||||
)
|
||||
assert.match(
|
||||
source,
|
||||
/@media\s*\(max-width:\s*900px\)[\s\S]*?\.report-filter\s+\.report-filter__date\s*\{[^}]*flex:\s*0\s+0\s+auto;[^}]*width:\s*100%;[^}]*min-width:\s*0;[^}]*max-width:\s*none;/,
|
||||
`${resource} 窄屏日期范围必须恢复全宽`
|
||||
)
|
||||
|
||||
for (const queryKey of contract.queryKeys) {
|
||||
assert.match(source, new RegExp(`\\b${queryKey}\\b`), `${resource} 缺少筛选键 ${queryKey}`)
|
||||
}
|
||||
@@ -1646,6 +1761,20 @@ test('六个基础资料页面逐页满足 API、权限、分页和安全删除
|
||||
assert.match(source, /const detail\s*=\s*response\.data/)
|
||||
assert.match(source, /function handleQuery\(\)\s*{[\s\S]*?queryParams\.value\.pageNum\s*=\s*1[\s\S]*?getList\(\)/)
|
||||
assert.match(source, /function resetQuery\(\)\s*{[\s\S]*?resetForm\(['"]queryRef['"]\)[\s\S]*?handleQuery\(\)/)
|
||||
const queryFormTag = source.match(/<el-form\b[^>]*ref="queryRef"[^>]*>/)?.[0] || ''
|
||||
assert.match(queryFormTag, /v-show="showSearch"/, `${resource} 搜索区必须支持显隐`)
|
||||
assert.match(source, /const showSearch = ref\(true\);/, `${resource} 搜索区必须默认展开`)
|
||||
|
||||
const masterToolbar = source.match(/<el-row\b[^>]*class="[^"]*\bmaster-toolbar\b[^"]*"[^>]*>[\s\S]*?<\/el-row>/)?.[0] || ''
|
||||
assert.ok(masterToolbar, `${resource} 缺少基础资料操作栏`)
|
||||
assert.doesNotMatch(masterToolbar.match(/^<el-row\b[^>]*>/)?.[0] || '', /v-if="isMasterWriteAllowed"/, `${resource} 搜索与刷新入口不得受写权限限制`)
|
||||
assert.match(masterToolbar, /<template v-if="isMasterWriteAllowed">/, `${resource} 写操作仍需受测试模式限制`)
|
||||
assert.match(masterToolbar, /<right-toolbar\s+v-model:showSearch="showSearch"\s+@queryTable="getList"\s*\/>/, `${resource} 必须复用右侧搜索与刷新工具栏`)
|
||||
assert.match(
|
||||
source,
|
||||
/\.master-query :deep\(\.el-input\),\s*\.master-query :deep\(\.el-input-number\),\s*\.master-query :deep\(\.el-select\)\s*\{[^}]*min-width:\s*0;/,
|
||||
`${resource} 查询控件必须允许收缩到网格内容宽度`
|
||||
)
|
||||
assert.match(source, /<pagination\b[\s\S]*?v-model:page="queryParams\.pageNum"[\s\S]*?v-model:limit="queryParams\.pageSize"[\s\S]*?@pagination="getList"/)
|
||||
assert.match(source, /<el-table\b[^>]*v-loading="loading"[^>]*empty-text="暂无数据"/)
|
||||
assert.match(source, /<el-alert\b[^>]*v-if="loadError"/)
|
||||
@@ -2495,6 +2624,12 @@ test('测试批次管理与旧数据纳入遵守 root、权限和预览提交契
|
||||
assert.match(source, /v-hasPermi="\['system:testbatch:adopt'\]"/, `${rootType} 纳入入口必须校验批次纳入权限`)
|
||||
assert.match(source, new RegExp(`<test-batch-adoption-dialog\\b[\\s\\S]*?:root-type="['\"]${rootType}['\"][\\s\\S]*?:resource-ids="selectedIds"`))
|
||||
assert.match(source, new RegExp(`selectedIds\\.value\\s*=\\s*selection\\.map\\(\\(item\\)\\s*=>\\s*item\\.${contract.id}\\)`), `${rootType} 必须传递正整数根 ID`)
|
||||
assertButtonInSingleToolbar(
|
||||
source,
|
||||
contract.path,
|
||||
contract.path.includes('/master/') ? 'master-toolbar' : 'page-toolbar',
|
||||
'openAdoptionDialog'
|
||||
)
|
||||
}
|
||||
|
||||
for (const rootType of ['strain', 'customer', 'site', 'line', 'warehouse', 'pallet']) {
|
||||
@@ -2720,3 +2855,21 @@ test('测试批次管理页真实 hydration 拒绝旧版本回写并保留当前
|
||||
assert.equal(failedPage.state.batchList.value[0].participantCount, undefined)
|
||||
assert.equal(failedPage.state.loadError.value, '', '当前详情失败必须是单项状态,不得升级为列表失败')
|
||||
})
|
||||
|
||||
test('三个业务菜单公共样式统一使用 Element Plus 主题变量', async () => {
|
||||
const styles = await source('src/assets/styles/data-pages.scss')
|
||||
|
||||
assert.match(styles, /background:\s*var\(--el-bg-color-page,\s*#f5f7fb\)/)
|
||||
assert.match(styles, /background:\s*var\(--el-bg-color,\s*#fff\)/)
|
||||
assert.match(styles, /border:\s*1px solid var\(--el-border-color-lighter,\s*#e4e7ed\)/)
|
||||
assert.match(styles, /background:\s*var\(--el-fill-color-light,\s*#f5f7fa\)/)
|
||||
assert.match(styles, /color:\s*var\(--el-text-color-regular,\s*#606266\)/)
|
||||
assert.match(styles, /background:\s*var\(--el-fill-color-lighter,\s*#fafafa\)/)
|
||||
assert.match(styles, /box-shadow:\s*var\(--el-box-shadow-lighter,/)
|
||||
|
||||
assert.doesNotMatch(styles, /background:\s*#f5f7fb\s*;/)
|
||||
assert.doesNotMatch(styles, /background:\s*#fff\s*;/)
|
||||
assert.doesNotMatch(styles, /border:\s*1px solid #e7ebf2\s*;/)
|
||||
assert.doesNotMatch(styles, /background:\s*#f7f9fc\s*!important/)
|
||||
assert.doesNotMatch(styles, /background:\s*#fbfcfe\s*;/)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user