diff --git a/frontend/prototype/assets/prototype.css b/frontend/prototype/assets/prototype.css index ecb7ccb..49f425d 100644 --- a/frontend/prototype/assets/prototype.css +++ b/frontend/prototype/assets/prototype.css @@ -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 { diff --git a/src/config/api.ts b/src/config/api.ts index 63fbcbd..6e4bf80 100644 --- a/src/config/api.ts +++ b/src/config/api.ts @@ -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, diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue index 1fd8bba..cf420de 100644 --- a/src/layouts/MainLayout.vue +++ b/src/layouts/MainLayout.vue @@ -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') }) @@ -190,10 +212,14 @@ onUnmounted(() => {