Files
BMS/bms-front/src/views/price/basic/components/CatalogActivateDialog.vue
T
2026-10-28 22:44:13 +08:00

213 lines
7.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 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="labEff" prop="effectiveTime">
<el-date-picker
v-model="form.effectiveTime"
type="datetime"
format="YYYY-MM-DD HH:00"
value-format="YYYY-MM-DD HH:00:00"
popper-class="activate-hour-picker"
:placeholder="ph"
:disabled-date="disabledDate"
:disabled-hours="disabledHours"
style="width: 100%"
@change="onTimeChange"
@visible-change="onPickerVisible"
/>
</el-form-item>
<div class="activate-hint">{{ hint }}</div>
</el-form>
<template #footer>
<el-button @click="visible = false">{{ btnCancel }}</el-button>
<el-button type="primary" :loading="submitting" @click="submit">{{ btnOk }}</el-button>
</template>
</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 }
})
const emit = defineEmits(['success'])
const { proxy } = getCurrentInstance()
const visible = ref(false)
const submitting = ref(false)
const formRef = ref()
const versions = ref([])
const prevEff = ref()
const isImportFresh = ref(false)
const boundaryTimes = ref([])
const form = reactive({ psvId: undefined, versionNo: '', versionSeq: 0, effectiveTime: undefined, priceBuildMode: undefined })
const dialogTitle = '启动版本'
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 = computed(() => ({
effectiveTime: [
{ required: true, message: ph, trigger: 'change' },
{
validator: (_r, v, cb) => {
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
return d.getFullYear() + '-' + pad2(d.getMonth() + 1) + '-' + pad2(d.getDate()) + ' ' + pad2(d.getHours()) + ':00:00'
}
function normalizeToHour(val) {
if (val == null || val === '') return val
if (val instanceof Date) return formatHour(val)
const s = String(val).trim().replace('T', ' ')
const m = s.match(/^(\d{4}-\d{2}-\d{2})[ ](\d{2})/)
if (m) return m[1] + ' ' + m[2] + ':00:00'
return formatHour(new Date(s.replace(/-/g, '/'))) || val
}
function toMs(val) {
const n = normalizeToHour(val)
if (!n) return NaN
return new Date(String(n).replace(/-/g, '/')).getTime()
}
function findPrev(row) {
const cur = Number(row && row.versionSeq) || 0
let prev = null
for (const v of versions.value || []) {
if (!v || v.psvId == null || String(v.psvId) === String(row.psvId)) continue
const vs = Number(v.versionSeq) || 0
if (vs >= cur || !v.effectiveTime) continue
if (!prev || vs > (Number(prev.versionSeq) || 0)) prev = v
}
return prev ? normalizeToHour(prev.effectiveTime) : undefined
}
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 (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)
const d = new Date(date)
d.setHours(0, 0, 0, 0)
return d.getTime() < prevDay.getTime()
}
function disabledHours() {
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)
return hours
}
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()
versions.value = list || []
const found = versions.value.find(v => String(v.psvId) === String(row.psvId)) || 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)
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() {
formRef.value.validate(valid => {
if (!valid) return
form.effectiveTime = normalizeToHour(form.effectiveTime)
submitting.value = true
props.activateFn(form.psvId, { effectiveTime: form.effectiveTime }).then(() => {
const pending = form.effectiveTime && new Date(String(form.effectiveTime).replace(/-/g, '/')).getTime() > Date.now()
proxy.$modal.msgSuccess(pending ? '已设为待生效' : '版本已启动')
visible.value = false
emit('success')
}).finally(() => { submitting.value = false })
})
}
defineExpose({ open })
</script>
<style scoped>
.activate-hint { color: var(--el-text-color-secondary); font-size: 12px; line-height: 1.5; }
</style>