Files
jczxw-pc/app/pages/index.vue
T
2026-09-11 12:25:38 +08:00

1520 lines
36 KiB
Vue

<template>
<div class="home-page">
<PortalHeader/>
<main class="container page-main">
<section class="headline-grid">
<NuxtLink :to="getArticleLink(headlinePrimary.id)" class="headline-main-title">
{{ headlinePrimary.title }}
</NuxtLink>
<div class="headline-content">
<div class="headline-left">
<NuxtLink :to="getArticleLink(headlineSecondary.id)" class="headline-image-card">
<img :src="headlineSecondary.image || articleFallbackImage" :alt="headlineSecondary.title"/>
<div class="headline-image-title">{{ headlineSecondary.title }}</div>
<div class="headline-image-date">{{ headlineSecondary.date }}</div>
</NuxtLink>
</div>
<div class="headline-right">
<div class="subnav-strip" :style="{ '--subnav-active-bg': `url(${subnavStripImage})` }">
<button
v-for="item in headlineCategories"
:key="item.path || item.title"
type="button"
class="subnav-strip-link"
:class="{ active: activeHeadlineCategoryKey === item.key }"
@click="handleHeadlineCategoryChange(item)"
>
{{ item.title }}
</button>
</div>
<div class="news-links">
<NuxtLink
v-for="item in headlineNewsLinks"
:key="item.id"
:to="getArticleLink(item.id)"
class="news-link-card"
>
<h3>{{ item.title }}</h3>
<p>{{ item.summary }}</p>
</NuxtLink>
</div>
</div>
</div>
</section>
<section class="section-block">
<div class="section-heading image-heading">
<component
v-if="topBannerAd?.image"
:is="topBannerAd?.linkUrl ? 'a' : 'div'"
v-bind="getAdLinkAttrs(topBannerAd)"
class="home-ad-link"
>
<img :src="topBannerAd.image" :alt="topBannerAd.title || '广告位1'"/>
</component>
<img :src="promoBannerImage" alt="决策咨询"/>
</div>
<div class="article-columns two">
<div class="article-panel" v-for="group in consultingGroups" :key="group.title">
<div class="panel-heading">
<span>{{ group.title }}</span>
<NuxtLink :to="group.more">更多&gt;&gt;</NuxtLink>
</div>
<div class="article-list">
<NuxtLink
v-for="item in group.items"
:key="item.id || item.title"
:to="getGroupItemLink(group, item)"
class="article-row"
:class="{ 'no-image': !shouldShowGroupImage(group) }"
>
<div class="article-text">
<h3>{{ item.title }}</h3>
<p>{{ item.summary }}</p>
<span>{{ item.date }}</span>
</div>
<img v-if="shouldShowGroupImage(group)" :src="item.image || articleFallbackImage" alt="文章配图"/>
</NuxtLink>
</div>
</div>
</div>
</section>
<section v-if="middleBannerAd?.image" class="ad-banner blue">
<component
:is="middleBannerAd?.linkUrl ? 'a' : 'div'"
v-bind="getAdLinkAttrs(middleBannerAd)"
class="home-ad-link"
>
<img :src="middleBannerAd.image" :alt="middleBannerAd.title || '广告位2'"/>
</component>
</section>
<section class="section-block">
<div class="article-columns two">
<div class="article-panel" v-for="group in referenceGroups" :key="group.title">
<div class="panel-heading">
<span>{{ group.title }}</span>
<NuxtLink :to="group.more">更多&gt;&gt;</NuxtLink>
</div>
<div class="article-list">
<NuxtLink
v-for="item in group.items"
:key="item.id || item.title"
:to="getGroupItemLink(group, item)"
class="article-row"
:class="{ 'no-image': !shouldShowGroupImage(group) }"
>
<div class="article-text">
<h3>{{ item.title }}</h3>
<p>{{ item.summary }}</p>
<span>{{ item.date }}</span>
</div>
<img v-if="shouldShowGroupImage(group)" :src="item.image || articleFallbackImage" alt="文章配图"/>
</NuxtLink>
</div>
</div>
</div>
</section>
<section class="experts-section">
<div class="section-heading experts-heading" :style="{ backgroundImage: `url(${expertSectionBgImage})` }">
<img class="experts-heading-title" :src="expertSectionTitleImage" alt="专家资讯"/>
</div>
<div class="experts-tabs">
<div class="experts-tabs-panel">
<div class="experts-tabs-panel-head">
<div class="experts-tabs-left">
<button
type="button"
class="experts-tab-btn"
:class="{ active: activeExpertTab === 'viewpoint' }"
@click="activeExpertTab = 'viewpoint'"
>
专家视点
</button>
<button
type="button"
class="experts-tab-btn"
:class="{ active: activeExpertTab === 'dynamic' }"
@click="activeExpertTab = 'dynamic'"
>
专家动态
</button>
</div>
<NuxtLink to="/expert" class="experts-more-link">更多&gt;&gt;</NuxtLink>
</div>
<!-- <div class="experts-panel-list">-->
<!-- <NuxtLink v-for="item in filteredExpertTextItems" :key="item.id" :to="getArticleLink(item.id)" class="experts-panel-item">-->
<!-- <span>{{ item.title }}</span>-->
<!-- <time>{{ item.date }}</time>-->
<!-- </NuxtLink>-->
<!-- </div>-->
</div>
<NuxtLink to="/expert/apply" class="expert-apply-btn">
<img :src="expertApplyImage" alt="专家申请"/>
</NuxtLink>
</div>
<div class="experts-grid">
<div class="experts-card-list">
<NuxtLink v-for="item in filteredExpertCards" :key="item.id" :to="getArticleLink(item.id)"
class="expert-card">
<img :src="item.image || articleFallbackImage" :alt="item.title"/>
<div class="expert-card-body">
<h3>{{ item.title }}</h3>
<p>{{ item.date }}</p>
</div>
</NuxtLink>
</div>
<div class="experts-tabs-panel notice-panel">
<div class="experts-tabs-panel-head">
<strong>通知公告</strong>
<NuxtLink to="/news?type=announcement" class="experts-more-link">更多&gt;&gt;</NuxtLink>
</div>
<div class="experts-panel-list">
<NuxtLink v-for="item in noticeItems" :key="item.id" :to="getArticleLink(item.id)"
class="experts-panel-item">
<span>{{ item.title }}</span>
<time>{{ item.date }}</time>
</NuxtLink>
</div>
</div>
</div>
</section>
<section v-if="bottomBannerAd?.image" class="ad-banner image-banner">
<component
:is="bottomBannerAd?.linkUrl ? 'a' : 'div'"
v-bind="getAdLinkAttrs(bottomBannerAd)"
class="home-ad-link"
>
<img :src="bottomBannerAd.image" :alt="bottomBannerAd.title || '广告位3'"/>
</component>
</section>
<section class="section-block">
<div class="article-columns two">
<div class="article-panel" v-for="group in thinkTankGroups" :key="group.title">
<div class="panel-heading">
<span>{{ group.title }}</span>
<NuxtLink :to="group.more">更多&gt;&gt;</NuxtLink>
</div>
<div class="article-list" :class="{ 'logo-list-wrap': group.type === 'logos' }">
<template v-if="group.type === 'logos'">
<NuxtLink
v-for="item in group.items"
:key="item.title"
:to="group.more"
class="logo-card"
>
<img v-if="item.image" :src="item.image" :alt="item.title" class="logo-image"/>
<span>{{ item.title }}</span>
</NuxtLink>
</template>
<template v-else>
<NuxtLink
v-for="item in group.items"
:key="item.id || item.title"
:to="getGroupItemLink(group, item)"
class="article-row"
:class="{ 'no-image': !shouldShowGroupImage(group) }"
>
<div class="article-text">
<h3>{{ item.title }}</h3>
<p>{{ item.summary }}</p>
<span>{{ item.date }}</span>
</div>
<img v-if="shouldShowGroupImage(group)" :src="item.image || articleFallbackImage" alt="文章配图"/>
</NuxtLink>
</template>
</div>
</div>
</div>
</section>
<section class="action-grid">
<NuxtLink v-for="item in actionLinks" :key="item.title" :to="item.to" class="action-card" :class="item.theme">
<span class="action-icon">{{ item.icon }}</span>
<span>{{ item.title }}</span>
</NuxtLink>
</section>
</main>
<footer class="site-footer">
<PortalFooter/>
</footer>
</div>
</template>
<script setup lang="ts">
import {
listSiteAdSlots,
listSiteArticleCategories,
pageSiteMembershipServices,
pageSiteModuleEntries,
pageSiteSuggestions,
pageSiteArticles,
resolveSiteAssetUrl,
type SiteAdSlot,
type SiteArticle,
type SiteArticleCategory,
type SiteMembershipService,
type SiteModuleEntry,
type SiteSuggestion,
} from '@/api/site'
import {usePageSeo} from '@/composables/usePageSeo'
definePageMeta({
layout: 'blank'
})
usePageSeo({
title: '首页',
description: '广西决策咨询网首页'
})
const promoBannerImage = '/images/jczx.png'
const subnavStripImage = '/images/%E5%9B%BE%E5%B1%82%2050.png'
const expertSectionTitleImage = '/images/zjzx.png'
const expertSectionBgImage = '/images/zjzxBg.png'
const expertApplyImage = '/images/zjsq.png'
const articleFallbackImage = '/images/%E6%97%A0%E5%9B%BE%E7%89%87.png'
type TextItem = {
id?: number
title: string
summary: string
date: string
image?: string
}
type LogoItem = {
title: string
image?: string
}
type Group = {
title: string
more: string
items: Array<TextItem | LogoItem>
type?: 'logos'
}
type HeroNewsItem = {
id: number
title: string
summary: string
image?: string
date?: string
}
type HeadlineCategoryItem = {
key: string
title: string
path: string
routeKey?: string
categoryId?: number
}
const EMPTY_HERO_ITEM: HeroNewsItem = {
id: 0,
title: '',
summary: '',
}
function getArticleLink(id?: number) {
return id ? `/article/${id}` : '/news'
}
function getGroupItemLink(group: Group, item: TextItem | LogoItem) {
if ('id' in item && item.id) {
if (group.title === '会员服务') {
return `/membership/${item.id}`
}
if (group.title === '支持单位') {
return `/support-units/${item.id}`
}
if (group.title === '建言献策') {
return `/suggestions/${item.id}`
}
return getArticleLink(item.id)
}
return group.more
}
function shouldShowGroupImage(group: Group) {
return !['会员服务', '建言献策'].includes(group.title)
}
const defaultConsultingGroups: Group[] = [
{title: '市县决策', more: '/consultation?type=city', items: []},
{title: '前沿观察', more: '/consultation?type=frontier', items: []},
{title: '行业资讯', more: '/consultation?type=industry', items: []},
{title: '企业动态', more: '/consultation?type=enterprise', items: []}
]
const defaultReferenceGroups: Group[] = [
{title: '政策原文', more: '/reference?type=policy', items: []},
{title: '东盟研究', more: '/reference?type=asean', items: []},
{title: '翰墨文谈', more: '/hanmo', items: []},
{title: '会员服务', more: '/membership', items: []}
]
const defaultThinkTankGroups: Group[] = [
{title: '智库观察', more: '/think-tank?type=view', items: []},
{title: '建言献策', more: '/suggestions', items: []},
{title: '翰墨文谈', more: '/hanmo', items: []},
{title: '支持单位', more: '/support-units', items: []}
]
type ExpertTab = 'viewpoint' | 'dynamic'
type ExpertCard = {
id: number
title: string
date: string
image: string
tab: ExpertTab
}
const activeExpertTab = ref<ExpertTab>('viewpoint')
const defaultExpertCards: ExpertCard[] = [
{id: 401, title: '专家试点一', date: '2026-03-19', image: '/images/video-poster.jpg', tab: 'viewpoint'},
{id: 402, title: '专家试点二', date: '2026-03-18', image: '/images/video-poster.jpg', tab: 'viewpoint'},
{id: 403, title: '专家试点三', date: '2026-03-17', image: '/images/video-poster.jpg', tab: 'viewpoint'},
{id: 404, title: '专家试点四', date: '2026-03-16', image: '/images/video-poster.jpg', tab: 'viewpoint'},
{id: 405, title: '专家动态一', date: '2026-03-15', image: '/images/video-poster.jpg', tab: 'dynamic'},
{id: 406, title: '专家动态二', date: '2026-03-14', image: '/images/video-poster.jpg', tab: 'dynamic'},
{id: 407, title: '专家动态三', date: '2026-03-13', image: '/images/video-poster.jpg', tab: 'dynamic'},
{id: 408, title: '专家动态四', date: '2026-03-12', image: '/images/video-poster.jpg', tab: 'dynamic'}
]
const heroNews = ref<HeroNewsItem[]>([])
const recommendedHeadline = ref<HeroNewsItem | null>(null)
const headlineCategoryNews = ref<HeroNewsItem[]>([])
const headlineCategories = ref<HeadlineCategoryItem[]>([
{key: 'central', title: '党中央国务院信息', path: '/news?type=central', routeKey: 'central'},
{key: 'region', title: '自治区党委政府信息', path: '/news?type=region', routeKey: 'region'},
{key: 'department', title: '其他(厅委办)', path: '/news?type=department', routeKey: 'department'},
{key: 'latest', title: '最新', path: '/news?type=latest', routeKey: 'latest'},
])
const activeHeadlineCategoryKey = ref(headlineCategories.value[0]?.key || '')
const consultingGroups = ref<Group[]>(defaultConsultingGroups)
const referenceGroups = ref<Group[]>(defaultReferenceGroups)
const thinkTankGroups = ref<Group[]>(defaultThinkTankGroups)
const expertCards = ref<ExpertCard[]>([])
const topBannerAd = ref<SiteAdSlot | null>(null)
const middleBannerAd = ref<SiteAdSlot | null>(null)
const bottomBannerAd = ref<SiteAdSlot | null>(null)
const headlinePrimary = computed(() => heroNews.value[0] || EMPTY_HERO_ITEM)
const headlineSecondary = computed(() => recommendedHeadline.value || heroNews.value[1] || heroNews.value[0] || EMPTY_HERO_ITEM)
const activeHeadlineCategoryPath = computed(() => {
return headlineCategories.value.find((item) => item.key === activeHeadlineCategoryKey.value)?.path || '/news'
})
const headlineNewsLinks = computed(() => headlineCategoryNews.value)
const filteredExpertCards = computed(() => expertCards.value.filter(item => item.tab === activeExpertTab.value))
const filteredExpertTextItems = computed(() => filteredExpertCards.value.map((item) => ({
id: item.id,
title: item.title,
date: item.date,
})))
const noticeItems = ref<TextItem[]>([])
const SECTION_SOURCE_BY_TITLE: Record<string, { category: string; type?: string; more: string }> = {
'市县决策': {category: 'consultation', type: 'city', more: '/consultation?type=city'},
'前沿观察': {category: 'consultation', type: 'frontier', more: '/consultation?type=frontier'},
'行业资讯': {category: 'consultation', type: 'industry', more: '/consultation?type=industry'},
'企业动态': {category: 'consultation', type: 'enterprise', more: '/consultation?type=enterprise'},
'政策原文': {category: 'reference', type: 'policy', more: '/reference?type=policy'},
'东盟研究': {category: 'reference', type: 'asean', more: '/reference?type=asean'},
'翰墨文谈': {category: 'hanmo', more: '/hanmo'},
'智库观察': {category: 'think-tank', type: 'view', more: '/think-tank?type=view'},
'建言献策': {category: 'suggestions', more: '/suggestions'},
}
function normalizeTextItem(item: SiteArticle): TextItem {
return {
id: item.articleId || item.id,
title: item.title,
summary: item.overview || '暂无摘要',
date: item.publishTime || item.createTime || '',
image: resolveSiteAssetUrl(item.image),
}
}
function normalizeHeroItem(item: SiteArticle): HeroNewsItem {
return {
id: item.articleId || item.id,
title: item.title,
summary: item.overview || '暂无摘要',
image: resolveSiteAssetUrl(item.image),
date: item.publishTime || item.createTime || '',
}
}
function normalizeExpertItem(item: SiteArticle, tab: ExpertTab): ExpertCard {
return {
id: item.articleId || item.id,
title: item.title,
date: item.publishTime || item.createTime || '',
image: resolveSiteAssetUrl(item.image),
tab,
}
}
function normalizeLogoItem(item: SiteAdSlot): LogoItem {
return {
title: item.title || item.slotName || '单位名称',
image: resolveSiteAssetUrl(item.image),
}
}
function normalizeModuleEntryItem(item: SiteModuleEntry): TextItem {
return {
id: item.entryId || item.id,
title: item.title,
summary: item.summary || item.content || '暂无简介',
date: item.publishTime || item.createTime?.slice(0, 10) || '',
image: resolveSiteAssetUrl(item.image),
}
}
function normalizeMembershipServiceItem(item: SiteMembershipService): TextItem {
return {
id: item.serviceId || item.id,
title: item.title,
summary: item.summary || '会员服务',
date: item.publishTime || '',
}
}
function normalizeSuggestionItem(item: SiteSuggestion): TextItem {
return {
id: item.suggestionId || item.id,
title: item.title,
summary: item.content || '暂无内容',
date: item.createTime?.slice(0, 10) || '',
}
}
function normalizeAdSlot(item?: SiteAdSlot): SiteAdSlot | null {
if (!item) {
return null
}
return {
...item,
image: resolveSiteAssetUrl(item.image),
}
}
function findHomeAdSlot(items: SiteAdSlot[], slotCode: string, legacySlotCode: string) {
return normalizeAdSlot(
items.find((item) => item.slotCode === slotCode) ||
items.find((item) => item.slotCode === legacySlotCode)
)
}
function getAdLinkAttrs(ad?: SiteAdSlot | null) {
if (!ad?.linkUrl) {
return {}
}
return {
href: ad.linkUrl,
target: Number(ad.target) === 1 ? '_blank' : undefined,
rel: Number(ad.target) === 1 ? 'noopener noreferrer' : undefined,
}
}
async function loadHeadlineCategoryNews(item?: HeadlineCategoryItem | null) {
if (!item) {
headlineCategoryNews.value = []
return
}
if (item.key === 'latest' || item.routeKey === 'latest') {
const latestData = await pageSiteArticles({
page: 1,
limit: 3,
category: 'news',
})
headlineCategoryNews.value = (latestData.list || []).map(normalizeHeroItem)
return
}
const data = await pageSiteArticles({
page: 1,
limit: 4,
category: 'news',
type: item.routeKey,
categoryId: item.routeKey ? undefined : item.categoryId,
})
headlineCategoryNews.value = (data.list || []).map(normalizeHeroItem)
}
async function handleHeadlineCategoryChange(item: HeadlineCategoryItem) {
if (activeHeadlineCategoryKey.value === item.key) {
return
}
activeHeadlineCategoryKey.value = item.key
await loadHeadlineCategoryNews(item)
}
async function fetchItems(category: string, type?: string, limit = 3) {
const data = await pageSiteArticles({
page: 1,
limit,
category,
type,
})
return (data.list || []).map(normalizeTextItem)
}
async function fetchItemsByTitle(title: string, limit = 3) {
const source = SECTION_SOURCE_BY_TITLE[title]
if (!source) {
return []
}
return fetchItems(source.category, source.type, limit)
}
async function buildGroupsByTitle(groups: Group[]) {
const itemResults = await Promise.all(
groups.map(async (group) => {
if (group.type === 'logos') {
return group.items
}
const items = await fetchItemsByTitle(group.title, 3)
return items.length ? items : group.items
})
)
return groups.map((group, index) => ({
...group,
more: SECTION_SOURCE_BY_TITLE[group.title]?.more || group.more,
items: itemResults[index],
}))
}
function normalizeHeadlineCategories(categories: SiteArticleCategory[]) {
return categories.map((item) => ({
key: item.routeKey || item.categoryCode || `${item.categoryId}`,
title: item.title,
path: item.path || `/news${item.routeKey ? `?type=${item.routeKey}` : ''}`,
routeKey: item.routeKey,
categoryId: item.categoryId,
}))
}
async function loadHomeData() {
try {
const [
newsCategories,
newsData,
recommendedData,
consultingGroupData,
referenceGroupData,
thinkTankGroupData,
expertViewItems,
expertDynamicItems,
noticeData,
homeAds,
membershipServicePage,
supportUnitPage,
suggestionPage,
supportUnitAds,
] = await Promise.all([
listSiteArticleCategories({category: 'news'}),
pageSiteArticles({page: 1, limit: 6, category: 'news'}),
pageSiteArticles({page: 1, limit: 1, recommend: 1}),
buildGroupsByTitle(defaultConsultingGroups),
buildGroupsByTitle(defaultReferenceGroups),
buildGroupsByTitle(defaultThinkTankGroups),
pageSiteArticles({page: 1, limit: 4, category: 'expert', type: 'view'}),
pageSiteArticles({page: 1, limit: 4, category: 'expert', type: 'dynamic'}),
pageSiteArticles({page: 1, limit: 4, category: 'announcement'}),
listSiteAdSlots(),
pageSiteMembershipServices({page: 1, limit: 3}),
pageSiteModuleEntries({page: 1, limit: 8, type: 'support_unit'}),
pageSiteSuggestions({page: 1, limit: 3}),
listSiteAdSlots({slotCode: 'home-support-units'}),
])
const newsRoot = (newsCategories || []).find((item) => item.children?.length)
if (newsRoot?.children?.length) {
headlineCategories.value = normalizeHeadlineCategories(newsRoot.children)
activeHeadlineCategoryKey.value = headlineCategories.value[0]?.key || ''
}
if (newsData.list?.length) {
heroNews.value = newsData.list.map(normalizeHeroItem)
}
recommendedHeadline.value = recommendedData.list?.[0] ? normalizeHeroItem(recommendedData.list[0]) : null
consultingGroups.value = consultingGroupData
referenceGroups.value = referenceGroupData.map((group) => group.title === '会员服务'
? {
...group,
items: [],
}
: group
)
referenceGroups.value = referenceGroups.value.map((group) => group.title === '会员服务'
? {
...group,
items: (membershipServicePage.list || []).map(normalizeMembershipServiceItem),
}
: group
)
thinkTankGroups.value = thinkTankGroupData.map((group) => {
if (group.title === '建言献策') {
return {
...group,
items: (suggestionPage.list || []).map(normalizeSuggestionItem),
}
}
if (group.title === '支持单位') {
return {
...group,
items: (supportUnitPage.list || []).map(normalizeModuleEntryItem),
}
}
return group
})
expertCards.value = [
...(expertViewItems.list || []).map((item) => normalizeExpertItem(item, 'viewpoint')),
...(expertDynamicItems.list || []).map((item) => normalizeExpertItem(item, 'dynamic')),
]
noticeItems.value = (noticeData.list || []).map(normalizeTextItem)
topBannerAd.value = findHomeAdSlot(homeAds || [], 'home-ad-1', 'home-top-banner')
middleBannerAd.value = findHomeAdSlot(homeAds || [], 'home-ad-2', 'home-middle-banner')
bottomBannerAd.value = findHomeAdSlot(homeAds || [], 'home-ad-3', 'home-bottom-banner')
await loadHeadlineCategoryNews(headlineCategories.value[0] || null)
} catch (error) {
console.error('首页数据加载失败', error)
}
}
const actionLinks = [
{title: '资料下载', to: '/downloads', icon: '⇩', theme: 'cyan'},
{title: '申报模板', to: '/material-templates', icon: '▣', theme: 'blue'},
{title: '成果摘编', to: '/achievement-digests', icon: '▤', theme: 'green'},
{title: '联系我们', to: '/contact', icon: '✆', theme: 'red'}
]
onMounted(() => {
loadHomeData()
})
</script>
<style scoped>
.home-page {
min-height: 100vh;
background: #ffffff;
color: #1f2d3d;
}
.container {
width: min(1200px, calc(100% - 32px));
margin: 0 auto;
}
.page-main {
padding: 14px 0 40px;
}
.headline-grid {
margin-bottom: 28px;
}
.headline-content {
display: grid;
grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
gap: 34px;
align-items: start;
}
.headline-main-title {
display: flex;
align-items: center;
justify-content: center;
min-height: 132px;
margin-bottom: 36px;
padding: 18px 24px 26px;
border-bottom: 1px solid #ececec;
color: #2236c6;
font-size: clamp(28px, 3.2vw, 42px);
font-weight: 500;
line-height: 1.35;
letter-spacing: 1px;
text-align: center;
}
.headline-left,
.headline-right,
.article-panel,
.experts-section {
background: #fff;
}
.headline-image-card {
display: block;
padding: 0;
background: #fff;
}
.headline-image-card img {
width: 100%;
display: block;
object-fit: contain;
height: 430px;
border-radius: 4px;
}
.headline-image-title {
margin-top: 20px;
color: #2d2d2d;
font-size: 18px;
font-weight: 700;
line-height: 1.45;
padding: 0 6px;
}
.headline-image-date {
margin-top: 14px;
color: #8e8e8e;
font-size: 14px;
font-weight: 500;
padding: 0 6px 10px;
}
.headline-right {
padding-top: 2px;
}
.subnav-strip {
display: flex;
align-items: center;
gap: 2px;
margin-bottom: 22px;
padding: 10px;
background: #266ed6;
color: #fff;
font-size: 14px;
line-height: 1;
min-height: 50px;
white-space: nowrap;
}
.subnav-strip .active {
padding: 10px;
background: var(--subnav-active-bg) center / 100% 100% no-repeat;
font-weight: 700;
text-align: center;
}
.subnav-strip-link,
.subnav-strip-more {
color: inherit;
}
.subnav-strip-link {
border: 0;
background: transparent;
cursor: pointer;
font: inherit;
line-height: inherit;
}
.subnav-strip-more {
margin-left: auto;
font-weight: 600;
}
.news-links {
display: flex;
flex-direction: column;
gap: 0;
}
.news-link-card {
padding: 18px 8px 20px;
border-bottom: 1px solid #e9e9e9;
}
.news-link-card h3 {
margin: 0 0 16px;
color: #3777c4;
font-size: 18px;
font-weight: 700;
line-height: 1.45;
}
.news-link-card p {
margin: 0;
color: #454545;
font-size: 14px;
line-height: 1.95;
display: -webkit-box;
overflow: hidden;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.ad-banner {
display: flex;
align-items: center;
justify-content: center;
min-height: 90px;
margin: 0 0 28px;
font-size: 20px;
letter-spacing: 1px;
}
.image-banner {
min-height: 0;
background: transparent;
}
.home-ad-link {
display: block;
width: 100%;
}
.image-banner img,
.image-heading img,
.home-ad-link img {
width: 100%;
display: block;
border-radius: 4px;
}
.ad-banner.soft {
background: linear-gradient(155deg, rgba(217, 15, 34, 0.95) 0 18%, transparent 18%),
linear-gradient(180deg, #fff4eb, #fff8f1);
color: #333;
}
.ad-banner.blue {
min-height: 74px;
background: #67add6;
color: #fff;
}
.ad-banner.scenic {
min-height: 110px;
background: linear-gradient(rgba(48, 142, 205, 0.22), rgba(48, 142, 205, 0.22)),
url('/images/banner.png') center 62%/cover;
color: #fff;
}
.section-block {
margin-bottom: 28px;
}
.section-heading {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 20px;
}
.image-heading {
margin-bottom: 22px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.section-heading-title {
position: relative;
padding: 0 26px;
text-align: center;
}
.section-heading-title::before,
.section-heading-title::after {
content: '';
position: absolute;
top: 14px;
width: 120px;
height: 1px;
background: #bfd5ea;
}
.section-heading-title::before {
right: 100%;
}
.section-heading-title::after {
left: 100%;
}
.section-heading-title span {
display: block;
color: #2f6da8;
font-size: 28px;
font-weight: 700;
line-height: 1.1;
}
.section-heading-title small {
color: #a6b9ca;
font-size: 11px;
letter-spacing: 2px;
}
.article-columns.two {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 22px;
}
.article-panel {
padding: 4px 0 0;
}
.panel-heading {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 0 10px;
border-bottom: 1px solid #e6eef5;
}
.panel-heading span {
color: #2c6eaa;
font-size: 20px;
font-weight: 700;
}
.panel-heading a {
color: #9aa3aa;
font-size: 12px;
}
.article-list {
padding-top: 6px;
}
.article-row {
display: grid;
grid-template-columns: 1fr 105px;
gap: 16px;
padding: 12px 0;
border-bottom: 1px dashed #e8edf2;
}
.article-row.no-image {
grid-template-columns: 1fr;
}
.article-row:last-child {
border-bottom: 0;
}
.article-text h3 {
margin: 0 0 8px;
color: #2a2a2a;
font-size: 15px;
font-weight: 700;
line-height: 1.5;
}
.article-text p {
margin: 0 0 8px;
color: #6e7881;
font-size: 13px;
line-height: 1.6;
}
.article-text span {
color: #a3aab0;
font-size: 12px;
}
.article-row img {
width: 105px;
height: 72px;
object-fit: contain;
background: #f7f9fb;
border-radius: 4px;
}
.experts-section {
margin-bottom: 28px;
}
.experts-heading {
justify-content: center;
margin-bottom: 18px;
min-height: 68px;
background-position: center;
background-repeat: no-repeat;
background-size: 100% 100%;
}
.experts-heading-title {
position: absolute;
left: 50%;
top: 50%;
width: 100%;
transform: translate(-50%, -50%);
}
.experts-tabs {
display: grid;
grid-template-columns: minmax(0, 1fr) 132px minmax(0, 1fr);
align-items: center;
gap: 28px;
padding-bottom: 18px;
border-bottom: 1px solid #e6eef5;
margin-bottom: 18px;
}
.experts-tabs-panel {
min-width: 0;
}
.experts-tabs-panel-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 12px;
}
.experts-tabs-left {
display: flex;
align-items: center;
gap: 26px;
}
.notice-panel strong {
color: #2c6eaa;
font-size: 15px;
}
.experts-more-link {
flex-shrink: 0;
color: #8b98a3;
font-size: 13px;
}
.experts-more-link:hover {
color: #2c6eaa;
}
.experts-panel-list {
display: grid;
gap: 9px;
}
.experts-panel-item {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
gap: 16px;
color: #3d4d5a;
font-size: 14px;
line-height: 1.4;
}
.experts-panel-item::before {
content: '';
width: 4px;
height: 4px;
border-radius: 50%;
background: #2c6eaa;
grid-column: 1;
grid-row: 1;
}
.experts-panel-item span {
min-width: 0;
padding-left: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
grid-column: 1;
grid-row: 1;
}
.experts-panel-item time {
color: #9aa5ad;
font-size: 12px;
white-space: nowrap;
}
.experts-tab-btn {
padding: 0;
border: 0;
background: transparent;
color: #798895;
font-size: 15px;
cursor: pointer;
transition: color 0.2s ease;
}
.experts-tab-btn:hover {
color: #2c6eaa;
}
.experts-tab-btn.active {
color: #2c6eaa;
font-weight: 700;
}
.expert-apply-btn {
display: block;
width: 132px;
line-height: 0;
justify-self: center;
}
.expert-apply-btn img {
width: 100%;
display: block;
border-radius: 4px;
}
.experts-grid {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
gap: 18px;
align-items: start;
}
.experts-card-list {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 18px;
}
.expert-card {
background: #fff;
border: 1px solid #edf2f5;
}
.expert-card img {
width: 100%;
height: 220px;
object-fit: contain;
background: #f7f9fb;
display: block;
border-radius: 4px;
}
.expert-card-body {
padding: 10px 10px 12px;
}
.expert-card-body h3 {
margin: 0 0 6px;
color: #d54235;
font-size: 14px;
font-weight: 700;
}
.expert-card-body p {
margin: 0;
color: #8f989f;
font-size: 12px;
}
.logo-list-wrap {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 14px 10px;
}
.logo-card {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 8px 0;
}
.logo-circle {
width: 70px;
height: 70px;
border-radius: 50%;
background: radial-gradient(circle at 50% 50%, #fff 0 24%, #1d2b4b 25% 36%, #7ea2d8 37% 44%, #0d1730 45% 100%);
color: #fff;
font-size: 10px;
display: flex;
align-items: center;
justify-content: center;
letter-spacing: 1px;
}
.logo-card span {
color: #3d4d5a;
font-size: 12px;
}
.logo-image {
border-radius: 4px;
}
.action-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
margin-bottom: 32px;
}
.action-card {
display: flex;
align-items: center;
justify-content: center;
gap: 14px;
min-height: 78px;
background: #fff;
border: 2px solid #dceaf3;
color: #5c8aa5;
font-size: 28px;
font-weight: 700;
}
.action-card span:last-child {
font-size: 28px;
}
.action-icon {
font-size: 34px;
line-height: 1;
}
.action-card.cyan {
border-color: #cce9ef;
color: #55abc0;
}
.action-card.blue {
border-color: #d6e6f6;
color: #4e87c8;
}
.action-card.green {
border-color: #d9efe7;
color: #7db9aa;
}
.action-card.red {
border-color: #f4d8d6;
color: #db7f77;
}
.site-footer {
background: #fff;
}
.footer-nav {
background: #0f69b9;
}
.footer-nav-inner {
display: flex;
gap: 22px;
padding: 10px 0;
}
.footer-nav a {
color: #fff;
font-size: 12px;
}
@media (max-width: 1100px) {
.headline-content,
.article-columns.two,
.experts-tabs,
.experts-grid,
.action-grid {
grid-template-columns: 1fr;
}
.experts-tabs {
align-items: stretch;
gap: 18px;
}
.expert-apply-btn {
width: 160px;
}
.headline-right,
.headline-left,
.experts-section {
padding-left: 0;
padding-right: 0;
}
.headline-main-title {
min-height: 108px;
margin-bottom: 24px;
font-size: 30px;
}
.headline-image-card img {
height: 380px;
}
.headline-image-title,
.news-link-card h3 {
font-size: 18px;
}
.headline-image-date,
.news-link-card p,
.subnav-strip {
font-size: 14px;
}
.logo-list-wrap {
grid-template-columns: repeat(3, 1fr);
}
}
@media (max-width: 768px) {
.container {
width: min(100%, calc(100% - 24px));
}
.headline-content {
gap: 18px;
}
.headline-right,
.headline-left,
.article-panel,
.experts-section {
padding: 0;
}
.experts-tabs-panel-head {
gap: 10px;
}
.experts-tabs-left {
gap: 18px;
}
.experts-panel-item {
gap: 10px;
font-size: 13px;
}
.headline-main-title {
min-height: auto;
margin-bottom: 18px;
padding: 10px 6px 18px;
font-size: 24px;
line-height: 1.45;
}
.headline-image-card img {
height: 250px;
}
.headline-image-title,
.news-link-card h3 {
font-size: 20px;
}
.headline-image-date,
.news-link-card p {
font-size: 14px;
}
.subnav-strip {
gap: 10px;
margin-bottom: 12px;
padding: 12px 14px;
font-size: 13px;
min-height: 52px;
overflow-x: auto;
}
.subnav-strip .active {
padding: 10px 12px;
}
.news-link-card {
padding: 14px 0 16px;
}
.section-heading {
justify-content: flex-start;
}
.section-heading-title {
padding: 0 0 0 2px;
text-align: left;
}
.section-heading-title::before,
.section-heading-title::after {
display: none;
}
.expert-apply-btn {
position: static;
transform: none;
margin-left: auto;
width: 100px;
}
.experts-grid {
grid-template-columns: repeat(2, 1fr);
}
.expert-card img {
height: 170px;
}
.logo-list-wrap {
grid-template-columns: repeat(2, 1fr);
}
.article-row {
grid-template-columns: 1fr;
}
.article-row img {
width: 100%;
height: 180px;
}
.action-card,
.action-card span:last-child {
font-size: 22px;
}
.action-icon {
font-size: 28px;
}
.footer-nav-inner {
flex-wrap: wrap;
gap: 10px 16px;
}
}
</style>