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