300 lines
9.0 KiB
Python
300 lines
9.0 KiB
Python
# -*- 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()
|