官网改版上线:关于我们与联系我们页重构、首页AI主视觉v4、品牌标识与导航交互;同步测试与设计文档

这个提交包含在:
wikiboss
2026-11-05 16:04:20 +08:00
父节点 96a3f8b672
当前提交 e924c8d872
共有 39 个文件被更改,包括 2742 次插入 和 2591 次删除

查看文件

@@ -0,0 +1,62 @@
// Run on the local contact preview with agent-browser eval. All requests are mocked.
(async function () {
if (!location.hostname.match(/^(127\.0\.0\.1|localhost)$/)) throw new Error('Use local preview only');
var form = document.querySelector('.ct-form');
var submit = form.querySelector('.ct-submit');
var status = form.querySelector('.ct-submit-status');
var originalFetch = window.fetch;
var calls = [];
var mode = 'reject';
var results = [];
function assert(condition, name) { if (!condition) throw new Error(name); results.push(name); }
window.fetch = async function (url, options) {
calls.push({url: String(url), method: options.method || 'GET', body: options.body || ''});
if (!options.method) {
if (mode === 'token-fail') throw new Error('network');
return {ok: true, json: async function () { return {code: 1, data: 'test-single-use-token'}; }};
}
if (mode === 'network-fail') throw new Error('network');
await new Promise(function (resolve) { setTimeout(resolve, 10); });
return {ok: true, json: async function () { return mode === 'success' ? {code: 1} : {code: 0, msg: '验证码不正确'}; }};
};
function fill() {
form.elements.title.value = '本地界面测试';
form.elements.name.value = '测试';
form.elements.phone.value = '0851-12345678';
form.elements.content.value = '本地模拟数据,不会向线上发送。';
if (form.elements.captcha) form.elements.captcha.value = 'test';
}
function send() { form.dispatchEvent(new Event('submit', {bubbles: true, cancelable: true})); }
async function settled() {
for (var i = 0; i < 100; i++) {
if (form.getAttribute('aria-busy') !== 'true') return;
await new Promise(function (resolve) { setTimeout(resolve, 5); });
}
throw new Error('Form did not settle');
}
try {
form.reset(); send();
assert(calls.length === 0 && document.activeElement === form.elements.title, 'Empty form: focus first error, no request');
form.querySelector('[data-topic]').click();
assert(form.elements.title.value === form.querySelector('[data-topic]').dataset.topic, 'Topic button fills title');
fill(); form.elements.phone.value = '123'; send();
assert(calls.length === 0 && form.elements.phone.getAttribute('aria-invalid') === 'true', 'Invalid phone blocked');
fill(); send(); send();
assert(submit.disabled, 'Submitting state disables button');
await settled();
assert(calls.filter(function (c) { return c.method === 'POST'; }).length === 1, 'Duplicate submit sends one POST');
var body = new URLSearchParams(calls.find(function (c) { return c.method === 'POST'; }).body);
assert(body.get('__token__') === 'test-single-use-token' && body.get('phone') === '0851-12345678', 'Uses fresh token and expected backend fields');
assert(status.dataset.state === 'error' && form.elements.content.value.length > 0 && !submit.disabled, 'Server rejection preserves fields and permits retry');
if (form.elements.captcha) assert(form.elements.captcha.value === '', 'Rejected submission refreshes captcha');
calls = []; fill(); mode = 'success'; send(); await settled();
assert(status.dataset.state === 'success' && form.elements.name.value === '' && form.elements.content.value === '', 'Confirmed success clears fields and announces result');
assert(calls.length === 2 && calls[0].method === 'GET', 'Retry obtains a new token');
calls = []; fill(); mode = 'network-fail'; send(); await settled();
assert(status.dataset.state === 'error' && status.textContent.includes('无法确认提交结果') && form.elements.content.value.length > 0, 'Unknown POST outcome preserves data without automatic retry');
assert(calls.length === 2 && !submit.disabled, 'Network failure releases button');
calls = []; fill(); mode = 'token-fail'; send(); await settled();
assert(calls.length === 1 && status.dataset.state === 'error', 'Token fetch failure never sends POST');
return {passed: results.length, checks: results, network: 'Mocked locally; no real messages sent'};
} finally { window.fetch = originalFetch; }
})();

