feat: 样式调整
This commit is contained in:
parent
68bc750604
commit
cd03f0fa3d
@ -726,9 +726,21 @@ export default {
|
||||
this.$nextTick(() => this.scrollListContentToTop());
|
||||
return true;
|
||||
}
|
||||
// 首页带 anchor 跳转进入:从一开始就锁定高亮,不让 observer 参与本次定位,
|
||||
// 避免侧边栏 activeTabIndex 在慢滚过程中从 0 一路闪到目标 tab。
|
||||
this.activeTabIndex = tabIndex;
|
||||
this.suppressSectionObserver = true;
|
||||
this.initScrollSpy();
|
||||
this.initSidebarSticky();
|
||||
this.scrollToSection(anchor, tabIndex);
|
||||
// 等两帧:第 1 帧让 gxnlpt-block 节点挂载完毕,
|
||||
// 第 2 帧让 layout/图片/字体把真实高度提交到 DOM,
|
||||
// 此时 getBoundingClientRect 才能算出"一次定位准确"的目标位置。
|
||||
const jumpToAnchor = () => {
|
||||
this.scrollToSection(anchor, tabIndex, { behavior: 'auto' });
|
||||
// 瞬时定位完成后恢复 observer,让用户后续手动滚动时侧边栏能正常高亮
|
||||
this.suppressSectionObserver = false;
|
||||
};
|
||||
requestAnimationFrame(() => requestAnimationFrame(jumpToAnchor));
|
||||
return true;
|
||||
},
|
||||
getIconUrl(iconName) {
|
||||
@ -993,11 +1005,15 @@ export default {
|
||||
this.activeTabIndex = tabIndex;
|
||||
}
|
||||
},
|
||||
scheduleScrollUnlock(tabIndex) {
|
||||
scheduleScrollUnlock(tabIndex, options = {}) {
|
||||
this.clearScrollUnlock();
|
||||
this.suppressSectionObserver = true;
|
||||
this.activeTabIndex = tabIndex;
|
||||
|
||||
// 瞬时定位路径:不依赖 900ms 定时器/scrollend 事件去延后解锁,
|
||||
// 由调用方(典型是 navigateToRouteAnchor)直接控制解锁时机。
|
||||
if (options.skipObserverLock) return;
|
||||
|
||||
const scrollRoot = this.getScrollRoot();
|
||||
let unlocked = false;
|
||||
const done = () => {
|
||||
@ -1156,7 +1172,7 @@ export default {
|
||||
* 通过 getBoundingClientRect 获取目标元素在视口内的视觉位置,
|
||||
* 反算至滚动容器布局坐标后 scrollTo,不受 scale 变换影响。
|
||||
*/
|
||||
scrollToSection(sectionId, tabIndex) {
|
||||
scrollToSection(sectionId, tabIndex, options = {}) {
|
||||
const el = document.getElementById(sectionId);
|
||||
const scrollRoot = this.getScrollRoot();
|
||||
if (!el || !scrollRoot) return;
|
||||
@ -1175,8 +1191,12 @@ export default {
|
||||
const OFFSET_DP = 88; // 设计稿像素
|
||||
const target = Math.max(0, layoutTarget - OFFSET_DP);
|
||||
|
||||
scrollRoot.scrollTo({ top: target, behavior: 'smooth' });
|
||||
this.scheduleScrollUnlock(tabIndex);
|
||||
// 默认仍是 smooth(用户手动点侧边栏时需要平滑过渡);
|
||||
// 从首页带 anchor 跳转进入时,使用 'auto' 瞬时定位,
|
||||
// 避免滚动过程中侧边栏 activeTabIndex 被 observer 反复改写造成的"跳变"。
|
||||
const behavior = options.behavior || 'smooth';
|
||||
scrollRoot.scrollTo({ top: target, behavior });
|
||||
this.scheduleScrollUnlock(tabIndex, { skipObserverLock: behavior === 'auto' });
|
||||
},
|
||||
/* ========== IntersectionObserver 驱动的 ScrollSpy ========== */
|
||||
/**
|
||||
|
||||
@ -787,10 +787,10 @@ export default {
|
||||
},
|
||||
// 企业出海卡片点击 → 专题页对应区块
|
||||
handleOverseasClick(item) {
|
||||
if (item && item.comingSoon) {
|
||||
this.showComingSoon('航运燃料专题');
|
||||
return;
|
||||
}
|
||||
// if (item && item.comingSoon) {
|
||||
// this.showComingSoon('航运燃料专题');
|
||||
// return;
|
||||
// }
|
||||
if (!item || !item.sectionId) {
|
||||
this.$router.push('/qych');
|
||||
return;
|
||||
|
||||
@ -1,11 +1,7 @@
|
||||
<template>
|
||||
<div class="hydt-page portal-page portal-page-shell">
|
||||
<!-- Figma banner-bg 150622:19115(背景 imageRef ec2e3dd…) -->
|
||||
<section
|
||||
class="hydt-banner"
|
||||
:class="{ 'hydt-banner--collapsed': bannerCollapsed }"
|
||||
aria-label="行业动态"
|
||||
>
|
||||
<section class="hydt-banner" aria-label="行业动态">
|
||||
<div class="hydt-banner-bg" aria-hidden="true"></div>
|
||||
<div class="hydt-banner-inner page-content-wrap">
|
||||
<p class="hydt-breadcrumb">
|
||||
@ -27,7 +23,7 @@
|
||||
<section class="hydt-body">
|
||||
<div class="hydt-body-inner page-content-wrap">
|
||||
<div class="hydt-panel">
|
||||
<div class="hydt-tabs" role="tablist" aria-label="动态分类">
|
||||
<div ref="tabsRef" class="hydt-tabs" role="tablist" aria-label="动态分类">
|
||||
<button
|
||||
v-for="(tab, index) in newsTabs"
|
||||
:key="tab.type"
|
||||
@ -45,7 +41,7 @@
|
||||
<div v-if="pageLoading" class="hydt-state">加载中...</div>
|
||||
<div v-else-if="!filteredList.length" class="hydt-state">暂无相关动态</div>
|
||||
<template v-else>
|
||||
<div class="hydt-list">
|
||||
<div ref="listRef" class="hydt-list">
|
||||
<article
|
||||
v-for="(item, index) in paginatedList"
|
||||
:key="item.id || item.uuid || item.bt || index"
|
||||
@ -105,8 +101,6 @@ export default {
|
||||
newsTabs: NEWS_TABS,
|
||||
activeTab: 0,
|
||||
pageLoading: false,
|
||||
/* 优化需求:点击 tab 后 banner 收起;首页进入不收起,tab 切换时收起 */
|
||||
bannerCollapsed: false,
|
||||
newsListByType: {
|
||||
gjzc: [],
|
||||
hyzx: [],
|
||||
@ -140,22 +134,10 @@ export default {
|
||||
activeTab() {
|
||||
this.page.pageNo = 1;
|
||||
},
|
||||
// 兜底:移动端断点切换 / Figma 缩放等导致 .content-wrap 重新挂载时,重绑滚动
|
||||
'$route.path'() {
|
||||
this.unbindScrollListener();
|
||||
this.$nextTick(() => this.bindScrollListener());
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.syncTabFromRoute(this.$route.query.type);
|
||||
this.fetchNewsData();
|
||||
// 注意:滚动容器是 main.vue 中的 .content-wrap,window 不会冒泡到滚动事件
|
||||
this.$nextTick(() => {
|
||||
this.bindScrollListener();
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.unbindScrollListener();
|
||||
},
|
||||
methods: {
|
||||
goHomeToNews() {
|
||||
@ -170,28 +152,42 @@ export default {
|
||||
// 同 tab 直接 no-op(避免无意义的状态抖动)
|
||||
if (this.activeTab === index) return;
|
||||
this.activeTab = index;
|
||||
// 收起 banner:列表区本来就在 banner 紧邻下方,收起后用户自然能看到更多列表。
|
||||
// 注意:这里不再调用 $router.replace,否则会触发 main.vue 中 $route.watch
|
||||
// 把 .content-wrap.scrollTop 瞬间置 0,导致用户感觉到"弹一下"。
|
||||
// 把页面滚动到 list 顶部(紧接在 sticky tabs 之下),
|
||||
// 让用户切完分类后能立即看到新分类的列表项。
|
||||
this.$nextTick(() => {
|
||||
this.bannerCollapsed = true;
|
||||
this.scrollToListTop();
|
||||
});
|
||||
},
|
||||
onPageChange() {
|
||||
const root = this.getScrollRoot();
|
||||
if (root && root !== window) {
|
||||
root.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
} else {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
// 切完分页滚到 list 顶部,让新一页从顶开始展示
|
||||
this.$nextTick(() => {
|
||||
this.scrollToListTop();
|
||||
});
|
||||
},
|
||||
onScroll() {
|
||||
// 滚回 banner 顶部时自动展开 banner
|
||||
if (!this.bannerCollapsed) return;
|
||||
/**
|
||||
* 滚动到 list 顶部。
|
||||
*
|
||||
* 注意点:.hydt-tabs 是 position: sticky; top: 0;,
|
||||
* 如果直接把 list 顶端对齐到视口顶,会被 sticky tabs 遮住一部分。
|
||||
* 所以目标 top 要"减去"tabs 的实际高度(动态读取,避免硬编码样式变量)。
|
||||
*/
|
||||
scrollToListTop() {
|
||||
const list = this.$refs.listRef;
|
||||
if (!list) return;
|
||||
const tabs = this.$refs.tabsRef;
|
||||
// tabs 实际渲染高度(含 padding),兜底 0
|
||||
const tabsHeight = tabs ? tabs.getBoundingClientRect().height : 0;
|
||||
const root = this.getScrollRoot();
|
||||
const scrollTop = root === window ? window.scrollY : (root && root.scrollTop) || 0;
|
||||
if (scrollTop <= 8) {
|
||||
this.bannerCollapsed = false;
|
||||
const rootRect = root === window ? { top: 0 } : root.getBoundingClientRect();
|
||||
// list 顶端到滚动容器顶部的距离,再减去 tabs 高度做留白
|
||||
const targetTop = (root === window ? window.scrollY : root.scrollTop)
|
||||
+ (list.getBoundingClientRect().top - rootRect.top)
|
||||
- tabsHeight;
|
||||
const finalTop = Math.max(0, targetTop);
|
||||
if (root === window) {
|
||||
window.scrollTo({ top: finalTop, behavior: 'smooth' });
|
||||
} else {
|
||||
root.scrollTo({ top: finalTop, behavior: 'smooth' });
|
||||
}
|
||||
},
|
||||
getScrollRoot() {
|
||||
@ -200,28 +196,6 @@ export default {
|
||||
if (portalRoot) return portalRoot;
|
||||
return window;
|
||||
},
|
||||
bindScrollListener() {
|
||||
// 必须把滚动监听挂到真正的滚动容器 .content-wrap 上;
|
||||
// 之前挂在 window 上,window 不会冒泡来自内部容器的滚动事件,导致 onScroll 永不触发
|
||||
this.unbindScrollListener();
|
||||
const root = this.getScrollRoot();
|
||||
if (!root || root === window) {
|
||||
this._scrollTarget = window;
|
||||
window.addEventListener('scroll', this.onScroll, { passive: true });
|
||||
} else {
|
||||
this._scrollTarget = root;
|
||||
root.addEventListener('scroll', this.onScroll, { passive: true });
|
||||
}
|
||||
},
|
||||
unbindScrollListener() {
|
||||
if (!this._scrollTarget) {
|
||||
// 兜底:组件销毁时即使 _scrollTarget 未初始化也要清掉 window 上的残留监听
|
||||
window.removeEventListener('scroll', this.onScroll);
|
||||
return;
|
||||
}
|
||||
this._scrollTarget.removeEventListener('scroll', this.onScroll);
|
||||
this._scrollTarget = null;
|
||||
},
|
||||
handleNewsClick(item) {
|
||||
const link = item.yyLj || item.lj;
|
||||
if (link) {
|
||||
@ -289,12 +263,6 @@ export default {
|
||||
overflow: hidden;
|
||||
background: @home-color-page-bg;
|
||||
box-sizing: border-box;
|
||||
transition: height 0.45s cubic-bezier(0.4, 0, 0.2, 1), margin-bottom 0.45s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* 优化需求:点击 tab 后 banner 收起,仅保留扁平标题条 */
|
||||
.hydt-banner--collapsed {
|
||||
height: 88px;
|
||||
}
|
||||
|
||||
/* Figma 150622:19115 画框背景 FILL,1440×350 */
|
||||
@ -319,19 +287,6 @@ export default {
|
||||
padding-top: @hydt-banner-pad-top;
|
||||
padding-bottom: @hydt-banner-pad-bottom;
|
||||
box-sizing: border-box;
|
||||
transition: gap 0.35s ease, padding 0.35s ease;
|
||||
}
|
||||
|
||||
.hydt-banner--collapsed .hydt-banner-inner {
|
||||
gap: 6px;
|
||||
padding-top: 18px;
|
||||
padding-bottom: 18px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hydt-banner--collapsed .hydt-banner-title {
|
||||
font-size: 22px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.hydt-breadcrumb {
|
||||
@ -391,7 +346,7 @@ export default {
|
||||
}
|
||||
|
||||
.hydt-body-inner {
|
||||
padding: @hydt-body-pad-top 0 @hydt-body-pad-bottom;
|
||||
padding: 24px 0 @hydt-body-pad-bottom;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@ -407,7 +362,10 @@ export default {
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
gap: @home-news-tab-gap;
|
||||
margin-bottom: @hydt-tabs-to-list-gap;
|
||||
padding: @hydt-tabs-to-list-gap 4px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.hydt-tab {
|
||||
@ -450,6 +408,7 @@ export default {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: @hydt-list-gap;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.hydt-item {
|
||||
|
||||
@ -44,12 +44,13 @@
|
||||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import portalFigmaScaleMixin from '@/pages/index/utils/portal-figma-scale-mixin';
|
||||
// 直接引用全局弹窗事件总线,规避 mixin 链路在动态路由组件中可能的副作用时序问题
|
||||
import comingSoonMixin from '@/pages/index/utils/coming-soon-mixin';
|
||||
// 引入全局“敬请期待”弹窗总线,main.vue 中的 t-dialog 监听该总线
|
||||
import '@/pages/index/utils/coming-soon-dialog.js';
|
||||
|
||||
export default {
|
||||
name: 'hyzt',
|
||||
mixins: [portalFigmaScaleMixin],
|
||||
mixins: [portalFigmaScaleMixin, comingSoonMixin],
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
@ -66,14 +67,10 @@ export default {
|
||||
goToHref(href) {
|
||||
if (href) {
|
||||
window.open(href, '_blank');
|
||||
} else if (typeof this.$showComingSoon === 'function') {
|
||||
// 全局“敬请期待”弹窗
|
||||
this.$showComingSoon('航运燃料专题');
|
||||
} else {
|
||||
// 退化方案:保证即便弹窗挂载未完成也能给用户反馈
|
||||
// eslint-disable-next-line no-alert
|
||||
alert('航运燃料专题:敬请期待');
|
||||
return;
|
||||
}
|
||||
// 与 home2 等其它落地页保持一致:使用 mixin 提供的"敬请期待"提示
|
||||
this.showComingSoon();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -324,6 +324,15 @@ export default {
|
||||
this.scrollToSectionFromQuery();
|
||||
});
|
||||
},
|
||||
watch: {
|
||||
// 修复:组件已挂载时(如从首页企业出海多次点击跳到不同 section),
|
||||
// query 变化不会触发 mounted,需要在这里显式处理滚动
|
||||
'$route.query.section': {
|
||||
handler() {
|
||||
this.scrollToSectionFromQuery();
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
goPage(url) {
|
||||
if (url) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user