main
lion 2 months ago
parent be788d44bf
commit 9e24377f0e

@ -1183,8 +1183,10 @@ a[target="_blank"]:focus {
text-decoration: none;
}
/* 登录页:内部系统布局 + 元禾主色(:root --primary */
/* 登录页:浅蓝整体背景;按钮仍用 --login-page-wls-btn-surface */
body.login-page-wls {
--login-page-wls-surface: linear-gradient(135deg, #f4f9fd 0%, #e8f3fa 38%, #eef7fc 100%);
--login-page-wls-btn-surface: radial-gradient(ellipse at 78% 22%, rgba(255, 211, 106, 0.14), transparent 42%), radial-gradient(ellipse at 24% 72%, rgba(255, 211, 106, 0.08), transparent 46%), radial-gradient(ellipse at 50% 50%, rgba(13, 84, 162, 0.24), transparent 58%), linear-gradient(128deg, #073b74 0%, #052d62 34%, #071f50 68%, #041433 100%);
margin: 0;
min-height: 100vh;
min-height: 100dvh;
@ -1192,7 +1194,8 @@ body.login-page-wls {
flex-direction: column;
font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;
color: #333;
background: linear-gradient(135deg, #fefcfc 0%, #fbe9eb 36%, #f7f5f6 88%);
background: var(--login-page-wls-surface);
background-color: #eef7fc;
-webkit-font-smoothing: antialiased;
}
@ -1229,6 +1232,20 @@ body.login-page-wls *::after {
max-width: 520px;
padding: 0;
text-align: left;
color: #222;
}
.login-page-wls__brand .login-page-wls__title,
.login-page-wls__brand .login-page-wls__title-main,
.login-page-wls__brand .login-page-wls__title-sub,
.login-page-wls__brand .login-page-wls__mark-en {
color: inherit;
}
.login-page-wls__brand .login-page-wls__mark,
.login-page-wls__brand .login-page-wls__mark-cn,
.login-page-wls__brand .login-page-wls__slogan {
color: #6b6f76;
}
.login-page-wls__logo {
@ -1353,8 +1370,8 @@ body.login-page-wls *::after {
}
.login-page-wls__input:focus {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(180, 0, 16, 0.12);
border-color: #052d62;
box-shadow: 0 0 0 3px rgba(5, 45, 98, 0.22);
}
.login-page-wls__code-row {
@ -1372,21 +1389,23 @@ body.login-page-wls *::after {
.login-page-wls__code-btn {
min-width: 6.3rem;
padding: 0 0.7rem;
border: 1px solid #dfc4c7;
border: 1px solid rgba(255, 211, 106, 0.38);
border-radius: 8px;
background: #fff7f8;
color: #9f0f1c;
background: var(--login-page-wls-btn-surface);
background-color: #052d62;
color: #fff;
font-size: 0.82rem;
font-weight: 500;
text-shadow: none;
-webkit-text-stroke: 0;
cursor: pointer;
transition: all 0.15s ease;
transition: filter 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.login-page-wls__code-btn:hover {
background: #feeef0;
border-color: #d9a6ac;
.login-page-wls__code-btn:hover:not(:disabled) {
filter: brightness(1.12);
border-color: rgba(255, 244, 204, 0.55);
box-shadow: 0 0 0 1px rgba(143, 232, 255, 0.12);
}
.login-page-wls__code-btn:disabled {
@ -1422,7 +1441,8 @@ body.login-page-wls *::after {
}
.login-page-wls__role-select:focus {
border-color: var(--primary);
border-color: #052d62;
box-shadow: 0 0 0 3px rgba(5, 45, 98, 0.18);
}
.login-page-wls__submit {
@ -1432,17 +1452,19 @@ body.login-page-wls *::after {
font-size: 1rem;
font-weight: 600;
color: #fff;
border: none;
border: 1px solid rgba(255, 211, 106, 0.38);
border-radius: 8px;
cursor: pointer;
background: linear-gradient(180deg, #cf2c3a 0%, #ba1424 55%, #a90a1a 100%);
box-shadow: 0 2px 8px rgba(180, 0, 16, 0.16);
transition: filter 0.15s ease, transform 0.1s ease, box-shadow 0.15s ease;
background: var(--login-page-wls-btn-surface);
background-color: #052d62;
box-shadow: 0 2px 14px rgba(0, 0, 0, 0.28);
transition: filter 0.15s ease, transform 0.1s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}
.login-page-wls__submit:hover {
filter: brightness(1.03);
box-shadow: 0 2px 10px rgba(180, 0, 16, 0.2);
filter: brightness(1.1);
border-color: rgba(255, 244, 204, 0.5);
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.32);
}
.login-page-wls__submit:active {
@ -1482,7 +1504,8 @@ body.login-page-wls *::after {
}
.login-page-wls__form--invalid .login-page-wls__input:invalid {
border-color: #e57373;
border-color: #052d62;
box-shadow: 0 0 0 2px rgba(5, 45, 98, 0.2);
}
.login-page-wls__footer {
@ -1493,7 +1516,7 @@ body.login-page-wls *::after {
.login-page-wls__copyright {
margin: 0;
font-size: 0.75rem;
color: #aaa;
color: #8b8e94;
}
.notice-modal-dialog {

@ -76,6 +76,23 @@ export function getApiBase(): string {
return ''
}
/** 规整附件公开 URL避免 Laravel `APP_URL` 带尾斜杠时出现 `//storage/...` 导致 404 */
export function normalizePublicAssetUrl(raw: string | null | undefined): string {
const s = String(raw ?? '').trim()
if (!s) return s
if (/^https?:\/\//i.test(s)) {
try {
const u = new URL(s)
const collapsed = u.pathname.replace(/\/{2,}/g, '/')
u.pathname = collapsed.startsWith('/') ? collapsed : `/${collapsed}`
return u.toString()
} catch {
return s.replace(/([^:])\/\/+/g, '$1/')
}
}
return s.replace(/\/{2,}/g, '/')
}
/** 评审端附件下载 URL需请求头 `Authorization: Bearer`,见详情页 `downloadAttachment` */
export function reviewApplicationFileDownloadUrl(
applicationId: number,

@ -65,20 +65,40 @@ watch(participantSlug, () => {
void loadParticipantBranding()
}, { immediate: true })
/** 与登录页一致:把运营配置的主题色写回 body避免离开登录页后 --primary 被清空导致首帧仍是 Bootstrap 默认绿 */
/** branding 未配置时与首页/登录同款深蓝,避免子元素 var(--primary) 回退到原型 :root 洋红 */
const PARTICIPANT_DEFAULT_PRIMARY = '#052d62'
function primaryHexToRgbTriplet(hex: string): string | null {
const s = hex.trim().replace('#', '')
if (!/^[0-9a-f]{6}$/i.test(s)) return null
const r = parseInt(s.slice(0, 2), 16)
const g = parseInt(s.slice(2, 4), 16)
const b = parseInt(s.slice(4, 6), 16)
return `${r}, ${g}, ${b}`
}
/** 与登录页一致:把运营配置的主题色写回 body无配置时用默认深蓝并同步 --bs-primary避免首屏 Bootstrap 主按钮仍是默认色 */
function applyParticipantBodyTheme() {
if (typeof document === 'undefined') return
const theme = brand.value.login.themePrimary?.trim() ?? ''
if (hasVisibleText(theme)) {
document.body.style.setProperty('--primary', theme)
const custom = brand.value.login.themePrimary?.trim() ?? ''
const theme = hasVisibleText(custom)
? /^(?:#|rgb|hsl)/i.test(custom)
? custom
: `#${custom.replace(/^#/, '')}`
: PARTICIPANT_DEFAULT_PRIMARY
document.body.style.setProperty('--primary', theme)
document.body.style.setProperty('--bs-primary', theme)
if (theme.startsWith('#')) {
document.body.style.setProperty('--primary-soft', `${theme}14`)
const rgb = primaryHexToRgbTriplet(theme)
if (rgb) document.body.style.setProperty('--bs-primary-rgb', rgb)
} else {
document.body.style.removeProperty('--primary')
document.body.style.removeProperty('--primary-soft')
document.body.style.removeProperty('--bs-primary-rgb')
}
}
watch(brand, () => applyParticipantBodyTheme(), { deep: true })
watch(brand, () => applyParticipantBodyTheme(), { deep: true, immediate: true })
const profileLabel = ref('')
@ -120,6 +140,8 @@ onUnmounted(() => {
document.body.classList.remove('prototype-page', 'user-mobile-no-menu')
document.body.style.removeProperty('--primary')
document.body.style.removeProperty('--primary-soft')
document.body.style.removeProperty('--bs-primary')
document.body.style.removeProperty('--bs-primary-rgb')
})
</script>
@ -190,10 +212,14 @@ onUnmounted(() => {
<style scoped>
:deep(a.router-link-active.nav-link) {
background: #f3e7e9;
color: #8f000c;
background: #eef7fc;
color: #052d62;
font-weight: 600;
}
:deep(.layout-sider .nav-link:not(.router-link-active)) {
color: #3a4a5c;
}
</style>
<style>

@ -19,6 +19,27 @@ body.login-page-wls #app {
color: #1f1f1f !important;
}
/** 选手报名工作台MainLayout页面底色与登录页浅蓝渐变一致 */
body.prototype-page.user-mobile-no-menu {
background: linear-gradient(135deg, #f4f9fd 0%, #e8f3fa 38%, #eef7fc 100%) !important;
background-color: #eef7fc !important;
}
/** 左侧「赛事报名」导航卡与右侧报名表外框:白底、#eef7fc 描边、统一圆角 */
body.prototype-page.user-mobile-no-menu .participant-layout .layout-sider > .card {
background: #fff !important;
border: 1px solid #eef7fc !important;
border-radius: var(--radius-lg, 10px) !important;
}
body.prototype-page .apply-form-page {
background: #fff !important;
border: 1px solid #eef7fc !important;
border-radius: var(--radius-lg, 10px) !important;
box-sizing: border-box;
padding: 1rem 1.25rem !important;
}
body.prototype-page .form-page-title {
color: #262224 !important;
font-size: 1.35rem !important;
@ -26,24 +47,118 @@ body.prototype-page .form-page-title {
letter-spacing: 0.3px;
}
/* 报名表滚动区内的主卡片PC/移动统一白底、描边与阴影(与左侧导航卡一致) */
/* 报名表:外层 .apply-form-page 已为白底+描边;内层卡片不再重复描边 */
body.prototype-page .apply-form-scroll > .card {
background: #fff;
border: 1px solid #eee2e4 !important;
border-radius: var(--radius-lg, 10px);
box-shadow: var(--shadow-soft, 0 4px 14px rgba(46, 24, 26, 0.06));
background: transparent !important;
border: none !important;
border-radius: 0 !important;
box-shadow: none !important;
}
body.prototype-page .apply-form-scroll > .card > .card-body {
padding-left: 0 !important;
padding-right: 0 !important;
}
/**
* /
* - 使 .participant-layout MainLayout body.prototype-page onMounted Bootstrap /绿
*/
.participant-layout {
--cxxfds-participant-btn-surface: radial-gradient(
ellipse at 78% 22%,
rgba(255, 211, 106, 0.14),
transparent 42%
),
radial-gradient(ellipse at 24% 72%, rgba(255, 211, 106, 0.08), transparent 46%),
radial-gradient(ellipse at 50% 50%, rgba(13, 84, 162, 0.24), transparent 58%),
linear-gradient(128deg, #073b74 0%, #052d62 34%, #071f50 68%, #041433 100%);
--cxxfds-participant-btn-fallback: #052d62;
}
.participant-layout #applyForm .btn-primary,
.participant-layout #applyForm .btn-success,
.participant-layout #promiseSignModal .btn-primary,
.participant-layout #applyNoticeModal .btn-primary {
color: #fff !important;
border: 1px solid rgba(255, 211, 106, 0.38) !important;
background: var(--cxxfds-participant-btn-surface) !important;
background-color: var(--cxxfds-participant-btn-fallback) !important;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2) !important;
transition: filter 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease !important;
}
.participant-layout #applyForm .btn-primary:hover:not(:disabled),
.participant-layout #applyForm .btn-success:hover:not(:disabled),
.participant-layout #promiseSignModal .btn-primary:hover:not(:disabled),
.participant-layout #applyNoticeModal .btn-primary:hover:not(:disabled) {
filter: brightness(1.08) !important;
border-color: rgba(255, 244, 204, 0.5) !important;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.24) !important;
}
/* 附件预览/删除:横向一排、宽度随文案(避免误用 flex 平分导致块级换行) */
.participant-layout #applyForm .btn-primary:disabled,
.participant-layout #applyForm .btn-success:disabled,
.participant-layout #promiseSignModal .btn-primary:disabled,
.participant-layout #applyNoticeModal .btn-primary:disabled {
opacity: 0.58 !important;
filter: none !important;
}
.participant-layout #applyForm .btn-outline-primary {
color: #052d62 !important;
border-color: rgba(7, 59, 116, 0.55) !important;
background-color: #fff !important;
transition: color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease !important;
}
.participant-layout #applyForm .btn-outline-primary:hover:not(:disabled) {
color: #fff !important;
border-color: rgba(255, 211, 106, 0.48) !important;
background: var(--cxxfds-participant-btn-surface) !important;
background-color: var(--cxxfds-participant-btn-fallback) !important;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.18) !important;
}
.participant-layout #applyForm .btn-outline-primary:focus-visible {
color: #fff !important;
border-color: rgba(255, 211, 106, 0.55) !important;
box-shadow: 0 0 0 0.2rem rgba(13, 84, 162, 0.22) !important;
}
.participant-layout #applyForm .btn-outline-primary:disabled {
opacity: 0.55 !important;
}
/* 附件预览等小按钮同为 outline-primary */
.participant-layout #applyForm .btn-sm.btn-outline-primary {
font-weight: 500;
}
/* 附件预览/删除:横向一排、右对齐 */
body.prototype-page #applyForm .supporting-file-actions {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
justify-content: flex-start;
justify-content: flex-end;
gap: 0.45rem;
}
body.prototype-page #applyForm .supporting-file-item {
justify-content: space-between;
width: 100%;
box-sizing: border-box;
}
/** 参赛承诺书:「已完成签署」与登录页验证码提示同款浅底深字 */
body.prototype-page #applyForm .promise-signed-badge {
background: #eef7fc !important;
color: #052d62 !important;
border: 1px solid rgba(5, 45, 98, 0.14) !important;
font-weight: 600 !important;
}
body.prototype-page #applyForm .supporting-file-actions .btn {
flex: 0 0 auto;
width: auto;
@ -51,6 +166,28 @@ body.prototype-page #applyForm .supporting-file-actions .btn {
white-space: nowrap;
}
/** 选手端侧栏:覆盖原型 .nav-link.active 洋红底,与页面浅蓝统一 */
body.prototype-page.user-mobile-no-menu .participant-layout .layout-sider .nav-link.active,
body.prototype-page.user-mobile-no-menu .participant-layout .layout-sider a.nav-link.router-link-active {
background: #eef7fc !important;
color: #052d62 !important;
font-weight: 600 !important;
}
/* PC已上传附件一行两个 */
@media (min-width: 768px) {
body.prototype-page #applyForm .supporting-file-list {
align-items: stretch;
}
body.prototype-page #applyForm .supporting-file-item {
flex: 0 0 calc((100% - 0.5rem) / 2);
max-width: calc((100% - 0.5rem) / 2);
box-sizing: border-box;
min-width: 0;
}
}
body.prototype-page #applyForm .form-label,
body.prototype-page #applyForm .form-check-label {
font-size: 0.9rem !important;
@ -155,10 +292,18 @@ body.prototype-page #applyForm.was-validated .form-control:invalid,
body.prototype-page #applyForm .form-control.is-invalid,
body.prototype-page #applyForm.was-validated textarea.form-control:invalid,
body.prototype-page #applyForm textarea.form-control.is-invalid {
border-color: #052d62 !important;
background-image: none !important;
padding-right: 0.75rem !important;
}
body.prototype-page #applyForm.was-validated .form-select:invalid:not([multiple]):not([size]),
body.prototype-page #applyForm.was-validated .form-select:invalid:not([multiple])[size='1'],
body.prototype-page #applyForm .form-select.is-invalid:not([multiple]):not([size]),
body.prototype-page #applyForm .form-select.is-invalid:not([multiple])[size='1'] {
border-color: #052d62 !important;
}
body.prototype-page #applyForm.was-validated .form-select:valid:not([multiple]):not([size]),
body.prototype-page #applyForm.was-validated .form-select:valid:not([multiple])[size='1'],
body.prototype-page #applyForm .form-select.is-valid:not([multiple]):not([size]),
@ -186,46 +331,61 @@ body.prototype-page #applyForm .form-select.is-valid:not([multiple])[size='1'] {
body.prototype-page #applyForm .form-control:focus,
body.prototype-page #applyForm .form-select:focus,
body.prototype-page #applyForm .track-custom-toggle.form-select:focus {
border-color: #cfaeb3 !important;
box-shadow: 0 0 0 0.2rem rgba(180, 0, 16, 0.12) !important;
border-color: #052d62 !important;
box-shadow: 0 0 0 0.2rem rgba(5, 45, 98, 0.2) !important;
}
body.prototype-page #applyForm.was-validated .form-control:valid:focus,
body.prototype-page #applyForm .form-control.is-valid:focus {
border-color: #cfaeb3 !important;
box-shadow: 0 0 0 0.2rem rgba(180, 0, 16, 0.12) !important;
border-color: #052d62 !important;
box-shadow: 0 0 0 0.2rem rgba(5, 45, 98, 0.2) !important;
}
body.prototype-page #applyForm.was-validated .form-control:invalid:focus,
body.prototype-page #applyForm .form-control.is-invalid:focus,
body.prototype-page #applyForm.was-validated .form-select:invalid:focus,
body.prototype-page #applyForm .form-select.is-invalid:focus {
border-color: #dc3545 !important;
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.15) !important;
border-color: #052d62 !important;
box-shadow: 0 0 0 0.2rem rgba(5, 45, 98, 0.22) !important;
}
body.prototype-page #applyForm .track-custom-toggle.is-invalid {
border-color: #dc3545 !important;
border-color: #052d62 !important;
}
body.prototype-page #applyForm .invalid-feedback {
font-size: 0.875rem !important;
margin-top: 0.28rem;
color: #dc3545 !important;
color: #052d62 !important;
}
body.prototype-page #applyForm .invalid-feedback.d-block {
display: block !important;
}
/** 报名表:必填星号与顶部加载错误文案与主题 #052d62 一致 */
body.prototype-page #applyForm .text-danger {
color: #052d62 !important;
}
body.prototype-page .apply-form-page > .form-page-header .text-danger {
color: #052d62 !important;
}
body.prototype-page #applyForm .intro-char-count {
font-size: 0.9rem !important;
color: #6b6f76 !important;
}
body.prototype-page #applyForm .prototype-subtitle {
color: #6b6f76 !important;
font-size: 0.9rem !important;
/** 多行文本:占位说明在输入框下方左侧(不用原生 placeholder */
body.prototype-page #applyForm .apply-textarea-placeholder-hint {
font-size: 0.875rem !important;
font-weight: 400 !important;
color: #9aa3ad !important;
line-height: 1.5 !important;
flex: 1 1 auto;
min-width: 0;
text-align: left !important;
}
/* 参赛承诺书弹窗:标题/签名区固定,仅正文区在过长时滚动;弹窗高度随内容变矮 */
@ -312,6 +472,7 @@ body.prototype-page #promiseSignModal .promise-doc-scroll {
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: flex-start;
gap: 0.5rem;
width: 100%;
max-width: none;
@ -328,7 +489,7 @@ body.prototype-page #promiseSignModal .promise-doc-scroll {
flex-direction: row !important;
flex-wrap: nowrap !important;
align-items: center;
justify-content: flex-start;
justify-content: flex-end;
width: 100%;
}

