修复产品创建表单:将产品名称和分类提升为公共字段

解决创建多规格产品时需要重复输入产品名称的问题,
避免因名称不一致导致多规格产品在列表中显示为多个独立产品。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
taiyi 2026-05-29 10:16:14 +08:00
parent 1053e96234
commit b65d3fa389

View File

@ -449,6 +449,24 @@
</div>
<div class="product-create-summary">
<p>同一产品可一次新增多个规格每个规格可配置不同的销售价和成本价</p>
</div>
<div class="form-grid product-master-fields">
<label>
<span>产品名称</span>
<input v-model.trim="productCreateForm.product_name" type="text" placeholder="请输入产品名称" />
</label>
<label>
<span>分类</span>
<select v-model="productCreateForm.category_id">
<option value="">请选择分类</option>
<option v-for="item in categoryOptions" :key="item.value" :value="String(item.value)">
{{ item.label }}
</option>
</select>
</label>
</div>
<div class="spec-section-header">
<h5>规格明细</h5>
<button class="ghost-btn small-btn" type="button" @click="addProductSpecRow">添加规格</button>
</div>
<div v-for="(specRow, index) in productSpecRows" :key="specRow.rowKey" class="spec-card">
@ -457,10 +475,6 @@
<button v-if="productSpecRows.length > 1" class="link-btn danger-link" type="button" @click="removeProductSpecRow(index)">删除</button>
</div>
<div class="form-grid">
<label>
<span>产品名称</span>
<input v-model.trim="specRow.product_name" type="text" placeholder="请输入产品名称" />
</label>
<label>
<span>规格</span>
<input v-model.trim="specRow.specification" type="text" placeholder="请输入规格" />
@ -469,15 +483,6 @@
<span>单位</span>
<input v-model.trim="specRow.unit" type="text" placeholder="请输入单位" />
</label>
<label>
<span>分类</span>
<select v-model="specRow.category_id">
<option value="">请选择分类</option>
<option v-for="item in categoryOptions" :key="item.value" :value="String(item.value)">
{{ item.label }}
</option>
</select>
</label>
<label>
<span>成本价</span>
<input v-model.number="specRow.cost_price" type="number" min="0" step="0.01" />
@ -996,6 +1001,10 @@ const productFilters = reactive({
category_id: "",
status: "",
});
const productCreateForm = reactive({
product_name: "",
category_id: "",
});
const productSpecRows = ref([]);
const showEditSpecForm = ref(false);
const updatingSpec = ref(false);
@ -1014,14 +1023,13 @@ const editingSpecForm = reactive({
function buildProductSpecRow() {
return {
rowKey: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
product_name: "",
specification: "",
unit: "",
category_id: "",
cost_price: 0,
sale_price: 0,
status: 1,
remark: "",
is_default: false,
};
}
@ -1268,6 +1276,8 @@ async function handleUpdateSupplier() {
}
function resetProductCreateForm() {
productCreateForm.product_name = "";
productCreateForm.category_id = "";
productSpecRows.value = [buildProductSpecRow()];
}
@ -1603,28 +1613,26 @@ async function handleResetProducts() {
}
async function handleCreateProduct() {
const validRows = productSpecRows.value.filter((row) => row.product_name.trim() && row.specification.trim() && row.unit.trim());
if (!validRows.length) {
toast.error("请至少填写一个完整的产品规格");
if (!productCreateForm.product_name.trim()) {
toast.error("请输入产品名称");
return;
}
const productName = validRows[0].product_name.trim();
if (validRows.some((row) => row.product_name.trim() !== productName)) {
toast.error("同一个产品主档下的规格名称必须一致");
const validRows = productSpecRows.value.filter((row) => row.specification.trim() && row.unit.trim());
if (!validRows.length) {
toast.error("请至少填写一个完整的产品规格");
return;
}
creatingProduct.value = true;
toast.success("");
try {
const result = await createProduct({
product_name: productName,
category_id: validRows[0].category_id ? Number(validRows[0].category_id) : null,
product_name: productCreateForm.product_name.trim(),
category_id: productCreateForm.category_id ? Number(productCreateForm.category_id) : null,
status: Number(validRows[0].status || 1),
remark: validRows[0].remark,
specifications: validRows.map((row) => ({
specification: row.specification,
unit: row.unit,
category_id: row.category_id ? Number(row.category_id) : null,
cost_price: Number(row.cost_price || 0),
sale_price: Number(row.sale_price || 0),
status: Number(row.status || 1),
@ -1861,6 +1869,9 @@ th { color: #334155; font-size: 13px; background: #f8fafc; }
.modal-close { width: 36px; height: 36px; border: 1px solid #d1d5db; background: #f8fafc; font-size: 22px; line-height: 1; color: #334155; }
.product-create-summary { display: flex; justify-content: space-between; gap: 12px; align-items: center; margin-bottom: 12px; padding: 12px 14px; border: 1px solid #dbe3ef; border-radius: 14px; background: #f8fbff; }
.product-create-summary p { margin: 0; color: #475569; }
.product-master-fields { margin-bottom: 16px; padding-bottom: 16px; border-bottom: 1px solid #e2e8f0; }
.spec-section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.spec-section-header h5 { margin: 0; font-size: 14px; color: #334155; }
.spec-card { margin-bottom: 14px; padding: 14px; border: 1px solid #dbe3ef; border-radius: 16px; background: #fff; }
.spec-card-header { display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-bottom: 12px; }
.spec-card-header h5 { margin: 0; font-size: 15px; }