全新popiart网站 react项目重构
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

14 KiB

响应式适配计划(Popiart SkillHub 前端)

本文档为移动端响应式适配的执行计划,按阶段推进。分支:responsive(切自 version/1.2.14)。

Context(背景与目标)

本项目 README 定位为 PC 端(≥1280px)网站,移动端基本未做真正重排。当前"适配"靠 src/hooks/useViewportScale.ts 以 1920 设计稿等比缩放,窄屏体验差、弹窗溢出、侧栏挤占。现需让整站在移动端可用

硬约束:只做 UI 适配,不修改任何业务逻辑 / 数据流 / 接口。

三项决策:

  1. 移动端导航 = 底部 Tab 栏,纯 CSS 实现(侧栏在窄屏 reflow 到底部,不做抽屉、不加开合 state)。
  2. 断点范围 = 手机 + 平板(多档断点)。
  3. 改动边界 = 允许最小"启用型"改动:修 viewport meta、新增 useIsMobile(matchMedia 封装)。不碰任何业务逻辑。

三个关键前提(决定方案骨架):

  • viewport 被锁死index.html:17<meta name="viewport" content="width=1280, user-scalable=no"> 覆盖了第 7 行的 width=device-width,真机被强制按 1280 宽渲染。不修此行,一切响应式在真机失效 —— 第 0 步必做。
  • 900px 是天然主断点useViewportScale.ts:17LAYOUT_SCALE_DISABLE_BELOW = 900,窗口 <900px 时自动关闭等比缩放交给媒体查询。所有"重排型"断点必须 ≤900px,否则与缩放坐标系打架。≥900px(含平板横屏 1024)仍在缩放系统内,只做轻量微调,不做重排。
  • 样式几乎全在 SCSS,Tailwind 基本空转:2724 处 className 绝大多数是 SCSS 语义化 BEM 类;Tailwind 响应式变体 sm:/md:/lg: 用了 0 处,原子类仅 ~19 处。Tailwind 的断点/栅格只在 className 生效,无法作用于 .scss 里的 @media,而本次要改的 77 处 @media 全在 SCSS。→ 结论:主战场是手写 SCSS @media,不重写 JSX 为原子类,也不自建平行 token 系统。
  • Arco 用得极少(仅 7 个组件):Message(35)/Dropdown(14)/Modal(8)/Input(6)/Tooltip(6)/Button(3)/Spin(2)。未用栅格 Row/Col、未用 Table、未用 Form——布局 100% 原生 SCSS,Arco 帮不上布局响应式。唯一痛点是 Modal 固定宽度,且散在约 9 处(见阶段 2)。无 ConfigProvider

断点体系(对齐 Tailwind 默认,不自建平行系统)

数值直接沿用 Tailwind v4 默认口径,仅保留一个自定义主断点 900(与缩放开关硬耦合):

名称 范围 对应 Tailwind 处理策略
desktop ≥1024px ≥lg 现状 + 等比缩放,基本不动
tablet-landscape 900–1024px lg 区间 仍在缩放系统内,仅轻量微调,不重排
mobile-main(主断点) ≤900px 自定义 缩放已自动关闭,重排主战场:侧栏→底部 Tab、多列→单/双列、弹窗→全屏
tablet-portrait ≤768px md 进一步收敛单列、隐藏次要信息
small-phone ≤640px sm 最小尺寸微调:字号、间距、图标化、按钮全宽

统一用 max-width 媒体查询(与现有方向一致)。现有 77 处 @media 不重构;新增的直接写 @media (max-width: 900/768/640px),数值口径与 Tailwind 一致,将来若在新 JSX 用 md:/lg: 亦不冲突。是否加 SCSS @mixin respond-to 属可选糖,非必需。


阶段 0 · 全局基建(先做,是后续所有改造的抓手)

基建刻意做薄:不引入平行 token 系统、不重构现有 @media、不把 JSX 改写成 Tailwind 原子类