@ -52,23 +52,31 @@ body.login-page-wls .login-page-wls__logo-img {
}
body.login-page-wls .login-page-wls__hint--error {
color: #c62828;
color: #052d62;
}
/** 调试「本次验证码」等成功提示:与选手端浅蓝主题一致 */
body.login-page-wls .login-page-wls__hint--success {
color: #2e7d32;
color: #052d62 !important;
background: #eef7fc !important;
border: 1px solid rgba(5, 45, 98, 0.14) !important;
border-radius: 8px !important;
padding: 0.5rem 0.75rem !important;
margin-top: 0.75rem !important;
box-sizing: border-box;
}
/** 显式校验红框(原型表单在提交前不用 :invalid */
/** 显式校验:与主题色 #052d62 一致 */
body.login-page-wls .login-page-wls__input.login-page-wls__input--error {
border-color: #e57373;
border-color: #052d62;
box-shadow: 0 0 0 2px rgba(5, 45, 98, 0.22);
}
body.login-page-wls .login-page-wls__input.login-page-wls__input--error:focus {
border-color: #e57373;
box-shadow: 0 0 0 3px rgba(229, 115, 115, 0.2);
border-color: #052d62;
box-shadow: 0 0 0 3px rgba(5, 45, 98, 0.25);
}
body.login-page-wls .login-page-wls__slogan.login-page-wls__slogan--error {
color: #e65100;
color: #052d62;
}

