修改导入套餐

This commit is contained in:
wuweihua
2026-09-30 11:30:34 +08:00
parent ea23fb0e55
commit 998aac74c8
7 changed files with 132 additions and 114 deletions
@@ -22,6 +22,7 @@
:products="products"
:price-set-id="priceSetId"
:psv-id="version.psvId"
@update:psv-id="v => { if (v != null && v !== '') version.psvId = v }"
:set-type="setType"
:effective-time="version.effectiveTime"
:version-no="version.versionNo"
@@ -6,34 +6,19 @@
{{ headerTitle }}
<span class="sub">{{ typeHint }}</span>
</span>
<div class="card-actions">
<el-button size="small" :disabled="!priceSetId || pulling" @click="handlePullLibrary">拉取产品列表</el-button>
<el-button
v-if="isFixedType(setType)"
type="primary"
size="small"
:disabled="!priceSetId || pulling"
@click="openImport"
>导入 Excel 建价</el-button>
</div>
</div>
</template>
<div v-if="showBuildActions" class="build-choice">
<p class="build-choice__tip">暂无套餐销售价,请选择一种建价方式</p>
<div class="build-choice__cards">
<button
type="button"
class="build-card build-card--pull"
:disabled="!priceSetId"
@click="handlePullLibrary"
>
<div class="build-card__title">拉取产品列表</div>
<div class="build-card__desc">按产品库基础价生成本版本套餐销售价</div>
</button>
<button
type="button"
class="build-card build-card--import"
:disabled="!priceSetId || !isFixedType(setType)"
@click="openImport"
>
<div class="build-card__title">导入 Excel 建价</div>
<div class="build-card__desc">上传威狮B2C小包价表,直接生成套餐销售价</div>
</button>
</div>
</div>
<template v-else>
<div class="filters">
<el-input v-model="query.keyword" clearable :placeholder="phKeyword" style="width: 180px" />
<el-select v-model="query.taxMode" clearable :placeholder="phTax" style="width: 140px">
@@ -42,7 +27,12 @@
<el-button size="small" @click="noop">查询</el-button>
</div>
<el-table :data="filteredProducts" border>
<el-empty
v-if="!(filteredProducts && filteredProducts.length)"
:description="emptyTip"
:image-size="80"
/>
<el-table v-else :data="filteredProducts" border>
<el-table-column :label="colCode" min-width="80">
<template #default="{ row }"><b>{{ row.productCode }}</b></template>
</el-table-column>
@@ -55,11 +45,6 @@
</template>
</el-table-column>
<el-table-column :label="colRegion" prop="destRegion" width="90" align="center" />
<!-- <el-table-column :label="colTax" width="90" align="center">
<template #default="{ row }">
<dict-tag :options="bms_tax_mode" :value="row.taxMode" />
</template>
</el-table-column> -->
<el-table-column :label="colAction" width="220" align="center" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="openPrice(row)">编辑价格</el-button>
@@ -68,7 +53,6 @@
</template>
</el-table-column>
</el-table>
</template>
<surcharge-picker-dialog
v-model="scVisible"
@@ -156,6 +140,7 @@ const props = defineProps({
headerTitle: { type: String, default: '第 2 步 · 编辑产品价格' }
})
const emit = defineEmits(['update:psvId'])
const { proxy } = getCurrentInstance()
const { bms_tax_mode } = useDict('bms_tax_mode')
const { hydrateRow } = useDetailCascade(props.refData)
@@ -204,9 +189,29 @@ const importDesc = [
]
const typeHint = computed(() => '· 当前:' + setTypeLabel(props.setType))
const emptyTip = '暂无产品,请先拉取产品列表或导入 Excel 建价'
/** Show pull/import when this version has no set-product rows yet. */
const showBuildActions = computed(() => !(props.products || []).some(p => p && p.productCode))
/** Resolve version id: prop first, else reload package current version. */
const resolvedPsvId = ref(props.psvId)
watch(() => props.psvId, v => { if (v != null && v !== '') resolvedPsvId.value = v })
async function resolvePsvId(force) {
if (!force && resolvedPsvId.value != null && resolvedPsvId.value !== '') {
return resolvedPsvId.value
}
if (!props.priceSetId) return null
const res = await getPriceSet(props.priceSetId)
const data = res.data || {}
const versions = data.versions || []
const fromData = (data.version && data.version.psvId) ? data.version : null
const current = fromData || versions[0] || {}
if (current.psvId != null) {
resolvedPsvId.value = current.psvId
emit('update:psvId', current.psvId)
}
return resolvedPsvId.value || null
}
/** productCode -> detail rows (loaded on demand) */
const detailByProduct = reactive({})
@@ -218,8 +223,9 @@ function clearDetailCache() {
async function loadProductDetails(productCode, force) {
if (!productCode) return []
if (!force && detailByProduct[productCode]) return detailByProduct[productCode]
if (!props.psvId) return []
const res = await listSetDetailByProduct(props.psvId, productCode)
const psvId = await resolvePsvId(false)
if (!psvId) return []
const res = await listSetDetailByProduct(psvId, productCode)
const rows = (res.data || []).map(d => hydrateRow(d, props.setType))
detailByProduct[productCode] = rows
// keep parent details array in sync for any legacy callers
@@ -258,7 +264,7 @@ const pulling = ref(false)
function noop() {}
function openImport() {
async function openImport() {
if (!props.priceSetId) {
proxy.$modal.msgWarning('缺少套餐ID')
return
@@ -267,6 +273,11 @@ function openImport() {
proxy.$modal.msgWarning('仅固定价套餐支持导入')
return
}
const psvId = await resolvePsvId(true)
if (!psvId) {
proxy.$modal.msgWarning('缺少套餐版本,请先保存基本信息')
return
}
importRef.value && importRef.value.open()
}
@@ -284,7 +295,7 @@ async function runImportAsync(uploadFile) {
try {
const { task } = await importSetDetailPricesAsync(
raw,
{ priceSetId: props.priceSetId, psvId: props.psvId },
{ priceSetId: props.priceSetId, psvId: await resolvePsvId(false) },
applyBuildProgress
)
importProgressPercent.value = 100
@@ -336,9 +347,14 @@ async function reloadDetailsFromServer() {
const res = await getPriceSet(props.priceSetId)
const data = res.data || {}
const versions = data.versions || []
const qPsv = props.psvId
const qPsv = resolvedPsvId.value || props.psvId
const byQuery = qPsv != null ? versions.find(v => String(v.psvId) === String(qPsv)) : null
const current = byQuery || data.version || versions[0] || {}
const fromData = (data.version && data.version.psvId) ? data.version : null
const current = byQuery || fromData || versions[0] || {}
if (current.psvId != null) {
resolvedPsvId.value = current.psvId
emit('update:psvId', current.psvId)
}
if (Array.isArray(props.details)) {
props.details.splice(0, props.details.length)
}
@@ -387,7 +403,7 @@ async function pullAllFromLibrary(opts = {}) {
pulling.value = true
try {
const { task } = await pullSetDetailsFromLibraryAsync(
{ priceSetId: props.priceSetId, psvId: props.psvId, force },
{ priceSetId: props.priceSetId, psvId: await resolvePsvId(false), force },
applyBuildProgress
)
importProgressPercent.value = 100
@@ -691,57 +707,11 @@ defineExpose({ pullAllFromLibrary })
font-size: 13px;
word-break: break-all;
}
.build-choice {
padding: 48px 24px 56px;
text-align: center;
}
.build-choice__tip {
margin: 0 0 28px;
color: #909399;
font-size: 14px;
}
.build-choice__cards {
.card-actions {
flex: none;
display: flex;
justify-content: center;
gap: 20px;
flex-wrap: wrap;
}
.build-card {
width: 280px;
min-height: 120px;
padding: 24px 20px;
border: 1px solid #e4e7ed;
border-radius: 10px;
background: #fff;
cursor: pointer;
text-align: left;
transition: border-color 0.2s, box-shadow 0.2s;
}
.build-card:hover:not(:disabled) {
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.06);
}
.build-card:disabled {
opacity: 0.55;
cursor: not-allowed;
}
.build-card--pull:hover:not(:disabled) {
border-color: #67c23a;
}
.build-card--import:hover:not(:disabled) {
border-color: #409eff;
}
.build-card__title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 10px;
}
.build-card--pull .build-card__title { color: #67c23a; }
.build-card--import .build-card__title { color: #409eff; }
.build-card__desc {
font-size: 13px;
line-height: 1.6;
color: #909399;
align-items: center;
gap: 8px;
}
.filters {
display: flex;
+30 -3
View File
@@ -16,6 +16,7 @@
:products="form.products"
:price-set-id="form.priceSetId"
:psv-id="form.version.psvId"
@update:psv-id="onPsvIdUpdate"
:set-type="form.basic.setType"
:version-no="form.version.versionNo"
:ref-data="refData"
@@ -141,7 +142,7 @@ async function runSaveWithProgress(payload) {
}
}
/** Step1 -> Step2: create empty draft version; pull/import on Step 2 */
/** Step1 -> Step2: create empty draft version only; products/details persist on pull or Excel import */
async function goStep2() {
const validBasic = await basicRef.value.validate()
if (!validBasic) return
@@ -153,10 +154,31 @@ async function goStep2() {
creating.value = true
try {
const priceSetId = await runSaveWithProgress(buildPayload())
// Never auto-pull product library on enter Step2 (even if old clients sent the flag).
const payload = buildPayload()
delete payload.pullFromLibrary
delete payload.forcePullLibrary
delete payload.details
const priceSetId = await runSaveWithProgress(payload)
form.priceSetId = priceSetId
const fresh = await getPriceSet(priceSetId)
applySetData(fresh.data || {}, false)
form.details = []
form.products = []
if (!form.version.psvId) {
const versions = (fresh.data && fresh.data.versions) || []
const v = (fresh.data && fresh.data.version && fresh.data.version.psvId)
? fresh.data.version
: versions[0]
if (v && v.psvId) {
form.version.psvId = v.psvId
form.version.versionNo = v.versionNo || form.version.versionNo
}
}
if (!form.version.psvId) {
proxy.$modal.msgError('套餐版本创建失败,请重试')
return
}
active.value = 1
proxy.$modal.msgSuccess('已创建空套餐版本,请在第 2 步拉取或导入价格')
} catch (e) {
@@ -241,6 +263,10 @@ function goBack() {
}
}
function onPsvIdUpdate(psvId) {
if (psvId != null && psvId !== '') form.version.psvId = psvId
}
function applySetData(data, asCopy) {
form.priceSetId = data.priceSetId
Object.assign(form.basic, {
@@ -252,7 +278,8 @@ function applySetData(data, asCopy) {
const versions = data.versions || []
const qPsv = route.query.psvId
const byQuery = qPsv != null ? versions.find(v => String(v.psvId) === String(qPsv)) : null
const current = byQuery || data.version || versions[0] || {}
const fromData = (data.version && data.version.psvId) ? data.version : null
const current = byQuery || fromData || versions[0] || {}
if (asCopy) {
form.version.psvId = undefined
form.version.versionNo = nextVersionNo(versions)