Files
Mycontent/reactH5/scripts/sync_styles_from_miniprogram.py
乘风 c78a0d531f feat: enhance login modal and avatar selection process
- Updated the login modal to include a user agreement section with clickable links to the user agreement and privacy policy.
- Improved button styling and layout for better user experience.
- Refactored avatar selection process to utilize local image paths, enhancing compatibility and reliability across different environments.
- Added utility functions for resolving avatar file paths and selecting images from the gallery or camera.

This update aims to streamline user interactions during login and avatar selection, ensuring a smoother experience.
2026-05-06 17:21:10 +08:00

394 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""从 miniprogram 同步 wxss 到 reactH5 的 cssP1 页面 + 全局 + 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;
}
"""
# TabBarH5 用内联 SVGTabBarIcons需固定尺寸类名
TABBAR_CSS_H5_TAIL = """
/* ----- H5 增补:普通 Tab 为矢量描边(见 TabBarIcons与 inactive #8e8e93 / active #00CED1 一致 ----- */
.tab-icon-svg {
width: 48rpx;
height: 48rpx;
display: block;
flex-shrink: 0;
}
/* ----- H5TabBar 贴合左右安全区,避免首尾图标贴直角边 ----- */
.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.wxss 一致),避免首页 Index 同名 .part-* 覆盖宽高 ----- */
.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 {
/* 与导航栏 nav-content 左右 32rpx 对齐,卡片区略收窄(参考小程序视觉边距) */
padding: 16rpx 32rpx;
width: 100%;
box-sizing: border-box;
}
.chapters-page .book-card-meta {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 6rpx;
}
.chapters-page .book-card-title {
font-size: 30rpx;
font-weight: 700;
color: #ffffff;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
letter-spacing: 1rpx;
}
.chapters-page .book-card-subtitle {
font-size: 22rpx;
color: rgba(255, 255, 255, 0.35);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.chapters-page .part-item {
/* 盖住全局 IndexPage.css .part-itempadding/背景/边框),只保留外层容器与小程序一致 */
display: block;
width: 100%;
margin-bottom: 12rpx;
padding: 0;
gap: 0;
align-items: unset;
justify-content: unset;
flex-wrap: unset;
background: transparent;
border: none;
border-radius: 0;
box-sizing: border-box;
}
.chapters-page .part-item:active {
transform: none;
background: transparent;
}
.chapters-page .part-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 22rpx 24rpx;
background: rgba(28, 28, 30, 0.7);
border-radius: 20rpx;
border: 1rpx solid rgba(255, 255, 255, 0.04);
width: 100%;
box-sizing: border-box;
}
.chapters-page .part-left {
display: flex;
align-items: center;
gap: 20rpx;
flex: 1;
min-width: 0;
}
.chapters-page .part-right {
display: flex;
align-items: center;
gap: 12rpx;
flex-shrink: 0;
white-space: nowrap;
}
.chapters-page .part-icon {
width: 56rpx;
height: 56rpx;
min-width: 56rpx;
min-height: 56rpx;
border-radius: 14rpx;
background: linear-gradient(135deg, #1a2e3e 0%, #0d1b2a 100%);
border: 1rpx solid rgba(0, 206, 209, 0.2);
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
font-weight: 700;
color: #ffffff;
flex-shrink: 0;
}
.chapters-page .part-icon-emoji {
font-size: 28rpx;
font-weight: 400;
line-height: 1;
background: linear-gradient(135deg, #1a2e3e 0%, #0d1b2a 100%);
border: 1rpx solid rgba(0, 206, 209, 0.25);
}
.chapters-page .part-icon-img {
width: 56rpx;
height: 56rpx;
border-radius: 14rpx;
flex-shrink: 0;
}
.chapters-page .part-info {
display: flex;
flex-direction: column;
min-width: 0;
flex: 1;
}
.chapters-page .part-title {
font-size: 27rpx;
font-weight: 600;
color: #ffffff;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: block;
margin-bottom: 0;
}
.chapters-page .part-subtitle {
font-size: 20rpx;
color: rgba(255, 255, 255, 0.35);
margin-top: 4rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: block;
}
.chapters-page .part-count {
font-size: 22rpx;
color: rgba(255, 255, 255, 0.35);
}
.chapters-page .part-arrow {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.4);
}
.chapters-page .section-title {
font-size: 25rpx;
color: #ffffff;
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 1.3;
}
.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;
flex-shrink: 0;
}
.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: center;
}
.chapters-page .chapters-list {
margin-top: 12rpx;
margin-left: 12rpx;
}
.chapters-page .part-item > .chapters-list {
display: block;
width: 100%;
}
.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: center;
gap: 14rpx;
flex: 1;
min-width: 0;
}
.chapters-page .section-right {
display: flex;
align-items: center;
gap: 12rpx;
flex-shrink: 0;
margin-left: 12rpx;
}
.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 + 注入与小程序一致的 CDNapp.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()