feat(admin): manage merged origin goods in good edit modal
This commit is contained in:
@@ -4,10 +4,11 @@ import { useVirtualList } from '@vueuse/core'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
Plus, Edit, Delete, Search, Top, Refresh,
|
||||
FolderAdd, Aim, ArrowDown,
|
||||
FolderAdd, Aim, ArrowDown, QuestionFilled,
|
||||
} from '@element-plus/icons-vue'
|
||||
import type {
|
||||
CategoryTree, Country, Tag, TagGroup, Good, GoodDetail,
|
||||
MergedOriginGoodSummary,
|
||||
OriginGoodsTreeResponse,
|
||||
} from '@/types'
|
||||
import { goodsApi } from '@/api/goods'
|
||||
@@ -513,6 +514,9 @@ const editLoading = ref(false)
|
||||
const editDetailLoading = ref(false)
|
||||
const detailSyncing = ref(false)
|
||||
const editGood = ref<Good | GoodDetail | null>(null)
|
||||
const editMerged = ref<MergedOriginGoodSummary[]>([])
|
||||
const editMergeSearch = ref('')
|
||||
const originalOriginGoodId = ref<string>('')
|
||||
const editForm = ref({
|
||||
id: '', goodName: '', goodImage: '', countryId: '', cascaderCategory: [] as string[],
|
||||
categoryId: '', tagIds: [] as string[], positionId: '',
|
||||
@@ -533,6 +537,53 @@ const editSizeRows = computed(() => {
|
||||
})
|
||||
const editPackageRows = computed(() => editOriginDetail.value?.packageSpecs?.rows ?? [])
|
||||
const editIsCustom = computed(() => editGood.value?.originGood?.isCustom === true)
|
||||
|
||||
// 候选 = 右栏树全部 og,按搜索词过滤
|
||||
const editMergeCandidates = computed(() => {
|
||||
if (!editMergeSearch.value) return []
|
||||
const kw = editMergeSearch.value
|
||||
const result: any[] = []
|
||||
function traverse(nodes: any[]) {
|
||||
for (const n of nodes) {
|
||||
if (n.isOG && (n.goodName ?? '').includes(kw)) result.push(n)
|
||||
if (n.children?.length) traverse(n.children)
|
||||
}
|
||||
}
|
||||
traverse(rightTreeData.value)
|
||||
return result.filter(
|
||||
(n) => String(n.rawId) !== String(editGood.value?.originGoodId)
|
||||
&& !editMerged.value.some((m) => m.id === String(n.rawId)),
|
||||
)
|
||||
})
|
||||
|
||||
function addEditMerge(node: any) {
|
||||
editMerged.value.push({
|
||||
id: String(node.rawId), sdsGoodId: node.sdsGoodId,
|
||||
goodName: node.goodName, goodImage: node.goodImage,
|
||||
goodPrice: node.goodPrice, hasDetail: Boolean(node.hasDetail),
|
||||
variantCount: node.variantCount ?? 0,
|
||||
})
|
||||
editMergeSearch.value = ''
|
||||
}
|
||||
|
||||
function promoteMerged(m: MergedOriginGoodSummary) {
|
||||
if (!editGood.value) return
|
||||
const oldPrimaryId = editGood.value.originGoodId
|
||||
const oldPrimary = editGood.value.originGood
|
||||
editGood.value = {
|
||||
...editGood.value,
|
||||
originGoodId: m.id,
|
||||
originGood: oldPrimary
|
||||
? { ...oldPrimary, id: m.id, sdsGoodId: m.sdsGoodId, goodName: m.goodName }
|
||||
: { id: m.id, sdsGoodId: m.sdsGoodId, goodName: m.goodName } as any,
|
||||
}
|
||||
editMerged.value = [
|
||||
{ id: oldPrimaryId, sdsGoodId: oldPrimary?.sdsGoodId ?? '', goodName: oldPrimary?.goodName ?? null,
|
||||
goodImage: oldPrimary?.goodImage ?? null, goodPrice: oldPrimary?.goodPrice ?? null,
|
||||
hasDetail: oldPrimary?.hasDetail ?? false, variantCount: (oldPrimary as any)?.variantCount ?? 0 },
|
||||
...editMerged.value.filter((x) => x.id !== m.id),
|
||||
]
|
||||
}
|
||||
const customContentForm = ref({
|
||||
goodPrice: '', productCode: '', englishName: '', productionCycleHours: undefined as number | undefined,
|
||||
minWeightG: '', productionProcess: '', materialDescription: '',
|
||||
@@ -604,6 +655,8 @@ function addCustomVariant() {
|
||||
|
||||
async function openEdit(g: Good) {
|
||||
editGood.value = g
|
||||
editMerged.value = (g.mergedOriginGoods as MergedOriginGoodSummary[]) ?? []
|
||||
originalOriginGoodId.value = g.originGoodId
|
||||
editForm.value = {
|
||||
id: g.id, goodName: g.goodName,
|
||||
goodImage: g.goodImage || g.originGood?.goodImage || '',
|
||||
@@ -618,6 +671,7 @@ async function openEdit(g: Good) {
|
||||
try {
|
||||
const detail = await goodsApi.getGoodById(g.id)
|
||||
editGood.value = detail
|
||||
editMerged.value = (detail.mergedOriginGoods as MergedOriginGoodSummary[]) ?? []
|
||||
if (detail.originGood?.isCustom) fillCustomContent(detail)
|
||||
} catch {
|
||||
ElMessage.warning('商品详情加载失败,当前显示列表数据')
|
||||
@@ -649,7 +703,9 @@ async function handleSyncOneDetail() {
|
||||
detailSyncing.value = true
|
||||
try {
|
||||
const result = await syncApi.syncOneProductDetail(goodId)
|
||||
editGood.value = await goodsApi.getGoodById(editGood.value!.id)
|
||||
const detail = await goodsApi.getGoodById(editGood.value!.id)
|
||||
editGood.value = detail
|
||||
editMerged.value = (detail.mergedOriginGoods as MergedOriginGoodSummary[]) ?? []
|
||||
ElMessage.success(`详情同步完成,共 ${result.variants} 个 SKU`)
|
||||
await Promise.all([refreshLeftTree(), refreshRightTree()])
|
||||
} catch (error: any) {
|
||||
@@ -732,6 +788,10 @@ async function handleEditSubmit() {
|
||||
await goodsApi.updateGood(editForm.value.id, {
|
||||
goodName: editForm.value.goodName,
|
||||
goodImage: editForm.value.goodImage || null,
|
||||
originGoodId: editGood.value?.originGoodId !== originalOriginGoodId.value && !editIsCustom.value
|
||||
? Number(editGood.value!.originGoodId)
|
||||
: undefined,
|
||||
mergedOriginGoodIds: editIsCustom.value ? undefined : editMerged.value.map((m) => Number(m.id)),
|
||||
countryId: Number(editForm.value.countryId),
|
||||
categoryId: Number(editForm.value.categoryId),
|
||||
tagIds: editForm.value.tagIds.map(Number),
|
||||
@@ -1876,6 +1936,31 @@ onMounted(() => loadAll())
|
||||
@click="handleSyncOneDetail"
|
||||
>同步详情</el-button>
|
||||
</div>
|
||||
<div v-if="!editIsCustom" class="edit-merged-box">
|
||||
<div class="edit-merged-title">
|
||||
关联原产品(主源 + 副源)
|
||||
<el-tooltip content="主源决定价格、详情与上下架;副源变体合并展示。切换主源后旧主源自动转为副源。">
|
||||
<el-icon><QuestionFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="edit-merged-list">
|
||||
<div class="edit-merged-item primary">
|
||||
<span class="edit-merged-tag">主</span>
|
||||
<span>{{ editGood?.originGood?.goodName }}</span>
|
||||
</div>
|
||||
<div v-for="m in editMerged" :key="m.id" class="edit-merged-item">
|
||||
<span class="edit-merged-tag sub">副</span>
|
||||
<span>{{ m.goodName }}</span>
|
||||
<el-button size="small" link type="primary" @click="promoteMerged(m)">设为主源</el-button>
|
||||
<el-button size="small" link type="danger" @click="editMerged = editMerged.filter(x => x.id !== m.id)">移除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-select v-model="editMergeSearch" filterable remote :remote-method="(q: string) => editMergeSearch = q"
|
||||
placeholder="搜索原产品名称以添加副源(用于合并同名商品)" clearable style="width:100%">
|
||||
<el-option v-for="c in editMergeCandidates" :key="c.rawId" :label="c.goodName" :value="String(c.rawId)"
|
||||
@click="addEditMerge(c)" />
|
||||
</el-select>
|
||||
</div>
|
||||
<el-form v-loading="editDetailLoading" label-width="80px" style="margin-top: 16px">
|
||||
<el-form-item label="名称"><el-input v-model="editForm.goodName" /></el-form-item>
|
||||
<el-form-item label="图片">
|
||||
@@ -2319,6 +2404,22 @@ onMounted(() => loadAll())
|
||||
.edit-og-sub { color: #909399; font-size: 12px; margin-top: 2px; }
|
||||
.edit-og-meta { flex: 1; min-width: 0; }
|
||||
.edit-og-status { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 10px; margin-top: 6px; color: #909399; font-size: 12px; }
|
||||
.edit-merged-box { margin-top: 12px; }
|
||||
.edit-merged-title {
|
||||
display: flex; align-items: center; gap: 4px;
|
||||
font-size: 12px; color: #909399; margin-bottom: 8px;
|
||||
}
|
||||
.edit-merged-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 8px; }
|
||||
.edit-merged-item {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
padding: 6px 10px; background: #f5f7fa; border-radius: 6px; font-size: 13px;
|
||||
}
|
||||
.edit-merged-item .el-button { margin-left: auto; }
|
||||
.edit-merged-tag {
|
||||
padding: 0 5px; border-radius: 4px; font-size: 11px; line-height: 18px;
|
||||
background: var(--el-color-primary); color: #fff; flex-shrink: 0;
|
||||
}
|
||||
.edit-merged-tag.sub { background: var(--el-color-info); }
|
||||
.detail-tabs { margin-top: 12px; padding-top: 4px; border-top: 1px solid #ebeef5; }
|
||||
|
||||
/* Config modal */
|
||||
|
||||
Reference in New Issue
Block a user