@ -2,7 +2,7 @@
import { ref, reactive, computed, watch, onMounted, onBeforeUnmount, nextTick, type Ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { Modal, Dropdown } from 'bootstrap'
import { getApiBase, TOKEN_KEY } from '../config/api'
import { getApiBase, normalizePublicAssetUrl, TOKEN_KEY } from '../config/api'
/** @types/bootstrap 未完全对齐 5.x 运行时 API */
type BootstrapDropdownInstance = { toggle: () => void; hide: () => void }
@ -249,6 +249,12 @@ function fieldColClass(field: SignupFormSchemaField): string {
return 'col-md-4'
}
/** 多行文本:配置项 placeholder 改在输入框下方展示 */
function textareaFieldPlaceholder(field: SignupFormSchemaField): string {
if (field.type !== 'textarea') return ''
return String(field.placeholder ?? '').trim()
}
function isFieldVisible(field: SignupFormSchemaField): boolean {
if (field.type === 'file') return true
if (field.key === 'location_province' || field.key === 'location_city') {
@ -653,12 +659,13 @@ async function uploadNewFiles(target: 'plan' | 'supporting') {
item.fromServer = true
item.original_name = data.original_name as string
item.size = data.size as number
item.url = data.url as string
item.previewUrl = data.url as string
const fileUrl = normalizePublicAssetUrl(String(data.url ?? ''))
item.url = fileUrl
item.previewUrl = fileUrl
if (blobUrl && String(blobUrl).startsWith('blob:')) URL.revokeObjectURL(blobUrl)
delete item.file
}
showNotice('', '上传成功', 'success')
showNotice('文件已上传。', '上传成功', 'success')
if (target === 'plan') validatePlanFile()
else validateSupportingFiles()
}
@ -862,24 +869,30 @@ function applyServerPayload(d: {
syncLocationFields()
planFileItems.value = (d.files || [])
.filter((f) => f.kind === 'plan')
.map((f) => ({
id: f.id,
fromServer: true,
original_name: f.original_name,
size: f.size,
url: f.url,
previewUrl: f.url,
}))
.map((f) => {
const u = normalizePublicAssetUrl(f.url)
return {
id: f.id,
fromServer: true,
original_name: f.original_name,
size: f.size,
url: u,
previewUrl: u,
}
})
supportingFileItems.value = (d.files || [])
.filter((f) => f.kind === 'supporting')
.map((f) => ({
id: f.id,
fromServer: true,
original_name: f.original_name,
size: f.size,
url: f.url,
previewUrl: f.url,
}))
.map((f) => {
const u = normalizePublicAssetUrl(f.url)
return {
id: f.id,
fromServer: true,
original_name: f.original_name,
size: f.size,
url: u,
previewUrl: u,
}
})
validatePlanFile()
validateSupportingFiles()
}
@ -913,7 +926,7 @@ async function saveDraftToServer() {
}
if (!r.ok) {
void r.json().catch(() => ({}))
showNotice('', '保存失败', 'warning')
showNotice('草稿未能保存,请检查网络或稍后重试。', '保存失败', 'warning')
return false
}
try {
@ -937,7 +950,7 @@ async function submitApplicationToServer() {
}
if (!r.ok) {
void r.json().catch(() => ({}))
showNotice('', '提交失败', 'warning')
showNotice('报名未能提交,请检查网络或稍后重试。', '提交失败', 'warning')
return false
}
const d = (await r.json()) as Parameters<typeof applyServerPayload>[0]
@ -953,7 +966,7 @@ async function onSaveClick() {
const ok = await saveDraftToServer()
if (!ok) return
saveDraftLocal()
showNotice('', '保存成功', 'success')
showNotice('已保存草稿(按当前已填写信息暂存)。', '保存成功', 'success')
}
async function onSubmitClick() {
@ -964,7 +977,7 @@ async function onSubmitClick() {
if (!validateForm()) return
const ok = await submitApplicationToServer()
if (!ok) return
showNotice('', '提交成功', 'success')
showNotice('已提交报名', '提交成功', 'success')
}
onBeforeUnmount(() => {
@ -1298,17 +1311,27 @@ onMounted(() => {
v-model="formModel[field.key]"
class="form-control editable"
rows="4"
placeholder=""
:required="effectiveRequired(field)"
:disabled="formDisabled"
:placeholder="(field.placeholder ?? '').trim()"
/>
<div v-if="field.key === 'intro'" class="d-flex justify-content-end mt-1">
<small class="text-secondary prototype-subtitle intro-char-count">{{ introCount }} </small>
<div
v-if="textareaFieldPlaceholder(field) || field.key === 'intro'"
class="d-flex flex-wrap align-items-baseline gap-2 mt-1 w-100"
>
<small v-if="textareaFieldPlaceholder(field)" class="apply-textarea-placeholder-hint mb-0">{{
textareaFieldPlaceholder(field)
}}</small>
<small
v-if="field.key === 'intro'"
class="text-secondary prototype-subtitle intro-char-count mb-0 ms-auto"
>{{ introCount }} </small
>
</div>
<small
v-if="participantFieldHelp(field.help)"
class="text-secondary prototype-subtitle d-block"
:class="{ 'mt-1': field.key === 'intro' }"
:class="{ 'mt-1': field.key === 'intro' || textareaFieldPlaceholder(field) }"
>{{ participantFieldHelp(field.help) }}</small
>
</div>
@ -1417,11 +1440,42 @@ onMounted(() => {
<button type="button" class="btn-close ms-auto" data-bs-dismiss="modal" aria-label="" />
</div>
<div class="modal-body notice-modal-body">
<div class="notice-icon" aria-hidden="true">
<svg
v-if="noticeIsSuccess"
viewBox="0 0 24 24"
width="36"
height="36"
fill="none"
aria-hidden="true"
>
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="1.8" />
<path
d="M7 12.2l3.1 3.1L17.2 8.8"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
<svg v-else viewBox="0 0 24 24" width="36" height="36" fill="none" aria-hidden="true">
<path
d="M12 3.6L2.9 19.2a1 1 0 0 0 .86 1.5h16.48a1 1 0 0 0 .86-1.5L12 3.6z"
stroke="currentColor"
stroke-width="1.8"
/>
<path d="M12 9v5" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
<circle cx="12" cy="16.8" r="1.1" fill="currentColor" />
</svg>
</div>
<h5 class="notice-title">{{ noticeTitle }}</h5>
<p class="notice-text text-break" style="white-space: pre-wrap">{{ noticeBody }}</p>
<p v-if="noticeBody.trim()" class="notice-text text-break" style="white-space: pre-wrap">
{{ noticeBody }}
</p>
</div>
<div class="modal-footer notice-modal-footer border-0">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">确定</button>
<button type="button" class="btn btn-light notice-cancel-btn" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary notice-ok-btn" data-bs-dismiss="modal">确定</button>
</div>
</div>
</div>

Loading…
Cancel
Save