|
|
|
|
@ -119,46 +119,53 @@ const loadError = ref('')
|
|
|
|
|
|
|
|
|
|
const pledgeSignedDateCn = computed(() => formatPledgeCnDate(detail.value?.promise_signed_at))
|
|
|
|
|
|
|
|
|
|
/** 各项得分草稿 key → 字符串 */
|
|
|
|
|
const scoreDraft = ref<Record<string, string>>({})
|
|
|
|
|
/** 项目总分草稿:始终用字符串,避免 number 输入把值变成 number 后 .trim 崩溃导致弹窗只剩遮罩 */
|
|
|
|
|
const totalDraft = ref('')
|
|
|
|
|
const scoreSubmitting = ref(false)
|
|
|
|
|
/** 失焦过的得分项,用于空值提示 */
|
|
|
|
|
const scoreBlurred = ref<Record<string, boolean>>({})
|
|
|
|
|
/** 点击提交后展示全部未填提示 */
|
|
|
|
|
const totalBlurred = ref(false)
|
|
|
|
|
const scoreShowErrors = ref(false)
|
|
|
|
|
|
|
|
|
|
function extractScoresFromPayload(payload: Record<string, unknown> | undefined): Record<string, unknown> {
|
|
|
|
|
if (!payload) return {}
|
|
|
|
|
const nested = payload.scores
|
|
|
|
|
if (nested && typeof nested === 'object' && !Array.isArray(nested)) {
|
|
|
|
|
return nested as Record<string, unknown>
|
|
|
|
|
}
|
|
|
|
|
return payload
|
|
|
|
|
const fullScore = computed(() => Number(detail.value?.scoring_sheet?.full_score ?? 100) || 100)
|
|
|
|
|
|
|
|
|
|
function totalDraftText(): string {
|
|
|
|
|
return String(totalDraft.value ?? '').trim()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 文本输入绑定:过滤非法字符,始终写回 string */
|
|
|
|
|
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')
|
|
|
|
|
totalDraft.value = cleaned
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
function syncScoreDraft(): void {
|
|
|
|
|
const d = detail.value
|
|
|
|
|
const items = d?.scoring_sheet?.items
|
|
|
|
|
if (!items?.length) {
|
|
|
|
|
scoreDraft.value = {}
|
|
|
|
|
scoreBlurred.value = {}
|
|
|
|
|
if (!d?.scoring_sheet?.items?.length) {
|
|
|
|
|
totalDraft.value = ''
|
|
|
|
|
totalBlurred.value = false
|
|
|
|
|
scoreShowErrors.value = false
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
const src = extractScoresFromPayload(d?.my_review_score?.payload_json)
|
|
|
|
|
const next: Record<string, string> = {}
|
|
|
|
|
for (const item of items) {
|
|
|
|
|
const v = src[item.key]
|
|
|
|
|
if (typeof v === 'number' && Number.isFinite(v)) {
|
|
|
|
|
next[item.key] = String(v)
|
|
|
|
|
} else if (typeof v === 'string' && v.trim() !== '') {
|
|
|
|
|
next[item.key] = v.trim()
|
|
|
|
|
} else {
|
|
|
|
|
next[item.key] = ''
|
|
|
|
|
}
|
|
|
|
|
const line = d.my_review_score?.line_total
|
|
|
|
|
const fromPayload = d.my_review_score?.payload_json?.line_total
|
|
|
|
|
const raw =
|
|
|
|
|
line != null && String(line).trim() !== ''
|
|
|
|
|
? String(line)
|
|
|
|
|
: fromPayload != null && String(fromPayload).trim() !== ''
|
|
|
|
|
? String(fromPayload)
|
|
|
|
|
: ''
|
|
|
|
|
if (raw === '') {
|
|
|
|
|
totalDraft.value = ''
|
|
|
|
|
} else {
|
|
|
|
|
const n = Number(raw)
|
|
|
|
|
// Number('') === 0,空分必须保持空串,不能写成默认 0
|
|
|
|
|
totalDraft.value = Number.isFinite(n) ? rtrimZeros(n.toFixed(4)) : ''
|
|
|
|
|
}
|
|
|
|
|
scoreDraft.value = next
|
|
|
|
|
scoreBlurred.value = {}
|
|
|
|
|
totalBlurred.value = false
|
|
|
|
|
scoreShowErrors.value = false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -201,73 +208,37 @@ const scoringRows = computed(() => {
|
|
|
|
|
return rows
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
function getScoreError(item: ScoringSheetItem): string | null {
|
|
|
|
|
const raw = String(scoreDraft.value[item.key] ?? '').trim()
|
|
|
|
|
function getTotalError(): string | null {
|
|
|
|
|
const raw = totalDraftText()
|
|
|
|
|
if (raw === '') {
|
|
|
|
|
if (scoreShowErrors.value || scoreBlurred.value[item.key]) {
|
|
|
|
|
return '请填写得分'
|
|
|
|
|
}
|
|
|
|
|
if (scoreShowErrors.value || totalBlurred.value) return '请填写项目总分'
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
const n = Number(raw)
|
|
|
|
|
if (!Number.isFinite(n)) return '须为数字'
|
|
|
|
|
if (n < 0) return '不能小于 0'
|
|
|
|
|
if (n > item.max_score) return `不得超过 ${formatMax(item.max_score)} 分`
|
|
|
|
|
if (n > fullScore.value) return `不得超过 ${formatMax(fullScore.value)} 分`
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onScoreBlur(key: string): void {
|
|
|
|
|
scoreBlurred.value = { ...scoreBlurred.value, [key]: true }
|
|
|
|
|
function onTotalBlur(): void {
|
|
|
|
|
totalBlurred.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const draftLineTotal = computed(() => {
|
|
|
|
|
let sum = 0
|
|
|
|
|
let allValid = scoringItems.value.length > 0
|
|
|
|
|
for (const item of scoringItems.value) {
|
|
|
|
|
const raw = String(scoreDraft.value[item.key] ?? '').trim()
|
|
|
|
|
if (raw === '') {
|
|
|
|
|
allValid = false
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
const n = Number(raw)
|
|
|
|
|
if (!Number.isFinite(n) || n < 0 || n > item.max_score) {
|
|
|
|
|
allValid = false
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
sum += n
|
|
|
|
|
}
|
|
|
|
|
return { sum, allValid }
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const canSubmitScore = computed(() => {
|
|
|
|
|
if (!detail.value?.scoring_allowed || !scoringItems.value.length) return false
|
|
|
|
|
for (const item of scoringItems.value) {
|
|
|
|
|
const raw = String(scoreDraft.value[item.key] ?? '').trim()
|
|
|
|
|
if (raw === '') return false
|
|
|
|
|
const n = Number(raw)
|
|
|
|
|
if (!Number.isFinite(n) || n < 0 || n > item.max_score) return false
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
const raw = totalDraftText()
|
|
|
|
|
if (raw === '') return false
|
|
|
|
|
const n = Number(raw)
|
|
|
|
|
return Number.isFinite(n) && n >= 0 && n <= fullScore.value
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const scoreFormHint = computed(() => {
|
|
|
|
|
if (!scoringItems.value.length) return ''
|
|
|
|
|
let empty = 0
|
|
|
|
|
let over = 0
|
|
|
|
|
for (const item of scoringItems.value) {
|
|
|
|
|
const raw = String(scoreDraft.value[item.key] ?? '').trim()
|
|
|
|
|
if (raw === '') {
|
|
|
|
|
empty += 1
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
const n = Number(raw)
|
|
|
|
|
if (!Number.isFinite(n) || n < 0 || n > item.max_score) over += 1
|
|
|
|
|
}
|
|
|
|
|
const parts: string[] = []
|
|
|
|
|
// 未填写仅在点击提交后汇总提示;超分/无效随时提示
|
|
|
|
|
if (scoreShowErrors.value && empty > 0) parts.push(`${empty} 项未填写`)
|
|
|
|
|
if (over > 0) parts.push(`${over} 项超出分值或无效`)
|
|
|
|
|
return parts.join(',')
|
|
|
|
|
const err = getTotalError()
|
|
|
|
|
return err && (scoreShowErrors.value || totalBlurred.value || totalDraftText() !== '')
|
|
|
|
|
? err
|
|
|
|
|
: ''
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const statusBadgeText = computed(() => {
|
|
|
|
|
@ -392,19 +363,44 @@ async function downloadAttachment(f: FileItem) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function buildSubmitPayload(): Record<string, unknown> {
|
|
|
|
|
const scores: Record<string, number> = {}
|
|
|
|
|
for (const item of scoringItems.value) {
|
|
|
|
|
const n = Number(String(scoreDraft.value[item.key] ?? '').trim())
|
|
|
|
|
scores[item.key] = Number.isFinite(n) ? n : 0
|
|
|
|
|
}
|
|
|
|
|
return { scores }
|
|
|
|
|
const n = Number(totalDraftText())
|
|
|
|
|
return { line_total: Number.isFinite(n) ? n : 0 }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function hideScoreModal(): void {
|
|
|
|
|
function cleanupModalArtifacts(): void {
|
|
|
|
|
if (typeof document === 'undefined') return
|
|
|
|
|
const el = document.getElementById('reviewerScoreModal')
|
|
|
|
|
if (!el) return
|
|
|
|
|
Modal.getOrCreateInstance(el).hide()
|
|
|
|
|
document.querySelectorAll('.modal-backdrop').forEach((n) => n.remove())
|
|
|
|
|
document.body.classList.remove('modal-open')
|
|
|
|
|
document.body.style.removeProperty('overflow')
|
|
|
|
|
document.body.style.removeProperty('padding-right')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function hideScoreModal(): Promise<void> {
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
if (typeof document === 'undefined') {
|
|
|
|
|
resolve()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
const el = document.getElementById('reviewerScoreModal')
|
|
|
|
|
if (!el) {
|
|
|
|
|
cleanupModalArtifacts()
|
|
|
|
|
resolve()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
let settled = false
|
|
|
|
|
const finish = () => {
|
|
|
|
|
if (settled) return
|
|
|
|
|
settled = true
|
|
|
|
|
el.removeEventListener('hidden.bs.modal', onHidden)
|
|
|
|
|
cleanupModalArtifacts()
|
|
|
|
|
resolve()
|
|
|
|
|
}
|
|
|
|
|
const onHidden = () => finish()
|
|
|
|
|
el.addEventListener('hidden.bs.modal', onHidden)
|
|
|
|
|
const instance = Modal.getInstance(el) ?? Modal.getOrCreateInstance(el)
|
|
|
|
|
instance.hide()
|
|
|
|
|
window.setTimeout(finish, 450)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function openScoreModal(): void {
|
|
|
|
|
@ -413,6 +409,8 @@ function openScoreModal(): void {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
scoreShowErrors.value = false
|
|
|
|
|
totalBlurred.value = false
|
|
|
|
|
cleanupModalArtifacts()
|
|
|
|
|
if (typeof document === 'undefined') return
|
|
|
|
|
const el = document.getElementById('reviewerScoreModal')
|
|
|
|
|
if (!el) return
|
|
|
|
|
@ -433,7 +431,7 @@ async function submitReviewScore() {
|
|
|
|
|
}
|
|
|
|
|
if (!canSubmitScore.value) {
|
|
|
|
|
scoreShowErrors.value = true
|
|
|
|
|
ElMessage.warning(scoreFormHint.value || '请为每一项填写得分(可为 0,且不得超过该项分值)')
|
|
|
|
|
ElMessage.warning(getTotalError() || '请填写 0~100 之间的项目总分')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -463,6 +461,9 @@ async function submitReviewScore() {
|
|
|
|
|
ElMessage.error(msg)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// 先关弹窗并清理遮罩,再更新详情,避免 Vue 重绘与 Bootstrap 关闭动画冲突
|
|
|
|
|
await hideScoreModal()
|
|
|
|
|
|
|
|
|
|
const data = (body as { data?: Partial<DetailPayload> }).data
|
|
|
|
|
if (detail.value && data) {
|
|
|
|
|
if (data.my_review_score !== undefined) {
|
|
|
|
|
@ -481,7 +482,6 @@ async function submitReviewScore() {
|
|
|
|
|
? String((body as { message: string }).message)
|
|
|
|
|
: '提交成功',
|
|
|
|
|
)
|
|
|
|
|
hideScoreModal()
|
|
|
|
|
} catch {
|
|
|
|
|
ElMessage.error('网络错误,提交失败')
|
|
|
|
|
} finally {
|
|
|
|
|
@ -513,7 +513,7 @@ async function submitReviewScore() {
|
|
|
|
|
|
|
|
|
|
<div v-else-if="detail" class="card mb-4 border-0 shadow-sm">
|
|
|
|
|
<div class="card-body pb-4">
|
|
|
|
|
<ApplicationSignupDetailFields :detail="detail" />
|
|
|
|
|
<ApplicationSignupDetailFields :detail="detail" :show-recommend="false" />
|
|
|
|
|
|
|
|
|
|
<div class="row g-3 mt-1">
|
|
|
|
|
<div class="col-12">
|
|
|
|
|
@ -639,10 +639,10 @@ async function submitReviewScore() {
|
|
|
|
|
<button type="button" class="btn-close scoring-modal-close" data-bs-dismiss="modal" aria-label="关闭" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="modal-body">
|
|
|
|
|
<p class="small text-secondary mb-2 text-center">
|
|
|
|
|
请为每一项填写得分(可为 0);单项不得超过「分值」上限;提交后自动汇总为项目得分。
|
|
|
|
|
<p class="small text-secondary mb-2 text-center scoring-modal-hint">
|
|
|
|
|
下表为评审参考;请在底部填写项目总分(0~{{ formatMax(fullScore) }},可为 0)。
|
|
|
|
|
</p>
|
|
|
|
|
<p v-if="scoreFormHint" class="small text-danger mb-3 text-center" role="alert">{{ scoreFormHint }}</p>
|
|
|
|
|
<p v-if="scoreFormHint" class="small text-danger mb-2 text-center" role="alert">{{ scoreFormHint }}</p>
|
|
|
|
|
<div class="table-responsive scoring-sheet-wrap">
|
|
|
|
|
<table class="table table-bordered align-middle mb-0 scoring-sheet-table">
|
|
|
|
|
<colgroup>
|
|
|
|
|
@ -651,7 +651,6 @@ async function submitReviewScore() {
|
|
|
|
|
<col class="col-indicator" />
|
|
|
|
|
<col class="col-criteria" />
|
|
|
|
|
<col class="col-max" />
|
|
|
|
|
<col class="col-score" />
|
|
|
|
|
</colgroup>
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
@ -660,7 +659,6 @@ async function submitReviewScore() {
|
|
|
|
|
<th class="text-nowrap text-center">评审指标</th>
|
|
|
|
|
<th class="text-center">核心评审要素(评委打分依据)</th>
|
|
|
|
|
<th class="text-nowrap text-center">分值</th>
|
|
|
|
|
<th class="text-nowrap text-center">得分</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
@ -676,39 +674,30 @@ async function submitReviewScore() {
|
|
|
|
|
<td class="text-center fw-semibold scoring-indicator">{{ row.item.title }}</td>
|
|
|
|
|
<td class="small text-secondary scoring-criteria">{{ row.item.criteria || '—' }}</td>
|
|
|
|
|
<td class="text-center">{{ formatMax(row.item.max_score) }}</td>
|
|
|
|
|
<td class="scoring-score-cell text-center">
|
|
|
|
|
<input
|
|
|
|
|
v-model="scoreDraft[row.item.key]"
|
|
|
|
|
class="form-control form-control-sm text-center scoring-score-input"
|
|
|
|
|
:class="{ 'is-invalid': !!getScoreError(row.item) }"
|
|
|
|
|
type="number"
|
|
|
|
|
min="0"
|
|
|
|
|
:max="row.item.max_score"
|
|
|
|
|
step="any"
|
|
|
|
|
required
|
|
|
|
|
autocomplete="off"
|
|
|
|
|
:aria-label="`${row.item.title}得分`"
|
|
|
|
|
@blur="onScoreBlur(row.item.key)"
|
|
|
|
|
/>
|
|
|
|
|
<div v-if="getScoreError(row.item)" class="invalid-feedback d-block text-center">
|
|
|
|
|
{{ getScoreError(row.item) }}
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr class="scoring-total-row">
|
|
|
|
|
<td colspan="4" class="text-end fw-semibold">项目得分</td>
|
|
|
|
|
<td class="text-center text-secondary">{{ detail?.scoring_sheet?.full_score ?? 100 }}</td>
|
|
|
|
|
<td class="text-center fw-semibold">
|
|
|
|
|
{{
|
|
|
|
|
draftLineTotal.allValid
|
|
|
|
|
? rtrimZeros(draftLineTotal.sum.toFixed(4))
|
|
|
|
|
: '—'
|
|
|
|
|
}}
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="scoring-total-block">
|
|
|
|
|
<label class="form-label mb-1 fw-semibold" for="reviewer-total-score-input">项目总分</label>
|
|
|
|
|
<div class="scoring-total-row-inner">
|
|
|
|
|
<input
|
|
|
|
|
id="reviewer-total-score-input"
|
|
|
|
|
v-model="totalDraftModel"
|
|
|
|
|
class="form-control scoring-total-input text-center"
|
|
|
|
|
:class="{ 'is-invalid': !!getTotalError() }"
|
|
|
|
|
type="text"
|
|
|
|
|
inputmode="decimal"
|
|
|
|
|
autocomplete="off"
|
|
|
|
|
placeholder="请输入总分"
|
|
|
|
|
aria-label="项目总分"
|
|
|
|
|
@blur="onTotalBlur"
|
|
|
|
|
@keydown.enter.prevent="submitReviewScore"
|
|
|
|
|
/>
|
|
|
|
|
<span class="scoring-total-full small text-secondary">满分 {{ formatMax(fullScore) }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="getTotalError()" class="invalid-feedback d-block">{{ getTotalError() }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="modal-footer flex-wrap gap-2">
|
|
|
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
|
|
|
|
@ -843,11 +832,39 @@ async function submitReviewScore() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.scoring-sheet-table .col-max {
|
|
|
|
|
width: 4.5rem;
|
|
|
|
|
width: 5rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.scoring-modal-hint {
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.scoring-total-block {
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
margin-top: 0.85rem;
|
|
|
|
|
padding: 0.85rem 1rem;
|
|
|
|
|
border: 1px solid #dce3ec;
|
|
|
|
|
border-radius: 0.5rem;
|
|
|
|
|
background: #f3f6fa;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.scoring-total-row-inner {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 0.75rem;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.scoring-total-input {
|
|
|
|
|
width: min(16rem, 100%);
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
font-size: 1.125rem;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
padding: 0.55rem 0.75rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.scoring-sheet-table .col-score {
|
|
|
|
|
width: 10rem;
|
|
|
|
|
.scoring-total-full {
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.scoring-sheet-table th {
|
|
|
|
|
@ -875,24 +892,6 @@ async function submitReviewScore() {
|
|
|
|
|
overflow-wrap: break-word;
|
|
|
|
|
white-space: normal;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.scoring-score-cell {
|
|
|
|
|
vertical-align: middle;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.scoring-score-input {
|
|
|
|
|
width: 100%;
|
|
|
|
|
min-width: 4.5rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.scoring-score-cell .invalid-feedback {
|
|
|
|
|
font-size: 0.75rem;
|
|
|
|
|
margin-top: 0.2rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.scoring-total-row td {
|
|
|
|
|
background: #f3f6fa;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
@ -946,6 +945,11 @@ async function submitReviewScore() {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#reviewerScoreModal .scoring-sheet-wrap {
|
|
|
|
|
flex: 1 1 auto;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#reviewerPledgeModal .promise-doc-scroll {
|
|
|
|
|
max-height: min(38vh, calc(100dvh - 15rem));
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|