213 lines
7.8 KiB
Vue
213 lines
7.8 KiB
Vue
<!-- 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>
|