- 修正合并同名候选匹配:此前用名称 3 段组键(含工艺段),工艺写法不同的 同款(如 光板不打印 vs 单面印花(前印))全部漏配导致候选为空、合并同名 区块被 v-if 隐藏 —— 用户看到的"checkbox 和搜索框被吞"即此因。 改为 族归属精确匹配 或 款号(名称第 2 段,如 BRTF004)一致,全局跨分类; 区块常显,无候选时空态提示 - 修左树「N 个配置」徽标错位:good-name-col 改 flex 布局,徽标紧贴名称 - 验证:admin typecheck + 22/22 + 构建绿;DB 实测 BRTF004 光板链接候选池 恢复(单面印花(前印),同族 857)
176 lines
6.7 KiB
Vue
176 lines
6.7 KiB
Vue
<script setup lang="ts">
|
||
import { computed, ref, watch } from 'vue'
|
||
import { ElMessage } from 'element-plus'
|
||
import type { CategoryTree, Country } from '@/types'
|
||
import { goodsApi } from '@/api/goods'
|
||
import { productFamiliesApi } from '@/api/product-families'
|
||
import { findCategoryPath, buildCascader } from '@/utils/category-tree'
|
||
|
||
const props = defineProps<{
|
||
visible: boolean
|
||
/** 拖拽/配置的链接节点(含 rawId/goodName/goodImage/goodPrice/sdsGoodId/familyId) */
|
||
og: any | null
|
||
/** 同分类下的可合并兄弟链接 */
|
||
siblings: any[]
|
||
/** 默认勾选的兄弟 rawId */
|
||
defaultCheckedIds: string[]
|
||
/** 左树拖放目标(国家/分类节点),为空时表单手动选 */
|
||
dropTarget: any | null
|
||
mode: 'category' | 'country' | 'global'
|
||
countries: Country[]
|
||
categories: CategoryTree[]
|
||
}>()
|
||
|
||
const emit = defineEmits<{
|
||
(e: 'update:visible', v: boolean): void
|
||
(e: 'configured'): void
|
||
}>()
|
||
|
||
const loading = ref(false)
|
||
const form = ref({
|
||
countryId: '', cascaderCategory: [] as string[], categoryId: '', goodImage: '',
|
||
})
|
||
|
||
watch(() => props.visible, (v) => {
|
||
if (!v) return
|
||
const og = props.og
|
||
form.value = { countryId: '', cascaderCategory: [], categoryId: '', goodImage: og?.goodImage || '' }
|
||
if (props.dropTarget) {
|
||
if (props.mode === 'category') {
|
||
form.value.categoryId = props.dropTarget.id
|
||
form.value.cascaderCategory = findCategoryPath(props.categories, props.dropTarget.id)
|
||
} else {
|
||
form.value.countryId = props.dropTarget.id
|
||
}
|
||
}
|
||
})
|
||
|
||
const categoryCascader = computed(() => buildCascader(props.categories))
|
||
|
||
const checkedIds = ref<string[]>([])
|
||
watch(() => props.visible, (v) => {
|
||
if (v) checkedIds.value = [...props.defaultCheckedIds]
|
||
})
|
||
|
||
// 合并同名候选搜索(本地过滤同分类链接)
|
||
const mergeKeyword = ref('')
|
||
watch(() => props.visible, (v) => {
|
||
if (v) mergeKeyword.value = ''
|
||
})
|
||
const filteredSiblings = computed(() => {
|
||
const kw = mergeKeyword.value.trim()
|
||
if (!kw) return props.siblings
|
||
return props.siblings.filter((s) => (s.goodName ?? '').includes(kw))
|
||
})
|
||
|
||
function onCascaderChange(val: any) {
|
||
form.value.categoryId = val.length ? val[val.length - 1] : ''
|
||
}
|
||
|
||
/** 勾选的兄弟链接与当前链接静默归入同一族(无族则自动建族);失败不阻断原配置流程 */
|
||
async function ensureFamilyMembership() {
|
||
const og = props.og
|
||
const primaryId = String(og.rawId)
|
||
const checked = checkedIds.value.filter((id) => id !== primaryId)
|
||
if (!checked.length) return
|
||
try {
|
||
if (og.familyId) {
|
||
await productFamiliesApi.updateMembers(og.familyId, {
|
||
addOriginGoodIds: [...new Set([primaryId, ...checked])],
|
||
})
|
||
} else {
|
||
await productFamiliesApi.create({
|
||
familyName: og.goodName ?? '',
|
||
originGoodIds: [primaryId, ...checked],
|
||
primaryOriginGoodId: primaryId,
|
||
})
|
||
}
|
||
} catch (e: any) {
|
||
ElMessage.warning(e?.response?.data?.message || '挂族失败,商品仍按原方式配置')
|
||
}
|
||
}
|
||
|
||
async function handleSubmit() {
|
||
const og = props.og
|
||
if (!form.value.countryId) { ElMessage.warning('请选择国家'); return }
|
||
if (!form.value.categoryId) { ElMessage.warning('请选择分类'); return }
|
||
loading.value = true
|
||
try {
|
||
await ensureFamilyMembership()
|
||
await goodsApi.createGood({
|
||
goodName: og.goodName,
|
||
goodImage: form.value.goodImage || undefined,
|
||
originGoodId: Number(og.rawId),
|
||
countryId: Number(form.value.countryId),
|
||
categoryId: Number(form.value.categoryId),
|
||
positionId: undefined,
|
||
} as any)
|
||
ElMessage.success('配置成功')
|
||
emit('update:visible', false)
|
||
emit('configured')
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.response?.data?.message || '配置失败')
|
||
} finally { loading.value = false }
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<el-dialog :model-value="visible" title="配置原产品" width="520px" destroy-on-close @update:model-value="emit('update:visible', $event)">
|
||
<div v-if="og" class="config-og-info">
|
||
<div>
|
||
<div class="config-og-name">{{ og.goodName }}</div>
|
||
<div class="config-og-meta">SDS ID: {{ og.sdsGoodId }}<template v-if="og.goodPrice"> · 价格: ¥{{ og.goodPrice }}</template></div>
|
||
</div>
|
||
</div>
|
||
<el-form label-width="80px" style="margin-top: 16px">
|
||
<el-form-item label="国家">
|
||
<div v-if="mode === 'category' || !dropTarget" class="select-inline">
|
||
<el-select v-model="form.countryId" placeholder="请选择国家" filterable>
|
||
<el-option v-for="c in countries" :key="c.id" :label="c.countryName" :value="c.id" />
|
||
</el-select>
|
||
</div>
|
||
<el-tag v-else>{{ countries.find(c => c.id === form.countryId)?.countryName }}</el-tag>
|
||
</el-form-item>
|
||
<el-form-item label="分类">
|
||
<el-cascader v-if="mode === 'country' || !dropTarget" v-model="form.cascaderCategory" :options="categoryCascader as any" :props="{ checkStrictly: true }" placeholder="请选择分类" @change="onCascaderChange" style="width:100%" />
|
||
<el-tag v-else>{{ dropTarget?.label }}</el-tag>
|
||
</el-form-item>
|
||
<el-form-item label="合并同名">
|
||
<div class="config-merge-box">
|
||
<el-input
|
||
v-model="mergeKeyword"
|
||
size="small"
|
||
clearable
|
||
placeholder="搜索同款链接(全局,含已配置)…"
|
||
style="margin-bottom:8px"
|
||
/>
|
||
<el-checkbox-group v-model="checkedIds">
|
||
<el-checkbox v-for="s in filteredSiblings" :key="s.rawId" :value="String(s.rawId)">
|
||
{{ s.goodName }}<template v-if="s.goodPrice"> · ¥{{ s.goodPrice }}</template>
|
||
</el-checkbox>
|
||
</el-checkbox-group>
|
||
<div v-if="!filteredSiblings.length" class="config-merge-empty">
|
||
{{ siblings.length ? '没有匹配的链接' : '未找到同款链接(同款号或同族)' }}
|
||
</div>
|
||
</div>
|
||
</el-form-item>
|
||
<el-form-item label="图片">
|
||
<ImageUpload v-model="form.goodImage" label="上传图片" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="emit('update:visible', false)">取消</el-button>
|
||
<el-button type="primary" :loading="loading" @click="handleSubmit">确认配置</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.config-og-info { padding: 12px; background: #f5f7fa; border-radius: 8px; }
|
||
.config-og-name { font-weight: 600; font-size: 14px; }
|
||
.config-og-meta { color: #909399; font-size: 12px; margin-top: 2px; }
|
||
.config-merge-box { width: 100%; }
|
||
.config-merge-empty { font-size: 12px; color: #909399; padding: 4px 0; }
|
||
.config-merge-box .el-checkbox-group { display: flex; flex-direction: column; align-items: flex-start; max-height: 200px; overflow-y: auto; }
|
||
</style>
|