From 1888d648505c1693d0ccf713ddf68c169c10e502 Mon Sep 17 00:00:00 2001
From: liulong <18539103286>
Date: Mon, 25 May 2026 20:19:55 +0800
Subject: [PATCH] =?UTF-8?q?feat:=E6=96=B0=E5=A2=9E=E6=94=B6=E8=97=8F?=
=?UTF-8?q?=E6=A0=A1=E9=AA=8C=E9=80=BB=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../qych-lowres-copy-spacing-report.md | 63 ++++++++++++++
txw-mhzc-web/package.json | 1 +
txw-mhzc-web/src/core/request.js | 27 ++++--
.../src/pages/index/utils/auth-guard.js | 57 +++++++++++++
.../index/utils/gxnlpt-scroll-helpers.js | 67 +++++++++++++++
.../src/pages/index/views/gxnlpt/index.vue | 35 +++++++-
.../tests/unit/gxnlpt-scroll-helpers.test.js | 85 +++++++++++++++++++
7 files changed, 327 insertions(+), 8 deletions(-)
create mode 100644 txw-mhzc-web/docs/test-reports/qych-lowres-copy-spacing-report.md
create mode 100644 txw-mhzc-web/src/pages/index/utils/auth-guard.js
create mode 100644 txw-mhzc-web/src/pages/index/utils/gxnlpt-scroll-helpers.js
create mode 100644 txw-mhzc-web/tests/unit/gxnlpt-scroll-helpers.test.js
diff --git a/txw-mhzc-web/docs/test-reports/qych-lowres-copy-spacing-report.md b/txw-mhzc-web/docs/test-reports/qych-lowres-copy-spacing-report.md
new file mode 100644
index 0000000..38d4aef
--- /dev/null
+++ b/txw-mhzc-web/docs/test-reports/qych-lowres-copy-spacing-report.md
@@ -0,0 +1,63 @@
+# 企业出海三版文案低分辨率留白修复报告
+
+## 设计源文件
+- 页面实现源文件:`src/pages/index/views/qych/index.vue`
+- 关联样式变量:`src/pages/index/styles/home-figma-variables.less`
+
+说明:当前仓库未包含独立的 Figma/PSD 设计源文件,本次输出的是已调整完成的前端实现源文件与测试报告。
+
+## 应用场景
+- 电池法案:`#section0`
+- CBAM(碳边境调节机制):`#section1`
+- 航运燃料:`#section2`
+
+三版文案均定义在 `src/pages/index/views/qych/index.vue`,共用同一套 `text-section / content-item1 / content-item2 / service-section-dcfa` 结构。
+
+## 问题表现
+- 在 `720P` 及以下高度场景中,文案区底部留白不足。
+- 右侧申请服务卡片下缘空间偏紧,整体显得贴底。
+- 左侧滚动文案区和右侧服务栏之间的纵向节奏偏紧,影响可读性和视觉呼吸感。
+
+## 根因分析
+- 三版文案区被统一锁定为固定高度:
+ - `@qych-policy-card-height: 490px`
+ - `@qych-policy-panel-height: 376px`
+- 低分辨率场景下,整体卡片高度、正文滚动区高度、右侧服务栏卡片内边距都没有针对设备高度做单独放大或重分配。
+- 底部留白主要依赖固定像素 `padding-bottom`,在 `720P` 一类场景中无法随视口高度自适应。
+
+## 调整方案
+- 在 `@media (max-width: 1366px), (max-height: 820px)` 下新增企业出海专题内容区适配。
+- 调整项:
+ - 增大专题上下留白:`--qych-topic-py`
+ - 放宽模块内部块间距:`.module gap`
+ - 文案主卡片改为响应式最小高度:`.text-section min-height`
+ - 标题区上下内边距改为 `clamp(...)`
+ - 正文区左右和底部内边距改为响应式值
+ - 左侧滚动文案区底部内边距提升到 `clamp(34px, 5vh, 48px)`
+ - 右侧服务栏底部内边距和卡片堆叠间距同步提升
+ - 服务卡片底部内边距提升到 `clamp(28px, 4.4vh, 40px)`
+ - 针对 CBAM 长文案单独上调正文滚动区高度
+
+## 关键修改文件
+- `src/pages/index/views/qych/index.vue`
+
+## 测试范围
+- 目标场景:
+ - `1280 x 720`
+ - `1366 x 768`
+ - `1366 x 820` 以下窗口高度
+- 验证点:
+ - 文案区底部是否存在足够留白
+ - 右侧服务卡片底部是否贴边
+ - 左右栏是否发生重叠
+ - 文案可读性和整体版式是否保持统一
+ - 高分辨率桌面样式是否被误伤
+
+## 验证结果
+- 代码诊断通过,未引入新的语法或样式错误。
+- 项目构建通过。
+- 低分辨率适配规则仅在目标断点内生效,高分辨率桌面原有版式不受影响。
+- 三版文案区的底部留白、服务栏下缘空间和整体纵向节奏均已统一收口。
+
+## 备注
+- 当前环境无法直接输出真实设计软件源文件,也无法进行真机截图回归;本报告基于代码实现、断点覆盖和构建结果生成。
diff --git a/txw-mhzc-web/package.json b/txw-mhzc-web/package.json
index 937214e..3c57abd 100644
--- a/txw-mhzc-web/package.json
+++ b/txw-mhzc-web/package.json
@@ -11,6 +11,7 @@
"build": "cross-env NODE_OPTIONS=--openssl-legacy-provider vue-cli-service build",
"build:site:dev": "vue-cli-service build --mode development",
"build:site:test": "vue-cli-service build --mode test",
+ "test:unit": "node --test tests/unit/gxnlpt-scroll-helpers.test.js",
"lint": "vue-cli-service lint",
"lint:style": "vue-cli-service lint:style",
"changelog": "conventional-changelog -p custom-config -i CHANGELOG.md -s -r 0",
diff --git a/txw-mhzc-web/src/core/request.js b/txw-mhzc-web/src/core/request.js
index 30331bb..c8d76c7 100644
--- a/txw-mhzc-web/src/core/request.js
+++ b/txw-mhzc-web/src/core/request.js
@@ -2,6 +2,7 @@ import { request } from '@gtff/tdesign-gt-vue';
import { MessagePlugin } from 'tdesign-vue';
import { mhLogout, getRedirectUri } from '@/pages/index/api/login';
import { LoadingPlugin, DialogPlugin } from '@gt4/common-front';
+import { showLoginGuide } from '@/pages/index/utils/auth-guard';
const SingleLoading = {
load: null,
@@ -111,9 +112,13 @@ request.interceptors.response.use(
// 获取错误信息
const msg = res.data.msg || '系统未知错误,请反馈给管理员';
if (code === 401) {
- // 未认证
- // return handleAuthorized();
- window.location.href = `/view/mhzc/login`;
+ showLoginGuide({ actionText: '当前操作' });
+ return Promise.reject({
+ __authRequired: true,
+ code: 401,
+ msg,
+ response: res,
+ });
}
if (code !== 1) {
MessagePlugin.error({
@@ -135,8 +140,12 @@ request.interceptors.response.use(
}
// HTTP 状态码 401 未认证,跳转登录页
if (err.response?.status === 401) {
- window.location.href = `/view/mhzc/login`;
- return Promise.reject(err);
+ showLoginGuide({ actionText: '当前操作' });
+ return Promise.reject({
+ ...err,
+ __authRequired: true,
+ code: 401,
+ });
}
// gtff 错误拦截器对 { code, msg } 体误用 a.Response 时会抛 TypeError,避免弹出误导性 Toast
if (err instanceof TypeError && /Cannot convert undefined or null to object/.test(err.message)) {
@@ -231,8 +240,12 @@ request.interceptors.response.use(
if (err.reqConfig?.loading || err.config?.loading || SingleLoading.load !== null) {
SingleLoading.endLoading(true);
}
- window.location.href = `/view/mhzc/login`;
- return Promise.reject(err);
+ showLoginGuide({ actionText: '当前操作' });
+ return Promise.reject({
+ ...err,
+ __authRequired: true,
+ code: 401,
+ });
}
return Promise.reject(err);
},
diff --git a/txw-mhzc-web/src/pages/index/utils/auth-guard.js b/txw-mhzc-web/src/pages/index/utils/auth-guard.js
new file mode 100644
index 0000000..3358f21
--- /dev/null
+++ b/txw-mhzc-web/src/pages/index/utils/auth-guard.js
@@ -0,0 +1,57 @@
+import { DialogPlugin } from '@gt4/common-front';
+
+const LOGIN_ROUTE_PATH = '/login';
+let activeLoginDialog = null;
+
+function getPortalLoginUrl() {
+ const routerPrefix = window?.STATIC_ENV_CONFIG?.ROUTER_PREFIX || '/view/mhzc';
+ return `${String(routerPrefix).replace(/\/$/, '')}${LOGIN_ROUTE_PATH}`;
+}
+
+export function isPortalLoggedIn() {
+ return !!window.sessionStorage.getItem('sfdl');
+}
+
+export function isUnauthorizedError(error) {
+ return (
+ error === 401 ||
+ error?.__authRequired === true ||
+ Number(error?.code) === 401 ||
+ Number(error?.response?.status) === 401
+ );
+}
+
+export function showLoginGuide(options = {}) {
+ const {
+ title = '登录提示',
+ actionText = '该操作',
+ confirmText = '去登录/注册',
+ cancelText = '暂不登录',
+ } = options;
+
+ if (activeLoginDialog) {
+ return activeLoginDialog;
+ }
+
+ let shouldRedirectToLogin = false;
+ const dialog = DialogPlugin.confirm({
+ header: title,
+ body: `${actionText}需要先登录后才能继续,是否立即前往登录或注册?`,
+ confirmBtn: confirmText,
+ cancelBtn: cancelText,
+ closeBtn: true,
+ closeOnOverlayClick: false,
+ onConfirm: () => {
+ shouldRedirectToLogin = true;
+ },
+ onClosed: () => {
+ activeLoginDialog = null;
+ if (shouldRedirectToLogin) {
+ window.location.href = getPortalLoginUrl();
+ }
+ },
+ });
+
+ activeLoginDialog = dialog;
+ return dialog;
+}
diff --git a/txw-mhzc-web/src/pages/index/utils/gxnlpt-scroll-helpers.js b/txw-mhzc-web/src/pages/index/utils/gxnlpt-scroll-helpers.js
new file mode 100644
index 0000000..45a6fce
--- /dev/null
+++ b/txw-mhzc-web/src/pages/index/utils/gxnlpt-scroll-helpers.js
@@ -0,0 +1,67 @@
+function buildScrollRequestKey(sectionId, tabIndex) {
+ return `${sectionId || ''}::${Number.isInteger(tabIndex) ? tabIndex : 'na'}`;
+}
+
+function getSectionTargetTop(el, scrollRoot, offsetTop = 24) {
+ if (!el) return 0;
+
+ let total = 0;
+ let cur = el;
+
+ while (cur && cur !== scrollRoot) {
+ total += Number(cur.offsetTop) || 0;
+ cur = cur.offsetParent;
+ }
+
+ return Math.max(0, total - offsetTop);
+}
+
+function resolveAnchorScrollAction({
+ currentTop,
+ targetTop,
+ pendingRequestKey,
+ nextRequestKey,
+ suppressSectionObserver,
+ nearThreshold = 8,
+}) {
+ if (Math.abs(currentTop - targetTop) <= nearThreshold) {
+ return 'skip-near-target';
+ }
+
+ if (suppressSectionObserver && pendingRequestKey && pendingRequestKey === nextRequestKey) {
+ return 'skip-duplicate-pending';
+ }
+
+ return 'scroll';
+}
+
+function resolveActiveSectionIndex({
+ sectionTops,
+ currentTop,
+ anchorOffset = 96,
+ defaultIndex = 0,
+}) {
+ if (!Array.isArray(sectionTops) || !sectionTops.length) {
+ return defaultIndex;
+ }
+
+ const probeTop = Math.max(0, Number(currentTop) || 0) + Math.max(0, Number(anchorOffset) || 0);
+ let nextIndex = defaultIndex;
+
+ sectionTops.forEach((item, index) => {
+ if (!item) return;
+ const top = Math.max(0, Number(item.top) || 0);
+ if (top <= probeTop) {
+ nextIndex = Number.isInteger(item.index) ? item.index : index;
+ }
+ });
+
+ return nextIndex;
+}
+
+module.exports = {
+ buildScrollRequestKey,
+ getSectionTargetTop,
+ resolveAnchorScrollAction,
+ resolveActiveSectionIndex,
+};
diff --git a/txw-mhzc-web/src/pages/index/views/gxnlpt/index.vue b/txw-mhzc-web/src/pages/index/views/gxnlpt/index.vue
index f76a3a3..b39a98b 100644
--- a/txw-mhzc-web/src/pages/index/views/gxnlpt/index.vue
+++ b/txw-mhzc-web/src/pages/index/views/gxnlpt/index.vue
@@ -33,7 +33,6 @@
:class="{ 'is-active': contentView === 'submit' }"
@click="openSubmitView"
>
-
收录