feat(admin): expandable family members with per-link tag config + SKU pricing dims columns
This commit is contained in:
@@ -18,7 +18,7 @@ import { tagGroupsApi } from '@/api/tag-groups'
|
||||
import { originGoodsApi } from '@/api/origin-goods'
|
||||
import { syncApi } from '@/api/sync'
|
||||
import { sameFamily } from '@/utils/family-match'
|
||||
import { cleanLinkName, deriveLinkTagNames, truncateToProcess } from '@/utils/origin-name'
|
||||
import { cleanLinkName, deriveLinkTagNames, linkDims, truncateToProcess } from '@/utils/origin-name'
|
||||
import { productFamiliesApi } from '@/api/product-families'
|
||||
|
||||
const mode = ref<'category' | 'country' | 'global'>('category')
|
||||
@@ -566,7 +566,7 @@ const editGood = ref<Good | GoodDetail | null>(null)
|
||||
const originalOriginGoodId = ref<string>('')
|
||||
const editForm = ref({
|
||||
id: '', goodName: '', goodImage: '', countryId: '', cascaderCategory: [] as string[],
|
||||
categoryId: '', tagIds: [] as string[], positionId: '',
|
||||
categoryId: '', positionId: '',
|
||||
})
|
||||
|
||||
const editOriginDetail = computed(() => (editGood.value as GoodDetail | null)?.originDetail ?? null)
|
||||
@@ -654,14 +654,57 @@ function addCustomVariant() {
|
||||
})
|
||||
}
|
||||
|
||||
// ─── Edit family(编辑弹窗的族成员管理,替代旧主源/副源) ───
|
||||
// ─── Edit family(编辑弹窗的「关联原产品」:成员可展开查看详情并配置标签) ───
|
||||
interface FamilyMemberRow {
|
||||
id: string
|
||||
goodName: string
|
||||
goodImage: string | null
|
||||
source: string
|
||||
delisted: boolean
|
||||
sdsGoodId: string
|
||||
goodPrice: string | null
|
||||
variantCount: number
|
||||
logisticsLabel: string | null
|
||||
craftLabel: string | null
|
||||
warehouseLabel: string | null
|
||||
tagsManual: boolean
|
||||
tags: Array<{ id: string; tagName: string; tagColor: string | null; manual: boolean }>
|
||||
}
|
||||
const editFamilyId = ref<string>('')
|
||||
const editFamilyCode = ref<string | null>(null)
|
||||
const editFamilyMembers = ref<Array<{ id: string; goodName: string; goodImage: string | null }>>([])
|
||||
const editFamilyMembers = ref<FamilyMemberRow[]>([])
|
||||
const familyMemberCount = ref<number | null>(null)
|
||||
const editFamilyLoaded = ref(false)
|
||||
const editFamilyAddKw = ref('')
|
||||
const editFamilyCandidates = ref<Array<{ id: string; goodName: string }>>([])
|
||||
/** 展开的成员行 key(og id) */
|
||||
const expandedMemberKeys = ref<Set<string>>(new Set())
|
||||
/** 成员标签编辑态(key = og id) */
|
||||
const memberTagEdits = ref<Record<string, string[]>>({})
|
||||
const memberTagSaving = ref<string>('')
|
||||
/** 商品自身的链接定价维度(SKU 表列) */
|
||||
const editLinkDims = computed(() => linkDims(editGood.value?.originGood?.goodName ?? null))
|
||||
|
||||
function toggleMemberExpand(key: string) {
|
||||
const next = new Set(expandedMemberKeys.value)
|
||||
if (next.has(key)) next.delete(key)
|
||||
else next.add(key)
|
||||
expandedMemberKeys.value = next
|
||||
}
|
||||
|
||||
/** 成员行视图模型:主链接行补充商品详情接口里的实时数据 */
|
||||
const familyRows = computed(() => {
|
||||
const primaryOgId = String(editGood.value?.originGoodId ?? '')
|
||||
return editFamilyMembers.value.map((m) => {
|
||||
const isPrimary = m.id === primaryOgId
|
||||
return {
|
||||
key: m.id,
|
||||
role: (isPrimary ? 'primary' : 'member') as 'primary' | 'member',
|
||||
...m,
|
||||
variantCount: isPrimary ? (editGood.value?.originGood?.variantCount ?? m.variantCount) : m.variantCount,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
function initEditFamily(family: { familyId?: string; familyCode?: string | null } | null) {
|
||||
editFamilyId.value = family?.familyId ?? ''
|
||||
@@ -671,24 +714,72 @@ function initEditFamily(family: { familyId?: string; familyCode?: string | null
|
||||
editFamilyLoaded.value = false
|
||||
editFamilyAddKw.value = ''
|
||||
editFamilyCandidates.value = []
|
||||
expandedMemberKeys.value = new Set()
|
||||
memberTagEdits.value = {}
|
||||
if (editFamilyId.value) loadEditFamily()
|
||||
}
|
||||
|
||||
async function loadEditFamily() {
|
||||
if (!editFamilyId.value) return
|
||||
try {
|
||||
const f = await productFamiliesApi.detail(editFamilyId.value)
|
||||
const f = await productFamiliesApi.detail(editFamilyId.value) as any
|
||||
editFamilyCode.value = f.familyCode
|
||||
familyMemberCount.value = f._count.originGoods
|
||||
const primaryId = f.primaryOriginGoodId ?? editGood.value?.originGoodId
|
||||
editFamilyMembers.value = f.originGoods
|
||||
.filter((m) => String(m.id) !== String(primaryId))
|
||||
.map((m) => ({ id: String(m.id), goodName: m.goodName, goodImage: m.goodImage }))
|
||||
editFamilyMembers.value = (f.originGoods ?? []).map((m: any) => ({
|
||||
id: String(m.id),
|
||||
goodName: m.goodName ?? '',
|
||||
goodImage: m.goodImage ?? null,
|
||||
source: m.source,
|
||||
delisted: Boolean(m.delisted),
|
||||
sdsGoodId: m.sdsGoodId ?? '',
|
||||
goodPrice: m.goodPrice ?? null,
|
||||
variantCount: m._count?.variants ?? 0,
|
||||
logisticsLabel: m.logisticsLabel ?? null,
|
||||
craftLabel: m.craftLabel ?? null,
|
||||
warehouseLabel: m.warehouseLabel ?? null,
|
||||
tagsManual: Boolean(m.tagsManual),
|
||||
tags: (m.originGoodTags ?? []).map((t: any) => ({
|
||||
id: String(t.tag.id),
|
||||
tagName: t.tag.tagName,
|
||||
tagColor: t.tag.tagColor ?? null,
|
||||
manual: Boolean(t.manual),
|
||||
})),
|
||||
}))
|
||||
// 标签编辑态 = 各成员已保存标签
|
||||
const edits: Record<string, string[]> = {}
|
||||
for (const m of editFamilyMembers.value) edits[m.id] = m.tags.map((t) => t.id)
|
||||
memberTagEdits.value = edits
|
||||
} finally {
|
||||
editFamilyLoaded.value = true
|
||||
}
|
||||
}
|
||||
|
||||
async function saveMemberTags(row: { id: string }) {
|
||||
memberTagSaving.value = row.id
|
||||
try {
|
||||
await originGoodsApi.updateTags(row.id, memberTagEdits.value[row.id] ?? [])
|
||||
ElMessage.success('标签已保存,自动同步不再覆盖该链接')
|
||||
await refreshAfterFamilyChange()
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.response?.data?.message || '保存失败')
|
||||
} finally {
|
||||
memberTagSaving.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
async function resetMemberTags(row: { id: string }) {
|
||||
memberTagSaving.value = row.id
|
||||
try {
|
||||
await originGoodsApi.resetTags(row.id)
|
||||
ElMessage.success('已恢复按链接名称自动解析')
|
||||
await refreshAfterFamilyChange()
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.response?.data?.message || '恢复失败')
|
||||
} finally {
|
||||
memberTagSaving.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshAfterFamilyChange() {
|
||||
if (!editGood.value) return
|
||||
const detail = await goodsApi.getGoodById(editGood.value.id)
|
||||
@@ -750,11 +841,6 @@ async function openEdit(g: Good) {
|
||||
countryId: g.countryId,
|
||||
cascaderCategory: findCategoryPath(allCategories.value, g.categoryId),
|
||||
categoryId: g.categoryId,
|
||||
// 有族时:自动组(物流/工艺/位置)标签为派生数据,不进入可编辑选择
|
||||
tagIds: (g.tags || []).filter((t: any) => {
|
||||
const og = (g as any).originGood?.family
|
||||
return !og || !t.tagGroupId || !autoTagGroupIds.value.has(t.tagGroupId)
|
||||
}).map((t: any) => t.id),
|
||||
positionId: g.positionId || '',
|
||||
}
|
||||
initEditFamily((g as any).originGood?.family ?? null)
|
||||
@@ -884,7 +970,6 @@ async function handleEditSubmit() {
|
||||
: undefined,
|
||||
countryId: Number(editForm.value.countryId),
|
||||
categoryId: Number(editForm.value.categoryId),
|
||||
tagIds: editForm.value.tagIds.map(Number),
|
||||
positionId: editForm.value.positionId ? Number(editForm.value.positionId) : null,
|
||||
} as any)
|
||||
if (customPayload) await goodsApi.updateCustomGoodContent(editForm.value.id, customPayload)
|
||||
@@ -1326,15 +1411,7 @@ const groupedTagOptions = computed(() => {
|
||||
return groups
|
||||
})
|
||||
|
||||
// ─── 派生标签:物流/工艺/印花数量组由系统按链接名称自动生成,表单中只读 ───
|
||||
const isAutoTagGroup = (name: string) => /物流|工艺|位置|印花数量/.test(name)
|
||||
const autoTagGroupIds = computed(() =>
|
||||
new Set(allTagGroups.value.filter((g) => isAutoTagGroup(g.groupName)).map((g) => g.id)),
|
||||
)
|
||||
/** 当前商品的自动组标签(只读展示,来自其链接名称的解析结果) */
|
||||
const editAutoTags = computed(() =>
|
||||
((editGood.value as any)?.tags ?? []).filter((t: any) => autoTagGroupIds.value.has(t.tagGroupId)),
|
||||
)
|
||||
// ─── 派生标签已下沉到链接级(origin_good_tags):在「关联原产品」成员行内配置 ───
|
||||
|
||||
function toggleTagInSelection(tagId: string): void {
|
||||
const idx = selectedTagIds.value.indexOf(tagId)
|
||||
@@ -1959,9 +2036,6 @@ onMounted(() => loadAll())
|
||||
<el-form-item label="图片">
|
||||
<ImageUpload v-model="configForm.goodImage" label="上传图片" />
|
||||
</el-form-item>
|
||||
<el-form-item label="标签">
|
||||
<div class="derived-tags-note">标签无需手动选择:保存后由系统按链接名称自动解析(印花数量 / 工艺 / 物流)</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="configVisible = false">取消</el-button>
|
||||
@@ -2006,62 +2080,75 @@ onMounted(() => loadAll())
|
||||
|
||||
<!-- Edit Good Modal (direct edit — no separate detail step) -->
|
||||
<el-dialog v-model="editVisible" :title="cleanLinkName(editGood?.goodName) || '编辑商品'" width="900px" destroy-on-close>
|
||||
<!-- Origin product reference -->
|
||||
<div v-if="editGood?.originGood" class="edit-og-ref">
|
||||
<el-image v-if="editGood.originGood.goodImage" :src="editGood.originGood.goodImage" fit="cover" class="edit-og-img" />
|
||||
<div class="edit-og-meta">
|
||||
<div class="edit-og-label">{{ editIsCustom ? '自定义商品' : '关联原产品' }}</div>
|
||||
<div class="edit-og-name" :title="editGood.originGood.goodName ?? undefined">{{ cleanLinkName(editGood.originGood.goodName) }}</div>
|
||||
<div class="edit-og-sub">{{ editIsCustom ? '自定义 ID' : 'SDS ID' }}: {{ editGood.originGood.sdsGoodId }}<template v-if="editGood.originGood.goodPrice"> · ¥{{ editGood.originGood.goodPrice }}</template></div>
|
||||
<div class="edit-og-status">
|
||||
<el-tag :type="editGood.originGood.hasDetail ? 'success' : 'warning'" size="small">
|
||||
{{ editGood.originGood.hasDetail ? '详情已同步' : '详情未同步' }}
|
||||
</el-tag>
|
||||
<span v-if="editGood.originGood.detailSyncedAt">
|
||||
{{ new Date(editGood.originGood.detailSyncedAt).toLocaleString() }}
|
||||
</span>
|
||||
<span>SKU {{ editGood.originGood.variantCount || 0 }}</span>
|
||||
<span>尺码 {{ editGood.originGood.sizeRowCount || 0 }}</span>
|
||||
<span>包装 {{ editGood.originGood.packageRowCount || 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-button
|
||||
v-if="!editIsCustom"
|
||||
type="primary"
|
||||
plain
|
||||
size="small"
|
||||
:icon="Refresh"
|
||||
:loading="detailSyncing"
|
||||
@click="handleSyncOneDetail"
|
||||
>同步详情</el-button>
|
||||
</div>
|
||||
<div v-if="!editIsCustom" class="edit-merged-box">
|
||||
<div class="edit-merged-title">
|
||||
关联原产品(族成员)
|
||||
<el-tooltip content="同族链接合并为一个商品:尺码/包装并集 + 价格矩阵;主链接决定详情与跳转。">
|
||||
关联原产品
|
||||
<el-tooltip content="同族链接合并为一个商品:尺码/包装并集 + 价格矩阵;主链接决定详情与跳转。展开成员可查看详情并修正标签。">
|
||||
<el-icon><QuestionFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
<span v-if="editFamilyCode" class="edit-family-code">族 {{ editFamilyCode }} · {{ familyMemberCount ?? editFamilyMembers.length + 1 }} 条链接</span>
|
||||
<span v-if="editFamilyCode" class="edit-family-code">族 {{ editFamilyCode }} · {{ familyMemberCount ?? editFamilyMembers.length }} 条链接</span>
|
||||
</div>
|
||||
<div class="edit-merged-list">
|
||||
<div class="edit-merged-item primary">
|
||||
<span class="edit-merged-tag">主</span>
|
||||
<span class="edit-merged-name" :title="editGood?.originGood?.goodName ?? undefined">{{ cleanLinkName(editGood?.originGood?.goodName) }}</span>
|
||||
<span v-if="deriveLinkTagNames(editGood?.originGood?.goodName).length" class="member-chips">
|
||||
<span v-for="c in deriveLinkTagNames(editGood?.originGood?.goodName)" :key="c" class="member-chip">{{ c }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div v-for="m in editFamilyMembers" :key="m.id" class="edit-merged-item">
|
||||
<span class="edit-merged-tag sub">族</span>
|
||||
<span class="edit-merged-name" :title="m.goodName">{{ cleanLinkName(m.goodName) }}</span>
|
||||
<span v-if="deriveLinkTagNames(m.goodName).length" class="member-chips">
|
||||
<span v-for="c in deriveLinkTagNames(m.goodName)" :key="c" class="member-chip">{{ c }}</span>
|
||||
</span>
|
||||
<div class="edit-merged-actions">
|
||||
<el-button size="small" link type="primary" @click="promoteFamilyPrimary(m)">设为主链接</el-button>
|
||||
<el-button size="small" link type="danger" @click="removeFamilyMember(m)">移除出族</el-button>
|
||||
<template v-for="row in familyRows" :key="row.key">
|
||||
<div class="edit-merged-item" :class="{ primary: row.role === 'primary' }">
|
||||
<el-icon
|
||||
class="member-expand-toggle"
|
||||
:class="{ 'is-expanded': expandedMemberKeys.has(row.key) }"
|
||||
@click="toggleMemberExpand(row.key)"
|
||||
><ArrowDown /></el-icon>
|
||||
<span class="edit-merged-tag" :class="{ sub: row.role !== 'primary' }">{{ row.role === 'primary' ? '主' : '族' }}</span>
|
||||
<span class="edit-merged-name" :title="row.goodName">{{ cleanLinkName(row.goodName) }}</span>
|
||||
<span v-if="row.tags.length || deriveLinkTagNames(row.goodName).length" class="member-chips">
|
||||
<template v-if="row.tags.length">
|
||||
<span v-for="c in row.tags" :key="c.id" class="member-chip" :class="{ 'is-manual': c.manual }">{{ c.tagName }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span v-for="c in deriveLinkTagNames(row.goodName)" :key="c" class="member-chip is-preview" :title="'按名称解析预览,展开可配置'">{{ c }}</span>
|
||||
</template>
|
||||
</span>
|
||||
<span v-if="row.tagsManual" class="member-manual-flag" title="人工接管:自动同步不再覆盖该链接的标签">人工</span>
|
||||
<div v-if="row.role !== 'primary'" class="edit-merged-actions">
|
||||
<el-button size="small" link type="primary" @click="promoteFamilyPrimary(row)">设为主链接</el-button>
|
||||
<el-button size="small" link type="danger" @click="removeFamilyMember(row)">移除出族</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="expandedMemberKeys.has(row.key)" class="member-detail-panel">
|
||||
<div class="member-detail-grid">
|
||||
<span class="md-label">SDS ID</span><span class="md-val">{{ row.sdsGoodId || '-' }}</span>
|
||||
<span class="md-label">链接价格</span><span class="md-val">{{ row.goodPrice ? `¥${row.goodPrice}` : '-' }}</span>
|
||||
<span class="md-label">SKU 数</span><span class="md-val">{{ row.variantCount }}</span>
|
||||
<span class="md-label">物流备注</span><span class="md-val">{{ row.logisticsLabel || '-' }}</span>
|
||||
<span class="md-label">工艺位置</span><span class="md-val">{{ row.craftLabel || '-' }}</span>
|
||||
<span class="md-label">仓库</span><span class="md-val">{{ row.warehouseLabel || '-' }}</span>
|
||||
<span class="md-label">原始名称</span><span class="md-val" :title="row.goodName">{{ row.goodName }}</span>
|
||||
</div>
|
||||
<div class="member-tags-edit">
|
||||
<span class="md-label">标签</span>
|
||||
<el-select
|
||||
v-model="memberTagEdits[row.key]"
|
||||
multiple filterable size="small"
|
||||
placeholder="选择标签"
|
||||
style="flex:1"
|
||||
>
|
||||
<el-option-group v-for="g in groupedTagOptions" :key="g.id" :label="g.label">
|
||||
<el-option v-for="t in g.tags" :key="t.id" :label="t.tagName" :value="t.id" />
|
||||
</el-option-group>
|
||||
</el-select>
|
||||
<el-button size="small" type="primary" :loading="memberTagSaving === row.key" @click="saveMemberTags(row)">保存标签</el-button>
|
||||
<el-button v-if="row.tagsManual" size="small" :loading="memberTagSaving === row.key" @click="resetMemberTags(row)">恢复自动</el-button>
|
||||
<el-button
|
||||
v-if="row.role === 'primary'"
|
||||
size="small"
|
||||
:icon="Refresh"
|
||||
:loading="detailSyncing"
|
||||
@click="handleSyncOneDetail"
|
||||
>同步详情</el-button>
|
||||
</div>
|
||||
<div class="derived-tags-note">
|
||||
标签按链接名称自动解析(印花数量 / 工艺 / 物流);解析不对可直接修改,保存后自动同步不再覆盖该链接,「恢复自动」回到按名称派生。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="!editFamilyLoaded && editFamilyId" class="edit-family-loading">族成员加载中…</div>
|
||||
<div v-else-if="!editFamilyId" class="edit-family-loading">暂未成族:配置合并时自动成族</div>
|
||||
</div>
|
||||
@@ -2087,15 +2174,6 @@ onMounted(() => loadAll())
|
||||
<el-form-item label="分类">
|
||||
<el-cascader v-model="editForm.cascaderCategory" :options="categoryCascader as any" :props="{ checkStrictly: true }" placeholder="请选择分类" @change="onEditCascaderChange" />
|
||||
</el-form-item>
|
||||
<el-form-item label="标签">
|
||||
<div class="derived-tags-row derived-tags-readonly">
|
||||
<template v-if="editAutoTags.length">
|
||||
<el-tag v-for="t in editAutoTags" :key="t.id" size="small" class="derived-tag">{{ t.tagName }}</el-tag>
|
||||
</template>
|
||||
<span v-else class="derived-tags-note">暂无自动标签</span>
|
||||
<span class="derived-tags-note">由链接名称自动解析(印花数量 / 工艺 / 物流),不可手动编辑</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="editIsCustom" label="基础价格">
|
||||
<el-input v-model="customContentForm.goodPrice" placeholder="例如 28.00" />
|
||||
</el-form-item>
|
||||
@@ -2181,17 +2259,29 @@ onMounted(() => loadAll())
|
||||
<el-table-column label="操作" width="70"><template #default="{ $index }"><el-button link type="danger" @click="customContentForm.variants.splice($index, 1)">删除</el-button></template></el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
<el-table v-else :data="editVariants" border max-height="320">
|
||||
<el-table-column prop="sku" label="SKU" min-width="170" fixed />
|
||||
<el-table-column prop="sizeName" label="尺码" width="90" />
|
||||
<el-table-column prop="colorName" label="颜色" width="100" />
|
||||
<el-table-column prop="price" label="价格" width="100" />
|
||||
<el-table-column label="状态" width="90">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.enabled ? 'success' : 'info'" size="small">{{ row.enabled ? '可用' : '停用' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template v-else>
|
||||
<div class="sku-dims-note">价格由 物流 × 工艺 × 印花数量 × 尺码 × 颜色 决定(族内同组合取最低价,人工改价走族价格覆盖)</div>
|
||||
<el-table :data="editVariants" border max-height="320">
|
||||
<el-table-column prop="sku" label="SKU" min-width="170" fixed />
|
||||
<el-table-column label="物流" width="110">
|
||||
<template #default>{{ editLinkDims.logistics || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="工艺" width="100">
|
||||
<template #default>{{ editLinkDims.crafts.join(' / ') || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="印花数量" width="100">
|
||||
<template #default>{{ editLinkDims.printCount || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sizeName" label="尺码" width="90" />
|
||||
<el-table-column prop="colorName" label="颜色" width="100" />
|
||||
<el-table-column prop="price" label="价格" width="100" />
|
||||
<el-table-column label="状态" width="90">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.enabled ? 'success' : 'info'" size="small">{{ row.enabled ? '可用' : '停用' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<el-empty v-else-if="!editDetailLoading" description="尚未同步商品详情" :image-size="60" />
|
||||
@@ -2514,16 +2604,6 @@ onMounted(() => loadAll())
|
||||
|
||||
.origin-ref { display: flex; align-items: center; gap: 8px; }
|
||||
|
||||
.edit-og-ref {
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
padding: 12px; background: #f5f7fa; border-radius: 8px;
|
||||
}
|
||||
.edit-og-img { width: 48px; height: 48px; border-radius: 6px; flex-shrink: 0; }
|
||||
.edit-og-label { font-size: 11px; color: #909399; text-transform: uppercase; letter-spacing: 0.5px; }
|
||||
.edit-og-name { font-weight: 600; font-size: 14px; margin-top: 2px; }
|
||||
.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;
|
||||
@@ -2562,6 +2642,30 @@ onMounted(() => loadAll())
|
||||
font-size: 11px; line-height: 16px; padding: 1px 7px; border-radius: 8px;
|
||||
background: #ecf5ff; color: var(--el-color-primary); white-space: nowrap;
|
||||
}
|
||||
.member-chip.is-manual { background: #f0f9eb; color: var(--el-color-success); }
|
||||
.member-chip.is-preview { background: #f4f4f5; color: #909399; border: 1px dashed #dcdfe6; }
|
||||
.member-manual-flag {
|
||||
font-size: 11px; line-height: 16px; padding: 0 6px; border-radius: 4px;
|
||||
background: #f0f9eb; color: var(--el-color-success); flex-shrink: 0;
|
||||
}
|
||||
.member-expand-toggle {
|
||||
cursor: pointer; flex-shrink: 0; color: #909399;
|
||||
transition: transform 0.18s;
|
||||
}
|
||||
.member-expand-toggle.is-expanded { transform: rotate(180deg); }
|
||||
.member-detail-panel {
|
||||
background: #fbfcfe; border: 1px solid #ebeef5; border-radius: 6px;
|
||||
padding: 10px 12px; margin: -2px 0 2px 30px;
|
||||
}
|
||||
.member-detail-grid {
|
||||
display: grid; grid-template-columns: 64px minmax(0, 1fr) 64px minmax(0, 1fr);
|
||||
gap: 4px 10px; font-size: 12px; margin-bottom: 8px;
|
||||
}
|
||||
.md-label { color: #909399; }
|
||||
.md-val { color: #606266; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.member-tags-edit { display: flex; align-items: center; gap: 8px; }
|
||||
.member-tags-edit .md-label { flex-shrink: 0; }
|
||||
.sku-dims-note { font-size: 12px; color: var(--el-text-color-secondary); margin-bottom: 8px; }
|
||||
.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;
|
||||
|
||||
Reference in New Issue
Block a user