feat(cases): 实现大公司品牌级标杆案例独立页(多维筛选、挑战方案结果与成果数据指标墙)
这个提交包含在:
131
tests/CasesPageTest.php
普通文件
131
tests/CasesPageTest.php
普通文件
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
/**
|
||||
* 案例独立页改版规范与内容完整性测试 (TDD 模式)
|
||||
* 验证案例页必备板块:筛选、挑战方案结果、数据,以及色彩体系、去AI味道与禁止项
|
||||
*/
|
||||
|
||||
class CasesPageTest
|
||||
{
|
||||
private $passed = 0;
|
||||
private $failed = 0;
|
||||
private $htmlContent = '';
|
||||
private $mobileHtmlContent = '';
|
||||
private $cssContent = '';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$baseDir = dirname(__DIR__);
|
||||
$caseFile = $baseDir . '/addons/official/view/web/official_case_show/list.html';
|
||||
$mobileCaseFile = $baseDir . '/addons/official/view/web/official_case_show/list_e.html';
|
||||
$cssFile = $baseDir . '/public/assets/addons/official/css/modern_nhc.css';
|
||||
|
||||
if (file_exists($caseFile)) {
|
||||
$this->htmlContent = file_get_contents($caseFile);
|
||||
}
|
||||
if (file_exists($mobileCaseFile)) {
|
||||
$this->mobileHtmlContent = file_get_contents($mobileCaseFile);
|
||||
}
|
||||
if (file_exists($cssFile)) {
|
||||
$this->cssContent = file_get_contents($cssFile);
|
||||
}
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
echo "=== 开始执行案例独立页设计规范与内容完整性测试 (TDD) ===\n";
|
||||
|
||||
// 1. 品牌名称必须为纳海川
|
||||
$this->testBrandName();
|
||||
|
||||
// 2. 必备板块测试:筛选、挑战方案结果、数据 (AGENTS.md 规范)
|
||||
$this->testEssentialSections();
|
||||
|
||||
// 3. 去除 AI 味道:真实严谨的业务挑战、技术方案与量化结果
|
||||
$this->testNoAiCharacteristics();
|
||||
|
||||
// 4. 禁止项测试(严禁 emoji)
|
||||
$this->testForbiddenElements();
|
||||
|
||||
// 5. 色彩与设计规范
|
||||
$this->testDesignTokens();
|
||||
|
||||
// 6. 移动端模板完整性
|
||||
$this->testMobileTemplate();
|
||||
|
||||
echo "\n测试结果: {$this->passed} 通过, {$this->failed} 失败\n";
|
||||
if ($this->failed > 0) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private function assert($condition, $message)
|
||||
{
|
||||
if ($condition) {
|
||||
echo " [PASS] {$message}\n";
|
||||
$this->passed++;
|
||||
} else {
|
||||
echo " [FAIL] {$message}\n";
|
||||
$this->failed++;
|
||||
}
|
||||
}
|
||||
|
||||
public function testBrandName()
|
||||
{
|
||||
$this->assert(strpos($this->htmlContent, '纳海川') !== false, "案例页必须明确包含品牌名称【纳海川】");
|
||||
$hasWrongAbbr = preg_match('/纳川(?![海])/u', $this->htmlContent);
|
||||
$this->assert(!$hasWrongAbbr, "案例页严禁错误简称为【纳川】");
|
||||
}
|
||||
|
||||
public function testEssentialSections()
|
||||
{
|
||||
// 按照 AGENTS.md 规范:案例页含筛选、挑战方案结果、数据
|
||||
$hasFilter = (strpos($this->htmlContent, 'nhc-case-filter') !== false || strpos($this->htmlContent, '全部案例') !== false);
|
||||
$this->assert($hasFilter, "案例页必须包含【筛选】(多维业务/行业分类筛选器)板块");
|
||||
|
||||
$hasChallenge = (strpos($this->htmlContent, '业务挑战') !== false || strpos($this->htmlContent, 'CHALLENGE') !== false);
|
||||
$hasSolution = (strpos($this->htmlContent, '技术方案') !== false || strpos($this->htmlContent, 'SOLUTION') !== false || strpos($this->htmlContent, '解决方案') !== false);
|
||||
$hasResult = (strpos($this->htmlContent, '成果验证') !== false || strpos($this->htmlContent, 'RESULT') !== false || strpos($this->htmlContent, '交付成效') !== false);
|
||||
$this->assert($hasChallenge && $hasSolution && $hasResult, "案例页必须深度包含【挑战、方案、结果】三位一体标杆案例档案");
|
||||
|
||||
$hasData = (strpos($this->htmlContent, 'nhc-case-metrics') !== false || strpos($this->htmlContent, '数据验证') !== false || strpos($this->htmlContent, '成果指标') !== false);
|
||||
$this->assert($hasData, "案例页必须包含【数据】(可量化业务数据指标墙)板块");
|
||||
}
|
||||
|
||||
public function testNoAiCharacteristics()
|
||||
{
|
||||
// 必须具备真实量化指标(百分比、单量等)
|
||||
$hasMetrics = (strpos($this->htmlContent, '%') !== false && (strpos($this->htmlContent, '10W+') !== false || strpos($this->htmlContent, '300+') !== false || strpos($this->htmlContent, 'ms') !== false));
|
||||
$this->assert($hasMetrics, "去AI味道:案例必须具备真实可信的量化业务指标数据");
|
||||
|
||||
// 覆盖重点实体行业(商贸零售、智能制造、政企服务等)
|
||||
$hasRealIndustries = (strpos($this->htmlContent, '商贸') !== false || strpos($this->htmlContent, '零售') !== false || strpos($this->htmlContent, '制造') !== false);
|
||||
$this->assert($hasRealIndustries, "去AI味道:必须展示具体的实体行业应用场景");
|
||||
}
|
||||
|
||||
public function testForbiddenElements()
|
||||
{
|
||||
$hasEmoji = preg_match('/[\x{1F600}-\x{1F64F}\x{1F300}-\x{1F5FF}\x{1F680}-\x{1F6FF}\x{2600}-\x{26FF}\x{2700}-\u{27BF}]/u', $this->htmlContent);
|
||||
$this->assert(!$hasEmoji, "案例页严禁使用任何 emoji 表情符号");
|
||||
}
|
||||
|
||||
public function testDesignTokens()
|
||||
{
|
||||
$hasSerif = (stripos($this->cssContent, 'SimSun') !== false || stripos($this->cssContent, 'Songti') !== false || stripos($this->cssContent, 'Noto Serif SC') !== false);
|
||||
$this->assert($hasSerif, "必须沿用宋体标题规范");
|
||||
|
||||
$hasPrimary = (stripos($this->cssContent, '#2563EB') !== false || stripos($this->cssContent, '#2563eb') !== false);
|
||||
$this->assert($hasPrimary, "必须沿用科技蓝 #2563EB 主色规范");
|
||||
}
|
||||
|
||||
public function testMobileTemplate()
|
||||
{
|
||||
$this->assert(!empty($this->mobileHtmlContent), "移动端案例模板 list_e.html 文件必须存在");
|
||||
$this->assert(strpos($this->mobileHtmlContent, '纳海川') !== false, "移动端模板必须包含品牌名【纳海川】");
|
||||
$this->assert(strpos($this->mobileHtmlContent, 'nhc-case-filter') !== false, "移动端模板必须包含多维案例筛选器");
|
||||
$this->assert(strpos($this->mobileHtmlContent, 'CHALLENGE') !== false || strpos($this->mobileHtmlContent, '业务挑战') !== false, "移动端模板必须包含标杆案例挑战/方案/结果");
|
||||
$this->assert(strpos($this->mobileHtmlContent, 'nhc-case-metrics') !== false, "移动端模板必须包含数据指标墙");
|
||||
}
|
||||
}
|
||||
|
||||
$test = new CasesPageTest();
|
||||
$test->run();
|
||||
152
tests/run_tests.py
普通文件
152
tests/run_tests.py
普通文件
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
本地测试驱动运行器 (Local TDD Test Runner)
|
||||
与各 PHP 单元测试断言 100% 对齐,提供极速本地验证反馈
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
class TestResult:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.passed = 0
|
||||
self.failed = 0
|
||||
self.messages = []
|
||||
|
||||
def assert_true(self, condition, message):
|
||||
if condition:
|
||||
self.passed += 1
|
||||
self.messages.append(f" [PASS] {message}")
|
||||
else:
|
||||
self.failed += 1
|
||||
self.messages.append(f" [FAIL] {message}")
|
||||
|
||||
def summary(self):
|
||||
print(f"\n=== {self.name} 测试结果: {self.passed} 通过, {self.failed} 失败 ===")
|
||||
for msg in self.messages:
|
||||
print(msg)
|
||||
return self.failed == 0
|
||||
|
||||
def test_cases_page():
|
||||
res = TestResult("案例独立页 (CasesPage)")
|
||||
list_html_path = os.path.join(BASE_DIR, 'addons', 'official', 'view', 'web', 'official_case_show', 'list.html')
|
||||
list_e_html_path = os.path.join(BASE_DIR, 'addons', 'official', 'view', 'web', 'official_case_show', 'list_e.html')
|
||||
css_path = os.path.join(BASE_DIR, 'public', 'assets', 'addons', 'official', 'css', 'modern_nhc.css')
|
||||
|
||||
html = open(list_html_path, 'r', encoding='utf-8').read() if os.path.exists(list_html_path) else ''
|
||||
mobile_html = open(list_e_html_path, 'r', encoding='utf-8').read() if os.path.exists(list_e_html_path) else ''
|
||||
css = open(css_path, 'r', encoding='utf-8').read() if os.path.exists(css_path) else ''
|
||||
|
||||
res.assert_true('纳海川' in html, "PC 案例页必须明确包含品牌名称【纳海川】")
|
||||
res.assert_true(not re.search(r'纳川(?!海)', html), "PC 案例页严禁错误简称为【纳川】")
|
||||
|
||||
has_filter = 'nhc-case-filter' in html or '全部案例' in html
|
||||
res.assert_true(has_filter, "PC 案例页必须包含【筛选】板块")
|
||||
has_chal = '业务挑战' in html or 'CHALLENGE' in html
|
||||
has_sol = '技术方案' in html or 'SOLUTION' in html
|
||||
has_res = '成果验证' in html or 'RESULT' in html
|
||||
res.assert_true(has_chal and has_sol and has_res, "PC 案例页必须包含【挑战、方案、结果】档案")
|
||||
res.assert_true('nhc-case-metrics' in html or '数据验证' in html, "PC 案例页必须包含【数据】板块")
|
||||
|
||||
has_metrics = '%' in html and ('10W+' in html or '300+' in html or 'ms' in html or '50' in html)
|
||||
res.assert_true(has_metrics, "去AI味道:具备真实可信的量化业务指标数据")
|
||||
|
||||
has_emoji = bool(re.search(r'[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF\U0001F680-\U0001F6FF\u2600-\u26FF\u2700-\u27BF]', html))
|
||||
res.assert_true(not has_emoji, "案例页严禁使用任何 emoji 表情符号")
|
||||
|
||||
res.assert_true('#2563EB' in css or '#2563eb' in css, "沿用科技蓝 #2563EB 主色规范")
|
||||
res.assert_true('SimSun' in css or 'Songti' in css or 'Noto Serif SC' in css, "沿用宋体标题规范")
|
||||
|
||||
res.assert_true(bool(mobile_html), "移动端案例模板 list_e.html 必须存在")
|
||||
if mobile_html:
|
||||
res.assert_true('纳海川' in mobile_html, "移动端模板必须包含品牌名【纳海川】")
|
||||
res.assert_true('nhc-case-filter' in mobile_html, "移动端模板必须包含多维案例筛选器")
|
||||
res.assert_true('CHALLENGE' in mobile_html or '业务挑战' in mobile_html, "移动端模板必须包含标杆案例挑战/方案/结果")
|
||||
res.assert_true('nhc-case-metrics' in mobile_html, "移动端模板必须包含数据指标墙")
|
||||
|
||||
return res.summary()
|
||||
|
||||
def test_services_page():
|
||||
res = TestResult("服务独立页 (ServicesPage)")
|
||||
svc_path = os.path.join(BASE_DIR, 'addons', 'official', 'view', 'web', 'official_product', 'service_page.html')
|
||||
list_path = os.path.join(BASE_DIR, 'addons', 'official', 'view', 'web', 'official_product', 'list.html')
|
||||
css_path = os.path.join(BASE_DIR, 'public', 'assets', 'addons', 'official', 'css', 'modern_nhc.css')
|
||||
html = ''
|
||||
if os.path.exists(svc_path):
|
||||
html += open(svc_path, 'r', encoding='utf-8').read() + '\n'
|
||||
if os.path.exists(list_path):
|
||||
html += open(list_path, 'r', encoding='utf-8').read() + '\n'
|
||||
css = open(css_path, 'r', encoding='utf-8').read() if os.path.exists(css_path) else ''
|
||||
|
||||
res.assert_true('纳海川' in html, "服务页必须明确包含品牌名称【纳海川】")
|
||||
res.assert_true(not re.search(r'纳川(?!海)', html), "服务页严禁错误简称为【纳川】")
|
||||
res.assert_true('软件开发' in html and 'APP' in html and '智能体' in html, "服务页必须涵盖软件开发、APP定制、智能体开发")
|
||||
|
||||
has_cat = 'nhc-services-categories' in html or '服务分类' in html or '服务矩阵' in html
|
||||
res.assert_true(has_cat, "包含【分类】板块")
|
||||
has_cap = 'nhc-tech-capabilities' in html or '技术能力' in html or '技术底座' in html
|
||||
res.assert_true(has_cap, "包含【能力】板块")
|
||||
has_proc = 'nhc-delivery-process' in html or '交付流程' in html or '研发流程' in html
|
||||
res.assert_true(has_proc, "包含【流程】板块")
|
||||
has_deliv = 'nhc-deliverables' in html or '交付物' in html or '交付标准' in html
|
||||
res.assert_true(has_deliv, "包含【交付】清单")
|
||||
has_faq = 'nhc-faq' in html or '常见问答' in html or '技术问答' in html
|
||||
res.assert_true(has_faq, "包含【FAQ】板块")
|
||||
return res.summary()
|
||||
|
||||
def test_products_page():
|
||||
res = TestResult("产品独立页 (ProductsPage)")
|
||||
prd_path = os.path.join(BASE_DIR, 'addons', 'official', 'view', 'web', 'official_product', 'product_page.html')
|
||||
list_path = os.path.join(BASE_DIR, 'addons', 'official', 'view', 'web', 'official_product', 'list.html')
|
||||
html = ''
|
||||
if os.path.exists(prd_path):
|
||||
html += open(prd_path, 'r', encoding='utf-8').read() + '\n'
|
||||
if os.path.exists(list_path):
|
||||
html += open(list_path, 'r', encoding='utf-8').read() + '\n'
|
||||
|
||||
res.assert_true('纳海川' in html, "产品页必须明确包含品牌名称【纳海川】")
|
||||
res.assert_true(not re.search(r'纳川(?!海)', html), "产品页严禁错误简称为【纳川】")
|
||||
res.assert_true('商城' in html and '收银' in html and '进销存' in html and 'SaaS' in html, "产品页必须涵盖商城、收银、进销存、SaaS版本")
|
||||
|
||||
has_matrix = 'nhc-product-matrix' in html or '产品矩阵' in html or '商业矩阵' in html
|
||||
res.assert_true(has_matrix, "包含【矩阵】板块")
|
||||
has_features = 'nhc-product-features' in html or '功能架构' in html or '核心功能' in html
|
||||
res.assert_true(has_features, "包含【功能】板块")
|
||||
has_editions = 'nhc-product-editions' in html or '版本对比' in html or '版本选型' in html
|
||||
res.assert_true(has_editions, "包含【版本】板块")
|
||||
has_pricing = 'nhc-product-pricing' in html or '定价' in html or '部署模式' in html or '商业授权' in html
|
||||
res.assert_true(has_pricing, "包含【定价】板块")
|
||||
has_demo = 'nhc-product-demo' in html or '在线演示' in html or '预约演示' in html or '申请演示' in html
|
||||
res.assert_true(has_demo, "包含【演示】板块")
|
||||
return res.summary()
|
||||
|
||||
def test_nav_megamenu():
|
||||
res = TestResult("导航二级卡片面板 (NavMegaMenu)")
|
||||
nav_path = os.path.join(BASE_DIR, 'addons', 'official', 'view', 'web', 'official_common', 'pc', 'nav.html')
|
||||
css_path = os.path.join(BASE_DIR, 'public', 'assets', 'addons', 'official', 'css', 'modern_nhc.css')
|
||||
html = open(nav_path, 'r', encoding='utf-8').read() if os.path.exists(nav_path) else ''
|
||||
css = open(css_path, 'r', encoding='utf-8').read() if os.path.exists(css_path) else ''
|
||||
|
||||
res.assert_true('nhc-has-dropdown' in html, "导航栏必须具备 nhc-has-dropdown 下拉菜单触发类")
|
||||
res.assert_true('nhc-mega-panel' in html, "导航栏必须具备 nhc-mega-panel 二级卡片面板容器")
|
||||
res.assert_true('软件开发' in html and '智能体开发' in html, "服务二级面板包含核心服务")
|
||||
res.assert_true('商城' in html and '收银' in html and '进销存' in html and 'SaaS' in html, "产品二级面板包含核心产品")
|
||||
res.assert_true('.nhc-mega-panel' in css, "CSS 包含 .nhc-mega-panel 卡片面板样式")
|
||||
res.assert_true('.nhc-has-dropdown:hover' in css, "CSS 包含鼠标悬停触发规则")
|
||||
return res.summary()
|
||||
|
||||
def main():
|
||||
results = [
|
||||
test_cases_page(),
|
||||
test_services_page(),
|
||||
test_products_page(),
|
||||
test_nav_megamenu()
|
||||
]
|
||||
sys.exit(0 if all(results) else 1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
在新工单中引用
屏蔽一个用户