docs(test): 更新电表箱装配业务回归测试文档

- 将文档从代码审计任务转换为端到端业务回归测试链路
- 重新组织文档结构为链路总览、示例数据基线和详细步骤
- 添加完整的业务场景清单涵盖WMS/MES/工位终端三大系统
- 定义统一的测试数据包括产品型号、物料清单、工位派工等
- 提供详细的步骤断言和数量等式验证方法
- 建立贯穿全链路的数据流向和状态变更追踪体系
This commit is contained in:
SunYF
2026-09-21 10:48:53 +08:00
parent 0d34bcd6a3
commit 132c3ed6bd
63 changed files with 3309 additions and 5784 deletions
+55 -49
View File
@@ -20,16 +20,6 @@ const props = defineProps({
// 帮助文档按菜单各自独立
const helpData = computed(() => (props.mode === 'material' ? helpMaterial : helpZone))
// 默认近3个月起始日期
function defaultStart() {
const d = new Date()
d.setMonth(d.getMonth() - 3)
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${y}-${m}-${day}`
}
/* ---------- 区域维护(库位四级:区域/货架/层/位置号 4 个独立 Tab ---------- */
const zoneLevels = [
{ level: 1, name: '区域', codeLabel: '区域编码', nameLabel: '区域名称' },
@@ -54,7 +44,7 @@ const regionOptions = ref([])
const f2Shelf = ref([]); const f3Shelf = ref([]); const f3Layer = ref([])
const f4Shelf = ref([]); const f4Layer = ref([])
const cShelf = ref([]); const cLayer = ref([])
const bShelf = ref([])
const bShelf = ref([]); const bLayer = ref([])
const lbShelf = ref([])
async function picker(level, params) {
@@ -71,6 +61,9 @@ async function loadLayerOpts(target, zoneCode, shelfNo) {
target.value = (zoneCode && shelfNo) ? await picker(3, { zoneCode, shelfNo }) : []
}
// 下拉框展开即刷新上级选项(方案2):新建区域/货架后无需切页或重进菜单即可选到最新数据。
function onDropdownOpen(visible, loader) { if (visible) loader() }
async function loadNodes(level) {
const s = nodeState[level]
s.loading = true
@@ -97,6 +90,8 @@ async function loadNodes(level) {
// 进入某 Tab:首次加载列表,并确保上级下拉选项就绪
async function onZoneTabChange(level) {
activeZoneTab.value = level
// 切 tab 刷新区域下拉(方案1):在「区域」Tab 新建后切到货架/层/位置号,区域下拉立即是最新。
await loadRegions()
const f = nodeState[level].filter
if (level >= 2 && f.zoneCode) await loadShelfOpts(level === 2 ? f2Shelf : level === 3 ? f3Shelf : f4Shelf, f.zoneCode)
if ((level === 3 || level === 4) && f.zoneCode && f.shelfNo) {
@@ -140,24 +135,22 @@ const nodeSaving = ref(false)
function openNodeCreate(level) {
nodeEditing.value = false
cShelf.value = []; cLayer.value = []
Object.assign(nodeForm, { level, id: 0, code: '', name: '', remark: '', status: '启用', regionCode: '', shelfCode: '', layerCode: '' })
nodeDialog.value = true
}
function openNodeEdit(row) {
async function openNodeEdit(row) {
nodeEditing.value = true
const f = { level: row.level, id: row.id, code: row.code, name: row.name || row.zoneName || '', remark: row.description || '', status: row.status || '启用', regionCode: '', shelfCode: '', layerCode: '' }
if (row.level === 2) f.regionCode = row.zoneCode
if (row.level === 3) { f.regionCode = row.zoneCode; f.shelfCode = row.shelfNo }
if (row.level === 4) { f.regionCode = row.zoneCode; f.shelfCode = row.shelfNo; f.layerCode = row.layerNo }
Object.assign(nodeForm, f)
// 编辑态必须预加载上级下拉选项:否则「所属货架 / 所属层」无选项、且当前值无法回显(用户报的编辑无下拉数据)
if (row.level >= 3) await loadShelfOpts(cShelf, f.regionCode)
if (row.level === 4) await loadLayerOpts(cLayer, f.regionCode, f.shelfCode)
nodeDialog.value = true
}
function nodeParentCode() {
if (nodeForm.level === 1) return ''
if (nodeForm.level === 2) return nodeForm.regionCode
if (nodeForm.level === 3) return nodeForm.shelfCode
return nodeForm.layerCode || nodeForm.shelfCode
}
async function onNodeRegionChange() {
nodeForm.shelfCode = ''
nodeForm.layerCode = ''
@@ -171,9 +164,10 @@ async function onNodeShelfChange() {
async function saveNode() {
if (!nodeForm.code) { ElMessage.warning('编码必填'); return }
if (nodeForm.level === 1 && !nodeForm.name) { ElMessage.warning('区域名称必填'); return }
if (nodeForm.level === 2 && !nodeForm.regionCode) { ElMessage.warning('请选择所属区域'); return }
if (nodeForm.level === 3 && !nodeForm.shelfCode) { ElMessage.warning('请选择所属货架'); return }
if (nodeForm.level === 4 && !nodeForm.shelfCode) { ElMessage.warning('请选择所属货架'); return }
// 父级必须不能为空(逐级必选):货架必选区域;层必选区域+货架;位置号必选区域+货架+层
if (nodeForm.level >= 2 && !nodeForm.regionCode) { ElMessage.warning('请选择所属区域'); return }
if (nodeForm.level >= 3 && !nodeForm.shelfCode) { ElMessage.warning('请选择所属货架'); return }
if (nodeForm.level === 4 && !nodeForm.layerCode) { ElMessage.warning('请选择所属层'); return }
const okSave = await confirmAction(
nodeEditing.value ? `确认保存【${nodeForm.code}】的修改?` : `确认新增【${nodeForm.code}${nodeForm.name ? ' ' + nodeForm.name : ''}】?`,
nodeEditing.value ? '编辑确认' : '新增确认'
@@ -187,13 +181,16 @@ async function saveNode() {
} else {
await request.post('/zone/node/create', {
level: nodeForm.level, code: nodeForm.code, name: nodeForm.name,
parentCode: nodeParentCode(), remark: nodeForm.remark
zoneCode: nodeForm.regionCode, shelfNo: nodeForm.shelfCode, layerNo: nodeForm.layerCode,
remark: nodeForm.remark
})
ElMessage.success('新增成功')
}
nodeDialog.value = false
zoneTabLoaded[nodeForm.level] = false
await loadNodes(nodeForm.level)
// 新建/修改区域后立刻刷新区域下拉,保证本页及其他 Tab 马上能选到新区域。
if (nodeForm.level === 1) await loadRegions()
} finally {
nodeSaving.value = false
}
@@ -201,22 +198,31 @@ async function saveNode() {
/* ---------- 区域维护:位置号批量生成 ---------- */
const batchDialog = ref(false)
const batchForm = reactive({ zoneCode: '', shelfNo: '', layerStart: 1, layerEnd: 1, posStart: 1, posEnd: 1 })
const batchForm = reactive({ zoneCode: '', shelfNo: '', layerNo: '', posStart: 1, posEnd: 1 })
const batchPreview = ref([])
const batchPreviewing = ref(false)
const batchSubmitting = ref(false)
function openBatchGen() {
Object.assign(batchForm, { zoneCode: '', shelfNo: '', layerStart: 1, layerEnd: 1, posStart: 1, posEnd: 1 })
Object.assign(batchForm, { zoneCode: '', shelfNo: '', layerNo: '', posStart: 1, posEnd: 1 })
bShelf.value = []; bLayer.value = []
batchPreview.value = []
batchDialog.value = true
}
async function onBatchRegionChange() {
batchForm.shelfNo = ''
batchForm.layerNo = ''
bLayer.value = []
await loadShelfOpts(bShelf, batchForm.zoneCode)
}
// 选定货架后加载该货架下已存在的层(只能选具体层,批量生成只生成位置、不创建父级层)
async function onBatchShelfChange() {
batchForm.layerNo = ''
await loadLayerOpts(bLayer, batchForm.zoneCode, batchForm.shelfNo)
}
async function previewBatch() {
if (!batchForm.zoneCode || !batchForm.shelfNo) { ElMessage.warning('请选择区域与货架'); return }
if (!batchForm.layerNo) { ElMessage.warning('请选择具体的层'); return }
batchPreviewing.value = true
try {
const data = await request.post('/zone/batch-generate', { ...batchForm, preview: true })
@@ -286,7 +292,7 @@ const materials = ref([])
const materialTotal = ref(0)
const materialLoading = ref(false)
const matFilter = reactive({
materialCode: '', name: '', spec: '', manageMode: '', itemType: '', startDate: defaultStart(), endDate: ''
materialCode: '', name: '', spec: '', manageMode: '', itemType: '', startDate: '', endDate: ''
})
const matPage = reactive({ current: 1, size: 10 })
@@ -573,7 +579,7 @@ async function onMaterialFile(e) {
<el-tab-pane :name="2" label="货架">
<el-form inline style="margin-bottom:14px" @submit.prevent="searchNodes(2)">
<el-form-item label="区域">
<el-select v-model="nodeState[2].filter.zoneCode" placeholder="全部区域" clearable filterable style="width:160px" @change="onFilterRegion(2)">
<el-select v-model="nodeState[2].filter.zoneCode" placeholder="全部区域" clearable filterable style="width:160px" @change="onFilterRegion(2)" @visible-change="(v) => onDropdownOpen(v, loadRegions)">
<el-option v-for="r in regionOptions" :key="r.code" :value="r.code" :label="r.code + (r.zoneName ? ' '+r.zoneName : '')" />
</el-select>
</el-form-item>
@@ -624,12 +630,12 @@ async function onMaterialFile(e) {
<el-tab-pane :name="3" label="">
<el-form inline style="margin-bottom:14px" @submit.prevent="searchNodes(3)">
<el-form-item label="区域">
<el-select v-model="nodeState[3].filter.zoneCode" placeholder="全部区域" clearable filterable style="width:150px" @change="onFilterRegion(3)">
<el-select v-model="nodeState[3].filter.zoneCode" placeholder="全部区域" clearable filterable style="width:150px" @change="onFilterRegion(3)" @visible-change="(v) => onDropdownOpen(v, loadRegions)">
<el-option v-for="r in regionOptions" :key="r.code" :value="r.code" :label="r.code + (r.zoneName ? ' '+r.zoneName : '')" />
</el-select>
</el-form-item>
<el-form-item label="货架">
<el-select v-model="nodeState[3].filter.shelfNo" placeholder="全部货架" clearable filterable style="width:130px" @change="onFilterShelf(3)">
<el-select v-model="nodeState[3].filter.shelfNo" placeholder="全部货架" clearable filterable style="width:130px" @change="onFilterShelf(3)" @visible-change="(v) => onDropdownOpen(v, () => loadShelfOpts(f3Shelf, nodeState[3].filter.zoneCode))">
<el-option v-for="s in f3Shelf" :key="s.code" :value="s.code" :label="s.code" />
</el-select>
</el-form-item>
@@ -674,17 +680,17 @@ async function onMaterialFile(e) {
<el-tab-pane :name="4" label="位置号">
<el-form inline style="margin-bottom:14px" @submit.prevent="searchNodes(4)">
<el-form-item label="区域">
<el-select v-model="nodeState[4].filter.zoneCode" placeholder="全部区域" clearable filterable style="width:150px" @change="onFilterRegion(4)">
<el-select v-model="nodeState[4].filter.zoneCode" placeholder="全部区域" clearable filterable style="width:150px" @change="onFilterRegion(4)" @visible-change="(v) => onDropdownOpen(v, loadRegions)">
<el-option v-for="r in regionOptions" :key="r.code" :value="r.code" :label="r.code + (r.zoneName ? ' '+r.zoneName : '')" />
</el-select>
</el-form-item>
<el-form-item label="货架">
<el-select v-model="nodeState[4].filter.shelfNo" placeholder="全部货架" clearable filterable style="width:130px" @change="onFilterShelf(4)">
<el-select v-model="nodeState[4].filter.shelfNo" placeholder="全部货架" clearable filterable style="width:130px" @change="onFilterShelf(4)" @visible-change="(v) => onDropdownOpen(v, () => loadShelfOpts(f4Shelf, nodeState[4].filter.zoneCode))">
<el-option v-for="s in f4Shelf" :key="s.code" :value="s.code" :label="s.code" />
</el-select>
</el-form-item>
<el-form-item label="">
<el-select v-model="nodeState[4].filter.layerNo" placeholder="全部层" clearable filterable style="width:110px" @change="searchNodes(4)">
<el-select v-model="nodeState[4].filter.layerNo" placeholder="全部层" clearable filterable style="width:110px" @change="searchNodes(4)" @visible-change="(v) => onDropdownOpen(v, () => loadLayerOpts(f4Layer, nodeState[4].filter.zoneCode, nodeState[4].filter.shelfNo))">
<el-option v-for="l in f4Layer" :key="l.code" :value="l.code" :label="'层 '+l.code" />
</el-select>
</el-form-item>
@@ -730,25 +736,24 @@ async function onMaterialFile(e) {
<!-- 新增/编辑节点弹窗 -->
<el-dialog v-model="nodeDialog" :title="(nodeEditing ? '编辑' : '新增') + (zoneLevels.find(z=>z.level===nodeForm.level)?.name || '')" width="560px" append-to-body destroy-on-close>
<el-form label-width="90px">
<el-form-item :label="zoneLevels.find(z=>z.level===nodeForm.level)?.codeLabel" required>
<el-input v-model="nodeForm.code" :placeholder="zoneLevels.find(z=>z.level===nodeForm.level)?.codeLabel" :disabled="nodeEditing && nodeForm.level===1" clearable
@input="nodeForm.code = (nodeForm.code || '').toUpperCase()" />
</el-form-item>
<el-form-item v-if="nodeForm.level === 2 || nodeForm.level === 3 || nodeForm.level === 4" label="所属区域" required>
<el-select v-model="nodeForm.regionCode" placeholder="请选择区域" filterable style="width:100%" @change="onNodeRegionChange">
<el-select v-model="nodeForm.regionCode" placeholder="请选择区域" filterable style="width:100%" @change="onNodeRegionChange" @visible-change="(v) => onDropdownOpen(v, loadRegions)">
<el-option v-for="r in regionOptions" :key="r.code" :value="r.code" :label="r.code + (r.zoneName ? ' '+r.zoneName : '')" />
</el-select>
</el-form-item>
<el-form-item v-if="nodeForm.level === 3 || nodeForm.level === 4" label="所属货架" required>
<el-select v-model="nodeForm.shelfCode" placeholder="请选择货架" filterable :disabled="!nodeForm.regionCode" style="width:100%" @change="onNodeShelfChange">
<el-select v-model="nodeForm.shelfCode" placeholder="请选择货架" filterable :disabled="!nodeForm.regionCode" style="width:100%" @change="onNodeShelfChange" @visible-change="(v) => onDropdownOpen(v, () => loadShelfOpts(cShelf, nodeForm.regionCode))">
<el-option v-for="s in cShelf" :key="s.code" :value="s.code" :label="s.code" />
</el-select>
</el-form-item>
<el-form-item v-if="nodeForm.level === 4" label="所属层">
<el-select v-model="nodeForm.layerCode" placeholder="选填留空=货架默认位置" filterable :disabled="!nodeForm.shelfCode" style="width:100%">
<el-form-item v-if="nodeForm.level === 4" label="所属层" required>
<el-select v-model="nodeForm.layerCode" placeholder="请选择层" filterable :disabled="!nodeForm.shelfCode" style="width:100%" @visible-change="(v) => onDropdownOpen(v, () => loadLayerOpts(cLayer, nodeForm.regionCode, nodeForm.shelfCode))">
<el-option v-for="l in cLayer" :key="l.code" :value="l.code" :label="'层 '+l.code" />
</el-select>
<div style="color:#909399;font-size:12px;margin-top:4px">留空则作为该货架的默认位置(无层)</div>
</el-form-item>
<el-form-item :label="zoneLevels.find(z=>z.level===nodeForm.level)?.codeLabel" required>
<el-input v-model="nodeForm.code" :placeholder="zoneLevels.find(z=>z.level===nodeForm.level)?.codeLabel" :disabled="nodeEditing && nodeForm.level===1" clearable
@input="nodeForm.code = (nodeForm.code || '').toUpperCase()" />
</el-form-item>
<el-form-item :label="zoneLevels.find(z=>z.level===nodeForm.level)?.nameLabel">
<el-input v-model="nodeForm.name" :placeholder="(zoneLevels.find(z=>z.level===nodeForm.level)?.nameLabel || '') + '(可空)'" clearable />
@@ -773,19 +778,20 @@ async function onMaterialFile(e) {
<el-dialog v-model="batchDialog" title="批量生成位置号" width="720px" append-to-body destroy-on-close>
<el-form label-width="90px">
<el-form-item label="区域" required>
<el-select v-model="batchForm.zoneCode" placeholder="请选择区域" filterable style="width:100%" @change="onBatchRegionChange">
<el-select v-model="batchForm.zoneCode" placeholder="请选择区域" filterable style="width:100%" @change="onBatchRegionChange" @visible-change="(v) => onDropdownOpen(v, loadRegions)">
<el-option v-for="r in regionOptions" :key="r.code" :value="r.code" :label="r.code + (r.zoneName ? ' '+r.zoneName : '')" />
</el-select>
</el-form-item>
<el-form-item label="货架" required>
<el-select v-model="batchForm.shelfNo" placeholder="请选择货架" filterable :disabled="!batchForm.zoneCode" style="width:100%">
<el-select v-model="batchForm.shelfNo" placeholder="请选择货架" filterable :disabled="!batchForm.zoneCode" style="width:100%" @change="onBatchShelfChange" @visible-change="(v) => onDropdownOpen(v, () => loadShelfOpts(bShelf, batchForm.zoneCode))">
<el-option v-for="s in bShelf" :key="s.code" :value="s.code" :label="s.code" />
</el-select>
</el-form-item>
<el-form-item label="范围">
<el-input-number v-model="batchForm.layerStart" :min="0" :step="1" step-strictly controls-position="right" style="width:120px" />
<span style="margin:0 8px">至</span>
<el-input-number v-model="batchForm.layerEnd" :min="0" :step="1" step-strictly controls-position="right" style="width:120px" />
<el-form-item label="" required>
<el-select v-model="batchForm.layerNo" placeholder="请选择具体的层" filterable :disabled="!batchForm.shelfNo" style="width:100%" @visible-change="(v) => onDropdownOpen(v, () => loadLayerOpts(bLayer, batchForm.zoneCode, batchForm.shelfNo))">
<el-option v-for="l in bLayer" :key="l.code" :value="l.code" :label="'层 '+l.code" />
</el-select>
<div style="color:#909399;font-size:12px;margin-top:4px">只能选择已存在的层;批量生成只在该层下生成位置号,不会创建父级层。</div>
</el-form-item>
<el-form-item label="位置范围">
<el-input-number v-model="batchForm.posStart" :min="0" :step="1" step-strictly controls-position="right" style="width:120px" />
@@ -814,12 +820,12 @@ async function onMaterialFile(e) {
<el-dialog v-model="layerBatchDialog" title="批量生成层" width="640px" append-to-body destroy-on-close>
<el-form label-width="90px">
<el-form-item label="区域" required>
<el-select v-model="layerBatchForm.zoneCode" placeholder="请选择区域" filterable style="width:100%" @change="onLayerBatchRegionChange">
<el-select v-model="layerBatchForm.zoneCode" placeholder="请选择区域" filterable style="width:100%" @change="onLayerBatchRegionChange" @visible-change="(v) => onDropdownOpen(v, loadRegions)">
<el-option v-for="r in regionOptions" :key="r.code" :value="r.code" :label="r.code + (r.zoneName ? ' '+r.zoneName : '')" />
</el-select>
</el-form-item>
<el-form-item label="货架" required>
<el-select v-model="layerBatchForm.shelfNo" placeholder="请选择货架" filterable :disabled="!layerBatchForm.zoneCode" style="width:100%">
<el-select v-model="layerBatchForm.shelfNo" placeholder="请选择货架" filterable :disabled="!layerBatchForm.zoneCode" style="width:100%" @visible-change="(v) => onDropdownOpen(v, () => loadShelfOpts(lbShelf, layerBatchForm.zoneCode))">
<el-option v-for="s in lbShelf" :key="s.code" :value="s.code" :label="s.code" />
</el-select>
</el-form-item>
@@ -887,7 +893,7 @@ async function onMaterialFile(e) {
<el-button v-if="can('material:create')" type="success" @click="openMatCreate">新增物料</el-button>
<el-button v-if="can('material:import')" type="warning" :loading="matImporting" @click="pickMaterialFile">导入</el-button>
<el-button v-if="can('material:export')" :loading="matExporting" @click="exportMaterials">导出</el-button>
<el-tooltip v-if="can('material:import')" content="Excel 模板首行表头图号 | 名称 | 规格 | 单位 | 类型(结构件/电气件) | 说明说明外均为必填">
<el-tooltip v-if="can('material:import')" content="Excel 首行表头按名称识别列顺序可变可含多余列图号 | 名称 | 规格 | 单位 | 类型(结构件/电气件) | 品类(原材料/半成品/成品/其他可选) | 说明说明品类外均为必填">
<el-link type="info" :underline="false" style="margin-left:4px">导入说明</el-link>
</el-tooltip>
<input ref="matFileRef" type="file" accept=".xlsx,.xls" style="display:none" @change="onMaterialFile" />