master
lion 2 months ago
parent 8e29b69558
commit 5a8e4198d8

@ -131,13 +131,26 @@ function totalDraftText(): string {
return String(totalDraft.value ?? '').trim()
}
/** 文本输入绑定:过滤非法字符,始终写回 string */
const SCORE_MAX = 100
const SCORE_DECIMALS = 2
/** 文本输入绑定:仅 0~100,最多两位小数 */
const totalDraftModel = computed({
get: () => totalDraft.value,
set: (v: string | number | null | undefined) => {
const raw = v == null ? '' : String(v)
// 允许空、整数/小数、一位前导负号(校验时再拦负数)
const cleaned = raw.replace(/[^\d.-]/g, '').replace(/(?!^)-/g, '').replace(/(\..*)\./g, '$1')
let cleaned = raw.replace(/[^\d.]/g, '').replace(/(\..*)\./g, '$1')
const m = cleaned.match(/^(\d*)(\.\d{0,2})?/)
cleaned = m ? `${m[1] ?? ''}${m[2] ?? ''}` : ''
if (cleaned === '' || cleaned === '.') {
totalDraft.value = cleaned === '.' ? '0.' : ''
return
}
const n = Number(cleaned)
if (Number.isFinite(n) && n > SCORE_MAX) {
totalDraft.value = String(SCORE_MAX)
return
}
totalDraft.value = cleaned
},
})
@ -163,7 +176,7 @@ function syncScoreDraft(): void {
} else {
const n = Number(raw)
// Number('') === 0,空分必须保持空串,不能写成默认 0
totalDraft.value = Number.isFinite(n) ? rtrimZeros(n.toFixed(4)) : ''
totalDraft.value = Number.isFinite(n) ? rtrimZeros(n.toFixed(SCORE_DECIMALS)) : ''
}
totalBlurred.value = false
scoreShowErrors.value = false
@ -176,7 +189,11 @@ function rtrimZeros(s: string): string {
}
function formatMax(n: number): string {
return rtrimZeros(Number(n).toFixed(4))
return rtrimZeros(Number(n).toFixed(SCORE_DECIMALS))
}
function hasMoreThanTwoDecimals(raw: string): boolean {
return /\.\d{3,}/.test(raw)
}
const scoringItems = computed(() => detail.value?.scoring_sheet?.items ?? [])
@ -214,23 +231,30 @@ function getTotalError(): string | null {
if (scoreShowErrors.value || totalBlurred.value) return '请填写项目总分'
return null
}
if (hasMoreThanTwoDecimals(raw)) return '最多保留两位小数'
const n = Number(raw)
if (!Number.isFinite(n)) return '须为数字'
if (n < 0) return '不能小于 0'
if (n > fullScore.value) return `不得超过 ${formatMax(fullScore.value)} 分`
if (n > SCORE_MAX) return '不得超过 100 分'
return null
}
function onTotalBlur(): void {
totalBlurred.value = true
const raw = totalDraftText()
if (raw === '' || raw.endsWith('.')) return
const n = Number(raw)
if (!Number.isFinite(n)) return
const clamped = Math.min(SCORE_MAX, Math.max(0, n))
totalDraft.value = rtrimZeros(clamped.toFixed(SCORE_DECIMALS))
}
const canSubmitScore = computed(() => {
if (!detail.value?.scoring_allowed || !scoringItems.value.length) return false
const raw = totalDraftText()
if (raw === '') return false
if (raw === '' || hasMoreThanTwoDecimals(raw)) return false
const n = Number(raw)
return Number.isFinite(n) && n >= 0 && n <= fullScore.value
return Number.isFinite(n) && n >= 0 && n <= SCORE_MAX
})
const scoreFormHint = computed(() => {
@ -364,7 +388,8 @@ async function downloadAttachment(f: FileItem) {
function buildSubmitPayload(): Record<string, unknown> {
const n = Number(totalDraftText())
return { line_total: Number.isFinite(n) ? n : 0 }
const score = Number.isFinite(n) ? Math.round(n * 100) / 100 : 0
return { line_total: score }
}
function cleanupModalArtifacts(): void {
@ -571,7 +596,7 @@ async function submitReviewScore() {
}}</span>
<span v-else-if="detail.my_review_score" class="small text-secondary"
>最近保存:{{ detail.my_review_score.updated_at }} · 项目得分
{{ rtrimZeros(Number(detail.my_review_score.line_total).toFixed(4)) }} 分</span
{{ rtrimZeros(Number(detail.my_review_score.line_total).toFixed(2)) }} 分</span
>
</div>
</div>
@ -640,7 +665,7 @@ async function submitReviewScore() {
</div>
<div class="modal-body">
<p class="small text-secondary mb-2 text-center scoring-modal-hint">
下表为评审参考;请在底部填写项目总分(0~{{ formatMax(fullScore) }},可为 0)。
下表为评审参考;请在底部填写项目总分(0~100,最多两位小数,可为 0)。
</p>
<p v-if="scoreFormHint" class="small text-danger mb-2 text-center" role="alert">{{ scoreFormHint }}</p>
<div class="table-responsive scoring-sheet-wrap">

Loading…
Cancel
Save