feat(journalism): 全面升级新闻资讯列表为标题、简介、缩略图专业图文排版模式并适配多端
这个提交包含在:
@@ -55,7 +55,7 @@ class OfficialJournalism extends OfficialBaseController
|
||||
->where('deletetime', null)
|
||||
->order('weigh', 'desc')
|
||||
->field(['id,title,image,subheading,createtime'])
|
||||
->paginate(15, $simple = $this->isMobile(), $config = [
|
||||
->paginate(8, $simple = $this->isMobile(), $config = [
|
||||
'query' => ['pid' => $p_id, 'sid' => $son_id]
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -44,15 +44,39 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 文章列表:只显示标题即可 -->
|
||||
<!-- 文章资讯列表:标题、简介、缩略图专业图文排版 -->
|
||||
{if count($data) > 0}
|
||||
<div class="nhc-title-list">
|
||||
<div class="nhc-news-list">
|
||||
{foreach $data as $index => $item}
|
||||
<a href="{:addon_url('official/web.official_journalism/details',[':pid'=>(int)input('param.pid'),':sid'=>(int)input('param.sid'),':id'=>$item['id']])}" class="nhc-title-item" title="{$item['title']}">
|
||||
<div class="nhc-title-left">
|
||||
<span class="nhc-title-text">{$item['title']}</span>
|
||||
<article class="nhc-news-row">
|
||||
<a href="{:addon_url('official/web.official_journalism/details',[':pid'=>(int)input('param.pid'),':sid'=>(int)input('param.sid'),':id'=>$item['id']])}" class="nhc-news-thumb" title="{$item['title']}">
|
||||
{if !empty($item['image'])}
|
||||
<img src="{$item['image']}" alt="{$item['title']}" loading="lazy">
|
||||
{else/}
|
||||
<div style="width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; color: #94A3B8; font-size: 13px;">纳海川科技</div>
|
||||
{/if}
|
||||
</a>
|
||||
<div class="nhc-news-body">
|
||||
<div>
|
||||
<div class="nhc-news-meta">
|
||||
<span class="nhc-news-badge">{if !empty($son_data)}{:ze($son_data,'name')}{else/}{:ze($res,'name')}{/if}</span>
|
||||
<span class="nhc-news-date">{:date('Y-m-d', $item['createtime'])}</span>
|
||||
</div>
|
||||
<a href="{:addon_url('official/web.official_journalism/details',[':pid'=>(int)input('param.pid'),':sid'=>(int)input('param.sid'),':id'=>$item['id']])}" style="text-decoration: none;">
|
||||
<h2 class="nhc-news-title">{$item['title']}</h2>
|
||||
</a>
|
||||
<p class="nhc-news-desc">
|
||||
{$item['subheading']}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{:addon_url('official/web.official_journalism/details',[':pid'=>(int)input('param.pid'),':sid'=>(int)input('param.sid'),':id'=>$item['id']])}" class="nhc-news-more">
|
||||
<span>阅读全文</span>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
{/foreach}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -32,15 +32,37 @@
|
||||
{/foreach}
|
||||
</div>
|
||||
|
||||
<!-- 移动端文章列表:只显示标题即可 -->
|
||||
<!-- 移动端文章资讯列表:标题、简介、缩略图专业图文排版 -->
|
||||
{if count($data) > 0}
|
||||
<div class="nhc-title-list">
|
||||
<div style="display: flex; flex-direction: column; gap: 16px;">
|
||||
{foreach $data as $index => $val}
|
||||
<a href="{:addon_url('official/web.official_journalism/details',[':pid'=>(int)input('param.pid'),':sid'=>(int)input('param.sid'),':id'=>$val['id']])}" class="nhc-title-item" style="padding: 14px 16px; border-bottom: 1px solid var(--border-color); display: block;">
|
||||
<h2 style="font-family: var(--font-serif); font-size: 15px; font-weight: 600; color: var(--text-primary); line-height: 1.45; margin: 0;">
|
||||
{$val['title']}
|
||||
</h2>
|
||||
</a>
|
||||
<article class="nhc-news-row" style="padding: 16px; flex-direction: column; gap: 14px;">
|
||||
<a href="{:addon_url('official/web.official_journalism/details',[':pid'=>(int)input('param.pid'),':sid'=>(int)input('param.sid'),':id'=>$val['id']])}" class="nhc-news-thumb" style="width: 100%; height: 160px;">
|
||||
{if !empty($val['image'])}
|
||||
<img src="{$val['image']}" alt="{$val['title']}" loading="lazy">
|
||||
{else/}
|
||||
<div style="width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; color: #94A3B8; font-size: 13px;">纳海川科技</div>
|
||||
{/if}
|
||||
</a>
|
||||
<div class="nhc-news-body" style="padding-left: 0;">
|
||||
<div class="nhc-news-meta">
|
||||
<span class="nhc-news-badge">{if !empty($son_data)}{:ze($son_data,'name')}{else/}{:ze($res,'name')}{/if}</span>
|
||||
<span class="nhc-news-date">{:date('Y-m-d', $val['createtime'])}</span>
|
||||
</div>
|
||||
<a href="{:addon_url('official/web.official_journalism/details',[':pid'=>(int)input('param.pid'),':sid'=>(int)input('param.sid'),':id'=>$val['id']])}" style="text-decoration: none;">
|
||||
<h2 class="nhc-news-title" style="font-size: 16px;">{$val['title']}</h2>
|
||||
</a>
|
||||
<p class="nhc-news-desc" style="font-size: 13px; margin-bottom: 10px;">
|
||||
{$val['subheading']}
|
||||
</p>
|
||||
<div>
|
||||
<a href="{:addon_url('official/web.official_journalism/details',[':pid'=>(int)input('param.pid'),':sid'=>(int)input('param.sid'),':id'=>$val['id']])}" class="nhc-news-more" style="font-size: 12px;">
|
||||
<span>阅读全文</span>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{/foreach}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4108,6 +4108,156 @@ img {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 7.5 现代中式专业图文行卡片规范(标题、简介、缩略图专业排版) */
|
||||
.nhc-news-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.nhc-news-row {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 24px;
|
||||
box-shadow: var(--shadow-subtle);
|
||||
transition: all var(--transition-fast);
|
||||
text-decoration: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nhc-news-row:hover {
|
||||
border-color: #BFDBFE;
|
||||
box-shadow: 0 10px 25px -5px rgba(37, 99, 235, 0.08);
|
||||
transform: translateY(-2px);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.nhc-news-thumb {
|
||||
flex-shrink: 0;
|
||||
width: 280px;
|
||||
height: 180px;
|
||||
border-radius: var(--radius-sm);
|
||||
overflow: hidden;
|
||||
background: #F8FAFC;
|
||||
border: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.nhc-news-thumb img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.4s ease;
|
||||
}
|
||||
|
||||
.nhc-news-row:hover .nhc-news-thumb img {
|
||||
transform: scale(1.04);
|
||||
}
|
||||
|
||||
.nhc-news-body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding-left: 28px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.nhc-news-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.nhc-news-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
padding: 2px 10px;
|
||||
border-radius: 3px;
|
||||
background: #EFF6FF;
|
||||
color: var(--primary);
|
||||
border: 1px solid #BFDBFE;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nhc-news-date {
|
||||
font-size: 13px;
|
||||
color: var(--text-muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.nhc-news-title {
|
||||
font-family: var(--font-serif);
|
||||
font-size: 19px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
line-height: 1.45;
|
||||
margin: 0 0 10px 0;
|
||||
transition: color var(--transition-fast);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nhc-news-row:hover .nhc-news-title {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.nhc-news-desc {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.65;
|
||||
margin: 0 0 14px 0;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nhc-news-more {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.nhc-news-row:hover .nhc-news-more svg {
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
.nhc-news-more svg {
|
||||
transition: transform var(--transition-fast);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.nhc-news-row {
|
||||
flex-direction: column;
|
||||
padding: 16px;
|
||||
gap: 14px;
|
||||
}
|
||||
.nhc-news-thumb {
|
||||
width: 100%;
|
||||
height: 180px;
|
||||
}
|
||||
.nhc-news-body {
|
||||
padding-left: 0;
|
||||
}
|
||||
.nhc-news-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 8. 现代中式紧凑纯标题列表规范 (nhc-title-list) */
|
||||
.nhc-title-list {
|
||||
background: #FFFFFF;
|
||||
|
||||
@@ -23,19 +23,18 @@ class TestJournalismAndArticles(unittest.TestCase):
|
||||
self.seed_sql_path = os.path.join(BASE_DIR, 'database', 'seeds', 'journalism_articles.sql')
|
||||
|
||||
def test_pc_list_template_spec(self):
|
||||
"""验证 PC 端文章资讯列表页规范(只显示标题即可)"""
|
||||
"""验证 PC 端文章资讯列表页规范(标题、简介、缩略图专业排版)"""
|
||||
self.assertTrue(os.path.exists(self.pc_list_path), "PC 文章列表模板 list.html 必须存在")
|
||||
content = open(self.pc_list_path, 'r', encoding='utf-8').read()
|
||||
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-title-list", content, "必须包含现代中式纯标题列表容器类 nhc-title-list")
|
||||
self.assertIn("nhc-title-item", content, "必须包含纯标题列表单项类 nhc-title-item")
|
||||
self.assertNotIn("nhc-article-card-thumb", content, "文章列表已切换为纯标题列表,严禁展示大封面图")
|
||||
self.assertNotIn("nhc-title-badge", content, "只显示标题:严禁在列表中显示分类徽章")
|
||||
self.assertNotIn("nhc-title-date", content, "只显示标题:严禁在列表中显示日期")
|
||||
self.assertNotIn("nhc-title-arrow", content, "只显示标题:严禁在列表中显示箭头")
|
||||
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")
|
||||
self.assertIn("nhc-news-desc", content, "必须包含文章简介 nhc-news-desc")
|
||||
self.assertIn("render", content, "必须包含分页渲染组件")
|
||||
|
||||
def test_pc_details_template_spec(self):
|
||||
@@ -50,23 +49,26 @@ class TestJournalismAndArticles(unittest.TestCase):
|
||||
self.assertIn("info_previous_next_wrap", content, "必须包含上一篇/下一篇自然导流结构")
|
||||
|
||||
def test_mobile_templates_spec_and_bugfix(self):
|
||||
"""验证移动端模板规范并彻底修复循环变量与遗留链接 Bug"""
|
||||
"""验证移动端模板规范(标题、简介、缩略图专业排版)"""
|
||||
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 = open(self.mobile_list_path, 'r', encoding='utf-8').read()
|
||||
details_content = open(self.mobile_details_path, 'r', encoding='utf-8').read()
|
||||
|
||||
self.assertIn("nhc-title-list", list_content, "移动端列表必须包含纯标题列表结构")
|
||||
self.assertNotIn("nhc-title-badge", list_content, "移动端只显示标题:严禁显示分类徽章")
|
||||
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")
|
||||
|
||||
def test_css_design_tokens_for_articles(self):
|
||||
"""验证 CSS 中已包含文章与资讯专属样式定义"""
|
||||
"""验证 CSS 中已包含文章与资讯专属图文排版样式定义"""
|
||||
css = open(self.css_path, 'r', encoding='utf-8').read()
|
||||
self.assertIn(".nhc-title-list", css, "CSS 必须定义 .nhc-title-list 样式")
|
||||
self.assertIn(".nhc-title-item", css, "CSS 必须定义 .nhc-title-item 样式")
|
||||
self.assertIn(".nhc-news-row", css, "CSS 必须定义 .nhc-news-row 图文行卡片样式")
|
||||
self.assertIn(".nhc-news-thumb", css, "CSS 必须定义 .nhc-news-thumb 缩略图样式")
|
||||
self.assertIn(".nhc-news-title", css, "CSS 必须定义 .nhc-news-title 标题样式")
|
||||
self.assertIn(".nhc-news-desc", css, "CSS 必须定义 .nhc-news-desc 简介样式")
|
||||
self.assertIn(".nhc-article-body", css, "CSS 必须定义 .nhc-article-body 样式")
|
||||
|
||||
def test_seed_sql_rich_articles(self):
|
||||
|
||||
在新工单中引用
屏蔽一个用户