|
|
|
@ -534,7 +534,7 @@ |
|
|
|
/> |
|
|
|
</div> |
|
|
|
<div class="section_12 flex-row"> |
|
|
|
<div class="box_16 flex-row"> |
|
|
|
<div class="box_16 flex-row" @click="onLearnMore"> |
|
|
|
<div class="image-text_8 flex-row justify-between"> |
|
|
|
<span class="text-group_17">了解更多</span> |
|
|
|
<div class="image-wrapper_16 flex-col"> |
|
|
|
@ -556,29 +556,21 @@ export default { |
|
|
|
data() { |
|
|
|
return { |
|
|
|
constants: {}, |
|
|
|
activeSection: 'top' /* 当前高亮的 nav 对应的 section id */ |
|
|
|
activeSection: 'top', |
|
|
|
scrollLocked: false |
|
|
|
}; |
|
|
|
}, |
|
|
|
mounted() { |
|
|
|
this.initScrollSpy(); |
|
|
|
}, |
|
|
|
methods: { |
|
|
|
/* scroll-spy:用 Vue 响应式 data(activeSection) 驱动 nav-link 的 :class 绑定。 |
|
|
|
关键设计:点击 nav-link 后,锁定 activeSection = id,屏蔽一切自动 update, |
|
|
|
直到平滑滚动结束再校准。这样彻底避免"点击瞬间设的 activeSection |
|
|
|
被 IO/scroll 回调里的 compute() 改掉"的竞态。 */ |
|
|
|
initScrollSpy() { |
|
|
|
const sectionIds = ['about', 'skills', 'platform', 'agent', 'media', 'practice', 'gateway']; |
|
|
|
|
|
|
|
// 单一 compute:section 顶 ≤ 视口高度的 50%(即"section 顶已过视口中线")就算激活 |
|
|
|
// 这避开 about 段高度只有 ~144px 的边界问题: |
|
|
|
// 点 skills 后,skills 顶 ≈ 260px < 375px(50% vh),platform 顶 > 375 → 选 skills |
|
|
|
// 而 about 顶 ≈ 116px 也 ≤ 375,但 for 循环从上到下遍历,后一个 skills 覆盖前一个 |
|
|
|
const compute = () => { |
|
|
|
const scrollY = window.scrollY || window.pageYOffset || 0; |
|
|
|
if (scrollY < 100) return 'top'; |
|
|
|
const vh = window.innerHeight || document.documentElement.clientHeight; |
|
|
|
const trigger = vh * 0.5; |
|
|
|
const trigger = 200; |
|
|
|
let activeId = 'top'; |
|
|
|
for (const id of sectionIds) { |
|
|
|
const el = document.getElementById(id); |
|
|
|
@ -594,14 +586,13 @@ export default { |
|
|
|
}; |
|
|
|
|
|
|
|
const update = () => { |
|
|
|
// 抑制期(scrollTo 触发的平滑滚动过程中)不响应任何 update |
|
|
|
if (this.scrollLocked) return; |
|
|
|
if (Date.now() < (this.scrollSuppressUntil || 0)) return; |
|
|
|
this.activeSection = compute(); |
|
|
|
}; |
|
|
|
|
|
|
|
// IO 兜底:section 进入/退出视口时重算 |
|
|
|
const observer = new IntersectionObserver(() => update(), { |
|
|
|
rootMargin: '0px 0px -50% 0px', |
|
|
|
rootMargin: '0px 0px -200px 0px', |
|
|
|
threshold: 0 |
|
|
|
}); |
|
|
|
sectionIds.forEach((id) => { |
|
|
|
@ -621,37 +612,27 @@ export default { |
|
|
|
|
|
|
|
update(); |
|
|
|
}, |
|
|
|
onLearnMore(event) { |
|
|
|
const btn = event.currentTarget; |
|
|
|
if (btn) { |
|
|
|
btn.classList.add('btn-flash'); |
|
|
|
setTimeout(() => btn.classList.remove('btn-flash'), 300); |
|
|
|
} |
|
|
|
setTimeout(() => this.scrollTo('about'), 200); |
|
|
|
}, |
|
|
|
scrollTo(id) { |
|
|
|
// 1) 立即同步响应式状态(不等滚动动画) |
|
|
|
this.activeSection = id; |
|
|
|
// 2) 抑制一切自动 update 1.2s(覆盖 smooth 滚动动画) |
|
|
|
const suppressMs = 1200; |
|
|
|
const suppressMs = 2000; |
|
|
|
this.scrollSuppressUntil = Date.now() + suppressMs; |
|
|
|
// 3) 抑制期结束后,按当前 scrollY 校准一次 |
|
|
|
// 用一个标志区分"用户点击锁定"和"自由滚动" |
|
|
|
// 锁定期间,scroll/IO 触发的 update() 全部 return,绝不覆盖 activeSection |
|
|
|
this.scrollLocked = true; |
|
|
|
setTimeout(() => { |
|
|
|
this.scrollSuppressUntil = 0; // 解除抑制 |
|
|
|
// 此时页面已稳定,scrollY 是最终值,compute() 必然正确 |
|
|
|
this.activeSection = (() => { |
|
|
|
const sectionIds = ['about', 'skills', 'platform', 'agent', 'media', 'practice', 'gateway']; |
|
|
|
const scrollY = window.scrollY || window.pageYOffset || 0; |
|
|
|
if (scrollY < 100) return 'top'; |
|
|
|
const vh = window.innerHeight || document.documentElement.clientHeight; |
|
|
|
const trigger = vh * 0.5; |
|
|
|
let activeId = 'top'; |
|
|
|
for (const sid of sectionIds) { |
|
|
|
const el = document.getElementById(sid); |
|
|
|
if (!el) continue; |
|
|
|
const top = el.getBoundingClientRect().top; |
|
|
|
if (top <= trigger) { |
|
|
|
activeId = sid; |
|
|
|
} else { |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
return activeId; |
|
|
|
})(); |
|
|
|
this.scrollSuppressUntil = 0; |
|
|
|
this.activeSection = id; |
|
|
|
// 再延迟 200ms 才解锁,防止抑制期结束后第一次 scroll 事件又触发 compute |
|
|
|
setTimeout(() => { this.scrollLocked = false; }, 200); |
|
|
|
}, suppressMs); |
|
|
|
// 4) 触发滚动 |
|
|
|
if (id === 'top') { |
|
|
|
window.scrollTo({ top: 0, behavior: 'smooth' }); |
|
|
|
return; |
|
|
|
@ -659,17 +640,13 @@ export default { |
|
|
|
const el = document.getElementById(id); |
|
|
|
if (el) { |
|
|
|
if (id === 'gateway') { |
|
|
|
// gateway 段高度 23.574rem 可能超过视口,按"底部对齐"算会让顶部滚出视口看不见 |
|
|
|
// 改成顶部对齐(留 100px 给 nav,不让"统一的大模型接口网关"标题被挡) |
|
|
|
const top = el.getBoundingClientRect().top + window.pageYOffset - 100; |
|
|
|
const top = el.getBoundingClientRect().top + window.pageYOffset - 300; |
|
|
|
window.scrollTo({ top: top, behavior: 'smooth' }); |
|
|
|
} else if (id === 'skills') { |
|
|
|
// skills 段是 absolute 定位 (group_27 top: 35.547rem),点击时让它顶部贴视口顶部, |
|
|
|
// 不要再留 260px(否则会露出上面 gateway 段的"降本/增效/留资/安全"等模块内容) |
|
|
|
const top = el.getBoundingClientRect().top + window.pageYOffset; |
|
|
|
const top = el.getBoundingClientRect().top + window.pageYOffset - 200; |
|
|
|
window.scrollTo({ top: top, behavior: 'smooth' }); |
|
|
|
} else { |
|
|
|
const top = el.getBoundingClientRect().top + window.pageYOffset - 260; |
|
|
|
const top = el.getBoundingClientRect().top + window.pageYOffset - 460; |
|
|
|
window.scrollTo({ top: top, behavior: 'smooth' }); |
|
|
|
} |
|
|
|
} |
|
|
|
@ -679,18 +656,14 @@ export default { |
|
|
|
</script> |
|
|
|
<style scoped lang="css" src="./assets/index.rem.css" /> |
|
|
|
<style scoped> |
|
|
|
/* 顶部导航固定,滚动时始终可见、可点击 */ |
|
|
|
.section_2 { |
|
|
|
position: fixed; |
|
|
|
top: 0; |
|
|
|
left: 0; |
|
|
|
z-index: 100; |
|
|
|
/* will-change:transform + 自身 transform:translateZ(0):把 fixed 导航推到独立 GPU 合成层, |
|
|
|
滚动时导航不再走主线程重绘,避免每帧重新画整个 nav 元素造成顶部闪/卡 */ |
|
|
|
will-change: transform; |
|
|
|
transform: translateZ(0); |
|
|
|
} |
|
|
|
/* 导航被 fixed 移出文档流,给内容补回 80px 顶部间距,避免上移错位 */ |
|
|
|
.section_1 { |
|
|
|
margin-top: 2.133rem; |
|
|
|
} |
|
|
|
@ -708,10 +681,24 @@ export default { |
|
|
|
.nav-link.nav-active { |
|
|
|
color: rgba(25, 140, 255, 1) !important; |
|
|
|
} |
|
|
|
/* 应用实践 nav 项(全局 CSS 的 .text_nav_practice 没被 scope,需要单独覆盖高亮) */ |
|
|
|
.text_nav_practice.nav-active { |
|
|
|
color: rgba(25, 140, 255, 1) !important; |
|
|
|
} |
|
|
|
.box_16 { |
|
|
|
cursor: pointer !important; |
|
|
|
transition: background-color 0.3s, transform 0.15s, box-shadow 0.2s; |
|
|
|
} |
|
|
|
.box_16:hover { |
|
|
|
background-color: rgba(40, 40, 40, 1) !important; |
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); |
|
|
|
} |
|
|
|
.btn-flash { |
|
|
|
background-color: rgba(60, 60, 60, 1) !important; |
|
|
|
transform: scale(0.95); |
|
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2) inset; |
|
|
|
} |
|
|
|
|
|
|
|
/* ===== 修复 logo:底部用与首页 image_1 同款整张合并图 ===== */ |
|
|
|
/* 顶部 image_1 (slice-3-black, 浅色页头) + 底部 .footer-logo (slice-3-white, 深色页脚 group_21 bg: rgba(16,16,16,1)) */ |
|
|
|
/* 尺寸/比例与首页 6.347rem × 0.854rem 完全一致,无图-字缝隙 */ |
|
|
|
.footer-logo { |
|
|
|
width: 6.347rem; |
|
|
|
height: 0.854rem; |
|
|
|
@ -720,9 +707,6 @@ export default { |
|
|
|
margin: 0; |
|
|
|
} |
|
|
|
|
|
|
|
/* ===== 4 个价值标签(降本/增效/留资/安全)的小字描述:强制两行 + 居中 + 跟参考图断句一字不差(不改文案) ===== */ |
|
|
|
/* 调小 font-size 到 0.39rem + letter-spacing 0rem 让 6.4rem 容器能容纳 16-17 字(留 1-2 字余量), |
|
|
|
配合 <br /> 在 14-15 字处硬换行,避免自然换行与 <br /> 重复触发导致 3 行 */ |
|
|
|
.text-group_1, |
|
|
|
.text-group_2, |
|
|
|
.text-group_3, |
|
|
|
@ -733,11 +717,9 @@ export default { |
|
|
|
.text_12, |
|
|
|
.text_14, |
|
|
|
.text_16 { |
|
|
|
/* 还原:不强制 2 行、不改 font-size / line-height,文字回到原本单行布局,行间距恢复默认 */ |
|
|
|
} |
|
|
|
|
|
|
|
/* ===== 底部 footer 版权信息上方加横线分隔 ===== */ |
|
|
|
/* 写在 <style scoped> 里(用 !important),因为外部 index.rem.css 的 .text-wrapper_7 没被 scoped 处理,匹配不上 */ |
|
|
|
/* 底部 footer 版权信息上方加横线分隔(用 !important 覆盖全局 .text-wrapper_7) */ |
|
|
|
.text-wrapper_7 { |
|
|
|
border-top: 1px solid rgba(255, 255, 255, 0.5) !important; |
|
|
|
padding-top: 1.067rem !important; |
|
|
|
@ -746,10 +728,9 @@ export default { |
|
|
|
} |
|
|
|
.group_20, |
|
|
|
.group_21 { |
|
|
|
height: 16rem !important; /* footer 内容实际累加 ≈ 16rem,缩 3.3rem 消除底部黑色空白 */ |
|
|
|
height: 16rem !important; |
|
|
|
} |
|
|
|
|
|
|
|
/* 知识大脑右侧装饰图(新版蓝湖:单张 339x327 替代原 image_35 + label_5 拼接) */ |
|
|
|
.image-26-knowledge { |
|
|
|
width: 9.04rem; |
|
|
|
height: 8.72rem; |
|
|
|
@ -757,9 +738,8 @@ export default { |
|
|
|
display: block; |
|
|
|
} |
|
|
|
|
|
|
|
/* ============ 应用实践板块(全部 class 用 practice_ 前缀避开与 gateway 的 class 冲突)============ */ |
|
|
|
/* 关键设计:这些 CSS 写在 <style scoped> 里(不是 index.rem.css), |
|
|
|
自动带 [data-v-xxx] 属性选择器,scope 隔离不会被全局污染 */ |
|
|
|
/* ============ 应用实践板块 ============ */ |
|
|
|
|
|
|
|
.practice_text_wrapper_13 { |
|
|
|
width: 4.694rem; |
|
|
|
height: 1.707rem; |
|
|
|
@ -916,20 +896,10 @@ export default { |
|
|
|
margin-top: 0.374rem; |
|
|
|
} |
|
|
|
|
|
|
|
/* ============ footer 应用实践列(比照 newcss text-wrapper_16,放在"创作平台"和"模型网关"之间)============ */ |
|
|
|
/* 比照 newcss .text-wrapper_16: |
|
|
|
- width: 1.92rem(原 withmind 误用 2.4rem) |
|
|
|
- height: 5.12rem(原 withmind 误用 3.734rem,4 项需要 5.12rem) |
|
|
|
- margin-left: 0.934rem(原 withmind 误用 1.787rem) |
|
|
|
写在 <style scoped> 里自动带 [data-v-xxx],scope 隔离不会污染其他列。 |
|
|
|
|
|
|
|
关键修复:不要用 justify-between,改用普通流式布局 + 每项固定 margin-top, |
|
|
|
这样 4 项的实际渲染高度 = 4*0.96 + 3*0.427 = 5.121rem ≈ 5.12rem,精确填满, |
|
|
|
不会因 justify-between 撑开产生视觉间距不一致 */ |
|
|
|
/* ============ footer 应用实践列 ============ */ |
|
|
|
.practice_footer_col { |
|
|
|
width: 1.92rem; |
|
|
|
height: 5.12rem; |
|
|
|
/* 再右移 0.2rem(原 0.747rem → 0.947rem) */ |
|
|
|
margin-left: 0.947rem; |
|
|
|
} |
|
|
|
|
|
|
|
|