# -*- coding: utf-8 -*- """从 miniprogram 同步 wxss 到 reactH5 的 css(P1 页面 + 全局 + TabBar + 登录)。""" import os import shutil BASE = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "..")) # 写入 index wxss 后追加(对齐 scroll-view 横滑 / 触摸) INDEX_CSS_H5_TAIL = """ /* ----- H5 增补(对齐小程序 scroll-view 与可点反馈;源 wxss 无此块) ----- */ .super-scroll { overflow-x: auto; overflow-y: hidden; scrollbar-width: none; -webkit-overflow-scrolling: touch; } .super-scroll-inner { min-height: min-content; } .banner-card, .contact-btn, .featured-item, .super-empty-btn, .super-item-h, .timeline-item, .latest-expand-hint { cursor: pointer; -webkit-tap-highlight-color: transparent; user-select: none; } .contact-btn:active, .featured-item:active, .banner-card:active, .super-empty-btn:active, .super-item-h:active { opacity: 0.97; } /* ----- H5:左右安全区(刘海/曲面);并提高首页作用域优先级,避免被其它页面同名类覆盖 ----- */ .index-page.page { padding-left: env(safe-area-inset-left, 0px); padding-right: env(safe-area-inset-right, 0px); box-sizing: border-box; } .index-page .main-content { padding-left: 32rpx; padding-right: 32rpx; box-sizing: border-box; } .index-page .main-content .super-scroll, .index-page .section .super-scroll, .index-page .main-content .super-loading, .index-page .section .super-loading { margin-left: 0; margin-right: 0; } """ # TabBar:H5 用内联 SVG(TabBarIcons),需固定尺寸类名 TABBAR_CSS_H5_TAIL = """ /* ----- H5 增补:普通 Tab 为矢量描边(见 TabBarIcons),与 inactive #8e8e93 / active #00CED1 一致 ----- */ .tab-icon-svg { width: 48rpx; height: 48rpx; display: block; flex-shrink: 0; } /* ----- H5:TabBar 贴合左右安全区,避免首尾图标贴直角边 ----- */ .tab-bar { padding-left: env(safe-area-inset-left, 0px); padding-right: env(safe-area-inset-right, 0px); box-sizing: border-box; } """ CHAPTERS_CSS_H5_TAIL = """ /* ----- H5:目录页 scoped 兜底,避免与首页/匹配页同名类互相覆盖导致样式错乱 ----- */ .chapters-page.page { padding-left: env(safe-area-inset-left, 0px); padding-right: env(safe-area-inset-right, 0px); box-sizing: border-box; } .chapters-page .chapters-content { padding: 16rpx 24rpx; width: 100%; box-sizing: border-box; } .chapters-page .book-card-title { font-size: 30rpx; font-weight: 700; color: #ffffff; } .chapters-page .part-title { font-size: 27rpx; font-weight: 600; color: #ffffff; } .chapters-page .section-title { font-size: 25rpx; color: #ffffff; white-space: normal; word-break: break-word; overflow-wrap: anywhere; line-height: 1.35; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; flex: 1; } .chapters-page .tag { display: inline-flex; align-items: center; justify-content: center; font-size: 20rpx; padding: 4rpx 14rpx; min-width: 64rpx; border-radius: 8rpx; box-sizing: border-box; text-align: center; } .chapters-page .tag-free { background: rgba(0, 206, 209, 0.1); color: #00CED1; } .chapters-page .section-item { display: flex; width: 100%; box-sizing: border-box; align-items: flex-start; } .chapters-page .chapters-list { margin-top: 12rpx; margin-left: 12rpx; } .chapters-page .part-item { display: block; width: 100%; margin-bottom: 12rpx; } .chapters-page .part-item > .chapters-list { display: block; width: 100%; } .chapters-page .part-header { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; column-gap: 12rpx; width: 100%; box-sizing: border-box; } .chapters-page .part-left { min-width: 0; } .chapters-page .part-right { justify-self: end; white-space: nowrap; display: inline-flex; align-items: center; gap: 12rpx; } .chapters-page .section-list { display: block; width: 100%; } .chapters-page .chapter-header { display: block; width: 100%; padding: 14rpx 24rpx; font-size: 23rpx; font-weight: 500; color: rgba(255, 255, 255, 0.5); border-bottom: 1rpx solid rgba(255, 255, 255, 0.04); } .chapters-page .section-left { display: flex; flex-direction: row; align-items: flex-start; gap: 14rpx; flex: 1; min-width: 0; } .chapters-page .section-right { display: flex; align-items: center; gap: 12rpx; flex-shrink: 0; margin-left: 12rpx; padding-top: 2rpx; } .chapters-page .card { background: rgba(28, 28, 30, 0.7); border-radius: 20rpx; border: 1rpx solid rgba(255, 255, 255, 0.04); margin: 0 12rpx 16rpx 12rpx; box-sizing: border-box; } """ PAIRS = [ ("miniprogram/app.wxss", "reactH5/src/styles/app-global.css"), ("miniprogram/pages/index/index.wxss", "reactH5/src/pages/Index/IndexPage.css"), ("miniprogram/pages/chapters/chapters.wxss", "reactH5/src/pages/Chapters/ChaptersPage.css"), ("miniprogram/pages/match/match.wxss", "reactH5/src/pages/Match/MatchPage.css"), ("miniprogram/pages/my/my.wxss", "reactH5/src/pages/My/MyPage.css"), ("miniprogram/custom-tab-bar/index.wxss", "reactH5/src/components/TabBar/TabBar.css"), ("miniprogram/components/login-modal/login-modal.wxss", "reactH5/src/components/LoginModal/LoginModal.css"), ("miniprogram/pages/dev-login/dev-login.wxss", "reactH5/src/pages/DevLogin/DevLoginPage.css"), ] def copy_tree_merge(src_dir: str, dst_dir: str) -> None: if not os.path.isdir(src_dir): print("SKIP dir missing:", src_dir) return os.makedirs(dst_dir, exist_ok=True) for root, _, files in os.walk(src_dir): rel = os.path.relpath(root, src_dir) out_root = os.path.join(dst_dir, rel) if rel != "." else dst_dir os.makedirs(out_root, exist_ok=True) for name in files: shutil.copy2(os.path.join(root, name), os.path.join(out_root, name)) print("ASSETS merged", src_dir, "->", dst_dir) def main(): # 静态资源:SVG/图片等与小程序共用一份 copy_tree_merge( os.path.join(BASE, "miniprogram", "assets"), os.path.join(BASE, "reactH5", "public", "assets"), ) st_src = os.path.join(BASE, "miniprogram", "static") st_dst = os.path.join(BASE, "reactH5", "public", "static") if os.path.isdir(st_src): os.makedirs(st_dst, exist_ok=True) for fn in os.listdir(st_src): shutil.copy2(os.path.join(st_src, fn), os.path.join(st_dst, fn)) print("STATIC", st_src, "->", st_dst) # iconfont:同步 wxss + 注入与小程序一致的 CDN(app.js loadIconFont) ic_wxss = os.path.join(BASE, "miniprogram", "static", "iconfont.wxss") ic_css = os.path.join(BASE, "reactH5", "src", "styles", "iconfont.css") if os.path.isfile(ic_wxss): with open(ic_wxss, "r", encoding="utf-8", errors="replace") as f: ic_text = f.read() CDN_MARK = "at.alicdn.com" if CDN_MARK not in ic_text: old = " src: url('/static/iconfont.woff2') format('woff2')," new = ( " src: url('https://at.alicdn.com/t/c/font_5142223_1sq6pv9vvbt.woff2') format('woff2'),\n" " url('/static/iconfont.woff2') format('woff2')," ) if old in ic_text: ic_text = ic_text.replace(old, new, 1) icon_cmp = os.path.join(BASE, "miniprogram", "components", "icon", "icon.wxss") if os.path.isfile(icon_cmp): with open(icon_cmp, "r", encoding="utf-8", errors="replace") as f: cmp_txt = f.read().strip() ic_text += "\n\n/* H5 appended: miniprogram/components/icon/icon.wxss */\n" ic_text += cmp_txt + "\n" ic_header = "/* Synced from miniprogram/static/iconfont.wxss + H5 CDN + icon/component */\n" os.makedirs(os.path.dirname(ic_css), exist_ok=True) with open(ic_css, "w", encoding="utf-8", newline="\n") as f: f.write(ic_header + ic_text) print("OK iconfont.css") for src_rel, dst_rel in PAIRS: src = os.path.join(BASE, src_rel.replace("/", os.sep)) dst = os.path.join(BASE, dst_rel.replace("/", os.sep)) if not os.path.isfile(src): print("SKIP missing:", src_rel) continue with open(src, "r", encoding="utf-8", errors="replace") as f: text = f.read() # app.wxss 中两处 page { 均映射为 H5 根选择器 if "app.wxss" in src_rel: text = text.replace("page {", ":root, html, body, #root {") header = "/* Auto-synced from miniprogram → reactH5; 源: %s */\n" % src_rel if "pages/index/index.wxss" in src_rel: text += INDEX_CSS_H5_TAIL if "pages/chapters/chapters.wxss" in src_rel: text += CHAPTERS_CSS_H5_TAIL if "custom-tab-bar/index.wxss" in src_rel: text += TABBAR_CSS_H5_TAIL os.makedirs(os.path.dirname(dst), exist_ok=True) with open(dst, "w", encoding="utf-8", newline="\n") as f: f.write(header + text) print("OK", dst_rel, len(text), "chars") if __name__ == "__main__": main()