查看文件

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
中华人民共和国广告法文案合规自动化专项测试
(Ad Law Compliance Test Suite)
@@ -34,7 +33,7 @@ class TestAdLawCompliance(unittest.TestCase):
self.contents = {}
for key, path in self.target_files.items():
if os.path.exists(path):
with open(path, 'r', encoding='utf-8') as f:
with open(path, encoding='utf-8') as f:
self.contents[key] = f.read()
else:
self.contents[key] = ""
@@ -56,9 +55,10 @@ class TestAdLawCompliance(unittest.TestCase):
# 严禁出现“国家级认定”、“国家级自主研发”等违法表述
self.assertNotIn('国家级自主研发', self.all_text, "不得使用'国家级自主研发'违规修饰")
self.assertNotIn('国家级科技创新认定', self.all_text, "不得使用'国家级科技创新认定'违规表述")
# 合规的表达应当为官方备案机构或高新技术企业全称
self.assertIn('全国高新技术企业认定管理机构备案认定', self.contents['single_pc'],
"资质表述须符合法定规范,应使用全国高新技术企业认定管理机构备案认定")
# 合规的表达应当使用法定全称,并以有效资料与证书为准
for page_name, content in (('PC 关于我们页', self.contents['single_pc']), ('移动端关于我们页', self.contents['single_m'])):
self.assertIn('计算机软件著作权', content, f"{page_name}资质表述应使用法定全称")
self.assertIn('以有效证书为准', content, f"{page_name}资质表述应以有效证书为准")
def test_no_absolute_guarantee_and_exaggeration(self):
"""广告法第四条/第二十八条:严禁绝对化与夸大免责承诺"""
@@ -73,7 +73,7 @@ class TestAdLawCompliance(unittest.TestCase):
clean_text = re.sub(r'[\'\"a-zA-Z0-9_\-:]*\s*:\s*100%', '', self.all_text)
clean_text = re.sub(r'width\s*=\s*[\'\"]100%[\'\"]', '', clean_text)
clean_text = re.sub(r'height\s*=\s*[\'\"]100%[\'\"]', '', clean_text)
self.assertNotIn('准确率: <strong>100%</strong>', clean_text, "分账准确率不得夸大承诺100%")
self.assertNotIn('自动对账率: <strong>100%</strong>', clean_text, "对账率不得夸大承诺100%")
self.assertNotIn('100% 完整源码', clean_text, "应表述为完整工程源码交付,避免100%绝对词")
@@ -133,7 +133,7 @@ class TestAdLawCompliance(unittest.TestCase):
"""排查 public/assets/addons/official/images/showcases/ 目录下所有原型 SVG 是否包含虚假宣传数据"""
svg_dir = os.path.join(ROOT_DIR, 'public/assets/addons/official/images/showcases')
self.assertTrue(os.path.exists(svg_dir), f"SVG 展示目录不存在: {svg_dir}")
# 针对每个 SVG 定义违规关键词
svg_forbidden_rules = {
'case_distrib.svg': [
@@ -168,7 +168,7 @@ class TestAdLawCompliance(unittest.TestCase):
for filename, forbidden_list in svg_forbidden_rules.items():
svg_path = os.path.join(svg_dir, filename)
if os.path.exists(svg_path):
with open(svg_path, 'r', encoding='utf-8') as f:
with open(svg_path, encoding='utf-8') as f:
content = f.read()
for forbidden in forbidden_list:
self.assertNotIn(forbidden, content, f"SVG 文件 {filename} 中严禁包含虚假宣传或夸大编造数据: '{forbidden}'")

查看文件

@@ -41,18 +41,13 @@ class TestClientRigorEnhancements(unittest.TestCase):
"案例独立页必须包含明确的验收与生产运行记录")
def test_authoritative_certifications_and_copyright_numbers(self):
"""关于我们资质板块必须展示规整的资质体系与等保合规"""
# ISO 与 等保
self.assertIn('ISO9001', self.about_html, "PC 关于我们必须包含 ISO9001 质量认证")
self.assertIn('ISO27001', self.about_html, "PC 关于我们必须包含 ISO27001 安全认证")
self.assertTrue('等保' in self.about_html, "PC 关于我们必须包含等保合规设计")
# 软著与知识产权
self.assertTrue('计算机软件著作权' in self.about_html and '自主可控' in self.about_html,
"PC 关于我们必须展示自主可控的计算机软件著作权与知识产权")
# 移动端同步
self.assertIn('ISO', self.about_mobile, "移动端关于我们必须同步展示权威资质")
self.assertTrue('等保' in self.about_mobile, "移动端关于我们必须同步展示等保合规")
"""资质和软件权属应说明核验依据,不以示意徽章冒充认证。"""
for content in (self.about_html, self.about_mobile):
for label in ('企业主体', '计算机软件著作权', '第三方组件', '合同', 'ISO9001', 'ISO27001', '等保'):
self.assertIn(label, content)
self.assertIn('具体认证情况以有效证书为准', content)
self.assertNotIn('cert_hi_tech.svg', content)
self.assertNotIn('cert_iso9001.svg', content)
def test_demo_sandbox_rigorous_commitment(self):
"""产品页演示沙箱必须具备极速建联与真实脱敏环境承诺"""

查看文件

@@ -5,6 +5,7 @@
2. 验证新版服务页摄影配图真实存在、尺寸满足首屏展示要求,并提供 WebP 兼容版本;
3. 验证联系我们页矢量配图符合浅色专业风规范。
"""
import os
import unittest
@@ -12,54 +13,86 @@ from PIL import Image
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
RASTER_ROOT = os.path.join(ROOT_DIR, 'public', 'assets', 'addons', 'official', 'images')
SHOWCASE_ROOT = os.path.join(RASTER_ROOT, 'showcases')
RASTER_ROOT = os.path.join(ROOT_DIR, "public", "assets", "addons", "official", "images")
SHOWCASE_ROOT = os.path.join(RASTER_ROOT, "showcases")
SERVICE_HEROES = {
'software': ('software/structure-v2.jpg', 1440, 960),
'app': ('app/terminals.jpg', 1440, 960),
'agent': ('agent/landscape-v2.jpg', 1920, 640),
"software": ("software/structure-v2.jpg", 1440, 960),
"app": ("app/terminals.jpg", 1440, 960),
"agent": ("agent/landscape-v2.jpg", 1920, 640),
"contact": ("contact/connection.jpg", 1440, 960),
}
class TestDedicatedPageIllustrations(unittest.TestCase):
def test_illustration_assets_exist(self):
"""测试专属配图文件存在、格式有效且尺寸满足展示要求"""
for key, (relative_path, minimum_width, minimum_height) in SERVICE_HEROES.items():
for key, (
relative_path,
minimum_width,
minimum_height,
) in SERVICE_HEROES.items():
with self.subTest(page=key):
jpg_path = os.path.join(RASTER_ROOT, relative_path)
webp_path = os.path.splitext(jpg_path)[0] + '.webp'
self.assertTrue(os.path.isfile(jpg_path), f"缺少专属精美配图: {relative_path}")
self.assertTrue(os.path.isfile(webp_path), f"缺少 WebP 兼容版本: {relative_path}")
webp_path = os.path.splitext(jpg_path)[0] + ".webp"
self.assertTrue(
os.path.isfile(jpg_path), f"缺少专属精美配图: {relative_path}"
)
self.assertTrue(
os.path.isfile(webp_path), f"缺少 WebP 兼容版本: {relative_path}"
)
with Image.open(jpg_path) as hero:
width, height = hero.size
self.assertGreaterEqual(width, minimum_width, f"{relative_path} 宽度不足首屏要求")
self.assertGreaterEqual(height, minimum_height, f"{relative_path} 高度不足首屏要求")
self.assertGreater(os.path.getsize(jpg_path), 50_000, f"{relative_path} 文件过小")
self.assertGreaterEqual(
width, minimum_width, f"{relative_path} 宽度不足首屏要求"
)
self.assertGreaterEqual(
height, minimum_height, f"{relative_path} 高度不足首屏要求"
)
self.assertGreater(
os.path.getsize(jpg_path), 50_000, f"{relative_path} 文件过小"
)
contact_path = os.path.join(SHOWCASE_ROOT, 'contact_headquarters.svg')
with open(contact_path, encoding='utf-8') as stream:
contact_path = os.path.join(SHOWCASE_ROOT, "contact_headquarters.svg")
with open(contact_path, encoding="utf-8") as stream:
contact_content = stream.read()
self.assertIn('<svg', contact_content, 'contact_headquarters.svg 必须是有效矢量 SVG')
self.assertIn(
"<svg", contact_content, "contact_headquarters.svg 必须是有效矢量 SVG"
)
def test_pages_embed_dedicated_illustrations(self):
"""测试各独立页面均正确嵌入对应的精美配图"""
pages = {
'software': ('official_product/service_software.html', 'images/software/structure-v2.jpg'),
'app': ('official_product/service_app.html', 'images/app/terminals.jpg'),
'agent': ('official_product/service_agent.html', 'images/agent/landscape-v2.jpg'),
'contact_pc': ('official_online_message/list.html', 'contact_headquarters.svg'),
'contact_m': ('official_online_message/list_e.html', 'contact_headquarters.svg'),
"software": (
"official_product/service_software.html",
"images/software/structure-v2.jpg",
),
"app": ("official_product/service_app.html", "images/app/terminals.jpg"),
"agent": (
"official_product/service_agent.html",
"images/agent/landscape-v2.jpg",
),
"contact_pc": (
"official_online_message/list.html",
"images/contact/connection.jpg",
),
"contact_m": (
"official_online_message/list_e.html",
"images/contact/connection.jpg",
),
}
template_dir = os.path.join(ROOT_DIR, 'addons', 'official', 'view', 'web')
template_dir = os.path.join(ROOT_DIR, "addons", "official", "view", "web")
for key, (template_relative_path, image_name) in pages.items():
with self.subTest(page=key):
template_path = os.path.join(template_dir, template_relative_path)
with open(template_path, encoding='utf-8') as stream:
with open(template_path, encoding="utf-8") as stream:
html = stream.read()
self.assertIn(image_name, html, f"页面 {template_relative_path} 必须正确引用专属配图 {image_name}")
self.assertIn(
image_name,
html,
f"页面 {template_relative_path} 必须正确引用专属配图 {image_name}",
)
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()

查看文件

@@ -3,6 +3,7 @@
新闻资讯与技术文章改版及丰富度测试 (TDD 模式)
验证文章独立页、移动端模板、首页联动、数据库种子文件及去AI味道规范
"""
import os
import re
import unittest
@@ -16,26 +17,86 @@ def _read_text(path):
class TestJournalismAndArticles(unittest.TestCase):
def setUp(self):
self.pc_list_path = os.path.join(BASE_DIR, 'addons', 'official', 'view', 'web', 'official_journalism', 'list.html')
self.pc_details_path = os.path.join(BASE_DIR, 'addons', 'official', 'view', 'web', 'official_journalism', 'details.html')
self.mobile_list_path = os.path.join(BASE_DIR, 'addons', 'official', 'view', 'web', 'official_journalism', 'list_e.html')
self.mobile_details_path = os.path.join(BASE_DIR, 'addons', 'official', 'view', 'web', 'official_journalism', 'details_e.html')
self.home_index_path = os.path.join(BASE_DIR, 'addons', 'official', 'view', 'web', 'official_home', 'index.html')
self.footer_path = os.path.join(BASE_DIR, 'addons', 'official', 'view', 'web', 'official_common', 'pc', 'footer.html')
self.css_path = os.path.join(BASE_DIR, 'public', 'assets', 'addons', 'official', 'css', 'modern_nhc.css')
self.seed_sql_path = os.path.join(BASE_DIR, 'database', 'seeds', 'journalism_articles.sql')
self.pc_list_path = os.path.join(
BASE_DIR,
"addons",
"official",
"view",
"web",
"official_journalism",
"list.html",
)
self.pc_details_path = os.path.join(
BASE_DIR,
"addons",
"official",
"view",
"web",
"official_journalism",
"details.html",
)
self.mobile_list_path = os.path.join(
BASE_DIR,
"addons",
"official",
"view",
"web",
"official_journalism",
"list_e.html",
)
self.mobile_details_path = os.path.join(
BASE_DIR,
"addons",
"official",
"view",
"web",
"official_journalism",
"details_e.html",
)
self.home_index_path = os.path.join(
BASE_DIR, "addons", "official", "view", "web", "official_home", "index.html"
)
self.footer_path = os.path.join(
BASE_DIR,
"addons",
"official",
"view",
"web",
"official_common",
"pc",
"footer.html",
)
self.css_path = os.path.join(
BASE_DIR, "public", "assets", "addons", "official", "css", "modern_nhc.css"
)
self.seed_sql_path = os.path.join(
BASE_DIR, "database", "seeds", "journalism_articles.sql"
)
def test_pc_list_template_spec(self):
"""验证 PC 端文章资讯列表页规范(标题、简介、缩略图专业排版)"""
self.assertTrue(os.path.exists(self.pc_list_path), "PC 文章列表模板 list.html 必须存在")
self.assertTrue(
os.path.exists(self.pc_list_path), "PC 文章列表模板 list.html 必须存在"
)
content = _read_text(self.pc_list_path)
self.assertIn("纳海川", content, "必须包含品牌名称【纳海川】")
self.assertIsNone(re.search(r'纳川(?!海)', content), "严禁将品牌错误简写为【纳川】")
self.assertFalse(re.search(r'[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF\u2600-\u26FF\u2700-\u27BF]', content), "严禁包含任何 emoji")
self.assertIn("nhc-article-hero", content, "必须包含现代中式 Hero Banner 容器类")
self.assertIn("nhc-news-list", content, "必须包含新闻资讯列表容器 nhc-news-list")
self.assertIsNone(
re.search(r"纳川(?!海)", content), "严禁将品牌错误简写为【纳川】"
)
self.assertFalse(
re.search(
r"[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF\u2600-\u26FF\u2700-\u27BF]",
content,
),
"严禁包含任何 emoji",
)
self.assertIn(
"nhc-article-hero", content, "必须包含现代中式 Hero Banner 容器类"
)
self.assertIn(
"nhc-news-list", content, "必须包含新闻资讯列表容器 nhc-news-list"
)
self.assertIn("nhc-news-row", content, "必须包含图文行卡片 nhc-news-row")
self.assertIn("nhc-news-thumb", content, "必须包含缩略图容器 nhc-news-thumb")
self.assertIn("nhc-news-title", content, "必须包含文章标题 nhc-news-title")
@@ -44,28 +105,62 @@ class TestJournalismAndArticles(unittest.TestCase):
def test_pc_details_template_spec(self):
"""验证 PC 端文章资讯详情页规范"""
self.assertTrue(os.path.exists(self.pc_details_path), "PC 文章详情模板 details.html 必须存在")
self.assertTrue(
os.path.exists(self.pc_details_path),
"PC 文章详情模板 details.html 必须存在",
)
content = _read_text(self.pc_details_path)
self.assertIn("纳海川", content, "必须包含品牌名称【纳海川】")
self.assertIsNone(re.search(r'纳川(?!海)', content), "严禁将品牌错误简写为【纳川】")
self.assertFalse(re.search(r'[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF\u2600-\u26FF\u2700-\u27BF]', content), "严禁包含任何 emoji")
self.assertIsNone(
re.search(r"纳川(?!海)", content), "严禁将品牌错误简写为【纳川】"
)
self.assertFalse(
re.search(
r"[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF\u2600-\u26FF\u2700-\u27BF]",
content,
),
"严禁包含任何 emoji",
)
self.assertIn("nhc-article-body", content, "必须包含规范的文章正文排版容器")
self.assertIn("nhc-breadcrumb", content, "必须包含严谨的面包屑导航")
self.assertIn("info_previous_next_wrap", content, "必须包含上一篇/下一篇自然导流结构")
self.assertIn(
"info_previous_next_wrap", content, "必须包含上一篇/下一篇自然导流结构"
)
def test_mobile_templates_spec_and_bugfix(self):
"""验证移动端模板规范(标题、简介、缩略图专业排版)"""
self.assertTrue(os.path.exists(self.mobile_list_path), "移动端列表模板 list_e.html 必须存在")
self.assertTrue(os.path.exists(self.mobile_details_path), "移动端详情模板 details_e.html 必须存在")
self.assertTrue(
os.path.exists(self.mobile_list_path), "移动端列表模板 list_e.html 必须存在"
)
self.assertTrue(
os.path.exists(self.mobile_details_path),
"移动端详情模板 details_e.html 必须存在",
)
list_content = _read_text(self.mobile_list_path)
details_content = _read_text(self.mobile_details_path)
self.assertIn("nhc-news-thumb", list_content, "移动端列表必须包含缩略图结构")
self.assertIn("nhc-news-title", list_content, "移动端列表必须包含标题结构")
self.assertIn("nhc-news-desc", list_content, "移动端列表必须包含简介结构")
self.assertNotIn("official_video/list", details_content, "详情页严禁遗留旧 official_video/list 错误路径")
self.assertFalse(re.search(r'[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF\u2600-\u26FF\u2700-\u27BF]', list_content), "移动端列表严禁 emoji")
self.assertFalse(re.search(r'[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF\u2600-\u26FF\u2700-\u27BF]', details_content), "移动端详情严禁 emoji")
self.assertNotIn(
"official_video/list",
details_content,
"详情页严禁遗留旧 official_video/list 错误路径",
)
self.assertFalse(
re.search(
r"[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF\u2600-\u26FF\u2700-\u27BF]",
list_content,
),
"移动端列表严禁 emoji",
)
self.assertFalse(
re.search(
r"[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF\u2600-\u26FF\u2700-\u27BF]",
details_content,
),
"移动端详情严禁 emoji",
)
def test_css_design_tokens_for_articles(self):
"""验证 CSS 中已包含文章与资讯专属图文排版样式定义"""
@@ -78,26 +173,47 @@ class TestJournalismAndArticles(unittest.TestCase):
def test_seed_sql_rich_articles(self):
"""验证数据库种子包含不少于 38 篇扎实深度专业文章"""
self.assertTrue(os.path.exists(self.seed_sql_path), "数据库种子文件 journalism_articles.sql 必须存在")
self.assertTrue(
os.path.exists(self.seed_sql_path),
"数据库种子文件 journalism_articles.sql 必须存在",
)
sql = _read_text(self.seed_sql_path)
# 验证至少 38 篇优质文章插入 (初始 8 篇 + 新增 30 篇)
insert_count = (sql.count("INSERT INTO `fa_official_list_journalism`") +
sql.count("INSERT INTO fa_official_list_journalism") +
sql.count("REPLACE INTO `fa_official_list_journalism`") +
sql.count("REPLACE INTO fa_official_list_journalism"))
self.assertGreaterEqual(insert_count, 38, f"必须有至少 38 篇高质量文章 SQL 写入语句 (当前: {insert_count})")
insert_count = (
sql.count("INSERT INTO `fa_official_list_journalism`")
+ sql.count("INSERT INTO fa_official_list_journalism")
+ sql.count("REPLACE INTO `fa_official_list_journalism`")
+ sql.count("REPLACE INTO fa_official_list_journalism")
)
self.assertGreaterEqual(
insert_count,
38,
f"必须有至少 38 篇高质量文章 SQL 写入语句 (当前: {insert_count})",
)
# 验证核心业务主题覆盖
self.assertIn("微服务", sql, "必须覆盖微服务架构深度文章")
self.assertIn("智能体", sql, "必须覆盖 AI 智能体/大模型与知识库落地文章")
self.assertTrue("代码" in sql or "软件质量" in sql, "必须覆盖代码规范与交付文章")
self.assertTrue(
"代码" in sql or "软件质量" in sql, "必须覆盖代码规范与交付文章"
)
self.assertIn("缓存", sql, "必须覆盖分布式缓存相关深度文章")
self.assertIn("分库分表", sql, "必须覆盖数据库分库分表相关文章")
self.assertIn("先进先出", sql, "必须覆盖批次库存先进先出算法相关文章")
# 严禁假大空与 AI 味营销词汇
for bad_word in ["赋能千行百业", "颠覆式创新", "颠覆传统", "未来已来", "数字化新纪元", "技术委员会", "研发委员会", "原创技术资产", "工程受控说明"]:
for bad_word in [
"赋能千行百业",
"颠覆式创新",
"颠覆传统",
"未来已来",
"数字化新纪元",
"技术委员会",
"研发委员会",
"原创技术资产",
"工程受控说明",
]:
self.assertNotIn(bad_word, sql, f"文章种子严禁包含 AI 味空话:{bad_word}")
def test_home_and_footer_linkage(self):
@@ -118,8 +234,13 @@ class TestJournalismAndArticles(unittest.TestCase):
self.assertTrue(has_news_section, "首页必须包含技术洞察与研发动态板块")
# 页脚必须有技术资讯入口
has_insights_link = "official_journalism" in footer or "技术洞察" in footer or "新闻资讯" in footer
has_insights_link = (
"official_journalism" in footer
or "技术洞察" in footer
or "新闻资讯" in footer
)
self.assertTrue(has_insights_link, "页脚必须包含技术洞察/新闻资讯链接")
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()

查看文件

@@ -143,9 +143,14 @@ class TestNaturalCopywriting(unittest.TestCase):
)
# 关于我们与联系我们
self.assertIn(
"找懂业务、讲规范的研发团队,让软件项目平稳落地",
self.about_html,
self.assertTrue(
any(
text in self.about_html
for text in (
"找懂业务、讲规范的研发团队,让软件项目平稳落地",
"沟通您的软件建设需求",
)
),
"关于我们CTA必须务实接地气",
)
self.assertIn(
@@ -286,7 +291,9 @@ class TestNaturalCopywriting(unittest.TestCase):
self.assertIn("动态安全库存模型与自动补货采购建议", self.products_html)
self.assertIn("企业知识问答", service_agent_html)
self.assertIn("业务流程辅助", service_agent_html)
self.assertIn("检索增强可以降低错误风险,但不能保证所有回答都正确", service_agent_html)
self.assertIn(
"检索增强可以降低错误风险,但不能保证所有回答都正确", service_agent_html
)
self.assertNotIn("不会自己编造", service_agent_html)
self.assertNotIn("一张或两张显卡即可", service_agent_html)
self.assertIn("根据使用方式,选择合适的终端", service_app_html)

查看文件

@@ -5,101 +5,143 @@
2. 确保面向客户与访客,使用大白话、实在话(如防重复扣款、源码交付、内网部署数据不泄露、标注文档原文);
3. 确保各个独立页面文案短小精悍、不啰嗦、讲人话。
"""
import os
import unittest
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
class TestPlainAndConciseCopywriting(unittest.TestCase):
class TestPlainAndConciseCopywriting(unittest.TestCase):
def setUp(self):
self.files = {
'index': os.path.join(ROOT_DIR, 'addons/official/view/web/official_home/index.html'),
'index_e': os.path.join(ROOT_DIR, 'addons/official/view/web/official_home/index_e.html'),
'service_page': os.path.join(ROOT_DIR, 'addons/official/view/web/official_product/service_page.html'),
'service_software': os.path.join(ROOT_DIR, 'addons/official/view/web/official_product/service_software.html'),
'service_app': os.path.join(ROOT_DIR, 'addons/official/view/web/official_product/service_app.html'),
'service_agent': os.path.join(ROOT_DIR, 'addons/official/view/web/official_product/service_agent.html'),
'product_page': os.path.join(ROOT_DIR, 'addons/official/view/web/official_product/product_page.html'),
'cases_page': os.path.join(ROOT_DIR, 'addons/official/view/web/official_case_show/list.html'),
'cases_e': os.path.join(ROOT_DIR, 'addons/official/view/web/official_case_show/list_e.html'),
'about_page': os.path.join(ROOT_DIR, 'addons/official/view/web/official_home/single.html'),
'about_e': os.path.join(ROOT_DIR, 'addons/official/view/web/official_home/single_e.html'),
'contact_page': os.path.join(ROOT_DIR, 'addons/official/view/web/official_online_message/list.html'),
'contact_e': os.path.join(ROOT_DIR, 'addons/official/view/web/official_online_message/list_e.html'),
'nav_pc': os.path.join(ROOT_DIR, 'addons/official/view/web/official_common/pc/nav.html'),
'footer_pc': os.path.join(ROOT_DIR, 'addons/official/view/web/official_common/pc/footer.html'),
"index": os.path.join(
ROOT_DIR, "addons/official/view/web/official_home/index.html"
),
"index_e": os.path.join(
ROOT_DIR, "addons/official/view/web/official_home/index_e.html"
),
"service_page": os.path.join(
ROOT_DIR, "addons/official/view/web/official_product/service_page.html"
),
"service_software": os.path.join(
ROOT_DIR,
"addons/official/view/web/official_product/service_software.html",
),
"service_app": os.path.join(
ROOT_DIR, "addons/official/view/web/official_product/service_app.html"
),
"service_agent": os.path.join(
ROOT_DIR, "addons/official/view/web/official_product/service_agent.html"
),
"product_page": os.path.join(
ROOT_DIR, "addons/official/view/web/official_product/product_page.html"
),
"cases_page": os.path.join(
ROOT_DIR, "addons/official/view/web/official_case_show/list.html"
),
"cases_e": os.path.join(
ROOT_DIR, "addons/official/view/web/official_case_show/list_e.html"
),
"about_page": os.path.join(
ROOT_DIR, "addons/official/view/web/official_home/single.html"
),
"about_e": os.path.join(
ROOT_DIR, "addons/official/view/web/official_home/single_e.html"
),
"contact_page": os.path.join(
ROOT_DIR, "addons/official/view/web/official_online_message/list.html"
),
"contact_e": os.path.join(
ROOT_DIR, "addons/official/view/web/official_online_message/list_e.html"
),
"nav_pc": os.path.join(
ROOT_DIR, "addons/official/view/web/official_common/pc/nav.html"
),
"footer_pc": os.path.join(
ROOT_DIR, "addons/official/view/web/official_common/pc/footer.html"
),
}
self.contents = {}
for key, path in self.files.items():
if os.path.exists(path):
with open(path, encoding='utf-8') as f:
with open(path, encoding="utf-8") as f:
self.contents[key] = f.read()
else:
self.contents[key] = ''
self.contents[key] = ""
self.all_text = "\n".join(self.contents.values())
def test_forbid_ai_jargon_and_cliches(self):
"""严格杜绝 AI/互联网黑话与套话口癖"""
forbidden_jargon = [
'闭环', # 典型大厂/AI黑话(如“业务闭环”、“闭环协同”、“交付闭环”)
'工匠精神打磨', # 空洞煽情修饰
'准备开启您的', # 典型 AI 祈使反问句
'矩阵赋能', # AI 常用生造词
'双向幂等校验', # 过于晦涩的技术术语,应通俗化为防重复扣款/防重复提交
'多路向量重排召回',# 技术晦涩,应通俗化说明知识库检索精准
'多路向量召回', # 算法黑话
'无感幂等同步', # 技术行话,应通俗化为自动同步防重复
'数字化落地样本', # 典型公关大词,客户需要的是业务系统而非“样本”
'确定性数字化样本',
'知识管线', # 算法黑话,应通俗化为企业知识库
'语料处理管线', # 算法行话,应通俗化为知识库处理脚本
'操作链路', # UX大厂黑话,应通俗化为操作步骤
'脱敏数据沙箱', # 过于晦涩,应通俗化为演示环境
'厘清业务痛点', # 居高临下大厂套话,应为调研实际业务流程
'全链路打通', # 大厂黑话,应为全面打通或全面贯通
'全链路压测', # 应为高并发压力测试
"闭环", # 典型大厂/AI黑话(如“业务闭环”、“闭环协同”、“交付闭环”)
"工匠精神打磨", # 空洞煽情修饰
"准备开启您的", # 典型 AI 祈使反问句
"矩阵赋能", # AI 常用生造词
"双向幂等校验", # 过于晦涩的技术术语,应通俗化为防重复扣款/防重复提交
"多路向量重排召回", # 技术晦涩,应通俗化说明知识库检索精准
"多路向量召回", # 算法黑话
"无感幂等同步", # 技术行话,应通俗化为自动同步防重复
"数字化落地样本", # 典型公关大词,客户需要的是业务系统而非“样本”
"确定性数字化样本",
"知识管线", # 算法黑话,应通俗化为企业知识库
"语料处理管线", # 算法行话,应通俗化为知识库处理脚本
"操作链路", # UX大厂黑话,应通俗化为操作步骤
"脱敏数据沙箱", # 过于晦涩,应通俗化为演示环境
"厘清业务痛点", # 居高临下大厂套话,应为调研实际业务流程
"全链路打通", # 大厂黑话,应为全面打通或全面贯通
"全链路压测", # 应为高并发压力测试
]
for jargon in forbidden_jargon:
self.assertNotIn(jargon, self.all_text, f"全站文案仍包含黑话或AI套话: '{jargon}'")
self.assertNotIn(
jargon, self.all_text, f"全站文案仍包含黑话或AI套话: '{jargon}'"
)
def test_customer_friendly_plain_language(self):
"""面向客户与访客的大白话表达:突出价值与保障"""
# 首页:通俗的防重复扣款与数据安全
self.assertIn('防重复扣款', self.contents['index'], "首页产品指标应使用客户一目了然的'防重复扣款'")
self.assertIn(
"防重复扣款",
self.contents["index"],
"首页产品指标应使用客户一目了然的'防重复扣款'",
)
# 智能体页:通俗解释
self.assertTrue(
'完全离线运行' in self.contents['service_agent'] or '私有化部署' in self.contents['service_agent'],
"智能体页必须用通俗大白话说明部署方式与数据边界"
"完全离线运行" in self.contents["service_agent"]
or "私有化部署" in self.contents["service_agent"],
"智能体页必须用通俗大白话说明部署方式与数据边界",
)
self.assertTrue(
any(label in self.contents['service_agent'] for label in ('引用来源', '引用溯源', '引用展示')),
"智能体页必须讲清楚回答带有引用来源"
any(
label in self.contents["service_agent"]
for label in ("引用来源", "引用溯源", "引用展示")
),
"智能体页必须讲清楚回答带有引用来源",
)
# 案例页:拒绝“样本”,使用“数字化系统”;使用“自动同步防重复”
self.assertIn('数字化系统', self.contents['cases_page'])
self.assertIn('自动同步防重复', self.contents['cases_page'])
self.assertNotIn('多路向量召回', self.contents['cases_page'])
self.assertIn("数字化系统", self.contents["cases_page"])
self.assertIn("自动同步防重复", self.contents["cases_page"])
self.assertNotIn("多路向量召回", self.contents["cases_page"])
# 联系我们表单:使用通俗的“业务需求”而非大厂“痛点”
self.assertNotIn('系统痛点', self.contents['contact_page'])
self.assertIn('业务需求', self.contents['contact_page'])
self.assertNotIn("系统痛点", self.contents["contact_page"])
self.assertIn("业务需求", self.contents["contact_page"])
# 软件开发页:面向客户的直白 CTA
self.assertNotIn('准备开启您的企业级定制软件工程?', self.contents['service_software'])
self.assertNotIn(
"准备开启您的企业级定制软件工程?", self.contents["service_software"]
)
# APP与小程序页:通俗表述
self.assertNotIn('私域闭环', self.contents['service_app'])
self.assertNotIn("私域闭环", self.contents["service_app"])
# 产品页:开箱即用与源码买断清晰明了
self.assertIn('成熟商业系统', self.contents['product_page'])
self.assertIn("成熟商业系统", self.contents["product_page"])
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()

查看文件

@@ -9,134 +9,251 @@
5. CSS 针对配图有严格的响应式(max-width: 100%、圆角、细线边框、悬停微光)控制
6. 无破损路径或 404 资源引用
"""
import os
import unittest
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
class SiteImagesTest(unittest.TestCase):
def setUp(self):
self.img_dir = os.path.join(BASE_DIR, 'public', 'assets', 'addons', 'official', 'images', 'showcases')
self.pc_home_path = os.path.join(BASE_DIR, 'addons', 'official', 'view', 'web', 'official_home', 'index.html')
self.mobile_home_path = os.path.join(BASE_DIR, 'addons', 'official', 'view', 'web', 'official_home', 'index_e.html')
self.service_page_path = os.path.join(BASE_DIR, 'addons', 'official', 'view', 'web', 'official_product', 'service_page.html')
self.product_page_path = os.path.join(BASE_DIR, 'addons', 'official', 'view', 'web', 'official_product', 'product_page.html')
self.case_page_path = os.path.join(BASE_DIR, 'addons', 'official', 'view', 'web', 'official_case_show', 'list.html')
self.case_mobile_path = os.path.join(BASE_DIR, 'addons', 'official', 'view', 'web', 'official_case_show', 'list_e.html')
self.about_page_path = os.path.join(BASE_DIR, 'addons', 'official', 'view', 'web', 'official_home', 'single.html')
self.about_mobile_path = os.path.join(BASE_DIR, 'addons', 'official', 'view', 'web', 'official_home', 'single_e.html')
self.css_path = os.path.join(BASE_DIR, 'public', 'assets', 'addons', 'official', 'css', 'modern_nhc.css')
self.img_dir = os.path.join(
BASE_DIR, "public", "assets", "addons", "official", "images", "showcases"
)
self.pc_home_path = os.path.join(
BASE_DIR, "addons", "official", "view", "web", "official_home", "index.html"
)
self.mobile_home_path = os.path.join(
BASE_DIR,
"addons",
"official",
"view",
"web",
"official_home",
"index_e.html",
)
self.service_page_path = os.path.join(
BASE_DIR,
"addons",
"official",
"view",
"web",
"official_product",
"service_page.html",
)
self.product_page_path = os.path.join(
BASE_DIR,
"addons",
"official",
"view",
"web",
"official_product",
"product_page.html",
)
self.case_page_path = os.path.join(
BASE_DIR,
"addons",
"official",
"view",
"web",
"official_case_show",
"list.html",
)
self.case_mobile_path = os.path.join(
BASE_DIR,
"addons",
"official",
"view",
"web",
"official_case_show",
"list_e.html",
)
self.about_page_path = os.path.join(
BASE_DIR,
"addons",
"official",
"view",
"web",
"official_home",
"single.html",
)
self.about_mobile_path = os.path.join(
BASE_DIR,
"addons",
"official",
"view",
"web",
"official_home",
"single_e.html",
)
self.css_path = os.path.join(
BASE_DIR, "public", "assets", "addons", "official", "css", "modern_nhc.css"
)
def test_01_image_assets_exist_and_non_empty(self):
"""核心配图资源必须物理存在且内容非空"""
required_images = [
# 服务类配图
'service_middle.svg',
'service_app.svg',
'service_agent.svg',
"service_middle.svg",
"service_app.svg",
"service_agent.svg",
# 产品类配图
'product_mall.svg',
'product_pos.svg',
'product_erp.svg',
'product_saas.svg',
"product_mall.svg",
"product_pos.svg",
"product_erp.svg",
"product_saas.svg",
# 案例类配图
'case_distrib.svg',
'case_catering.svg',
'case_gov_agent.svg',
'case_manufacture.svg',
"case_distrib.svg",
"case_catering.svg",
"case_gov_agent.svg",
"case_manufacture.svg",
# 专家与资质徽标
'team_architect.svg',
'team_fullstack.svg',
'team_ai.svg',
'team_security.svg',
'cert_hi_tech.svg',
'cert_iso9001.svg'
"team_architect.svg",
"team_fullstack.svg",
"team_ai.svg",
"team_security.svg",
"cert_hi_tech.svg",
"cert_iso9001.svg",
]
self.assertTrue(os.path.isdir(self.img_dir), f"图片目录 {self.img_dir} 必须存在")
self.assertTrue(
os.path.isdir(self.img_dir), f"图片目录 {self.img_dir} 必须存在"
)
for img_name in required_images:
img_path = os.path.join(self.img_dir, img_name)
self.assertTrue(os.path.isfile(img_path), f"图片文件 {img_name} 必须存在于 showcases 目录下")
self.assertTrue(
os.path.isfile(img_path),
f"图片文件 {img_name} 必须存在于 showcases 目录下",
)
size = os.path.getsize(img_path)
self.assertGreater(size, 100, f"图片文件 {img_name} 大小不能过小")
def test_02_pc_and_mobile_homepage_service_images(self):
"""首页服务入口必须包含三个一致的服务视觉卡片"""
with open(self.pc_home_path, encoding='utf-8') as f:
with open(self.pc_home_path, encoding="utf-8") as f:
pc_content = f.read()
with open(self.mobile_home_path, encoding='utf-8') as f:
with open(self.mobile_home_path, encoding="utf-8") as f:
mobile_content = f.read()
for content, page_name in [(pc_content, "PC 首页"), (mobile_content, "移动端首页")]:
self.assertEqual(content.count('hn-service-top'), 3, f"{page_name} 应包含三个服务视觉卡片")
for service_name in ['软件开发', 'APP与小程序定制', '智能体开发']:
self.assertIn(service_name, content, f"{page_name} 应包含服务入口:{service_name}")
for content, page_name in [
(pc_content, "PC 首页"),
(mobile_content, "移动端首页"),
]:
self.assertEqual(
content.count("hn-service-top"),
3,
f"{page_name} 应包含三个服务视觉卡片",
)
for service_name in ["软件开发", "APP与小程序定制", "智能体开发"]:
self.assertIn(
service_name, content, f"{page_name} 应包含服务入口:{service_name}"
)
def test_03_pc_and_mobile_homepage_product_images(self):
"""首页产品区必须包含经营工作台示意与四类产品入口"""
with open(self.pc_home_path, encoding='utf-8') as f:
with open(self.pc_home_path, encoding="utf-8") as f:
pc_content = f.read()
with open(self.mobile_home_path, encoding='utf-8') as f:
with open(self.mobile_home_path, encoding="utf-8") as f:
mobile_content = f.read()
for content, page_name in [(pc_content, "PC 首页"), (mobile_content, "移动端首页")]:
self.assertIn('hn-app-window', content, f"{page_name} 应包含经营工作台界面示意")
self.assertIn('hn-chart', content, f"{page_name} 应包含经营趋势示意")
for product_name in ['商城', '收银', '进销存', 'SaaS版本']:
self.assertIn(product_name, content, f"{page_name} 应包含产品入口:{product_name}")
for content, page_name in [
(pc_content, "PC 首页"),
(mobile_content, "移动端首页"),
]:
self.assertIn(
"hn-app-window", content, f"{page_name} 应包含经营工作台界面示意"
)
self.assertIn("hn-chart", content, f"{page_name} 应包含经营趋势示意")
for product_name in ["商城", "收银", "进销存", "SaaS版本"]:
self.assertIn(
product_name, content, f"{page_name} 应包含产品入口:{product_name}"
)
def test_04_cases_pages_have_preview_images(self):
"""标杆案例卡片必须配有真实的业务系统视窗图"""
with open(self.case_page_path, encoding='utf-8') as f:
with open(self.case_page_path, encoding="utf-8") as f:
case_pc = f.read()
with open(self.case_mobile_path, encoding='utf-8') as f:
with open(self.case_mobile_path, encoding="utf-8") as f:
case_mobile = f.read()
for content, page_name in [(case_pc, "PC 案例独立页"), (case_mobile, "移动端案例独立页")]:
self.assertIn('case_distrib.svg', content, f"{page_name} 应包含分销订货中台界面图")
self.assertIn('case_catering.svg', content, f"{page_name} 应包含连锁餐饮收银界面图")
self.assertIn('case_gov_agent.svg', content, f"{page_name} 应包含政务智能体界面图")
self.assertIn('case_manufacture.svg', content, f"{page_name} 应包含工业制造进销存界面图")
for content, page_name in [
(case_pc, "PC 案例独立页"),
(case_mobile, "移动端案例独立页"),
]:
self.assertIn(
"case_distrib.svg", content, f"{page_name} 应包含分销订货中台界面图"
)
self.assertIn(
"case_catering.svg", content, f"{page_name} 应包含连锁餐饮收银界面图"
)
self.assertIn(
"case_gov_agent.svg", content, f"{page_name} 应包含政务智能体界面图"
)
self.assertIn(
"case_manufacture.svg",
content,
f"{page_name} 应包含工业制造进销存界面图",
)
def test_05_service_and_product_pages_have_showcases(self):
"""独立服务页与产品页必须嵌入高质量系统拓扑与界面大图"""
with open(self.service_page_path, encoding='utf-8') as f:
with open(self.service_page_path, encoding="utf-8") as f:
service_content = f.read()
with open(self.product_page_path, encoding='utf-8') as f:
with open(self.product_page_path, encoding="utf-8") as f:
product_content = f.read()
self.assertIn('service_middle.svg', service_content)
self.assertIn('service_app.svg', service_content)
self.assertIn('service_agent.svg', service_content)
self.assertIn("service_middle.svg", service_content)
self.assertIn("service_app.svg", service_content)
self.assertIn("service_agent.svg", service_content)
self.assertIn('product_mall.svg', product_content)
self.assertIn('product_pos.svg', product_content)
self.assertIn('product_erp.svg', product_content)
self.assertIn('product_saas.svg', product_content)
self.assertIn("product_mall.svg", product_content)
self.assertIn("product_pos.svg", product_content)
self.assertIn("product_erp.svg", product_content)
self.assertIn("product_saas.svg", product_content)
def test_06_about_pages_have_expert_and_cert_badges(self):
"""关于我们页必须包含专家团队专业徽标与资质认证图"""
with open(self.about_page_path, encoding='utf-8') as f:
def test_06_about_pages_have_abstract_brand_images(self):
"""关于我们使用已标注的品牌抽象配图,保留格式回退。"""
with open(self.about_page_path, encoding="utf-8") as f:
about_pc = f.read()
with open(self.about_mobile_path, encoding='utf-8') as f:
with open(self.about_mobile_path, encoding="utf-8") as f:
about_mobile = f.read()
for content, page_name in [(about_pc, "PC 关于我们页"), (about_mobile, "移动端关于我们页")]:
self.assertIn('team_architect.svg', content, f"{page_name} 应包含首席架构师徽章")
self.assertIn('team_fullstack.svg', content, f"{page_name} 应包含全栈研发总监徽章")
self.assertIn('team_ai.svg', content, f"{page_name} 应包含AI智能体专家徽章")
self.assertIn('team_security.svg', content, f"{page_name} 应包含质量安全合规专家徽章")
self.assertIn('cert_hi_tech.svg', content, f"{page_name} 应包含高新技术企业认证图")
self.assertIn('cert_iso9001.svg', content, f"{page_name} 应包含ISO9001认证图")
for content, page_name in [
(about_pc, "PC 关于我们页"),
(about_mobile, "移动端关于我们页"),
]:
for name in ("landscape", "collaboration"):
for extension in ("webp", "jpg"):
relative_path = f"images/about/{name}.{extension}"
self.assertIn(
relative_path, content, f"{page_name} 应包含抽象配图及格式回退"
)
self.assertTrue(
os.path.isfile(
os.path.join(
BASE_DIR,
"public",
"assets",
"addons",
"official",
relative_path,
)
)
)
self.assertIn("抽象建筑示意", content)
self.assertNotIn("cert_hi_tech.svg", content)
self.assertNotIn("cert_iso9001.svg", content)
def test_07_responsive_image_styles_in_css(self):
"""CSS 必须具备严谨的配图自适应与质感样式定义"""
with open(self.css_path, encoding='utf-8') as f:
with open(self.css_path, encoding="utf-8") as f:
css = f.read()
self.assertIn('.nhc-card-thumb', css, "CSS 应定义 .nhc-card-thumb 图片容器类")
self.assertIn('object-fit', css, "CSS 应规范图片的适应展示模式")
self.assertIn('.nhc-case-img-wrap', css, "CSS 应定义案例图视窗包装器")
self.assertIn('.nhc-service-media-box', css, "CSS 应定义服务媒体容器")
self.assertIn(".nhc-card-thumb", css, "CSS 应定义 .nhc-card-thumb 图片容器类")
self.assertIn("object-fit", css, "CSS 应规范图片的适应展示模式")
self.assertIn(".nhc-case-img-wrap", css, "CSS 应定义案例图视窗包装器")
self.assertIn(".nhc-service-media-box", css, "CSS 应定义服务媒体容器")
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()

查看文件

@@ -1,7 +1,8 @@
"""
测试首页高端主视觉系统 (Test Premium Home Hero Visual)
验证宽幅首屏摄影的高清画质、响应式图片源、桌面与移动端一致引用、
无文字遮挡的渐变处理、响应式断点与减少动态效果适配。
验证宽幅首屏摄影的高清画质、桌面与移动端响应式图片源、
两端模板一致引用、优先加载与有效替代文本、
渐变处理、响应式断点与减少动态效果适配。
"""
import os
@@ -10,25 +11,39 @@ import unittest
from PIL import Image
ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
HERO_IMAGE_DIR = os.path.join(ROOT_DIR, "public", "assets", "addons", "official", "images", "home")
CSS_PATH = os.path.join(ROOT_DIR, "public", "assets", "addons", "official", "css", "home-v3.css")
PC_HOME_PATH = os.path.join(ROOT_DIR, "addons", "official", "view", "web", "official_home", "index.html")
M_HOME_PATH = os.path.join(ROOT_DIR, "addons", "official", "view", "web", "official_home", "index_e.html")
HERO_IMAGE_DIR = os.path.join(
ROOT_DIR, "public", "assets", "addons", "official", "images", "home"
)
CSS_PATH = os.path.join(
ROOT_DIR, "public", "assets", "addons", "official", "css", "home-v3.css"
)
PC_HOME_PATH = os.path.join(
ROOT_DIR, "addons", "official", "view", "web", "official_home", "index.html"
)
M_HOME_PATH = os.path.join(
ROOT_DIR, "addons", "official", "view", "web", "official_home", "index_e.html"
)
class PremiumHeroBannerTest(unittest.TestCase):
"""验证首页宽幅主视觉"""
def test_01_hero_image_high_quality(self):
"""1. 验证首屏宽幅摄影物理存在、分辨率高、画质饱满"""
hero_path = os.path.join(HERO_IMAGE_DIR, "river-courtyard-v3.jpg")
self.assertTrue(os.path.exists(hero_path), "首屏宽幅摄影不存在")
self.assertGreater(os.path.getsize(hero_path), 150 * 1024, "首屏摄影文件过小")
"""1. 验证桌面与移动端首屏摄影物理存在、分辨率高、画质饱满"""
hero_images = {
"ai-computing-v4.jpg": (1983, 793),
"ai-computing-mobile-v4.jpg": (1024, 1536),
}
for filename, (minimum_width, minimum_height) in hero_images.items():
with self.subTest(hero=filename):
hero_path = os.path.join(HERO_IMAGE_DIR, filename)
self.assertTrue(os.path.exists(hero_path), f"首屏宽幅摄影不存在: {filename}")
self.assertGreater(os.path.getsize(hero_path), 100 * 1024, f"首屏摄影文件过小: {filename}")
with Image.open(hero_path) as hero:
width, height = hero.size
self.assertGreaterEqual(width, 1800, "首屏摄影宽度不足宽幅要求")
self.assertGreaterEqual(height, 700, "首屏摄影高度不足首屏要求")
with Image.open(hero_path) as hero:
width, height = hero.size
self.assertGreaterEqual(width, minimum_width, f"首屏摄影宽度不足要求: {filename}")
self.assertGreaterEqual(height, minimum_height, f"首屏摄影高度不足要求: {filename}")
def test_02_css_uses_responsive_hero_and_accessible_motion(self):
"""2. 验证专属 CSS 提供首屏布局、响应式断点与减少动态效果适配"""
@@ -42,15 +57,24 @@ class PremiumHeroBannerTest(unittest.TestCase):
self.assertIn("prefers-reduced-motion", css_content, "CSS 应支持减少动态效果")
def test_03_pc_and_mobile_template_integrity(self):
"""3. 验证 PC 与移动端模板引用同一套响应式首屏图片"""
"""3. 验证 PC 与移动端模板引用桌面与移动端两套响应式首屏图片"""
for page_path in [PC_HOME_PATH, M_HOME_PATH]:
with open(page_path, encoding="utf-8") as stream:
html = stream.read()
self.assertEqual(html.count("hn-hero-picture"), 1, f"首屏图片容器数量应为 1:{page_path}")
self.assertIn("river-courtyard-v3.webp", html, f"页面缺少 WebP 首屏图片源:{page_path}")
self.assertIn("river-courtyard-v3.jpg", html, f"页面缺少 JPEG 首屏图片回退:{page_path}")
for filename in (
"ai-computing-v4.webp",
"ai-computing-v4.jpg",
"ai-computing-mobile-v4.webp",
"ai-computing-mobile-v4.jpg",
):
self.assertIn(filename, html, f"页面缺少首屏图片资源 {filename}:{page_path}")
self.assertIn('fetchpriority="high"', html, f"首屏图片应优先加载:{page_path}")
self.assertRegex(html, r'<img[^>]+alt="[^"]{4,}"', f"首屏图片应包含有效替代文本:{page_path}")
self.assertRegex(
html,
r'<img[^>]+alt="[^"]{4,}"',
f"首屏图片应包含有效替代文本:{page_path}",
)
if __name__ == "__main__":