|
|
|
|
@ -1,6 +1,7 @@
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
|
|
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
|
|
|
import { ElMessageBox } from 'element-plus'
|
|
|
|
|
import {
|
|
|
|
|
getApiBase,
|
|
|
|
|
REVIEW_AUTH_LOGIN_PATH,
|
|
|
|
|
@ -12,6 +13,11 @@ import {
|
|
|
|
|
hasVisibleText,
|
|
|
|
|
type BrandingForm,
|
|
|
|
|
} from '../../utils/competitionBranding'
|
|
|
|
|
import {
|
|
|
|
|
closedReviewPortalMessage,
|
|
|
|
|
invalidateReviewPortalCache,
|
|
|
|
|
reviewPortalFromPublicPayload,
|
|
|
|
|
} from '../../utils/reviewPortal'
|
|
|
|
|
import '../../styles/prototype-styles.css'
|
|
|
|
|
import '../../styles/login-page-overrides.css'
|
|
|
|
|
|
|
|
|
|
@ -29,6 +35,9 @@ const apiHintTone = ref<'neutral' | 'error' | 'success'>('neutral')
|
|
|
|
|
const competitionSlug = computed(() => String(route.params.slug ?? '').trim())
|
|
|
|
|
const competitionLoadError = ref('')
|
|
|
|
|
const brand = ref<BrandingForm>(emptyBrandingForm())
|
|
|
|
|
const reviewPortalEnabled = ref(true)
|
|
|
|
|
const reviewPortalMessage = ref('')
|
|
|
|
|
const reviewClosedDialogShown = ref(false)
|
|
|
|
|
|
|
|
|
|
const apiHintClass = computed(() => {
|
|
|
|
|
if (apiHintTone.value === 'error') return 'login-page-wls__hint login-page-wls__hint--error'
|
|
|
|
|
@ -111,9 +120,35 @@ function parseJsonErrorPayload(data: unknown): string {
|
|
|
|
|
return '请求失败'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function showReviewClosedDialog(message: string) {
|
|
|
|
|
if (reviewClosedDialogShown.value) return
|
|
|
|
|
reviewClosedDialogShown.value = true
|
|
|
|
|
const text = closedReviewPortalMessage(message)
|
|
|
|
|
const html = escapeHtml(text).replace(/\r\n|\r|\n/g, '<br>')
|
|
|
|
|
try {
|
|
|
|
|
await ElMessageBox.alert(html, '评审暂未开放', {
|
|
|
|
|
confirmButtonText: '知道了',
|
|
|
|
|
confirmButtonClass: 'login-page-wls__msgbox-confirm',
|
|
|
|
|
customClass: 'login-page-wls__msgbox',
|
|
|
|
|
closeOnClickModal: false,
|
|
|
|
|
closeOnPressEscape: false,
|
|
|
|
|
showClose: false,
|
|
|
|
|
autofocus: false,
|
|
|
|
|
dangerouslyUseHTMLString: true,
|
|
|
|
|
})
|
|
|
|
|
} catch {
|
|
|
|
|
/* 忽略 */
|
|
|
|
|
} finally {
|
|
|
|
|
reviewClosedDialogShown.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadCompetitionBrand() {
|
|
|
|
|
competitionLoadError.value = ''
|
|
|
|
|
brand.value = emptyBrandingForm()
|
|
|
|
|
reviewPortalEnabled.value = true
|
|
|
|
|
reviewPortalMessage.value = ''
|
|
|
|
|
reviewClosedDialogShown.value = false
|
|
|
|
|
const slug = competitionSlug.value
|
|
|
|
|
const titleSnapshot = typeof document !== 'undefined' ? document.title : ''
|
|
|
|
|
if (!slug) {
|
|
|
|
|
@ -121,6 +156,7 @@ async function loadCompetitionBrand() {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
invalidateReviewPortalCache(slug)
|
|
|
|
|
const r = await fetch(`${apiBase()}/api/v1/public/competitions/by-slug/${encodeURIComponent(slug)}`, {
|
|
|
|
|
headers: { Accept: 'application/json' },
|
|
|
|
|
})
|
|
|
|
|
@ -138,6 +174,13 @@ async function loadCompetitionBrand() {
|
|
|
|
|
|
|
|
|
|
brand.value = brandingFormFromApi(d?.branding_json ?? d?.branding ?? null)
|
|
|
|
|
|
|
|
|
|
const portal = reviewPortalFromPublicPayload(d)
|
|
|
|
|
reviewPortalEnabled.value = portal.enabled
|
|
|
|
|
reviewPortalMessage.value = portal.message
|
|
|
|
|
if (!portal.enabled) {
|
|
|
|
|
void showReviewClosedDialog(portal.message)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const docTitle = brand.value.documentTitle
|
|
|
|
|
if (typeof document !== 'undefined') {
|
|
|
|
|
document.title =
|
|
|
|
|
@ -176,6 +219,11 @@ function normalizeTitle(s: string): string {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function onSubmit() {
|
|
|
|
|
if (!reviewPortalEnabled.value) {
|
|
|
|
|
void showReviewClosedDialog(reviewPortalMessage.value)
|
|
|
|
|
setHint(closedReviewPortalMessage(reviewPortalMessage.value), 'error')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
const u = username.value.trim()
|
|
|
|
|
const p = password.value
|
|
|
|
|
usernameErr.value = !u
|
|
|
|
|
@ -212,7 +260,16 @@ async function onSubmit() {
|
|
|
|
|
})
|
|
|
|
|
const data = await r.json().catch((): Record<string, unknown> => ({}))
|
|
|
|
|
if (!r.ok) {
|
|
|
|
|
setHint(parseJsonErrorPayload(data), 'error')
|
|
|
|
|
const errMsg = parseJsonErrorPayload(data)
|
|
|
|
|
setHint(errMsg, 'error')
|
|
|
|
|
const errors = (data as { errors?: Record<string, unknown> }).errors
|
|
|
|
|
const slugErrs = errors?.competition_slug
|
|
|
|
|
const slugMsg = Array.isArray(slugErrs) ? String(slugErrs[0] ?? '') : ''
|
|
|
|
|
if (slugMsg && !slugMsg.includes('不存在') && !slugMsg.includes('未发布')) {
|
|
|
|
|
reviewPortalEnabled.value = false
|
|
|
|
|
reviewPortalMessage.value = slugMsg
|
|
|
|
|
void showReviewClosedDialog(slugMsg)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
clearHint()
|
|
|
|
|
@ -345,6 +402,7 @@ onUnmounted(() => {
|
|
|
|
|
autocomplete="username"
|
|
|
|
|
maxlength="64"
|
|
|
|
|
placeholder="账号"
|
|
|
|
|
:disabled="!reviewPortalEnabled"
|
|
|
|
|
@input="usernameErr = false"
|
|
|
|
|
/>
|
|
|
|
|
<input
|
|
|
|
|
@ -356,10 +414,15 @@ onUnmounted(() => {
|
|
|
|
|
autocomplete="current-password"
|
|
|
|
|
placeholder="密码"
|
|
|
|
|
maxlength="255"
|
|
|
|
|
:disabled="!reviewPortalEnabled"
|
|
|
|
|
@input="passwordErr = false"
|
|
|
|
|
/>
|
|
|
|
|
<button type="submit" class="login-page-wls__submit" :disabled="submitting">
|
|
|
|
|
{{ submitting ? '登录中…' : '登录' }}
|
|
|
|
|
<button
|
|
|
|
|
type="submit"
|
|
|
|
|
class="login-page-wls__submit"
|
|
|
|
|
:disabled="submitting || !reviewPortalEnabled"
|
|
|
|
|
>
|
|
|
|
|
{{ submitting ? '登录中…' : reviewPortalEnabled ? '登录' : '评审暂未开放' }}
|
|
|
|
|
</button>
|
|
|
|
|
<p v-if="apiHint" :class="apiHintClass" style="margin-top: 0.75rem">{{ apiHint }}</p>
|
|
|
|
|
</form>
|
|
|
|
|
|