# 事项 文件/位置 说明
0.1 修 viewport index.html:17 删除/改为 width=device-width, initial-scale=1(保留第 7 行即可)。这是一切前提
0.2 约定断点数值 无需新文件;沿用 Tailwind 默认口径 统一用 900(主)/768(md)/640(sm)/1024(lg)。新增 @media 直接写数值;如需 SCSS mixin 仅加一个轻量 @mixin respond-to(可选)。若个别新 JSX 要响应式类,直接用 Tailwind 自带 md:/lg:,不自建
0.3 useIsMobile hook 新增 src/hooks/useIsMobile.ts + 挂 src/hooks/index.ts barrel matchMedia("(max-width: 900px)") 封装,与 useViewportScale 的 900 对齐;仅供少数"隐藏 PC 专属 UI"的条件渲染(不改业务逻辑)。底部 Tab 为纯 CSS,不依赖它
0.4 全局兜底 src/index.css 全局 *{min-width:0} 防 flex/grid 溢出、img/video{max-width:100%}overflow-x:hidden 兜底、移动端 body 字号基准

阶段 1 · 应用外壳 Layout → 底部 Tab 栏(纯 CSS)

核心文件:src/layouts/Layout/Layout.tsxsrc/layouts/Layout/Layout.scss

改造锚点(均在 Layout.scss):

  • --layout-sidebar-width: 90px:50-51)、grid-template-columns:61)、grid-template-rows.layoutShell__sidebar:840)、.layoutShell__topbar:159)、.layoutShell__contentFrame:1069)。

≤900px 的 CSS 重排(不动 TSX 结构、不加 state):

  1. .layoutShell 网格从"左栏 + 右内容"改为纵向三行:顶栏 / 内容区(1fr) / 底部导航。grid-template-columns: 1frgrid-template-rows: auto 1fr auto
  2. .layoutShell__sidebargrid-row 改为最后一行、grid-column:1flex-direction: row、横向排布、position: sticky/fixed; bottom:0、高度约 56–64px、overflow-x:auto(导航项多时横滑)、加 env(safe-area-inset-bottom) 安全区。品牌 logo(__brand) 在窄屏隐藏。主导航 __sideNav 与底部 __footer(subscribe/profile)合并为一条底部 Tab(复用现有 LAYOUT_SIDE_NAV_ITEMS(Layout.tsx:93) / LAYOUT_FOOTER_NAV_ITEMS(Layout.tsx:286) 渲染出的 DOM,纯 CSS 重排,不改数据)。
  3. .layoutShell__topbar:精简——保留登录/头像/积分,隐藏次要项(教程、客服二维码可收进头像菜单,纯 CSS 隐藏);grid-column:1
  4. .layoutShell__contentFrame / __contentScroll:单列,底部留出 Tab 栏高度的 padding-bottom
  5. 与缩放系统协调:≤900px useViewportScale 已返回 1(不缩放),故 .layoutShellScaleInnerLayout.scss:32-45)在窄屏 zoom/transform 自动为 1,无需改 hook;仅需确认 spacer 占位在 scale=1 时不产生多余留白(Layout.scsslayoutShellScaleSpacer 窄屏归零)。

阶段 2 · 弹窗统一改造(抓手:src/dialog/index.tsx

主注入点openDialogWithHistory()src/dialog/index.tsx ~L96-190)内 barrel 弹窗汇聚到同一个 Modal.confirm({...config})在此对 config.style 做窄屏降级可一处覆盖多数 barrel 弹窗:≤900px 统一 width:100%maxWidth:100vw、近全屏(复用已有 layoutFullscreenDetailModalShellStyle:57-67)作窄屏 shell)。

