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(() => {