feat(web-admin): edit product brands and aliases in product form

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-07-28 09:22:29 +08:00
1 parent bfa0d50597
commit bcfadd23d9
2 files changed
+204 -2

No files matched your search

+43
View File
@@ -1,5 +1,48 @@
import request from '@/utils/http'
export interface ProductBrandItem {
id?: string
brandName: string
status?: string
orderNum?: number
}
export interface ProductAliasItem {
id?: string
aliasName: string
orderNum?: number
}
export interface ProductFormData {
id?: string
productName: string
productNo?: string
categoryId?: string
model?: string
barcode?: string
stock?: number
lowStock?: number
taxRate?: number
status?: string
isToxic?: number
isExplosive?: number
description?: string
images?: string
remark?: string
brands?: ProductBrandItem[]
aliases?: ProductAliasItem[]
}
export interface ProductDetail extends ProductFormData {
id: string
categoryName?: string
price?: number
costPrice?: number
cost?: number
saleCount?: number
createTime?: string
}
// ── 产品分类 ──
/** 产品分类树(不分页) */
+161 -2
View File
@@ -192,6 +192,18 @@
header-align="center"
show-overflow-tooltip
/>
<el-table-column
v-if="isColumnVisible('brands')"
label="品牌"
min-width="140"
align="center"
header-align="center"
show-overflow-tooltip
>
<template #default="{ row }">
{{ formatProductBrands(row.brands) }}
</template>
</el-table-column>
<el-table-column
v-if="isColumnVisible('model')"
prop="model"
@@ -273,8 +285,7 @@
ref="formRef"
:model="formData"
:rules="formRules"
label-position="right"
label-width="100px"
label-position="top"
class="crm-dialog-form"
scroll-to-error
:scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
@@ -375,6 +386,78 @@
/>
</el-form-item>
</div>
<div class="sub-table-section">
<div class="sub-table-header">
<span class="sub-table-title">品牌</span>
<el-button type="primary" size="small" @click="handleAddBrand">
<el-icon><Plus /></el-icon>新增品牌
</el-button>
</div>
<el-table :data="formData.brands" stripe class="list-table sub-table">
<el-table-column
type="index"
label="序号"
min-width="68"
align="center"
header-align="center"
/>
<el-table-column label="品牌名称" min-width="200">
<template #default="{ row }">
<el-input v-model="row.brandName" placeholder="请输入品牌名称" />
</template>
</el-table-column>
<el-table-column label="状态" min-width="120" align="center" header-align="center">
<template #default="{ row }">
<el-select v-model="row.status" placeholder="请选择状态" style="width: 100%">
<el-option
v-for="opt in dictStore.getOptions('crm_product_status')"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="操作" min-width="80" align="center" header-align="center">
<template #default="{ $index }">
<el-button link type="danger" @click="handleRemoveBrand($index)">
<el-icon><Delete /></el-icon>
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="sub-table-section">
<div class="sub-table-header">
<span class="sub-table-title">别名</span>
<el-button type="primary" size="small" @click="handleAddAlias">
<el-icon><Plus /></el-icon>新增别名
</el-button>
</div>
<el-table :data="formData.aliases" stripe class="list-table sub-table">
<el-table-column
type="index"
label="序号"
min-width="68"
align="center"
header-align="center"
/>
<el-table-column label="别名" min-width="240">
<template #default="{ row }">
<el-input v-model="row.aliasName" placeholder="请输入别名" />
</template>
</el-table-column>
<el-table-column label="操作" min-width="80" align="center" header-align="center">
<template #default="{ $index }">
<el-button link type="danger" @click="handleRemoveAlias($index)">
<el-icon><Delete /></el-icon>
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-form>
<template #footer>
<el-button class="btn-ghost" @click="formDialogVisible = false">
@@ -523,6 +606,8 @@ import {
fetchImportProducts,
fetchExportProducts,
fetchImportTemplate,
type ProductBrandItem,
type ProductAliasItem,
} from '@/api/crm/product'
const dictStore = useDictStore()
@@ -543,6 +628,12 @@ function formatProductPrice(value: unknown) {
return `¥${formatMoneyDisplay(num)}`
}
function formatProductBrands(brands?: ProductBrandItem[]) {
if (!brands?.length) return '-'
const names = brands.map((b) => b.brandName).filter(Boolean)
return names.length ? names.join('、') : '-'
}
const productList = ref<any[]>([])
const categoryOptions = ref<any[]>([])
const loading = ref(false)
@@ -550,6 +641,7 @@ const loading = ref(false)
const listColumnDefs = [
{ prop: 'productNo', label: '产品编号' },
{ prop: 'categoryName', label: '产品分类' },
{ prop: 'brands', label: '品牌' },
{ prop: 'model', label: '规格型号' },
{ prop: 'price', label: '标准价(元)' },
{ prop: 'cost', label: '成本价(元)', defaultVisible: false },
@@ -595,6 +687,8 @@ const formData = reactive({
description: '',
images: '',
remark: '',
brands: [] as ProductBrandItem[],
aliases: [] as ProductAliasItem[],
})
const formRules = {
productName: [{ required: true, message: '产品名称不能为空', trigger: 'blur' }],
@@ -782,6 +876,17 @@ async function handleUpdate(row: any) {
description: data.description || '',
images: data.images || '',
remark: data.remark || '',
brands: (data.brands || []).map((item: ProductBrandItem) => ({
id: item.id,
brandName: item.brandName || '',
status: item.status || 'on',
orderNum: item.orderNum,
})),
aliases: (data.aliases || []).map((item: ProductAliasItem) => ({
id: item.id,
aliasName: item.aliasName || '',
orderNum: item.orderNum,
})),
})
formDialogVisible.value = true
}
@@ -864,6 +969,21 @@ async function submitForm() {
description: formData.description || undefined,
images: formData.images || undefined,
remark: formData.remark || undefined,
brands: formData.brands
.filter((item) => item.brandName?.trim())
.map((item, index) => ({
...(item.id ? { id: item.id } : {}),
brandName: item.brandName.trim(),
status: item.status || 'on',
orderNum: item.orderNum ?? index + 1,
})),
aliases: formData.aliases
.filter((item) => item.aliasName?.trim())
.map((item, index) => ({
...(item.id ? { id: item.id } : {}),
aliasName: item.aliasName.trim(),
orderNum: item.orderNum ?? index + 1,
})),
}
if (isEdit.value) {
await fetchUpdateProduct({ id: formData.id, ...data })
@@ -969,6 +1089,24 @@ function resetFormData() {
formData.description = ''
formData.images = ''
formData.remark = ''
formData.brands = []
formData.aliases = []
}
function handleAddBrand() {
formData.brands.push({ brandName: '', status: 'on' })
}
function handleRemoveBrand(index: number) {
formData.brands.splice(index, 1)
}
function handleAddAlias() {
formData.aliases.push({ aliasName: '' })
}
function handleRemoveAlias(index: number) {
formData.aliases.splice(index, 1)
}
function resetForm() {
@@ -1026,6 +1164,27 @@ onMounted(() => {
justify-content: flex-end;
gap: 8px;
}
.sub-table-section {
margin-top: 16px;
}
.sub-table-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.sub-table-title {
font-size: 14px;
font-weight: 500;
color: var(--text, #1e293b);
}
.sub-table {
margin-bottom: 8px;
}
</style>
<style lang="scss">