⚠️ Modal 非单点收口,另有约 9 个散点需单独覆盖:

  • 直接调 Modal.confirm 的(3 文件):src/dialog/pay-subscribe/index.tsxsrc/pages/Create/panels/FolderPanel/index.tsxsrc/pages/Profile/index.tsx
  • 直接写 <Modal> JSX 的(5 文件):src/dialog/LoginRegisterModal/index.tsxsrc/dialog/asset-details/index.tsx 及其子 AssetExpandImageModal/AssetDetailRepairModalsrc/components/DeleteConfirmModal/index.tsx
  • 每处按同一窄屏 shell 规则给 style/className。可考虑抽一个共享的窄屏 modal style 常量(放 src/dialog/index.tsx 导出)复用,避免重复。

不走 barrel 的自定义 overlay: src/dialog/alice-live-class/--dialog-layout-scale 缩放)、src/dialog/app-download-mac/

弹窗逐个(≤900px 目标 = 全屏/单列,内部并排→堆叠):

弹窗目录 现状宽度 窄屏风险 适配要点
character-picker 1699px/80vw 极高 重灾区:固定 1699 画布 → 全屏、卡片网格降为 2/1 列
material-picker 1699px/80vw 极高 同上,全屏 + 单列
voice-audio-upload 1042px/92vw 全屏,波形/表单纵向堆叠
points-detail-modal fit/100vw-32 大表格:改卡片式列表或横向滚动容器
pay-subscribe fit/100vw-32 套餐并排→纵向堆叠(配合组件 subscribe-plan-list
character-detail 全屏 shell 中(内容级) 宽度 OK,内部"预览+信息"双栏→单列纵向
asset-details 全屏 shell 中(内容级) 同上;子弹窗 AssetExpandImageModal/AssetDetailRepairModal 已有 @media,补齐
folders-asset-details 全屏 shell 内部双栏→单列
LoginRegisterModal fit/100vw-32 已有 @media540,补到 480;表单全宽
points-recharge fit/100vw-32 已有 @media720/480,校验网格 repeat(4,87px)
user-edit fit/100vw-32 表单单列全宽
gateway-apikey fit/100vw-32 已有 @media768,补齐
copyright-deposit 885px 全屏/单列
voice-library-picker 833px 全屏;依赖组件 VoiceLibraryPicker(见阶段 4)
contact-service 454px/80vw 微调
insufficient-points fit 微调
alice-live-class 自定义缩放 单独:窄屏改真实全屏布局,脱离 --dialog-layout-scale
app-download-mac overlay 单独:已有 @media720,补齐

清理:src/dialog/Untitled(21 字节杂散文件)随手删除。


阶段 3 · 页面逐个适配(≤900px 为主,≤768/≤480 收敛)

统一原则:多列网格降列(→2→1)、并排双栏→纵向、定宽 min-width/固定pxmin-width:0+流式、overflow-x 区域改可控横滑或换行、按钮/输入窄屏全宽。均只改 .scss 与必要 className,不动组件逻辑。

优先级 A — 无/弱响应式,窄屏高风险(重点投入)

页面 文件 适配要点
CharactersCreate src/pages/CharactersCreate/index.{tsx,scss}(最大 scss 1833 行)+ 子 CreateRoleModal 画布型编辑器,多处 min-width:720/660/494/430。窄屏:画布区改纵向流、工具/结果面板堆叠、去定宽。工作量最大
Voice src/pages/Voice/index.scss(4055 行)+ components/music/*components/speech/VoiceSpeechPanel.tsx(3226 行) 左表单/右画廊/播放器/歌词/波形裁剪。窄屏:repeat(2,…)→1、min-width:280/160/125去定宽、播放器 bar 贴底、裁剪/歌词编辑纵向
Profile src/pages/Profile/index.{tsx,scss} 仅有 @media900:补 768/480;用户信息头行重排、画廊网格降列、批量选择栏适配
Create/InspirationLibraryPanel panels/InspirationLibraryPanel/*BackgroundAssets/* 无断点:新增网格降列

优先级 B — 已有较完整响应式(补齐 + 校验)

页面 文件 适配要点
index(首页) src/pages/index/index.scss(1709 行) 营销页多处三栏定宽栅格(150px 1fr 150px 等);已有 1320/1100/860/640,补 480、去定宽段
Explore(+子 feed) src/pages/Explore/* 响应式最完整(6→1 列);仅校验 Hero minmax(420px,633px) 定宽段 ≤480
Teaching src/pages/Teaching/index.scss 已 5→1 列;校验 hero 双栏与 minmax(160px,192fr)
TeachingDocument src/pages/TeachingDocument/* 402px 定宽侧栏已有单列降级;校验富文本多列 --rich-column-count
Characters src/pages/Characters/* 卡片墙 + 横滑区,已有 1280/900/560;补 480
Create 容器 + 其余面板 src/pages/Create/*MyCreationsPanel/FolderPanel/MyFavoritePanel/TemplateAssets/CharacterAssetsPanel 多含 overflow-xrepeat(auto-fill,168px) 定宽单元格;FolderPanel(1687 行 scss)为重点,补断点
subscribe src/pages/subscribe/* + Swiper 已 820 降单列;校验 repeat(4,87px) 积分网格、Swiper 窄屏 slidesPerView
content/page src/pages/content/page/* 已 @media768,基本 OK
NotFound src/pages/NotFound/index.tsx 纯 Tailwind,天然自适应,无需改

阶段 4 · 公共组件适配(src/components/

组件 适配要点
subscribe-plan-list 重:并排套餐卡(已 @media820)→ 窄屏纵向堆叠/横滑
VoiceLibraryPicker 重:列表+试听并排 → 纵向
AvatarCropModal 重:图片裁剪画布,校验触摸手势与固定尺寸(已 @media768/520)
UserAvatarMenu 下拉菜单定宽(已 @media860),窄屏防越界
AssetGalleryTile / PreviewImageThumb 网格瓦片:流式宽度、min-width:0
AssetAudioPlayer 固定宽 → 流式全宽
UserGuide 新手引导覆盖层:定位随窄屏元素校准
CategoryDropdown / FolderMoveSubmenu / DeleteConfirmModal 固定宽 → max-width:calc(100vw-32px)
subscribe-qa-section(已768)、TabPill/SectionPillHorizontalWheelScrollErrorBoundaryUploadPendingIndicatorRouteLoading 小改或无需改,逐个校验不溢出

执行顺序建议

阶段 0(基建)→ 阶段 1(外壳 Tab 栏,先让整体骨架能用)→ 阶段 2(弹窗统一注入,一处覆盖多数)→ 阶段 3A(重灾页面)→ 阶段 3B(补齐页面)→ 阶段 4(组件)。每阶段独立可提交(Conventional Commits,如 style(layout): 移动端底部导航栏)。

验证

  • 本地 npm run dev(端口 5195,--host 0.0.0.0 可真机同网访问)。
  • 用 Chrome DevTools 设备模拟逐屏核对:390px(手机)/ 768px(平板竖)/ 900px(主断点临界)/ 1024px(平板横)/ 1280px(桌面回归)
  • 重点验证清单:① 首屏无横向滚动条(各断点);② 底部 Tab 可点、内容不被遮挡(safe-area);③ 全部弹窗窄屏不溢出、可关闭;④ 重灾区页面(CharactersCreate/Voice)无内容被裁切;⑤ ≥900px 桌面视觉零回归(缩放系统未被破坏)。
  • 收尾 npm run lint(零 warning)+ npm run build(tsc 类型检查通过)。
  • 提示:≤900 与 ≥900 两侧要交叉验证,因为 900 是缩放开关的临界点。

注意事项 / 不做

  • 不改任何 src/api/*src/store/* 业务逻辑与数据流。
  • 不新增业务组件;useIsMobile 仅为条件渲染纯 UI(如隐藏/切换展示),非业务分支。
  • 遵循 .cursor/skills/react-best-practices:SCSS 嵌套跟随 DOM、语义化 class;如需新增任何可见文案须走 i18n(本次应尽量零新增文案)。