diff --git a/.gitignore b/.gitignore index 9021c53..d8c9787 100644 --- a/.gitignore +++ b/.gitignore @@ -70,3 +70,6 @@ devops/docker-compose.override.yml # Added by code-review-graph .code-review-graph/ + +# graphify 临时分析产物(不提交,可随时删除) +**/graphify-out/ diff --git a/docs/sql/init_database.sql b/docs/sql/init_database.sql index 52606d7..ff92e97 100644 --- a/docs/sql/init_database.sql +++ b/docs/sql/init_database.sql @@ -423,4 +423,10 @@ CREATE TABLE `system_oauth2_approve` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='OAuth2授权批准表'; +-- ---------------------------- +-- txw-mhzc 共性能力平台(收录 + 收藏) +-- DDL 详见 txw-mhzc/sql/txw_mhzc_gxnl_slxxb_gxscb.sql +-- 初始数据详见 txw-mhzc/sql/gxnl_wzxx_import_from_excel.sql +-- ---------------------------- + SET FOREIGN_KEY_CHECKS = 1; diff --git a/docs/sql/nacos-config/txw-gateway.yaml b/docs/sql/nacos-config/txw-gateway.yaml index 43c9c08..54c5e41 100644 --- a/docs/sql/nacos-config/txw-gateway.yaml +++ b/docs/sql/nacos-config/txw-gateway.yaml @@ -40,6 +40,7 @@ css: - /sso/userinfo/get - /sso/oauth2/token - /mhzc/sy/** + - /mhzc/gxnl/wzxx/list - /sso/did/pub/** - /sso/auth/didBindPhone api: diff --git a/scripts/gen_gxnl_import_sql.py b/scripts/gen_gxnl_import_sql.py new file mode 100644 index 0000000..009d208 --- /dev/null +++ b/scripts/gen_gxnl_import_sql.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- +import openpyxl +import uuid +import re +from datetime import datetime +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +EXCEL_PATH = ROOT / '可信碳共性能力网站导航.xlsx' +OUT_SQL = ROOT / 'txw-mhzc' / 'sql' / 'gxnl_wzxx_import_from_excel.sql' + +FL_NAMES = { + '01': '碳核算平台', + '02': '碳认证机构', + '03': '碳交易平台', + '04': '碳金融服务', + '05': '碳技术咨询', +} + +TYPE_TO_FL = { + '产品碳足迹': '01', + '企业碳管理平台': '01', + 'CBAM': '01', + '碳核算/排放数据': '01', + '科研平台': '01', + '软件服务': '01', + '国家部委': '01', + '地方发改部门': '01', + '地方生态环境部门': '01', + '地方工信部门': '01', + '国际碳标准/绿证': '02', + '核查机构': '02', + '行业标准/倡议': '02', + '普惠平台': '02', + '交易机构': '03', + '咨询机构': '05', + '国际组织': '05', + '国际能源/环保机构': '05', + '行业协会/平台': '05', +} + + +def parse_sheet(ws, skip_first_col=False): + rows = list(ws.iter_rows(values_only=True)) + items = [] + cur_type = None + for row in rows[1:]: + vals = list(row) + if skip_first_col: + vals = vals[1:] + t = vals[0] if len(vals) > 0 else None + name = vals[1] if len(vals) > 1 else None + url = vals[2] if len(vals) > 2 else None + tags = vals[3] if len(vals) > 3 else None + if t: + cur_type = str(t).strip() + name = str(name).strip() if name else '' + url = str(url).strip() if url else '' + tags = str(tags).strip() if tags else '' + if not name or not url or url.lower() == 'none': + continue + if not url.startswith(('http://', 'https://')): + continue + items.append({'excelType': cur_type or '', 'bt': name, 'wzLj': url, 'bqjh': tags}) + return items + + +def esc(s): + if s is None: + return 'NULL' + return "'" + str(s).replace('\\', '\\\\').replace("'", "''") + "'" + + +def norm_tags(excel_type, raw_tags): + tags = [] + if raw_tags: + parts = re.split(r'[,,、;;|/]', raw_tags) + for p in parts: + p = p.strip() + if p and p not in tags: + tags.append(p) + if excel_type and excel_type not in tags: + tags.insert(0, excel_type) + if not tags and excel_type: + tags = [excel_type] + return ','.join(tags[:10]) + + +def main(): + wb = openpyxl.load_workbook(EXCEL_PATH, read_only=True) + items = parse_sheet(wb['Sheet1'], True) + parse_sheet(wb['Sheet2'], False) + + seen = set() + unique = [] + for it in items: + key = it['wzLj'].rstrip('/').lower() + if key in seen: + continue + seen.add(key) + unique.append(it) + + now = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + lines = [ + '-- ============================================================', + '-- 共性能力网站导航 Excel 导入数据', + '-- 来源: 可信碳共性能力网站导航.xlsx', + f'-- 生成时间: {now}', + f'-- 记录数: {len(unique)}', + '-- ============================================================', + 'SET NAMES utf8mb4;', + '', + ] + + unmapped = {} + for idx, it in enumerate(unique, 1): + fl = TYPE_TO_FL.get(it['excelType']) + if not fl: + unmapped[it['excelType']] = unmapped.get(it['excelType'], 0) + 1 + fl = '05' + fl_mc = FL_NAMES[fl] + bqjh = norm_tags(it['excelType'], it['bqjh']) + jj = (it['bqjh'] or it['excelType'] or it['bt'])[:40] + wz_uuid = uuid.uuid4().hex + pxh = 1000 - idx + lines.append( + 'INSERT INTO `txw_mhzc_gxnl_slxxb` ' + '(`wz_uuid`,`bt`,`wz_lj`,`jj`,`gxnl_fl_dm`,`gxnl_fl_mc`,`bqjh`,`zt`,`sjzt`,`lyqd_dm`,`qymc`,`pxh`,`lrrq`,`yxbz`) VALUES (' + f"{esc(wz_uuid)}, {esc(it['bt'])}, {esc(it['wzLj'])}, {esc(jj)}, {esc(fl)}, {esc(fl_mc)}, {esc(bqjh)}, " + f"'2', 'Y', 'import', {esc(it['excelType'])}, {pxh}, '{now}', 'Y');" + ) + + OUT_SQL.parent.mkdir(parents=True, exist_ok=True) + OUT_SQL.write_text('\n'.join(lines) + '\n', encoding='utf-8') + print('records', len(unique)) + print('unmapped', unmapped) + print('written', OUT_SQL) + + +if __name__ == '__main__': + main() diff --git a/txw-mhzc-web/src/pages/index/api/gxnl/index.js b/txw-mhzc-web/src/pages/index/api/gxnl/index.js new file mode 100644 index 0000000..0f68665 --- /dev/null +++ b/txw-mhzc-web/src/pages/index/api/gxnl/index.js @@ -0,0 +1,42 @@ +import { fetchSso } from '@/core/request'; + +const basurl = '/mhzc/gxnl'; + +export default { + /** 共性能力网站列表 */ + wzxxList(params = {}) { + return fetchSso({ + url: `${basurl}/wzxx/list`, + method: 'post', + data: JSON.stringify(params), + }); + }, + + /** 收录提交 */ + submitSlxx(params) { + return fetchSso({ + url: `${basurl}/slxx/submit`, + method: 'post', + loading: true, + data: JSON.stringify(params), + }); + }, + + /** 收藏/取消收藏 */ + toggleGxsc(params) { + return fetchSso({ + url: `${basurl}/gxsc/toggle`, + method: 'post', + data: JSON.stringify(params), + }); + }, + + /** 我的收藏 */ + myFavoriteList(params = {}) { + return fetchSso({ + url: `${basurl}/gxsc/myList`, + method: 'post', + data: JSON.stringify(params), + }); + }, +}; diff --git a/txw-mhzc-web/src/pages/index/assets/Group 7.png b/txw-mhzc-web/src/pages/index/assets/Group 7.png new file mode 100644 index 0000000..5144a23 Binary files /dev/null and b/txw-mhzc-web/src/pages/index/assets/Group 7.png differ diff --git a/txw-mhzc-web/src/pages/index/assets/home-cta-group7-2x.png b/txw-mhzc-web/src/pages/index/assets/home-cta-group7-2x.png deleted file mode 100644 index 33b0d63..0000000 Binary files a/txw-mhzc-web/src/pages/index/assets/home-cta-group7-2x.png and /dev/null differ diff --git a/txw-mhzc-web/src/pages/index/components/nav/index2.vue b/txw-mhzc-web/src/pages/index/components/nav/index2.vue index 0916ede..0549272 100644 --- a/txw-mhzc-web/src/pages/index/components/nav/index2.vue +++ b/txw-mhzc-web/src/pages/index/components/nav/index2.vue @@ -5,6 +5,8 @@ class="nav-box" :class="{ 'nav-box--mobile-menu-open': mobileMenuOpen }" > + +
@@ -596,6 +600,11 @@ export default { min-height: var(--page-nav-height); } +.nav-figma-scale-viewport, +.nav-figma-scale-stage { + width: 100%; +} + .logo-box { display: flex; align-items: center; diff --git a/txw-mhzc-web/src/pages/index/styles/home-figma-variables.less b/txw-mhzc-web/src/pages/index/styles/home-figma-variables.less index c757c29..2151d26 100644 --- a/txw-mhzc-web/src/pages/index/styles/home-figma-variables.less +++ b/txw-mhzc-web/src/pages/index/styles/home-figma-variables.less @@ -266,6 +266,7 @@ @home-cta-head-to-btn-gap: 47px; @home-footer-top: 4867px; +@home-cta-design-height: (@home-footer-top - @home-cta-top); @home-footer-height: 212px; @home-footer-info-height: 148px; @home-footer-bar-height: 64px; @@ -543,6 +544,495 @@ --home-title-bar-height: @home-section-title-deco-height; } +// ----------------------------------------------------------------------------- +// Figma 整页等比缩放(设计画布 1440 × 5079,随视口宽度 scale) +// ----------------------------------------------------------------------------- +.home-figma-scale-page() { + .home-figma-scale-viewport { + width: 100%; + } + + .home-figma-scale-stage { + width: 100%; + } + + html.portal-figma-scale-active & { + .home-figma-scale-viewport { + width: 100%; + height: var(--home-figma-visual-height, auto); + overflow: hidden; + } + + .home-figma-scale-stage { + width: @home-design-width; + margin-left: auto; + margin-right: auto; + transform: scale(var(--home-figma-scale, 1)); + transform-origin: top center; + margin-bottom: var(--home-figma-stage-margin-bottom, 0); + } + + .container { + width: @home-design-width; + max-width: none; + } + + /* 各区块高度由 JS 注入 --home-figma-*-design-height,至少填满一屏 */ + --home-section-min-h: var(--home-figma-section-design-height, @home-core-height); + --home-news-section-min-h: var(--home-figma-news-section-design-height, @home-news-height); + --home-cta-section-min-h: var(--home-figma-cta-design-height, @home-cta-design-height); + + #section-hero.top-box { + min-height: var(--home-figma-hero-design-height, @home-hero-height); + height: var(--home-figma-hero-design-height, @home-hero-height); + } + + .core-section { + min-height: var(--home-figma-section-design-height, @home-core-height); + } + + .capability-section { + min-height: var(--home-figma-section-design-height, @home-ability-height); + } + + .overseas2-section { + min-height: var(--home-figma-section-design-height, @home-export-height); + } + + .news-section { + min-height: var(--home-figma-news-section-design-height, @home-news-height); + } + + .partner-section { + min-height: var(--home-figma-partner-section-design-height, @home-partner-height); + } + + .bottom-box { + height: var(--home-figma-cta-design-height, @home-cta-design-height); + min-height: var(--home-figma-cta-design-height, @home-cta-design-height); + } + + .home-shelf--hero { + flex: 1; + min-height: var(--home-figma-hero-design-height, @home-hero-height); + height: 100%; + padding: @home-hero-content-padding-top 0 @home-space-64; + gap: 0; + } + + .home-hero-content { + flex: 0 1 auto; + width: 100%; + max-width: @home-content-width; + margin-top: auto; + gap: @home-hero-content-gap; + } + + .top-hero-actions { + gap: @home-space-32; + margin-top: 0; + } + + .top-title-text { + font-size: @home-font-size-56; + } + + .top-title-desc { + font-size: @home-font-size-28; + } + + .hero-cards { + gap: @home-feature-cards-gap; + } + + .home-shelf--core { + min-height: var(--home-figma-section-design-height, @home-core-height); + gap: @home-space-52; + } + + .home-shelf--capability { + min-height: var(--home-figma-section-design-height, @home-ability-height); + gap: @home-ability-title-to-grid-gap; + } + + .home-shelf--overseas { + min-height: var(--home-figma-section-design-height, @home-export-height); + gap: @home-export-title-to-list-gap; + } + + .home-shelf--news { + min-height: var(--home-figma-news-section-design-height, @home-news-height); + } + + .home-shelf--partner { + min-height: var(--home-figma-partner-section-design-height, @home-partner-height); + } + } +} + +/* 覆盖 home2 桌面端 media query 中的 fluid/clamp,避免破坏 Figma 等比缩放 */ +html.portal-figma-scale-active { + .portal-page .home-shelf--hero { + justify-content: flex-start; + gap: 0; + } + + .portal-page .home-hero-content { + margin-top: auto; + gap: @home-hero-content-gap; + } + + .portal-page .top-hero-actions { + gap: @home-space-32; + } + + .portal-page .top-title-text { + font-size: @home-font-size-56; + } + + .portal-page .top-title-desc { + font-size: @home-font-size-28; + } + + .portal-page .hero-cards { + gap: @home-feature-cards-gap; + } + + .portal-page #section-hero.top-box { + min-height: var(--home-figma-hero-design-height, @home-hero-height) !important; + height: var(--home-figma-hero-design-height, @home-hero-height) !important; + } + + .portal-page .home-shelf--hero { + min-height: var(--home-figma-hero-design-height, @home-hero-height) !important; + } + + .portal-page .core-section, + .portal-page .capability-section, + .portal-page .overseas2-section { + min-height: var(--home-figma-section-design-height, @home-core-height) !important; + height: var(--home-figma-section-design-height, @home-core-height) !important; + box-sizing: border-box; + } + + .portal-page .news-section { + min-height: var(--home-figma-news-section-design-height, @home-news-height) !important; + height: var(--home-figma-news-section-design-height, @home-news-height) !important; + box-sizing: border-box; + } + + .portal-page .partner-section { + min-height: var(--home-figma-partner-section-design-height, @home-partner-height) !important; + height: var(--home-figma-partner-section-design-height, @home-partner-height) !important; + box-sizing: border-box; + } + + .portal-page .home-shelf--core, + .portal-page .home-shelf--capability, + .portal-page .home-shelf--overseas { + min-height: var(--home-figma-section-design-height, @home-core-height) !important; + } + + .portal-page .home-shelf--news { + min-height: var(--home-figma-news-section-design-height, @home-news-height) !important; + } + + .portal-page .home-shelf--partner { + min-height: var(--home-figma-partner-section-design-height, @home-partner-height) !important; + } + + .portal-page .capability-section { + background-color: @home-color-page-bg; + background-size: 100% 100%; + background-position: center center; + background-repeat: no-repeat; + } + + .portal-page .overseas2-section { + background-size: 100% 100%; + background-position: center center; + background-repeat: no-repeat; + } + + .portal-page .home-shelf--capability, + .portal-page .home-shelf--overseas { + min-height: 100% !important; + height: 100%; + } + + /* CTA 独占一屏;页脚最后一屏贴底;整页高度由 JS 实测锁定 */ + .portal-page.page-box { + box-sizing: border-box; + height: var(--home-figma-visual-height) !important; + max-height: var(--home-figma-visual-height) !important; + overflow: hidden !important; + padding: 0 !important; + margin: 0 !important; + background: @home-color-footer-bar-bg !important; + } + + .portal-page .home-figma-scale-viewport { + height: var(--home-figma-visual-height) !important; + overflow: hidden !important; + } + + .portal-page .bottom-box { + width: @home-design-width !important; + max-width: @home-design-width !important; + margin-left: 0 !important; + margin-right: 0 !important; + height: var(--home-figma-cta-design-height, @home-cta-design-height) !important; + min-height: var(--home-figma-cta-design-height, @home-cta-design-height) !important; + overflow: hidden; + background-color: @home-color-cta-section-bg !important; + } + + .portal-page .bottom-box-bg { + position: absolute !important; + left: 0 !important; + top: 0 !important; + width: 100% !important; + height: 100% !important; + min-width: 0 !important; + transform: none !important; + object-fit: fill !important; + object-position: top center !important; + } + + .portal-page .bottom-box::after { + display: none !important; + } + + .portal-page .portal-footer-shell, + .portal-page .site-footer { + flex-shrink: 0; + width: @home-design-width !important; + max-width: @home-design-width !important; + margin-left: auto !important; + margin-right: auto !important; + margin-top: 0 !important; + } + + .content-wrap--home { + background: @home-color-footer-bar-bg; + } +} + +// ----------------------------------------------------------------------------- +// 门户落地页 Figma 等比缩放(fwsc / hyzt / qych / gxnlpt) +// ----------------------------------------------------------------------------- +.portal-figma-scale-page() { + .portal-figma-scale-viewport { + width: 100%; + } + + .portal-figma-scale-stage { + width: 100%; + } + + html.portal-figma-scale-active & { + .portal-figma-scale-viewport { + width: 100%; + height: var(--home-figma-visual-height, auto); + overflow: hidden; + } + + .portal-figma-scale-stage { + width: @home-design-width; + margin-left: auto; + margin-right: auto; + transform: scale(var(--home-figma-scale, 1)); + transform-origin: top center; + margin-bottom: var(--home-figma-stage-margin-bottom, 0); + } + } +} + +html.portal-figma-scale-active.portal-landing-figma-scale-active { + .portal-landing-figma-page { + box-sizing: border-box; + height: var(--home-figma-visual-height, auto) !important; + max-height: var(--home-figma-visual-height, none) !important; + overflow: hidden !important; + background: #f6f7fa !important; + } + + .content-wrap--landing-hub .portal-route-outlet { + min-height: var(--portal-landing-first-screen-height) !important; + } + + .portal-page-shell.portal-landing-figma-page, + .portal-landing-figma-page .main-content, + .qych-landing { + min-height: var(--portal-landing-first-screen-height) !important; + } + + .portal-page-shell .banner-section, + .qych-landing .banner-section { + height: @portal-landing-banner-height !important; + flex-shrink: 0; + } + + .hyzt-wrap .banner-bg { + top: @hyzt-landing-bg-offset-top !important; + width: 100% !important; + height: @hyzt-landing-bg-height !important; + min-height: @hyzt-landing-bg-height !important; + } + + .content-wrap--landing-hub .portal-page-shell .services-section, + .qych-landing .services-section { + margin-top: -@portal-landing-cards-overlap !important; + padding-top: @portal-landing-cards-pt !important; + padding-bottom: @portal-landing-cards-pb !important; + + &::before, + &::after { + flex-grow: 0 !important; + flex-basis: 0 !important; + min-height: 0 !important; + height: 0 !important; + content: none !important; + display: none !important; + } + } + + .hyzt-page-inner, + .qych-page-inner { + max-width: @hyzt-landing-content-width !important; + } + + .fwsc-page .services-grid { + max-width: @home-content-width !important; + } + + .gxnlpt-page .page-content-wrap { + max-width: @home-content-width !important; + } + + .qych-snap-section { + min-height: var(--portal-landing-first-screen-height) !important; + } + + .content-wrap--landing-hub, + .content-wrap--figma-portal { + background: #f6f7fa; + } +} + +// 服务中心子页:碳服务/需求/数据/金融市场列表 +html.portal-figma-scale-active.portal-market-figma-scale-active { + .portal-market-figma-page { + box-sizing: border-box; + width: 100%; + flex: 1 1 auto; + min-height: var(--home-figma-visual-height, auto); + background: #f5f5f5 !important; + } + + .portal-market-figma-page .portal-figma-scale-stage { + min-height: var(--portal-market-shell-min-height, auto); + box-sizing: border-box; + } + + .content-wrap--figma-market .portal-route-outlet { + display: flex; + flex: 1 1 auto; + flex-direction: column; + min-height: 0; + } + + .portal-page-stack, + .portal-page-stack--figma-market { + display: flex; + flex-direction: column; + min-height: var(--portal-scroll-height, 100%); + } + + .portal-page-stack .portal-layout-footer { + flex-shrink: 0; + width: 100%; + margin-top: 0; + } + + .portal-market-figma-page .secondary-nav-content { + width: @home-content-width !important; + max-width: @home-content-width !important; + margin-left: auto !important; + margin-right: auto !important; + box-sizing: border-box; + } + + .portal-market-figma-page .fwsc-main, + .portal-market-figma-page .xqsc-main, + .portal-market-figma-page .jrsc-main, + .portal-market-figma-page .page-body, + .portal-market-figma-page .content-wrapper { + width: @home-content-width !important; + max-width: @home-content-width !important; + margin-left: auto !important; + margin-right: auto !important; + box-sizing: border-box; + flex: 1 1 auto; + display: flex; + flex-direction: column; + min-height: calc(var(--portal-market-shell-min-height) - 62px); + } + + .portal-market-figma-page .content-area, + .portal-market-figma-page .content-wrapper { + flex: 1 1 auto; + align-items: stretch; + } + + .content-wrap--figma-market { + background: @home-color-footer-bar-bg; + } +} + +// 顶栏与首页共用 html.portal-figma-scale-active + --home-figma-scale +.home-figma-scale-nav() { + html.portal-figma-scale-active .nav-box { + height: var(--page-nav-height); + overflow: hidden; + } + + html.portal-figma-scale-active .nav-figma-scale-viewport { + display: flex; + width: 100%; + height: var(--page-nav-height); + overflow: hidden; + justify-content: center; + } + + html.portal-figma-scale-active .nav-figma-scale-stage { + width: @home-design-width; + height: @home-nav-height; + flex-shrink: 0; + transform: scale(var(--home-figma-scale, 1)); + transform-origin: top center; + margin-bottom: var(--home-figma-nav-stage-margin-bottom, 0); + } + + html.portal-figma-scale-active .nav-figma-scale-stage .page-nav-inner { + width: @home-content-width; + max-width: @home-content-width; + margin-left: auto; + margin-right: auto; + padding-left: 0; + padding-right: 0; + box-sizing: border-box; + } + + html.portal-figma-scale-active .nav-figma-scale-stage .nav-inner, + html.portal-figma-scale-active .nav-figma-scale-stage .menu-box, + html.portal-figma-scale-active .nav-figma-scale-stage .option-wrapper, + html.portal-figma-scale-active .nav-figma-scale-stage .option-box { + height: @home-nav-height; + min-height: @home-nav-height; + } +} + // ----------------------------------------------------------------------------- // 共性能力页 150605:2210 / 150605:2534 // ----------------------------------------------------------------------------- @@ -600,6 +1090,12 @@ @portal-landing-cards-overlap-sm: 32px; @portal-landing-cards-pt: 60px; @portal-landing-cards-pb: 80px; +@portal-landing-cards-pt-fluid: clamp(36px, 4.5vh, 60px); +@portal-landing-cards-pb-fluid: clamp(24px, 3.5vh, 80px); +@portal-landing-cards-pb-screen: clamp(20px, 2.5vh, 40px); +/* 首屏卡片区垂直黄金分割:剩余空间上 38.2% / 下 61.8%,卡片落在 φ 点附近 */ +@portal-landing-golden-flex-top: 382; +@portal-landing-golden-flex-bottom: 618; @hyzt-landing-cards-overlap: @portal-landing-cards-overlap; @hyzt-landing-cards-pt: @portal-landing-cards-pt; @hyzt-landing-cards-px: 30px; diff --git a/txw-mhzc-web/src/pages/index/styles/page-layout.less b/txw-mhzc-web/src/pages/index/styles/page-layout.less index c66e6b9..5e71efc 100644 --- a/txw-mhzc-web/src/pages/index/styles/page-layout.less +++ b/txw-mhzc-web/src/pages/index/styles/page-layout.less @@ -1,4 +1,9 @@ // 门户页边距:Figma 1440 画布 · 1200 版心 · 左右各 120px 自适应留白 +@import './portal-landing-hub-layout.less'; +@import './home-figma-variables.less'; + +.home-figma-scale-nav(); + :root { --page-content-max-width: 1200px; /* Figma 导航 logo:图标区 34px + 与字间距 12px → 「可」字左缘距版心左 46px */ @@ -161,26 +166,6 @@ background: #f6f7fa; } -/* 首屏落地页:首屏占满滚动区,页脚在下滑后出现(勿用 flex:1 把页脚吸到首屏底) */ -.content-wrap--landing-hub { - background: #f6f7fa; -} - -.content-wrap--landing-hub .portal-page-stack--landing-hub { - min-height: auto; -} - -.content-wrap--landing-hub .portal-page-stack--landing-hub .portal-route-outlet { - flex: 0 0 auto; - min-height: var(--portal-scroll-height, 100%); -} - -.content-wrap--landing-hub .portal-page-stack--landing-hub .portal-page-shell { - box-sizing: border-box; - min-height: var(--portal-scroll-height, 100%); - background: #f6f7fa; -} - /* 短页:栈高度至少铺满滚动区,页脚贴底;长页:随内容增高,不在页脚后留白 */ .portal-page-stack { display: flex; diff --git a/txw-mhzc-web/src/pages/index/styles/portal-landing-hub-layout.less b/txw-mhzc-web/src/pages/index/styles/portal-landing-hub-layout.less new file mode 100644 index 0000000..7367eb3 --- /dev/null +++ b/txw-mhzc-web/src/pages/index/styles/portal-landing-hub-layout.less @@ -0,0 +1,152 @@ +// ============================================================================= +// 落地页首屏:Banner 350px + 卡片区黄金分割垂直定位 + 页脚下滑出现 +// ============================================================================= + +@import './home-figma-variables.less'; +@import './portal-landing-cards.less'; + +.portal-landing-first-screen() { + display: flex; + flex-direction: column; + box-sizing: border-box; + min-height: var(--portal-scroll-height, calc(100vh - var(--page-offset-top, 64px))); + background: #f6f7fa; +} + +// 卡片区:首屏剩余高度按黄金比 38.2% : 61.8% 分配上下留白,卡片落在 φ 点(略高于贴底) +.portal-landing-cards-area() { + position: relative; + z-index: 2; + display: flex; + flex: 1 1 auto; + flex-direction: column; + justify-content: flex-start; + min-height: 0; + margin-top: -@portal-landing-cards-overlap; + padding: @portal-landing-cards-pt-fluid 0 0; + box-sizing: border-box; + + &::before, + &::after { + content: ''; + display: block; + flex-shrink: 1; + flex-basis: 0; + min-height: 0; + width: 100%; + pointer-events: none; + } + + &::before { + flex-grow: @portal-landing-golden-flex-top; + } + + &::after { + flex-grow: @portal-landing-golden-flex-bottom; + } + + .services-grid { + flex-shrink: 0; + width: 100%; + } +} + +.portal-landing-cards-area-flat() { + &::before, + &::after { + content: none; + display: none; + flex: 0 0 0; + } + + padding-bottom: @portal-landing-cards-pb-screen; +} + +/* fwsc / hyzt:Main 布局 landingHubScrollFooter */ +.content-wrap--landing-hub { + background: #f6f7fa; +} + +.content-wrap--landing-hub .portal-page-stack--landing-hub { + min-height: auto; +} + +.content-wrap--landing-hub .portal-page-stack--landing-hub .portal-route-outlet { + flex: 0 0 auto; + min-height: var(--portal-scroll-height, calc(100vh - var(--page-offset-top, 64px))); +} + +.content-wrap--landing-hub .portal-page-stack--landing-hub .portal-page-shell { + .portal-landing-first-screen(); +} + +.content-wrap--landing-hub .portal-page-shell .banner-section { + flex-shrink: 0; +} + +.content-wrap--landing-hub .fwsc-page .main-content { + display: flex; + flex: 1 1 auto; + flex-direction: column; + min-height: 0; +} + +.content-wrap--landing-hub .portal-page-shell .services-section { + .portal-landing-cards-area(); +} + +.content-wrap--landing-hub .hyzt-wrap .services-section { + padding-left: @hyzt-landing-cards-px; + padding-right: @hyzt-landing-cards-px; +} + +/* 企业出海首屏 */ +.qych-landing { + .portal-landing-first-screen(); + overflow: visible; +} + +.qych-landing .banner-section { + flex-shrink: 0; +} + +.qych-landing .services-section { + .portal-landing-cards-area(); + padding-left: @hyzt-landing-cards-px; + padding-right: @hyzt-landing-cards-px; +} + +// 窄屏:取消黄金分割、固定卡高(CSS 变量可穿透 scoped),改为自然流式排列 +@media (max-width: 1200px) { + .content-wrap--landing-hub .portal-page-shell .services-section, + .qych-landing .services-section { + --portal-service-card-min-height: auto; + margin-top: -@portal-landing-cards-overlap-md; + .portal-landing-cards-area-flat(); + .portal-landing-card-shell-reset(); + } +} + +@media (max-width: 900px) { + .content-wrap--landing-hub .portal-page-shell .services-section, + .qych-landing .services-section { + margin-top: -@portal-landing-cards-overlap-md; + } +} + +@media (max-width: 768px) { + .content-wrap--landing-hub .portal-page-shell .services-section, + .qych-landing .services-section { + margin-top: -@portal-landing-cards-overlap-sm; + padding-left: var(--page-gutter-x, 16px); + padding-right: var(--page-gutter-x, 16px); + } +} + +@media screen and (max-height: 720px) { + .content-wrap--landing-hub .portal-page-shell .services-section, + .qych-landing .services-section { + padding-top: clamp(28px, 3vh, 40px); + .portal-landing-cards-area-flat(); + } +} diff --git a/txw-mhzc-web/src/pages/index/utils/home-figma-scale.js b/txw-mhzc-web/src/pages/index/utils/home-figma-scale.js new file mode 100644 index 0000000..9e270d8 --- /dev/null +++ b/txw-mhzc-web/src/pages/index/utils/home-figma-scale.js @@ -0,0 +1,128 @@ +/** Figma 首页画布 1440 × 5079,整页(含顶栏)等比缩放 */ +export const HOME_FIGMA_DESIGN_WIDTH = 1440; +export const HOME_FIGMA_DESIGN_HEIGHT = 5079; +export const HOME_FIGMA_NAV_HEIGHT = 64; +export const HOME_FIGMA_HERO_HEIGHT = 686; +export const HOME_FIGMA_CORE_HEIGHT = 686; +export const HOME_FIGMA_ABILITY_HEIGHT = 686; +export const HOME_FIGMA_EXPORT_HEIGHT = 686; +export const HOME_FIGMA_NEWS_HEIGHT = 684; +export const HOME_FIGMA_PARTNER_HEIGHT = 689; +export const HOME_FIGMA_CTA_HEIGHT = 686; +export const HOME_FIGMA_FOOTER_HEIGHT = 212; +export const HOME_FIGMA_HERO_CONTENT_PAD = 64; +export const HOME_FIGMA_SCALE_MIN_WIDTH = 768; +/** 抵消 transform:scale 取整误差,避免 section 底部露 1~12px 白边 */ +export const HOME_FIGMA_FILL_VISUAL_BUFFER = 12; + +function calcFillDesignHeight(baseHeight, vh, scale) { + const filled = Math.ceil((vh + HOME_FIGMA_FILL_VISUAL_BUFFER) / scale) + 1; + return Math.max(baseHeight, filled); +} + +export function calcHomeFigmaScale(viewportWidth, viewportHeight) { + if (viewportWidth < HOME_FIGMA_SCALE_MIN_WIDTH) { + return { + enabled: false, + scale: 1, + stageMarginBottom: 0, + navHeightPx: HOME_FIGMA_NAV_HEIGHT, + heroDesignHeight: HOME_FIGMA_HERO_HEIGHT, + sectionDesignHeight: HOME_FIGMA_CORE_HEIGHT, + newsSectionDesignHeight: HOME_FIGMA_NEWS_HEIGHT, + partnerSectionDesignHeight: HOME_FIGMA_PARTNER_HEIGHT, + ctaDesignHeight: HOME_FIGMA_CTA_HEIGHT, + designHeight: HOME_FIGMA_DESIGN_HEIGHT, + }; + } + + const scale = viewportWidth / HOME_FIGMA_DESIGN_WIDTH; + const navHeightPx = HOME_FIGMA_NAV_HEIGHT * scale; + const vh = viewportHeight || (typeof window !== 'undefined' ? window.innerHeight : 900); + + const heroDesignHeight = calcFillDesignHeight(HOME_FIGMA_HERO_HEIGHT, vh, scale); + const sectionDesignHeight = calcFillDesignHeight(HOME_FIGMA_CORE_HEIGHT, vh, scale); + const newsSectionDesignHeight = calcFillDesignHeight(HOME_FIGMA_NEWS_HEIGHT, vh, scale); + const partnerSectionDesignHeight = calcFillDesignHeight(HOME_FIGMA_PARTNER_HEIGHT, vh, scale); + // CTA 独占一屏;页脚为固定高度 footer,不单独占屏 + const ctaDesignHeight = calcFillDesignHeight(HOME_FIGMA_CTA_HEIGHT, vh, scale); + + const designHeight = HOME_FIGMA_DESIGN_HEIGHT + + (heroDesignHeight - HOME_FIGMA_HERO_HEIGHT) + + (sectionDesignHeight - HOME_FIGMA_CORE_HEIGHT) + + (sectionDesignHeight - HOME_FIGMA_ABILITY_HEIGHT) + + (sectionDesignHeight - HOME_FIGMA_EXPORT_HEIGHT) + + (newsSectionDesignHeight - HOME_FIGMA_NEWS_HEIGHT) + + (partnerSectionDesignHeight - HOME_FIGMA_PARTNER_HEIGHT) + + (ctaDesignHeight - HOME_FIGMA_CTA_HEIGHT); + + return { + enabled: true, + scale, + stageMarginBottom: designHeight * (scale - 1), + navHeightPx, + heroDesignHeight, + sectionDesignHeight, + newsSectionDesignHeight, + partnerSectionDesignHeight, + ctaDesignHeight, + designHeight, + }; +} + +/** 写入 documentElement,供首页与顶栏共用同一 scale */ +export function applyHomeFigmaScaleToRoot(viewportWidth, viewportHeight) { + const result = calcHomeFigmaScale(viewportWidth, viewportHeight); + const root = document.documentElement; + + if (!result.enabled) { + root.classList.remove('portal-figma-scale-active'); + root.style.removeProperty('--home-figma-scale'); + root.style.removeProperty('--home-figma-stage-margin-bottom'); + root.style.removeProperty('--home-figma-nav-stage-margin-bottom'); + root.style.removeProperty('--home-figma-hero-design-height'); + root.style.removeProperty('--home-figma-section-design-height'); + root.style.removeProperty('--home-figma-news-section-design-height'); + root.style.removeProperty('--home-figma-partner-section-design-height'); + root.style.removeProperty('--home-figma-cta-design-height'); + root.style.removeProperty('--home-figma-visual-height'); + root.style.removeProperty('--home-section-min-h'); + root.style.removeProperty('--home-news-section-min-h'); + root.style.removeProperty('--page-nav-height'); + return result; + } + + root.classList.add('portal-figma-scale-active'); + root.style.setProperty('--home-figma-scale', String(result.scale)); + root.style.setProperty('--home-figma-stage-margin-bottom', `${result.stageMarginBottom}px`); + root.style.setProperty('--home-figma-hero-design-height', `${result.heroDesignHeight}px`); + root.style.setProperty('--home-figma-section-design-height', `${result.sectionDesignHeight}px`); + root.style.setProperty('--home-figma-news-section-design-height', `${result.newsSectionDesignHeight}px`); + root.style.setProperty('--home-figma-partner-section-design-height', `${result.partnerSectionDesignHeight}px`); + root.style.setProperty('--home-figma-cta-design-height', `${result.ctaDesignHeight}px`); + root.style.setProperty('--home-figma-visual-height', `${Math.ceil(result.designHeight * result.scale)}px`); + root.style.setProperty('--home-section-min-h', `${result.sectionDesignHeight}px`); + root.style.setProperty('--home-news-section-min-h', `${result.newsSectionDesignHeight}px`); + root.style.setProperty('--page-nav-height', `${result.navHeightPx}px`); + root.style.setProperty('--home-figma-nav-stage-margin-bottom', `${HOME_FIGMA_NAV_HEIGHT * (result.scale - 1)}px`); + return result; +} + +export function clearHomeFigmaScaleFromRoot() { + applyHomeFigmaScaleToRoot(HOME_FIGMA_SCALE_MIN_WIDTH - 1); +} + +/** 按 stage 实测布局高度修正 margin-bottom 与整页视觉高度,消除页脚下方空白 */ +export function syncHomeFigmaStageLayout(stageEl, scale) { + if (!stageEl || !scale) return null; + + const layoutHeight = stageEl.offsetHeight; + const marginBottom = layoutHeight * (scale - 1); + const visualHeight = layoutHeight * scale; + const root = document.documentElement; + + root.style.setProperty('--home-figma-stage-margin-bottom', `${marginBottom}px`); + root.style.setProperty('--home-figma-visual-height', `${Math.ceil(visualHeight)}px`); + + return { layoutHeight, visualHeight, marginBottom }; +} diff --git a/txw-mhzc-web/src/pages/index/utils/portal-figma-scale-mixin.js b/txw-mhzc-web/src/pages/index/utils/portal-figma-scale-mixin.js new file mode 100644 index 0000000..3ffa70d --- /dev/null +++ b/txw-mhzc-web/src/pages/index/utils/portal-figma-scale-mixin.js @@ -0,0 +1,31 @@ +import { syncPortalFigmaStageLayout } from './portal-figma-scale'; + +/** 门户 Figma 等比缩放页:mounted/resize 时同步 stage 实测高度 */ +export default { + mounted() { + this._onPortalFigmaResize = () => { + window.requestAnimationFrame(() => { + window.requestAnimationFrame(() => this.syncPortalFigmaStageLayout()); + }); + }; + window.addEventListener('resize', this._onPortalFigmaResize); + this.$nextTick(() => this.syncPortalFigmaStageLayout()); + }, + beforeDestroy() { + if (this._onPortalFigmaResize) { + window.removeEventListener('resize', this._onPortalFigmaResize); + this._onPortalFigmaResize = null; + } + }, + methods: { + syncPortalFigmaStageLayout() { + if (window.innerWidth < 768) return; + const stage = this.$refs.figmaStage; + if (!stage) return; + const scale = parseFloat( + getComputedStyle(document.documentElement).getPropertyValue('--home-figma-scale'), + ) || (window.innerWidth / 1440); + syncPortalFigmaStageLayout(stage, scale); + }, + }, +}; diff --git a/txw-mhzc-web/src/pages/index/utils/portal-figma-scale.js b/txw-mhzc-web/src/pages/index/utils/portal-figma-scale.js new file mode 100644 index 0000000..84fffd7 --- /dev/null +++ b/txw-mhzc-web/src/pages/index/utils/portal-figma-scale.js @@ -0,0 +1,199 @@ +import { + HOME_FIGMA_DESIGN_WIDTH, + HOME_FIGMA_SCALE_MIN_WIDTH, + HOME_FIGMA_NAV_HEIGHT, + HOME_FIGMA_FILL_VISUAL_BUFFER, + applyHomeFigmaScaleToRoot, + clearHomeFigmaScaleFromRoot, + syncHomeFigmaStageLayout, +} from './home-figma-scale'; + +/** 落地页 / 市场页首屏最小设计高度(随视口撑满) */ +export const PORTAL_LANDING_FIRST_SCREEN_HEIGHT = 686; +/** main.vue 中未参与 scale 的页脚大致高度 */ +export const PORTAL_LAYOUT_FOOTER_HEIGHT = 212; + +const PORTAL_LANDING_PATHS = new Set(['/fwsc', '/hyzt', '/qych', '/gxnlpt']); +const PORTAL_MARKET_PATHS = new Set(['/tfwsc', '/txqsc', '/tsjsc', '/tjrsc', '/tsjlbc']); + +export function normalizePortalPath(path) { + if (!path) return ''; + let p = path.split('?')[0]; + if (p.startsWith('/view/mhzc')) { + p = p.slice('/view/mhzc'.length) || '/'; + } + return p; +} + +export function isPortalMarketPage(path) { + return PORTAL_MARKET_PATHS.has(normalizePortalPath(path)); +} + +export function isPortalLandingHubPage(path) { + return PORTAL_LANDING_PATHS.has(normalizePortalPath(path)); +} + +export function isHomeFigmaPage(path) { + const p = normalizePortalPath(path); + return p === '/home' || p === '/'; +} + +export function isPortalFigmaScalePage(path) { + return isHomeFigmaPage(path) + || isPortalLandingHubPage(path) + || isPortalMarketPage(path); +} + +function calcFillDesignHeight(baseHeight, vh, scale) { + const filled = Math.ceil((vh + HOME_FIGMA_FILL_VISUAL_BUFFER) / scale) + 1; + return Math.max(baseHeight, filled); +} + +export function calcLandingFigmaScale(viewportWidth, viewportHeight) { + if (viewportWidth < HOME_FIGMA_SCALE_MIN_WIDTH) { + return { + enabled: false, + scale: 1, + shellMinDesignHeight: PORTAL_LANDING_FIRST_SCREEN_HEIGHT, + navHeightPx: HOME_FIGMA_NAV_HEIGHT, + }; + } + + const scale = viewportWidth / HOME_FIGMA_DESIGN_WIDTH; + const vh = viewportHeight || (typeof window !== 'undefined' ? window.innerHeight : 900); + const shellMinDesignHeight = calcFillDesignHeight(PORTAL_LANDING_FIRST_SCREEN_HEIGHT, vh, scale); + const navHeightPx = HOME_FIGMA_NAV_HEIGHT * scale; + + return { + enabled: true, + scale, + shellMinDesignHeight, + navHeightPx, + }; +} + +/** 市场列表页:扣除顶栏与页脚后的可视区至少一屏 */ +export function calcMarketFigmaScale(viewportWidth, viewportHeight) { + if (viewportWidth < HOME_FIGMA_SCALE_MIN_WIDTH) { + return { + enabled: false, + scale: 1, + shellMinDesignHeight: PORTAL_LANDING_FIRST_SCREEN_HEIGHT, + navHeightPx: HOME_FIGMA_NAV_HEIGHT, + }; + } + + const scale = viewportWidth / HOME_FIGMA_DESIGN_WIDTH; + const vh = viewportHeight || (typeof window !== 'undefined' ? window.innerHeight : 900); + const navHeightPx = HOME_FIGMA_NAV_HEIGHT * scale; + const contentAreaPx = Math.max(vh - navHeightPx - PORTAL_LAYOUT_FOOTER_HEIGHT, 320); + const shellMinDesignHeight = calcFillDesignHeight( + PORTAL_LANDING_FIRST_SCREEN_HEIGHT, + contentAreaPx, + scale, + ); + + return { + enabled: true, + scale, + shellMinDesignHeight, + navHeightPx, + }; +} + +function clearPortalScaleModeClasses(root) { + root.classList.remove('portal-landing-figma-scale-active'); + root.classList.remove('portal-market-figma-scale-active'); +} + +function applySharedPortalScaleVars(root, result) { + root.classList.add('portal-figma-scale-active'); + root.style.setProperty('--home-figma-scale', String(result.scale)); + root.style.setProperty('--portal-shell-min-design-height', `${result.shellMinDesignHeight}px`); + root.style.setProperty('--page-nav-height', `${result.navHeightPx}px`); + root.style.setProperty('--home-figma-nav-stage-margin-bottom', `${HOME_FIGMA_NAV_HEIGHT * (result.scale - 1)}px`); + + root.style.removeProperty('--home-figma-hero-design-height'); + root.style.removeProperty('--home-figma-section-design-height'); + root.style.removeProperty('--home-figma-news-section-design-height'); + root.style.removeProperty('--home-figma-partner-section-design-height'); + root.style.removeProperty('--home-figma-cta-design-height'); + root.style.removeProperty('--home-section-min-h'); + root.style.removeProperty('--home-news-section-min-h'); +} + +function applyLandingFigmaScaleToRoot(viewportWidth, viewportHeight) { + const result = calcLandingFigmaScale(viewportWidth, viewportHeight); + const root = document.documentElement; + + if (!result.enabled) { + clearPortalFigmaScaleFromRoot(); + return result; + } + + clearPortalScaleModeClasses(root); + root.classList.add('portal-landing-figma-scale-active'); + applySharedPortalScaleVars(root, result); + root.style.setProperty('--portal-landing-first-screen-height', `${result.shellMinDesignHeight}px`); + root.style.removeProperty('--portal-market-shell-min-height'); + + return result; +} + +function applyMarketFigmaScaleToRoot(viewportWidth, viewportHeight) { + const result = calcMarketFigmaScale(viewportWidth, viewportHeight); + const root = document.documentElement; + + if (!result.enabled) { + clearPortalFigmaScaleFromRoot(); + return result; + } + + clearPortalScaleModeClasses(root); + root.classList.add('portal-market-figma-scale-active'); + applySharedPortalScaleVars(root, result); + root.style.setProperty('--portal-market-shell-min-height', `${result.shellMinDesignHeight}px`); + root.style.setProperty( + '--home-figma-visual-height', + `${Math.ceil(result.shellMinDesignHeight * result.scale)}px`, + ); + root.style.setProperty( + '--home-figma-stage-margin-bottom', + `${result.shellMinDesignHeight * (result.scale - 1)}px`, + ); + root.style.removeProperty('--portal-landing-first-screen-height'); + + return result; +} + +/** 首页 / 落地 hub / 市场列表页 统一入口 */ +export function applyPortalFigmaScaleToRoot(viewportWidth, viewportHeight, routePath) { + if (viewportWidth < HOME_FIGMA_SCALE_MIN_WIDTH || !isPortalFigmaScalePage(routePath)) { + clearPortalFigmaScaleFromRoot(); + return { enabled: false }; + } + + if (isHomeFigmaPage(routePath)) { + clearPortalScaleModeClasses(document.documentElement); + return applyHomeFigmaScaleToRoot(viewportWidth, viewportHeight); + } + + if (isPortalMarketPage(routePath)) { + return applyMarketFigmaScaleToRoot(viewportWidth, viewportHeight); + } + + return applyLandingFigmaScaleToRoot(viewportWidth, viewportHeight); +} + +export function clearPortalFigmaScaleFromRoot() { + const root = document.documentElement; + clearPortalScaleModeClasses(root); + root.style.removeProperty('--portal-landing-first-screen-height'); + root.style.removeProperty('--portal-market-shell-min-height'); + root.style.removeProperty('--portal-shell-min-design-height'); + clearHomeFigmaScaleFromRoot(); +} + +export function syncPortalFigmaStageLayout(stageEl, scale) { + return syncHomeFigmaStageLayout(stageEl, scale); +} diff --git a/txw-mhzc-web/src/pages/index/views/fwsc/fwsc.vue b/txw-mhzc-web/src/pages/index/views/fwsc/fwsc.vue index 871631e..e96a994 100644 --- a/txw-mhzc-web/src/pages/index/views/fwsc/fwsc.vue +++ b/txw-mhzc-web/src/pages/index/views/fwsc/fwsc.vue @@ -1,5 +1,7 @@