修改产品价和红线价功能
This commit is contained in:
@@ -1,10 +1,7 @@
|
||||
<!-- start catalog version: pick effective hour -->
|
||||
<!-- start catalog version: pick effective hour;IMPORT_FRESH 对齐销售价规则 -->
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="dialogTitle" width="460px" append-to-body @closed="onClosed">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item :label="labNo">
|
||||
<span>{{ form.versionNo || '-' }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="labEff" prop="effectiveTime">
|
||||
<el-date-picker
|
||||
v-model="form.effectiveTime"
|
||||
@@ -17,6 +14,7 @@
|
||||
:disabled-hours="disabledHours"
|
||||
style="width: 100%"
|
||||
@change="onTimeChange"
|
||||
@visible-change="onPickerVisible"
|
||||
/>
|
||||
</el-form-item>
|
||||
<div class="activate-hint">{{ hint }}</div>
|
||||
@@ -28,6 +26,8 @@
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup>
|
||||
import { isImportFreshBuildMode } from '@/views/price/set/utils/priceSet'
|
||||
|
||||
const props = defineProps({
|
||||
loadVersions: { type: Function, required: true },
|
||||
activateFn: { type: Function, required: true }
|
||||
@@ -39,31 +39,43 @@ const submitting = ref(false)
|
||||
const formRef = ref()
|
||||
const versions = ref([])
|
||||
const prevEff = ref()
|
||||
const form = reactive({ psvId: undefined, versionNo: '', versionSeq: 0, effectiveTime: undefined })
|
||||
const isImportFresh = ref(false)
|
||||
const boundaryTimes = ref([])
|
||||
const form = reactive({ psvId: undefined, versionNo: '', versionSeq: 0, effectiveTime: undefined, priceBuildMode: undefined })
|
||||
const dialogTitle = '启动版本'
|
||||
const labNo = '版本号'
|
||||
const labEff = '生效时间'
|
||||
const ph = '请选择生效时间'
|
||||
const btnCancel = '取消'
|
||||
const btnOk = '确认启动'
|
||||
const beforePrev = '生效时间不能早于上一版本的生效时间'
|
||||
const hitBoundary = '生效时间不能与已有版本的生效时间或失效时间相同'
|
||||
const hint = computed(() => {
|
||||
if (isImportFresh.value) {
|
||||
return '重新建价:生效时间可落在历史版本区间内;落在版本 B 区间时,B 失效时间改为本次启动时间,本版本失效时间取 B 原失效时间,并同步产品/明细。不可与本类型已有版本的生效时间或失效时间相同。'
|
||||
}
|
||||
const base = '若生效时间晚于当前,版本将进入待生效;到时自动生效并使上一版本失效。'
|
||||
return prevEff.value ? (base + '不得早于上一版:' + prevEff.value + '。') : base
|
||||
})
|
||||
const rules = {
|
||||
const rules = computed(() => ({
|
||||
effectiveTime: [
|
||||
{ required: true, message: ph, trigger: 'change' },
|
||||
{
|
||||
validator: (_r, v, cb) => {
|
||||
if (!v || !prevEff.value) return cb()
|
||||
if (toMs(v) < toMs(prevEff.value)) return cb(new Error(beforePrev))
|
||||
if (!v) return cb()
|
||||
if (isImportFresh.value) {
|
||||
const ms = toMs(v)
|
||||
if (boundaryTimes.value.some(b => toMs(b) === ms)) {
|
||||
return cb(new Error(hitBoundary))
|
||||
}
|
||||
return cb()
|
||||
}
|
||||
if (prevEff.value && toMs(v) < toMs(prevEff.value)) return cb(new Error(beforePrev))
|
||||
cb()
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
}
|
||||
}))
|
||||
function pad2(n) { return String(n).padStart(2, '0') }
|
||||
function formatHour(d) {
|
||||
if (!d || Number.isNaN(d.getTime())) return undefined
|
||||
@@ -93,14 +105,29 @@ function findPrev(row) {
|
||||
}
|
||||
return prev ? normalizeToHour(prev.effectiveTime) : undefined
|
||||
}
|
||||
function pickDefault(prev) {
|
||||
function collectBoundaryTimes(excludePsvId) {
|
||||
const out = []
|
||||
const seen = new Set()
|
||||
for (const v of versions.value || []) {
|
||||
if (!v || v.psvId == null || String(v.psvId) === String(excludePsvId)) continue
|
||||
;[v.effectiveTime, v.expireTime].forEach(t => {
|
||||
const n = t ? normalizeToHour(t) : ''
|
||||
if (!n || seen.has(n)) return
|
||||
seen.add(n)
|
||||
out.push(n)
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
function pickDefault(prev, importFresh) {
|
||||
const d = new Date()
|
||||
d.setHours(9, 0, 0, 0)
|
||||
const now = formatHour(d)
|
||||
if (!prev) return now
|
||||
if (importFresh || !prev) return now
|
||||
return toMs(now) >= toMs(prev) ? now : prev
|
||||
}
|
||||
function disabledDate(date) {
|
||||
if (isImportFresh.value) return false
|
||||
if (!prevEff.value || !date) return false
|
||||
const prevDay = new Date(String(prevEff.value).replace(/-/g, '/'))
|
||||
prevDay.setHours(0, 0, 0, 0)
|
||||
@@ -109,11 +136,25 @@ function disabledDate(date) {
|
||||
return d.getTime() < prevDay.getTime()
|
||||
}
|
||||
function disabledHours() {
|
||||
if (!prevEff.value) return []
|
||||
const prevD = new Date(String(prevEff.value).replace(/-/g, '/'))
|
||||
const curD = form.effectiveTime
|
||||
? new Date(String(normalizeToHour(form.effectiveTime)).replace(/-/g, '/'))
|
||||
: new Date()
|
||||
if (isImportFresh.value) {
|
||||
const hours = new Set()
|
||||
for (const b of boundaryTimes.value) {
|
||||
const bd = new Date(String(b).replace(/-/g, '/'))
|
||||
if (
|
||||
bd.getFullYear() === curD.getFullYear() &&
|
||||
bd.getMonth() === curD.getMonth() &&
|
||||
bd.getDate() === curD.getDate()
|
||||
) {
|
||||
hours.add(bd.getHours())
|
||||
}
|
||||
}
|
||||
return [...hours]
|
||||
}
|
||||
if (!prevEff.value) return []
|
||||
const prevD = new Date(String(prevEff.value).replace(/-/g, '/'))
|
||||
if (prevD.getFullYear() !== curD.getFullYear() || prevD.getMonth() !== curD.getMonth() || prevD.getDate() !== curD.getDate()) return []
|
||||
const hours = []
|
||||
for (let h = 0; h < prevD.getHours(); h++) hours.push(h)
|
||||
@@ -123,6 +164,9 @@ function onTimeChange(val) {
|
||||
form.effectiveTime = normalizeToHour(val)
|
||||
formRef.value && formRef.value.validateField('effectiveTime')
|
||||
}
|
||||
function onPickerVisible(visible) {
|
||||
if (visible && formRef.value) formRef.value.validateField('effectiveTime')
|
||||
}
|
||||
async function open(row) {
|
||||
if (!row || row.psvId == null) return
|
||||
const list = await props.loadVersions()
|
||||
@@ -131,15 +175,21 @@ async function open(row) {
|
||||
form.psvId = found.psvId
|
||||
form.versionNo = found.versionNo || ''
|
||||
form.versionSeq = found.versionSeq
|
||||
form.priceBuildMode = found.priceBuildMode
|
||||
isImportFresh.value = isImportFreshBuildMode(found.priceBuildMode)
|
||||
prevEff.value = findPrev(found)
|
||||
form.effectiveTime = pickDefault(prevEff.value)
|
||||
boundaryTimes.value = isImportFresh.value ? collectBoundaryTimes(found.psvId) : []
|
||||
form.effectiveTime = pickDefault(prevEff.value, isImportFresh.value)
|
||||
visible.value = true
|
||||
}
|
||||
function onClosed() {
|
||||
form.psvId = undefined
|
||||
form.versionNo = ''
|
||||
form.effectiveTime = undefined
|
||||
form.priceBuildMode = undefined
|
||||
prevEff.value = undefined
|
||||
isImportFresh.value = false
|
||||
boundaryTimes.value = []
|
||||
formRef.value && formRef.value.clearValidate()
|
||||
}
|
||||
function submit() {
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
<div class="build-choice__cards">
|
||||
<button
|
||||
type="button"
|
||||
class="build-card build-card--pull"
|
||||
:class="{ selected: buildMode === 'COPY' }"
|
||||
class="build-card build-card--import"
|
||||
:class="{ selected: buildMode === 'IMPORT' }"
|
||||
:disabled="!!psvId || choosing || finishing"
|
||||
@click="chooseMode(true)"
|
||||
>
|
||||
@@ -39,8 +39,8 @@
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="build-card build-card--import"
|
||||
:class="{ selected: buildMode === 'IMPORT' }"
|
||||
class="build-card build-card--import-fresh"
|
||||
:class="{ selected: buildMode === 'IMPORT_FRESH' }"
|
||||
:disabled="!!psvId || choosing || finishing"
|
||||
@click="chooseMode(false)"
|
||||
>
|
||||
@@ -183,7 +183,7 @@ const route = useRoute()
|
||||
const labTip = '创建价格方式'
|
||||
const labModeCopy = '按现有生效版本建价'
|
||||
const labModeCopyDesc = '先本地预览已生效版本产品,点击「完成」时再复制到待生效草稿'
|
||||
const labModeFresh = '重新建产品价'
|
||||
const labModeFresh = '新建产品价'
|
||||
const labModeFreshDesc = "不沿用已生效版本;导入解析后先展示,点「完成」时再写入草稿"
|
||||
const labImport = '导入 Excel 建价'
|
||||
const labImportAll = '全部导入(多国家价表)'
|
||||
@@ -300,9 +300,10 @@ function applyVersion(ver) {
|
||||
}
|
||||
psvId.value = ver.psvId
|
||||
versionNo.value = ver.versionNo || ''
|
||||
// Catalog: IMPORT = 沿用生效版; IMPORT_FRESH = 重新建价. Legacy COPY → IMPORT.
|
||||
const mode = String(ver.priceBuildMode || '').toUpperCase()
|
||||
if (mode === 'COPY') buildMode.value = 'COPY'
|
||||
else if (mode === 'IMPORT' || mode === 'PULL') buildMode.value = 'IMPORT'
|
||||
if (mode === 'IMPORT' || mode === 'COPY') buildMode.value = 'IMPORT'
|
||||
else if (mode === 'IMPORT_FRESH' || mode === 'PULL') buildMode.value = 'IMPORT_FRESH'
|
||||
}
|
||||
|
||||
async function loadProducts() {
|
||||
@@ -329,7 +330,7 @@ async function bootstrap() {
|
||||
psvId.value = Number(qPsv)
|
||||
versionNo.value = (route.query && route.query.versionNo) ? String(route.query.versionNo) : ''
|
||||
editingExisting.value = true
|
||||
if (!buildMode.value) buildMode.value = 'IMPORT'
|
||||
if (!buildMode.value) buildMode.value = 'IMPORT_FRESH'
|
||||
await loadProducts()
|
||||
}
|
||||
} finally {
|
||||
@@ -337,10 +338,10 @@ async function bootstrap() {
|
||||
}
|
||||
}
|
||||
|
||||
/** Mode select: local only, no DB write. */
|
||||
/** Mode select: local only, no DB write. inherit=IMPORT, fresh=IMPORT_FRESH. */
|
||||
async function chooseMode(copyFromLatest) {
|
||||
if (psvId.value || choosing.value || finishing.value) return
|
||||
const next = copyFromLatest ? 'COPY' : 'IMPORT'
|
||||
const next = copyFromLatest ? 'IMPORT' : 'IMPORT_FRESH'
|
||||
if (buildMode.value === next && (copyFromLatest ? rows.value.length : true)) {
|
||||
if (copyFromLatest && rows.value.length) return
|
||||
if (!copyFromLatest && !rows.value.length && !pendingImports.value.length) return
|
||||
@@ -426,7 +427,7 @@ async function persistDraft() {
|
||||
proxy.$modal.msgWarning('请先选择建价方式')
|
||||
return false
|
||||
}
|
||||
const res = await props.prepareFn(buildMode.value === 'COPY')
|
||||
const res = await props.prepareFn(buildMode.value === 'IMPORT')
|
||||
applyVersion(res.data)
|
||||
return !!psvId.value
|
||||
}
|
||||
@@ -682,8 +683,10 @@ onMounted(() => { bootstrap() })
|
||||
.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__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--import-fresh .build-card__title { color: #e6a23c; }
|
||||
.build-card--import:hover:not(:disabled) { border-color: #409eff; }
|
||||
.build-card--import-fresh:hover:not(:disabled) { border-color: #e6a23c; }
|
||||
.build-card__desc { font-size: 13px; line-height: 1.6; color: #909399; }
|
||||
.filters { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
|
||||
.filters__left { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
:disabled="!canChooseBuildMode || !isFixedType(setType)"
|
||||
@click="chooseBuildMode('IMPORT_FRESH')"
|
||||
>
|
||||
<div class="build-card__title">Excel 重新建价</div>
|
||||
<div class="build-card__title">Excel 新建价</div>
|
||||
<div class="build-card__desc">不延用以前的产品价格,不带出上一版,上传 Excel 直接生成套餐销售价</div>
|
||||
</button>
|
||||
</div>
|
||||
@@ -338,7 +338,7 @@ const importDesc = computed(() => {
|
||||
return [
|
||||
'适用威狮B2C小包价表(含「产品目录」)。',
|
||||
'不延用以前的产品价格;导入按「产品+国家」写入套餐销售价,直接生成产品明细。',
|
||||
'同一产品下重复国家+重量段将整单失败。仅固定价套餐「Excel 重新建价」可用。'
|
||||
'同一产品下重复国家+重量段将整单失败。仅固定价套餐「Excel 新建价」可用。'
|
||||
]
|
||||
}
|
||||
return [
|
||||
@@ -351,7 +351,7 @@ const importDesc = computed(() => {
|
||||
const typeHint = computed(() => {
|
||||
if (isPullBuildMode(props.priceBuildMode)) return '· 按产品价建价'
|
||||
if (isImportBuildMode(props.priceBuildMode)) return '· 按上一版 Excel 建价'
|
||||
if (isImportFreshBuildMode(props.priceBuildMode)) return '· Excel 重新建价'
|
||||
if (isImportFreshBuildMode(props.priceBuildMode)) return '· Excel 新建价'
|
||||
if (hasBuildMode.value) return '· 当前:' + setTypeLabel(props.setType)
|
||||
return '· 请选择创建价格方式'
|
||||
})
|
||||
|
||||
@@ -235,8 +235,8 @@ const activateEffHitBoundaryMsg = '生效时间不能与已有版本的生效时
|
||||
const activateDialogHint = computed(() => {
|
||||
if (activateIsImportFresh.value) {
|
||||
const base = activateMode.value === 'reschedule'
|
||||
? 'Excel 重新建价:生效时间可落在历史版本区间内,将自动截断该版本失效时间并同步产品/明细。'
|
||||
: 'Excel 重新建价:生效时间可落在历史版本区间内;落在版本 B 区间时,B 失效时间改为本次启动时间,本版本失效时间取 B 原失效时间,并同步双方产品/明细。'
|
||||
? 'Excel 新建价:生效时间可落在历史版本区间内,将自动截断该版本失效时间并同步产品/明细。'
|
||||
: 'Excel 新建价:生效时间可落在历史版本区间内;落在版本 B 区间时,B 失效时间改为本次启动时间,本版本失效时间取 B 原失效时间,并同步双方产品/明细。'
|
||||
return base + '不可与已有版本的生效时间或失效时间相同。'
|
||||
}
|
||||
const base = activateMode.value === 'reschedule'
|
||||
|
||||
Reference in New Issue
Block a user