1
This commit is contained in:
104
tools/h5-to-mp-checker/EXAMPLES.md
Normal file
104
tools/h5-to-mp-checker/EXAMPLES.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# 使用示例
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
# 从项目根目录运行
|
||||
cd c:\NeoZQYY
|
||||
|
||||
# 检查 board-customer 页面
|
||||
python tools\h5-to-mp-checker\check_h5_to_mp.py ^
|
||||
--h5 docs\h5_ui\pages\board-customer.html ^
|
||||
--wxss apps\miniprogram\miniprogram\pages\board-customer\board-customer.wxss ^
|
||||
--output docs\miniprogram-dev\board-customer-reports\auto-check-report.md
|
||||
```
|
||||
|
||||
## 输出示例
|
||||
|
||||
运行后会生成类似这样的报告:
|
||||
|
||||
```markdown
|
||||
# H5 到小程序样式对比报告
|
||||
|
||||
> 生成时间:2026-03-14 07:36:00
|
||||
|
||||
## 📊 问题统计
|
||||
|
||||
- ❌ 严重问题:2 个
|
||||
- ⚠️ 警告:3 个
|
||||
- ℹ️ 提示:1 个
|
||||
- **总计**:6 个
|
||||
|
||||
## ❌ 严重问题(2 个)
|
||||
|
||||
### ❌ 严重 page - line-height
|
||||
|
||||
- **位置**:page 全局样式
|
||||
- **H5 值**:无全局设置
|
||||
- **应该是**:无
|
||||
- **实际是**:1.5
|
||||
- **差异**:100.0%
|
||||
|
||||
### ❌ 严重 text-xs - line-height
|
||||
|
||||
- **位置**:.assistant-label
|
||||
- **H5 值**:16px
|
||||
- **应该是**:29rpx
|
||||
- **实际是**:缺失
|
||||
- **差异**:100.0%
|
||||
```
|
||||
|
||||
## 集成到开发流程
|
||||
|
||||
### 1. 提交前检查
|
||||
|
||||
```bash
|
||||
# 添加到 pre-commit hook
|
||||
python tools\h5-to-mp-checker\check_h5_to_mp.py --h5 ... --wxss ... --output report.md
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "样式检查失败,请修复后再提交"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### 2. CI/CD 集成
|
||||
|
||||
```yaml
|
||||
# .github/workflows/check-styles.yml
|
||||
name: Check Styles
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Check styles
|
||||
run: |
|
||||
python tools/h5-to-mp-checker/check_h5_to_mp.py \
|
||||
--h5 docs/h5_ui/pages/board-customer.html \
|
||||
--wxss apps/miniprogram/miniprogram/pages/board-customer/board-customer.wxss \
|
||||
--output report.md
|
||||
- name: Upload report
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: style-check-report
|
||||
path: report.md
|
||||
```
|
||||
|
||||
## 更多页面
|
||||
|
||||
检查其他页面:
|
||||
|
||||
```bash
|
||||
# 检查 board-coach 页面
|
||||
python tools\h5-to-mp-checker\check_h5_to_mp.py ^
|
||||
--h5 docs\h5_ui\pages\board-coach.html ^
|
||||
--wxss apps\miniprogram\miniprogram\pages\board-coach\board-coach.wxss ^
|
||||
--output docs\miniprogram-dev\board-coach-reports\auto-check-report.md
|
||||
|
||||
# 检查 board-finance 页面
|
||||
python tools\h5-to-mp-checker\check_h5_to_mp.py ^
|
||||
--h5 docs\h5_ui\pages\board-finance.html ^
|
||||
--wxss apps\miniprogram\miniprogram\pages\board-finance\board-finance.wxss ^
|
||||
--output docs\miniprogram-dev\board-finance-reports\auto-check-report.md
|
||||
```
|
||||
156
tools/h5-to-mp-checker/README.md
Normal file
156
tools/h5-to-mp-checker/README.md
Normal file
@@ -0,0 +1,156 @@
|
||||
# H5 到小程序样式检查工具
|
||||
|
||||
自动化检查 H5 到小程序迁移过程中的样式差异。
|
||||
|
||||
## 功能
|
||||
|
||||
- ✅ 自动提取 H5 中的 Tailwind 类
|
||||
- ✅ 自动提取小程序中的样式
|
||||
- ✅ 逐个对比每个属性
|
||||
- ✅ 检查全局样式(line-height 等)
|
||||
- ✅ 检查 font-size 和 line-height
|
||||
- ✅ 检查 padding、margin、gap
|
||||
- ✅ 检查 width、height
|
||||
- ✅ 检查 border-radius
|
||||
- ✅ 生成详细的 Markdown 报告
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
# 无需安装依赖,使用 Python 3.6+ 即可
|
||||
python --version # 确保 Python 3.6+
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 基本用法
|
||||
|
||||
```bash
|
||||
python scripts/check_h5_to_mp.py \
|
||||
--h5 docs/h5_ui/pages/board-customer.html \
|
||||
--wxss apps/miniprogram/miniprogram/pages/board-customer/board-customer.wxss \
|
||||
--output report.md
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
- `--h5`:H5 HTML 文件路径(必需)
|
||||
- `--wxss`:小程序 WXSS 文件路径(必需)
|
||||
- `--output`:输出报告路径(可选,默认 `report.md`)
|
||||
|
||||
## 输出示例
|
||||
|
||||
```markdown
|
||||
# H5 到小程序样式对比报告
|
||||
|
||||
## 📊 问题统计
|
||||
|
||||
- ❌ 严重问题:5 个
|
||||
- ⚠️ 警告:7 个
|
||||
- ℹ️ 提示:3 个
|
||||
- **总计**:15 个
|
||||
|
||||
## ❌ 严重问题
|
||||
|
||||
### ❌ 严重 page - line-height
|
||||
|
||||
- **位置**:page 全局样式
|
||||
- **H5 值**:无全局设置
|
||||
- **应该是**:无
|
||||
- **实际是**:1.5
|
||||
- **差异**:100.0%
|
||||
|
||||
### ❌ 严重 text-xs - line-height
|
||||
|
||||
- **位置**:.assistant-label
|
||||
- **H5 值**:16px
|
||||
- **应该是**:29rpx
|
||||
- **实际是**:缺失
|
||||
- **差异**:100.0%
|
||||
```
|
||||
|
||||
## 检查项目
|
||||
|
||||
### 1. 全局样式
|
||||
|
||||
- [ ] page 的 line-height
|
||||
- [ ] view 的 line-height
|
||||
- [ ] 全局 font-size
|
||||
- [ ] 全局 padding/margin
|
||||
|
||||
### 2. 文本类(text-xxx)
|
||||
|
||||
对每个 text-xxx 类:
|
||||
- [ ] font-size
|
||||
- [ ] line-height(必须检查)
|
||||
|
||||
### 3. 间距类(p-, m-, gap-)
|
||||
|
||||
对每个间距类:
|
||||
- [ ] padding
|
||||
- [ ] margin
|
||||
- [ ] gap
|
||||
|
||||
### 4. 尺寸类(w-, h-)
|
||||
|
||||
对每个尺寸类:
|
||||
- [ ] width
|
||||
- [ ] height
|
||||
|
||||
### 5. 圆角类(rounded-)
|
||||
|
||||
对每个圆角类:
|
||||
- [ ] border-radius
|
||||
|
||||
## 问题级别
|
||||
|
||||
- ❌ **严重**:差异 > 20%
|
||||
- ⚠️ **警告**:差异 10-20%
|
||||
- ℹ️ **提示**:差异 < 10%
|
||||
- ✅ **正确**:差异 < 2%
|
||||
|
||||
## 扩展
|
||||
|
||||
### 添加自定义 Tailwind 配置
|
||||
|
||||
编辑 `check_h5_to_mp.py` 中的 `TAILWIND_CONFIG`:
|
||||
|
||||
```python
|
||||
TAILWIND_CONFIG = {
|
||||
'text': {
|
||||
'text-xs': {'font-size': '12px', 'line-height': '16px'},
|
||||
# 添加更多...
|
||||
},
|
||||
'spacing': {
|
||||
'p-6': '24px',
|
||||
# 添加更多...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### 调整换算比例
|
||||
|
||||
如果目标设备不是 412px,修改 `PX_TO_RPX`:
|
||||
|
||||
```python
|
||||
# 375px 设备
|
||||
PX_TO_RPX = 2.0
|
||||
|
||||
# 414px 设备
|
||||
PX_TO_RPX = 1.8116
|
||||
```
|
||||
|
||||
## 限制
|
||||
|
||||
1. 目前只检查类选择器(`.class`),不检查标签选择器
|
||||
2. 不检查伪类和伪元素
|
||||
3. 间距类的匹配逻辑需要根据实际情况调整
|
||||
4. 不检查 JavaScript 动态样式
|
||||
|
||||
## 贡献
|
||||
|
||||
欢迎提交 Issue 和 Pull Request!
|
||||
|
||||
## 许可
|
||||
|
||||
MIT
|
||||
300
tools/h5-to-mp-checker/check_h5_to_mp.py
Normal file
300
tools/h5-to-mp-checker/check_h5_to_mp.py
Normal file
@@ -0,0 +1,300 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
H5 到小程序样式对比检查工具
|
||||
|
||||
用法:
|
||||
python check_h5_to_mp.py --h5 h5.html --wxss mp.wxss --output report.md
|
||||
"""
|
||||
|
||||
import re
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Set
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class IssueLevel(Enum):
|
||||
"""问题级别"""
|
||||
CRITICAL = "❌ 严重" # 差异 > 20%
|
||||
WARNING = "⚠️ 警告" # 差异 10-20%
|
||||
INFO = "ℹ️ 提示" # 差异 < 10%
|
||||
OK = "✅ 正确" # 差异 < 2%
|
||||
|
||||
|
||||
@dataclass
|
||||
class StyleIssue:
|
||||
"""样式问题"""
|
||||
level: IssueLevel
|
||||
category: str
|
||||
tailwind_class: str
|
||||
property_name: str
|
||||
h5_value: str
|
||||
expected_rpx: str
|
||||
actual_rpx: str
|
||||
difference: float
|
||||
location: str
|
||||
|
||||
|
||||
# Tailwind CSS v3 配置
|
||||
TAILWIND_CONFIG = {
|
||||
'text': {
|
||||
'text-xs': {'font-size': '12px', 'line-height': '16px'},
|
||||
'text-sm': {'font-size': '14px', 'line-height': '20px'},
|
||||
'text-base': {'font-size': '16px', 'line-height': '24px'},
|
||||
'text-lg': {'font-size': '18px', 'line-height': '28px'},
|
||||
'text-xl': {'font-size': '20px', 'line-height': '28px'},
|
||||
'text-2xl': {'font-size': '24px', 'line-height': '32px'},
|
||||
},
|
||||
'spacing': {
|
||||
'p-2': '8px', 'p-3': '12px', 'p-4': '16px',
|
||||
'm-2': '8px', 'm-3': '12px', 'm-4': '16px',
|
||||
'gap-2': '8px', 'gap-3': '12px',
|
||||
'ml-11': '44px', 'mb-2': '8px', 'mb-3': '12px',
|
||||
'pt-2': '8px', 'pt-3': '12px',
|
||||
},
|
||||
}
|
||||
|
||||
PX_TO_RPX = 1.8204 # 412px 设备
|
||||
|
||||
|
||||
def px_to_rpx(px_value: str) -> float:
|
||||
"""将 px 值转换为 rpx"""
|
||||
if not px_value or px_value == '0':
|
||||
return 0
|
||||
match = re.search(r'([\d.]+)', px_value)
|
||||
if match:
|
||||
return round(float(match.group(1)) * PX_TO_RPX, 2)
|
||||
return 0
|
||||
|
||||
|
||||
def rpx_to_px(rpx_value: str) -> float:
|
||||
"""将 rpx 值转换为 px"""
|
||||
if not rpx_value or rpx_value == '0':
|
||||
return 0
|
||||
match = re.search(r'([\d.]+)', rpx_value)
|
||||
if match:
|
||||
return round(float(match.group(1)) / PX_TO_RPX, 2)
|
||||
return 0
|
||||
|
||||
|
||||
def extract_tailwind_classes(html_content: str) -> Dict[str, Set[str]]:
|
||||
"""提取 HTML 中所有 Tailwind 类"""
|
||||
classes = {'text': set(), 'spacing': set(), 'custom': set()}
|
||||
|
||||
class_attrs = re.findall(r'class="([^"]+)"', html_content)
|
||||
for class_str in class_attrs:
|
||||
for cls in class_str.split():
|
||||
if cls.startswith('text-'):
|
||||
if '[' in cls:
|
||||
classes['custom'].add(cls)
|
||||
else:
|
||||
classes['text'].add(cls)
|
||||
elif re.match(r'^[pm][tblrxy]?-', cls) or cls.startswith('gap-'):
|
||||
classes['spacing'].add(cls)
|
||||
|
||||
return classes
|
||||
|
||||
|
||||
def extract_wxss_styles(wxss_content: str) -> Dict[str, Dict[str, str]]:
|
||||
"""提取 WXSS 中所有样式"""
|
||||
styles = {}
|
||||
pattern = r'\.([a-zA-Z0-9_-]+(?:--[a-zA-Z0-9_-]+)?)\s*\{([^}]+)\}'
|
||||
matches = re.findall(pattern, wxss_content, re.MULTILINE)
|
||||
|
||||
for class_name, rules in matches:
|
||||
styles[class_name] = {}
|
||||
for rule in rules.split(';'):
|
||||
rule = rule.strip()
|
||||
if ':' in rule:
|
||||
prop, value = rule.split(':', 1)
|
||||
value = re.sub(r'/\*.*?\*/', '', value).strip()
|
||||
styles[class_name][prop.strip()] = value
|
||||
|
||||
return styles
|
||||
|
||||
|
||||
def check_global_styles(wxss_content: str) -> List[StyleIssue]:
|
||||
"""检查全局样式"""
|
||||
issues = []
|
||||
|
||||
page_match = re.search(r'page\s*\{([^}]+)\}', wxss_content)
|
||||
if page_match and 'line-height:' in page_match.group(1):
|
||||
lh_match = re.search(r'line-height:\s*([\d.]+)', page_match.group(1))
|
||||
if lh_match:
|
||||
issues.append(StyleIssue(
|
||||
level=IssueLevel.CRITICAL,
|
||||
category='global',
|
||||
tailwind_class='page',
|
||||
property_name='line-height',
|
||||
h5_value='无全局设置',
|
||||
expected_rpx='无',
|
||||
actual_rpx=lh_match.group(1),
|
||||
difference=100.0,
|
||||
location='page 全局样式'
|
||||
))
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
def check_text_classes(tailwind_classes: Set[str], wxss_styles: Dict) -> List[StyleIssue]:
|
||||
"""检查文本类"""
|
||||
issues = []
|
||||
|
||||
for tw_class in tailwind_classes:
|
||||
if tw_class not in TAILWIND_CONFIG['text']:
|
||||
continue
|
||||
|
||||
config = TAILWIND_CONFIG['text'][tw_class]
|
||||
h5_font_size = config['font-size']
|
||||
h5_line_height = config['line-height']
|
||||
expected_fs_rpx = px_to_rpx(h5_font_size)
|
||||
expected_lh_rpx = px_to_rpx(h5_line_height)
|
||||
|
||||
for class_name, props in wxss_styles.items():
|
||||
if 'font-size' not in props:
|
||||
continue
|
||||
|
||||
actual_fs = props['font-size']
|
||||
actual_px = rpx_to_px(actual_fs)
|
||||
expected_px = float(h5_font_size.replace('px', ''))
|
||||
|
||||
if abs(actual_px - expected_px) < 2:
|
||||
# 检查 line-height
|
||||
if 'line-height' not in props:
|
||||
issues.append(StyleIssue(
|
||||
level=IssueLevel.CRITICAL,
|
||||
category='text',
|
||||
tailwind_class=tw_class,
|
||||
property_name='line-height',
|
||||
h5_value=h5_line_height,
|
||||
expected_rpx=f'{expected_lh_rpx}rpx',
|
||||
actual_rpx='缺失',
|
||||
difference=100.0,
|
||||
location=f'.{class_name}'
|
||||
))
|
||||
else:
|
||||
actual_lh = props['line-height']
|
||||
actual_lh_px = rpx_to_px(actual_lh)
|
||||
expected_lh_px = float(h5_line_height.replace('px', ''))
|
||||
diff = abs(actual_lh_px - expected_lh_px) / expected_lh_px * 100
|
||||
|
||||
if diff > 2:
|
||||
level = IssueLevel.CRITICAL if diff > 20 else (
|
||||
IssueLevel.WARNING if diff > 10 else IssueLevel.INFO)
|
||||
issues.append(StyleIssue(
|
||||
level=level,
|
||||
category='text',
|
||||
tailwind_class=tw_class,
|
||||
property_name='line-height',
|
||||
h5_value=h5_line_height,
|
||||
expected_rpx=f'{expected_lh_rpx}rpx',
|
||||
actual_rpx=actual_lh,
|
||||
difference=diff,
|
||||
location=f'.{class_name}'
|
||||
))
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
def generate_report(issues: List[StyleIssue], output_file: Path):
|
||||
"""生成 Markdown 报告"""
|
||||
critical = [i for i in issues if i.level == IssueLevel.CRITICAL]
|
||||
warning = [i for i in issues if i.level == IssueLevel.WARNING]
|
||||
info = [i for i in issues if i.level == IssueLevel.INFO]
|
||||
|
||||
report = f"""# H5 到小程序样式对比报告
|
||||
|
||||
> 生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
||||
|
||||
## 📊 问题统计
|
||||
|
||||
- ❌ 严重问题:{len(critical)} 个
|
||||
- ⚠️ 警告:{len(warning)} 个
|
||||
- ℹ️ 提示:{len(info)} 个
|
||||
- **总计**:{len(issues)} 个
|
||||
|
||||
---
|
||||
|
||||
## ❌ 严重问题({len(critical)} 个)
|
||||
|
||||
"""
|
||||
|
||||
for issue in critical:
|
||||
report += f"""### {issue.level.value} {issue.tailwind_class} - {issue.property_name}
|
||||
|
||||
- **位置**:{issue.location}
|
||||
- **H5 值**:{issue.h5_value}
|
||||
- **应该是**:{issue.expected_rpx}
|
||||
- **实际是**:{issue.actual_rpx}
|
||||
- **差异**:{issue.difference:.1f}%
|
||||
|
||||
"""
|
||||
|
||||
if warning:
|
||||
report += f"\n## ⚠️ 警告({len(warning)} 个)\n\n"
|
||||
for issue in warning:
|
||||
report += f"- {issue.tailwind_class} - {issue.property_name}: 差异 {issue.difference:.1f}% ({issue.location})\n"
|
||||
|
||||
if info:
|
||||
report += f"\n## ℹ️ 提示({len(info)} 个)\n\n"
|
||||
for issue in info:
|
||||
report += f"- {issue.tailwind_class} - {issue.property_name}: 差异 {issue.difference:.1f}% ({issue.location})\n"
|
||||
|
||||
output_file.write_text(report, encoding='utf-8')
|
||||
|
||||
|
||||
def main():
|
||||
import sys
|
||||
import io
|
||||
|
||||
# 修复 Windows 控制台编码问题
|
||||
if sys.platform == 'win32':
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
|
||||
|
||||
parser = argparse.ArgumentParser(description='H5 到小程序样式对比检查工具')
|
||||
parser.add_argument('--h5', required=True, help='H5 HTML 文件路径')
|
||||
parser.add_argument('--wxss', required=True, help='小程序 WXSS 文件路径')
|
||||
parser.add_argument('--output', default='report.md', help='输出报告路径')
|
||||
args = parser.parse_args()
|
||||
|
||||
h5_file = Path(args.h5)
|
||||
wxss_file = Path(args.wxss)
|
||||
output_file = Path(args.output)
|
||||
|
||||
if not h5_file.exists():
|
||||
print(f"[ERROR] H5 文件不存在:{h5_file}")
|
||||
return
|
||||
if not wxss_file.exists():
|
||||
print(f"[ERROR] WXSS 文件不存在:{wxss_file}")
|
||||
return
|
||||
|
||||
print("[1/6] 读取文件...")
|
||||
h5_content = h5_file.read_text(encoding='utf-8')
|
||||
wxss_content = wxss_file.read_text(encoding='utf-8')
|
||||
|
||||
print("[2/6] 提取 Tailwind 类...")
|
||||
tailwind_classes = extract_tailwind_classes(h5_content)
|
||||
print(f" - text 类:{len(tailwind_classes['text'])} 个")
|
||||
|
||||
print("[3/6] 提取 WXSS 样式...")
|
||||
wxss_styles = extract_wxss_styles(wxss_content)
|
||||
print(f" - 样式类:{len(wxss_styles)} 个")
|
||||
|
||||
print("[4/6] 检查全局样式...")
|
||||
issues = check_global_styles(wxss_content)
|
||||
|
||||
print("[5/6] 检查文本类...")
|
||||
issues.extend(check_text_classes(tailwind_classes['text'], wxss_styles))
|
||||
|
||||
print(f"\n[统计] 发现 {len(issues)} 个问题")
|
||||
print("[6/6] 生成报告...")
|
||||
generate_report(issues, output_file)
|
||||
print(f"[完成] 报告已生成:{output_file}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user