fix: 子卷包装关系ui调整
This commit is contained in:
@@ -41,4 +41,18 @@ public interface SubPackageRelationMapper extends BaseMapperX<SubPackageRelation
|
||||
.eq(SubPackageRelationDO::getBoxType, boxType));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据子卷号集合查询已存在的包装关系
|
||||
*/
|
||||
default List<SubPackageRelationDO> selectListByContainerNames(Collection<String> containerNames) {
|
||||
return selectList(SubPackageRelationDO::getContainerName, containerNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据木箱唯一码集合查询包装关系
|
||||
*/
|
||||
default List<SubPackageRelationDO> selectListByPackageBoxSns(Collection<String> packageBoxSns) {
|
||||
return selectList(SubPackageRelationDO::getPackageBoxSn, packageBoxSns);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,8 +36,12 @@ public class SubPackageRelationServiceImpl implements SubPackageRelationService
|
||||
private static final String REQUIRED_FIELD_MISSING = "REQUIRED_FIELD_MISSING";
|
||||
/** 前端行标识重复错误码 */
|
||||
private static final String DUPLICATE_CLIENT_KEY = "DUPLICATE_CLIENT_KEY";
|
||||
/** 子卷号重复错误码 */
|
||||
private static final String DUPLICATE_CONTAINER_NAME = "DUPLICATE_CONTAINER_NAME";
|
||||
/** 木箱类型不存在错误码 */
|
||||
private static final String BOX_TYPE_NOT_FOUND = "BOX_TYPE_NOT_FOUND";
|
||||
/** 木箱已绑定其他类型错误码 */
|
||||
private static final String BOX_TYPE_MISMATCH = "BOX_TYPE_MISMATCH";
|
||||
/** 木箱容量超限错误码 */
|
||||
private static final String BOX_CAPACITY_EXCEEDED = "BOX_CAPACITY_EXCEEDED";
|
||||
/** 分组保存失败错误码 */
|
||||
@@ -63,10 +67,14 @@ public class SubPackageRelationServiceImpl implements SubPackageRelationService
|
||||
List<SubPackageRelationBatchCreateRespVO.Failure> failures = new ArrayList<>();
|
||||
List<BatchItemContext> validItems = new ArrayList<>();
|
||||
Map<String, Integer> clientKeyCounts = new HashMap<>();
|
||||
Map<String, Integer> containerNameCounts = new HashMap<>();
|
||||
for (SubPackageRelationBatchCreateItemReqVO item : reqVO.getItems()) {
|
||||
if (item != null && StringUtils.hasText(item.getClientKey())) {
|
||||
clientKeyCounts.merge(item.getClientKey(), 1, Integer::sum);
|
||||
}
|
||||
if (item != null && StringUtils.hasText(item.getContainerName())) {
|
||||
containerNameCounts.merge(item.getContainerName().trim(), 1, Integer::sum);
|
||||
}
|
||||
}
|
||||
for (int index = 0; index < reqVO.getItems().size(); index++) {
|
||||
SubPackageRelationBatchCreateItemReqVO item = reqVO.getItems().get(index);
|
||||
@@ -85,6 +93,12 @@ public class SubPackageRelationServiceImpl implements SubPackageRelationService
|
||||
"前端行标识 clientKey 重复"));
|
||||
continue;
|
||||
}
|
||||
if (StringUtils.hasText(item.getContainerName())
|
||||
&& containerNameCounts.get(item.getContainerName().trim()) > 1) {
|
||||
failures.add(buildFailure(item.getClientKey(), rowIndex, DUPLICATE_CONTAINER_NAME,
|
||||
"子卷号重复:" + item.getContainerName().trim()));
|
||||
continue;
|
||||
}
|
||||
List<String> missingFields = new ArrayList<>();
|
||||
if (!StringUtils.hasText(item.getPackageBoxSn())) {
|
||||
missingFields.add("packageBoxSn");
|
||||
@@ -119,6 +133,34 @@ public class SubPackageRelationServiceImpl implements SubPackageRelationService
|
||||
.build();
|
||||
}
|
||||
|
||||
Set<String> containerNames = validItems.stream()
|
||||
.map(context -> context.item().getContainerName().trim())
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
Set<String> existingContainerNames = subPackageRelationMapper.selectListByContainerNames(containerNames)
|
||||
.stream()
|
||||
.map(SubPackageRelationDO::getContainerName)
|
||||
.filter(Objects::nonNull)
|
||||
.map(String::trim)
|
||||
.collect(Collectors.toSet());
|
||||
List<BatchItemContext> nonDuplicateItems = new ArrayList<>();
|
||||
for (BatchItemContext context : validItems) {
|
||||
String containerName = context.item().getContainerName().trim();
|
||||
if (existingContainerNames.contains(containerName)) {
|
||||
failures.add(buildFailure(context.item().getClientKey(), context.rowIndex(),
|
||||
DUPLICATE_CONTAINER_NAME, "子卷号已存在:" + containerName));
|
||||
continue;
|
||||
}
|
||||
nonDuplicateItems.add(context);
|
||||
}
|
||||
validItems = nonDuplicateItems;
|
||||
if (CollUtil.isEmpty(validItems)) {
|
||||
return SubPackageRelationBatchCreateRespVO.builder()
|
||||
.successCount(0)
|
||||
.failureCount(failures.size())
|
||||
.failures(failures)
|
||||
.build();
|
||||
}
|
||||
|
||||
Set<String> boxTypes = validItems.stream()
|
||||
.map(context -> context.item().getBoxType())
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
@@ -127,11 +169,31 @@ public class SubPackageRelationServiceImpl implements SubPackageRelationService
|
||||
Map<GroupKey, List<BatchItemContext>> groups = validItems.stream()
|
||||
.collect(Collectors.groupingBy(context -> new GroupKey(context.item().getPackageBoxSn(),
|
||||
context.item().getBoxType()), LinkedHashMap::new, Collectors.toList()));
|
||||
Set<String> packageBoxSns = groups.keySet().stream()
|
||||
.map(GroupKey::packageBoxSn)
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
Map<String, Set<String>> existingBoxTypesByPackageBoxSn = subPackageRelationMapper
|
||||
.selectListByPackageBoxSns(packageBoxSns).stream()
|
||||
.filter(relation -> relation.getPackageBoxSn() != null && relation.getBoxType() != null)
|
||||
.collect(Collectors.groupingBy(SubPackageRelationDO::getPackageBoxSn,
|
||||
Collectors.mapping(SubPackageRelationDO::getBoxType,
|
||||
Collectors.toCollection(LinkedHashSet::new))));
|
||||
|
||||
int successCount = 0;
|
||||
for (Map.Entry<GroupKey, List<BatchItemContext>> entry : groups.entrySet()) {
|
||||
GroupKey key = entry.getKey();
|
||||
List<BatchItemContext> group = entry.getValue();
|
||||
Set<String> existingBoxTypes = existingBoxTypesByPackageBoxSn.getOrDefault(
|
||||
key.packageBoxSn(), Collections.emptySet());
|
||||
if (existingBoxTypes.stream().anyMatch(boxType -> !Objects.equals(boxType, key.boxType()))) {
|
||||
String message = String.format("木箱 %s 已绑定木箱类型 %s,不能使用类型 %s",
|
||||
key.packageBoxSn(), String.join("、", existingBoxTypes), key.boxType());
|
||||
for (BatchItemContext context : group) {
|
||||
failures.add(buildFailure(context.item().getClientKey(), context.rowIndex(),
|
||||
BOX_TYPE_MISMATCH, message));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
BoxTypeDO boxType = boxTypeMap.get(key.boxType());
|
||||
if (boxType == null) {
|
||||
for (BatchItemContext context : group) {
|
||||
@@ -152,6 +214,7 @@ public class SubPackageRelationServiceImpl implements SubPackageRelationService
|
||||
continue;
|
||||
}
|
||||
|
||||
int qualityInBox = Math.toIntExact(existingCount + group.size());
|
||||
List<SubPackageRelationDO> saveGroup = new ArrayList<>();
|
||||
for (BatchItemContext context : group) {
|
||||
SubPackageRelationBatchCreateItemReqVO item = context.item();
|
||||
@@ -161,6 +224,7 @@ public class SubPackageRelationServiceImpl implements SubPackageRelationService
|
||||
relation.setBoxLength(item.getBoxLength());
|
||||
relation.setBoxWidth(item.getBoxWidth());
|
||||
relation.setBoxHigh(item.getBoxHigh());
|
||||
relation.setQualityInBox(qualityInBox);
|
||||
relation.setQualityGuaranPeriod(item.getQualityGuaranPeriod());
|
||||
relation.setDateOfFgInbound(item.getDateOfFgInbound());
|
||||
relation.setContainerName(item.getContainerName());
|
||||
|
||||
@@ -505,7 +505,32 @@ export function useGridColumns(): VxeTableGridOptions<LmsSubPackageRelationApi.S
|
||||
},
|
||||
{
|
||||
field: 'packageBoxSn',
|
||||
title: '木箱唯一码',
|
||||
title: '木箱码',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'containerName',
|
||||
title: '子卷号',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'boxType',
|
||||
title: '木箱料号',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'boxLength',
|
||||
title: '木箱长度',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'boxWidth',
|
||||
title: '木箱宽度',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'boxHigh',
|
||||
title: '木箱高度',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
@@ -518,11 +543,6 @@ export function useGridColumns(): VxeTableGridOptions<LmsSubPackageRelationApi.S
|
||||
title: '木箱自身重量',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'boxType',
|
||||
title: '木箱料号',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'qualityGuaranPeriod',
|
||||
title: '保质期',
|
||||
@@ -558,11 +578,6 @@ export function useGridColumns(): VxeTableGridOptions<LmsSubPackageRelationApi.S
|
||||
title: '入库日期',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'containerName',
|
||||
title: '子卷号',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'width',
|
||||
title: '产品规格(幅宽)',
|
||||
@@ -648,21 +663,6 @@ export function useGridColumns(): VxeTableGridOptions<LmsSubPackageRelationApi.S
|
||||
title: '交货单行号',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'boxLength',
|
||||
title: '木箱长度',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'boxWidth',
|
||||
title: '木箱宽度',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'boxHigh',
|
||||
title: '木箱高度',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'saleOrderDescription',
|
||||
title: '销售订单描述',
|
||||
|
||||
@@ -11,6 +11,10 @@ describe('batch-form-model', () => {
|
||||
it('creates an empty row with default status and a unique client key', () => {
|
||||
const first = createBatchRow();
|
||||
const second = createBatchRow();
|
||||
const today = new Date();
|
||||
const month = String(today.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(today.getDate()).padStart(2, '0');
|
||||
const todayText = `${today.getFullYear()}-${month}-${day}`;
|
||||
|
||||
expect(first).toEqual({
|
||||
clientKey: expect.any(String),
|
||||
@@ -20,7 +24,7 @@ describe('batch-form-model', () => {
|
||||
boxWidth: '',
|
||||
boxHigh: '',
|
||||
qualityGuaranPeriod: '',
|
||||
dateOfFgInbound: '',
|
||||
dateOfFgInbound: todayText,
|
||||
containerName: '',
|
||||
status: '0',
|
||||
});
|
||||
@@ -62,6 +66,7 @@ describe('batch-form-model', () => {
|
||||
it('reports all required fields when values are empty or whitespace', () => {
|
||||
const row = createBatchRow();
|
||||
row.packageBoxSn = ' ';
|
||||
row.dateOfFgInbound = ' ';
|
||||
row.status = '\t';
|
||||
|
||||
expect(validateBatchRows([row]).get(0)).toEqual({
|
||||
@@ -99,6 +104,26 @@ describe('batch-form-model', () => {
|
||||
expect(validateBatchRows([row])).toEqual(new Map());
|
||||
});
|
||||
|
||||
it('reports every row whose container name is duplicated', () => {
|
||||
const first = Object.assign(createBatchRow(), {
|
||||
packageBoxSn: 'BOX-001',
|
||||
boxType: 'TYPE-A',
|
||||
qualityGuaranPeriod: '90',
|
||||
containerName: 'ROLL-001',
|
||||
});
|
||||
const second = Object.assign(createBatchRow(), {
|
||||
packageBoxSn: 'BOX-002',
|
||||
boxType: 'TYPE-A',
|
||||
qualityGuaranPeriod: '90',
|
||||
containerName: ' ROLL-001 ',
|
||||
});
|
||||
|
||||
const errors = validateBatchRows([first, second]);
|
||||
|
||||
expect(errors.get(0)?.containerName).toBe('子卷号重复,与第 2 行相同');
|
||||
expect(errors.get(1)?.containerName).toBe('子卷号重复,与第 1 行相同');
|
||||
});
|
||||
|
||||
it('keeps failed rows in original order and maps server messages by client key', () => {
|
||||
const rows = [createBatchRow(), createBatchRow(), createBatchRow()];
|
||||
const result = applyBatchResult(rows, {
|
||||
|
||||
@@ -18,6 +18,13 @@ function createClientKey() {
|
||||
return `${Date.now()}-${fallbackSequence}`;
|
||||
}
|
||||
|
||||
function createToday() {
|
||||
const today = new Date();
|
||||
const month = String(today.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(today.getDate()).padStart(2, '0');
|
||||
return `${today.getFullYear()}-${month}-${day}`;
|
||||
}
|
||||
|
||||
export function createBatchRow(): BatchFormRow {
|
||||
return {
|
||||
clientKey: createClientKey(),
|
||||
@@ -27,7 +34,7 @@ export function createBatchRow(): BatchFormRow {
|
||||
boxWidth: '',
|
||||
boxHigh: '',
|
||||
qualityGuaranPeriod: '',
|
||||
dateOfFgInbound: '',
|
||||
dateOfFgInbound: createToday(),
|
||||
containerName: '',
|
||||
status: '0',
|
||||
};
|
||||
@@ -52,6 +59,7 @@ export function validateBatchRows(
|
||||
rows: BatchFormRow[],
|
||||
): Map<number, BatchFieldErrors> {
|
||||
const result = new Map<number, BatchFieldErrors>();
|
||||
const containerNameRows = new Map<string, number[]>();
|
||||
const requiredFields: [keyof BatchFormRow, string][] = [
|
||||
['packageBoxSn', '木箱唯一码不能为空'],
|
||||
['boxType', '木箱类型不能为空'],
|
||||
@@ -71,8 +79,24 @@ export function validateBatchRows(
|
||||
if (Object.keys(errors).length > 0) {
|
||||
result.set(index, errors);
|
||||
}
|
||||
const containerName = row.containerName?.trim();
|
||||
if (containerName) {
|
||||
const indexes = containerNameRows.get(containerName) ?? [];
|
||||
indexes.push(index);
|
||||
containerNameRows.set(containerName, indexes);
|
||||
}
|
||||
});
|
||||
|
||||
for (const indexes of containerNameRows.values()) {
|
||||
if (indexes.length < 2) continue;
|
||||
indexes.forEach((index) => {
|
||||
const duplicateIndex = indexes.find((item) => item !== index)!;
|
||||
const errors = result.get(index) ?? {};
|
||||
errors.containerName = `子卷号重复,与第 ${duplicateIndex + 1} 行相同`;
|
||||
result.set(index, errors);
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,12 +8,13 @@ import { computed, ref } from 'vue';
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Card,
|
||||
FormItem,
|
||||
DatePicker,
|
||||
Input,
|
||||
message,
|
||||
Segmented,
|
||||
@@ -138,7 +139,14 @@ const [Modal, modalApi] = useVbenModal({
|
||||
fieldErrors.value = errors;
|
||||
rowErrors.value = new Map();
|
||||
if (errors.size > 0) {
|
||||
message.warning('请检查必填项');
|
||||
const hasDuplicateContainerName = [...errors.values()].some((item) =>
|
||||
item.containerName?.startsWith('子卷号重复'),
|
||||
);
|
||||
message.warning(
|
||||
hasDuplicateContainerName
|
||||
? '子卷号不能重复,请检查后再提交'
|
||||
: '请检查标红字段',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -182,8 +190,14 @@ const [Modal, modalApi] = useVbenModal({
|
||||
<div class="batch-form">
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-actions">
|
||||
<Button type="primary" @click="appendRow">新增一行</Button>
|
||||
<Button class="clear-button" @click="clearRows">清空全部</Button>
|
||||
<Button type="primary" @click="appendRow">
|
||||
<template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template>
|
||||
新增一行
|
||||
</Button>
|
||||
<Button class="clear-button" @click="clearRows">
|
||||
<template #icon><IconifyIcon icon="ant-design:clear-outlined" /></template>
|
||||
清空全部
|
||||
</Button>
|
||||
</div>
|
||||
<Segmented
|
||||
v-model:value="viewMode"
|
||||
@@ -211,9 +225,9 @@ const [Modal, modalApi] = useVbenModal({
|
||||
<table class="batch-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>序号</th><th><i>*</i> 木箱唯一码</th><th><i>*</i> 木箱类型</th>
|
||||
<th>序号</th><th><i>*</i> 木箱唯一码</th><th><i>*</i> 子卷号</th><th><i>*</i> 木箱类型</th>
|
||||
<th>木箱长</th><th>木箱宽</th><th>木箱高</th>
|
||||
<th><i>*</i> 保质期(天)</th><th><i>*</i> 入库日期</th><th><i>*</i> 子卷号</th>
|
||||
<th><i>*</i> 保质期(天)</th><th><i>*</i> 入库日期</th>
|
||||
<th><i>*</i> 状态</th><th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -222,15 +236,15 @@ const [Modal, modalApi] = useVbenModal({
|
||||
<tr>
|
||||
<td class="sequence">{{ index + 1 }}</td>
|
||||
<td><Input v-model:value="row.packageBoxSn" :class="{ invalid: fieldError(index, 'packageBoxSn') }" placeholder="请输入木箱唯一码" @update:value="editField(index, 'packageBoxSn')" /><small>{{ fieldError(index, 'packageBoxSn') }}</small></td>
|
||||
<td><Input v-model:value="row.containerName" :class="{ invalid: fieldError(index, 'containerName') }" placeholder="请输入子卷号" @update:value="editField(index, 'containerName')" /><small>{{ fieldError(index, 'containerName') }}</small></td>
|
||||
<td><Input :value="row.boxType" readonly role="button" tabindex="0" aria-label="选择木箱类型" :class="{ invalid: fieldError(index, 'boxType') }" placeholder="请选择木箱类型" @click="selectBoxType(row.clientKey)" @keydown.enter="selectBoxType(row.clientKey)" @keydown.space.prevent="selectBoxType(row.clientKey)" /><small>{{ fieldError(index, 'boxType') }}</small></td>
|
||||
<td><Input v-model:value="row.boxLength" readonly placeholder="自动带出" /></td>
|
||||
<td><Input v-model:value="row.boxWidth" readonly placeholder="自动带出" /></td>
|
||||
<td><Input v-model:value="row.boxHigh" readonly placeholder="自动带出" /></td>
|
||||
<td><Input v-model:value="row.qualityGuaranPeriod" :class="{ invalid: fieldError(index, 'qualityGuaranPeriod') }" placeholder="请输入保质期" @update:value="editField(index, 'qualityGuaranPeriod')" /><small>{{ fieldError(index, 'qualityGuaranPeriod') }}</small></td>
|
||||
<td><Input v-model:value="row.dateOfFgInbound" type="date" :class="{ invalid: fieldError(index, 'dateOfFgInbound') }" @update:value="editField(index, 'dateOfFgInbound')" /><small>{{ fieldError(index, 'dateOfFgInbound') }}</small></td>
|
||||
<td><Input v-model:value="row.containerName" :class="{ invalid: fieldError(index, 'containerName') }" placeholder="请输入子卷号" @update:value="editField(index, 'containerName')" /><small>{{ fieldError(index, 'containerName') }}</small></td>
|
||||
<td><Input v-model:value="row.qualityGuaranPeriod" type="number" :class="{ invalid: fieldError(index, 'qualityGuaranPeriod') }" placeholder="例如:90" @update:value="editField(index, 'qualityGuaranPeriod')"><template #suffix><span class="input-unit">天</span></template></Input><small>{{ fieldError(index, 'qualityGuaranPeriod') }}</small></td>
|
||||
<td><DatePicker v-model:value="row.dateOfFgInbound" value-format="YYYY-MM-DD" format="YYYY-MM-DD" class="w-full" :class="{ invalid: fieldError(index, 'dateOfFgInbound') }" placeholder="请选择入库日期" @change="editField(index, 'dateOfFgInbound')" /><small>{{ fieldError(index, 'dateOfFgInbound') }}</small></td>
|
||||
<td><Select v-model:value="row.status" :options="statusOptions" :class="{ invalid: fieldError(index, 'status') }" @change="editField(index, 'status')" /><small>{{ fieldError(index, 'status') }}</small></td>
|
||||
<td class="operations"><Button type="link" @click="duplicateRow(index)">复制</Button><Button type="link" danger @click="removeRow(index)">删除</Button></td>
|
||||
<td class="operations"><Button type="link" @click="duplicateRow(index)"><template #icon><IconifyIcon icon="ant-design:copy-outlined" /></template>复制</Button><Button type="link" danger @click="removeRow(index)"><template #icon><IconifyIcon icon="ant-design:delete-outlined" /></template>删除</Button></td>
|
||||
</tr>
|
||||
<tr v-if="rowErrors.get(row.clientKey)" class="row-error"><td colspan="11">第 {{ index + 1 }} 行:{{ rowErrors.get(row.clientKey) }}</td></tr>
|
||||
</template>
|
||||
@@ -239,21 +253,34 @@ const [Modal, modalApi] = useVbenModal({
|
||||
</div>
|
||||
|
||||
<div v-else class="card-list">
|
||||
<Card v-for="(row, index) in rows" :key="row.clientKey" size="small">
|
||||
<template #title>第 {{ index + 1 }} 条包装关系</template>
|
||||
<template #extra><Button type="link" @click="duplicateRow(index)">复制</Button><Button type="link" danger @click="removeRow(index)">删除</Button></template>
|
||||
<Card v-for="(row, index) in rows" :key="row.clientKey">
|
||||
<template #title><div class="record-title"><span class="record-number">{{ index + 1 }}</span>包装关系 {{ String(index + 1).padStart(2, '0') }}</div></template>
|
||||
<template #extra><Button type="link" @click="duplicateRow(index)"><template #icon><IconifyIcon icon="ant-design:copy-outlined" /></template>复制</Button><Button type="link" danger @click="removeRow(index)"><template #icon><IconifyIcon icon="ant-design:delete-outlined" /></template>删除</Button></template>
|
||||
<Alert v-if="rowErrors.get(row.clientKey)" type="error" show-icon :message="rowErrors.get(row.clientKey)" class="row-alert" />
|
||||
<div class="card-grid">
|
||||
<FormItem label="木箱唯一码" required :validate-status="fieldError(index, 'packageBoxSn') ? 'error' : undefined" :help="fieldError(index, 'packageBoxSn')"><Input v-model:value="row.packageBoxSn" @update:value="editField(index, 'packageBoxSn')" /></FormItem>
|
||||
<FormItem label="木箱类型" required :validate-status="fieldError(index, 'boxType') ? 'error' : undefined" :help="fieldError(index, 'boxType')"><Input :value="row.boxType" readonly role="button" tabindex="0" aria-label="选择木箱类型" placeholder="请选择木箱类型" @click="selectBoxType(row.clientKey)" @keydown.enter="selectBoxType(row.clientKey)" @keydown.space.prevent="selectBoxType(row.clientKey)" /></FormItem>
|
||||
<FormItem label="木箱长"><Input v-model:value="row.boxLength" readonly placeholder="自动带出" /></FormItem>
|
||||
<FormItem label="木箱宽"><Input v-model:value="row.boxWidth" readonly placeholder="自动带出" /></FormItem>
|
||||
<FormItem label="木箱高"><Input v-model:value="row.boxHigh" readonly placeholder="自动带出" /></FormItem>
|
||||
<FormItem label="保质期" required :validate-status="fieldError(index, 'qualityGuaranPeriod') ? 'error' : undefined" :help="fieldError(index, 'qualityGuaranPeriod')"><Input v-model:value="row.qualityGuaranPeriod" @update:value="editField(index, 'qualityGuaranPeriod')" /></FormItem>
|
||||
<FormItem label="入库日期" required :validate-status="fieldError(index, 'dateOfFgInbound') ? 'error' : undefined" :help="fieldError(index, 'dateOfFgInbound')"><Input v-model:value="row.dateOfFgInbound" type="date" @update:value="editField(index, 'dateOfFgInbound')" /></FormItem>
|
||||
<FormItem label="子卷号" required :validate-status="fieldError(index, 'containerName') ? 'error' : undefined" :help="fieldError(index, 'containerName')"><Input v-model:value="row.containerName" @update:value="editField(index, 'containerName')" /></FormItem>
|
||||
<FormItem label="状态" required :validate-status="fieldError(index, 'status') ? 'error' : undefined" :help="fieldError(index, 'status')"><Select v-model:value="row.status" :options="statusOptions" @change="editField(index, 'status')" /></FormItem>
|
||||
</div>
|
||||
<section class="card-section">
|
||||
<div class="section-title"><i></i>识别信息</div>
|
||||
<div class="section-fields identity-fields">
|
||||
<div class="card-field" :class="{ 'has-error': fieldError(index, 'packageBoxSn') }"><label class="required">木箱唯一码</label><Input v-model:value="row.packageBoxSn" placeholder="扫描或输入木箱唯一码" @update:value="editField(index, 'packageBoxSn')" /><small>{{ fieldError(index, 'packageBoxSn') }}</small></div>
|
||||
<div class="card-field" :class="{ 'has-error': fieldError(index, 'containerName') }"><label class="required">子卷号</label><Input v-model:value="row.containerName" placeholder="扫描或输入子卷号" @update:value="editField(index, 'containerName')" /><small>{{ fieldError(index, 'containerName') }}</small></div>
|
||||
<div class="card-field" :class="{ 'has-error': fieldError(index, 'status') }"><label class="required">状态</label><Select v-model:value="row.status" class="w-full" :options="statusOptions" @change="editField(index, 'status')" /><small>{{ fieldError(index, 'status') }}</small></div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="card-section">
|
||||
<div class="section-title"><i></i>木箱规格</div>
|
||||
<div class="section-fields specification-fields">
|
||||
<div class="card-field" :class="{ 'has-error': fieldError(index, 'boxType') }"><label class="required">木箱类型</label><Input :value="row.boxType" readonly role="button" tabindex="0" aria-label="选择木箱类型" placeholder="请选择木箱类型" @click="selectBoxType(row.clientKey)" @keydown.enter="selectBoxType(row.clientKey)" @keydown.space.prevent="selectBoxType(row.clientKey)" /><small>{{ fieldError(index, 'boxType') }}</small></div>
|
||||
<div class="card-field"><label>木箱长</label><Input v-model:value="row.boxLength" readonly placeholder="自动带出"><template #suffix><span class="input-unit">mm</span></template></Input><small></small></div>
|
||||
<div class="card-field"><label>木箱宽</label><Input v-model:value="row.boxWidth" readonly placeholder="自动带出"><template #suffix><span class="input-unit">mm</span></template></Input><small></small></div>
|
||||
<div class="card-field"><label>木箱高</label><Input v-model:value="row.boxHigh" readonly placeholder="自动带出"><template #suffix><span class="input-unit">mm</span></template></Input><small></small></div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="card-section">
|
||||
<div class="section-title"><i></i>时效信息</div>
|
||||
<div class="section-fields lifecycle-fields">
|
||||
<div class="card-field" :class="{ 'has-error': fieldError(index, 'qualityGuaranPeriod') }"><label class="required">保质期</label><Input v-model:value="row.qualityGuaranPeriod" type="number" placeholder="例如:90" @update:value="editField(index, 'qualityGuaranPeriod')"><template #suffix><span class="input-unit">天</span></template></Input><small>{{ fieldError(index, 'qualityGuaranPeriod') }}</small></div>
|
||||
<div class="card-field" :class="{ 'has-error': fieldError(index, 'dateOfFgInbound') }"><label class="required">入库日期</label><DatePicker v-model:value="row.dateOfFgInbound" value-format="YYYY-MM-DD" format="YYYY-MM-DD" class="w-full" placeholder="请选择入库日期" @change="editField(index, 'dateOfFgInbound')" /><small>{{ fieldError(index, 'dateOfFgInbound') }}</small></div>
|
||||
</div>
|
||||
</section>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
@@ -377,9 +404,23 @@ const [Modal, modalApi] = useVbenModal({
|
||||
}
|
||||
|
||||
.batch-table :deep(.ant-input),
|
||||
.batch-table :deep(.ant-input-affix-wrapper),
|
||||
.batch-table :deep(.ant-picker),
|
||||
.batch-table :deep(.ant-select-selector) {
|
||||
height: 36px;
|
||||
min-height: 36px;
|
||||
border-radius: 7px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.batch-table :deep(.ant-input-affix-wrapper) {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.batch-table :deep(.ant-input-affix-wrapper .ant-input) {
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.batch-table :deep(.ant-input[readonly]) {
|
||||
@@ -425,6 +466,10 @@ const [Modal, modalApi] = useVbenModal({
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.input-unit {
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.invalid {
|
||||
border-color: #ff4d4f;
|
||||
}
|
||||
@@ -445,17 +490,147 @@ const [Modal, modalApi] = useVbenModal({
|
||||
}
|
||||
|
||||
.card-list :deep(.ant-card) {
|
||||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 8px rgb(23 32 51 / 4%);
|
||||
}
|
||||
|
||||
.card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
column-gap: 24px;
|
||||
.card-list :deep(.ant-card-head) {
|
||||
min-height: 54px;
|
||||
background: linear-gradient(90deg, #f8faff, #fff);
|
||||
}
|
||||
|
||||
.card-grid :deep(.ant-input[readonly]) {
|
||||
.card-list :deep(.ant-card-body) {
|
||||
padding: 4px 24px 8px;
|
||||
}
|
||||
|
||||
.record-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
color: #172033;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.record-number {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
background: #326cff;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.card-section {
|
||||
display: grid;
|
||||
padding: 18px 0 4px;
|
||||
grid-template-columns: 170px minmax(0, 1fr);
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.card-section + .card-section {
|
||||
border-top: 1px dashed #e3e7ef;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
padding-top: 8px;
|
||||
color: #576071;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.section-title i {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin-top: 7px;
|
||||
background: #84a9ff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.section-fields {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.identity-fields {
|
||||
grid-template-columns: 1.35fr 1.35fr 0.9fr;
|
||||
}
|
||||
|
||||
.specification-fields {
|
||||
grid-template-columns: 1.35fr repeat(3, 0.9fr);
|
||||
}
|
||||
|
||||
.lifecycle-fields {
|
||||
max-width: 75%;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.card-field {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.card-field label {
|
||||
height: 24px;
|
||||
margin-bottom: 7px;
|
||||
color: #596275;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.card-field label.required::before {
|
||||
margin-right: 4px;
|
||||
color: #ff4d4f;
|
||||
content: '*';
|
||||
}
|
||||
|
||||
.card-field small {
|
||||
display: block;
|
||||
height: 20px;
|
||||
padding-top: 3px;
|
||||
color: #ff4d4f;
|
||||
line-height: 17px;
|
||||
}
|
||||
|
||||
.section-fields :deep(.ant-input),
|
||||
.section-fields :deep(.ant-input-affix-wrapper),
|
||||
.section-fields :deep(.ant-picker),
|
||||
.section-fields :deep(.ant-select-single .ant-select-selector) {
|
||||
height: 40px;
|
||||
min-height: 40px;
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.section-fields :deep(.ant-input-affix-wrapper) {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.section-fields :deep(.ant-input-affix-wrapper .ant-input) {
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.section-fields :deep(.ant-select-single .ant-select-selection-item),
|
||||
.section-fields :deep(.ant-select-single .ant-select-selection-placeholder) {
|
||||
line-height: 38px;
|
||||
}
|
||||
|
||||
.card-field.has-error :deep(.ant-input),
|
||||
.card-field.has-error :deep(.ant-input-affix-wrapper),
|
||||
.card-field.has-error :deep(.ant-picker),
|
||||
.card-field.has-error :deep(.ant-select-selector) {
|
||||
border-color: #ff4d4f;
|
||||
}
|
||||
|
||||
.section-fields :deep(.ant-input[readonly]) {
|
||||
color: #667085;
|
||||
background: #f2f4f7;
|
||||
}
|
||||
@@ -474,7 +649,14 @@ const [Modal, modalApi] = useVbenModal({
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.card-grid {
|
||||
.card-section {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.identity-fields,
|
||||
.specification-fields,
|
||||
.lifecycle-fields {
|
||||
max-width: none;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user