Compare commits

...

3 Commits

Author SHA1 Message Date
9e24153eac 更新首页 2026-04-29 10:28:56 +08:00
dccb358d4f Merge remote-tracking branch 'origin/main'
# Conflicts:
#	.data/content/contents.sqlite
2026-04-29 10:06:16 +08:00
188b73a551 更新首页 2026-04-29 10:05:55 +08:00
17 changed files with 2028 additions and 1046 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
/.nuxt/
/node_modules/

10
.idea/UniappTool.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="cn.fjdmy.uniapp.UniappProjectDataService">
<option name="generalBasePath" value="$PROJECT_DIR$" />
<option name="manifestPath" value="$PROJECT_DIR$/manifest.json" />
<option name="pagesPath" value="$PROJECT_DIR$/pages.json" />
<option name="scanNum" value="1" />
<option name="type" value="store" />
</component>
</project>

View File

@@ -0,0 +1,125 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="HtmlUnknownAttribute" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myValues">
<value>
<list size="2">
<item index="0" class="java.lang.String" itemvalue="class" />
<item index="1" class="java.lang.String" itemvalue="style" />
</list>
</value>
</option>
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myValues">
<value>
<list size="100">
<item index="0" class="java.lang.String" itemvalue="nobr" />
<item index="1" class="java.lang.String" itemvalue="noembed" />
<item index="2" class="java.lang.String" itemvalue="comment" />
<item index="3" class="java.lang.String" itemvalue="noscript" />
<item index="4" class="java.lang.String" itemvalue="embed" />
<item index="5" class="java.lang.String" itemvalue="script" />
<item index="6" class="java.lang.String" itemvalue="el-form" />
<item index="7" class="java.lang.String" itemvalue="el-input" />
<item index="8" class="java.lang.String" itemvalue="el-form-item" />
<item index="9" class="java.lang.String" itemvalue="el-button" />
<item index="10" class="java.lang.String" itemvalue="el-container" />
<item index="11" class="java.lang.String" itemvalue="el-header" />
<item index="12" class="java.lang.String" itemvalue="el-menu" />
<item index="13" class="java.lang.String" itemvalue="el-menu-item" />
<item index="14" class="java.lang.String" itemvalue="el-submenu" />
<item index="15" class="java.lang.String" itemvalue="el-breadcrumb" />
<item index="16" class="java.lang.String" itemvalue="el-breadcrumb-item" />
<item index="17" class="java.lang.String" itemvalue="el-dialog" />
<item index="18" class="java.lang.String" itemvalue="el-row" />
<item index="19" class="java.lang.String" itemvalue="el-col" />
<item index="20" class="java.lang.String" itemvalue="el-table-column" />
<item index="21" class="java.lang.String" itemvalue="el-table" />
<item index="22" class="java.lang.String" itemvalue="el-pagination" />
<item index="23" class="java.lang.String" itemvalue="el-upload" />
<item index="24" class="java.lang.String" itemvalue="el-select" />
<item index="25" class="java.lang.String" itemvalue="el-option" />
<item index="26" class="java.lang.String" itemvalue="el-date-picker" />
<item index="27" class="java.lang.String" itemvalue="el-switch" />
<item index="28" class="java.lang.String" itemvalue="u-swiper" />
<item index="29" class="java.lang.String" itemvalue="u-image" />
<item index="30" class="java.lang.String" itemvalue="u-button" />
<item index="31" class="java.lang.String" itemvalue="web-view" />
<item index="32" class="java.lang.String" itemvalue="u-avatar" />
<item index="33" class="java.lang.String" itemvalue="u-text" />
<item index="34" class="java.lang.String" itemvalue="u-grid" />
<item index="35" class="java.lang.String" itemvalue="u-grid-item" />
<item index="36" class="java.lang.String" itemvalue="u-icon" />
<item index="37" class="java.lang.String" itemvalue="u-cell-group" />
<item index="38" class="java.lang.String" itemvalue="u-cell" />
<item index="39" class="java.lang.String" itemvalue="u-tabs" />
<item index="40" class="java.lang.String" itemvalue="u-gap" />
<item index="41" class="java.lang.String" itemvalue="scroll-view" />
<item index="42" class="java.lang.String" itemvalue="u-toast" />
<item index="43" class="java.lang.String" itemvalue="u-subsection" />
<item index="44" class="java.lang.String" itemvalue="u-empty" />
<item index="45" class="java.lang.String" itemvalue="u-popup" />
<item index="46" class="java.lang.String" itemvalue="uqrcode" />
<item index="47" class="java.lang.String" itemvalue="temlate" />
<item index="48" class="java.lang.String" itemvalue="el-tree" />
<item index="49" class="java.lang.String" itemvalue="el-card" />
<item index="50" class="java.lang.String" itemvalue="el-tag" />
<item index="51" class="java.lang.String" itemvalue="u-modal" />
<item index="52" class="java.lang.String" itemvalue="rich-text" />
<item index="53" class="java.lang.String" itemvalue="u--text" />
<item index="54" class="java.lang.String" itemvalue="u-line-progress" />
<item index="55" class="java.lang.String" itemvalue="u--image" />
<item index="56" class="java.lang.String" itemvalue="page" />
<item index="57" class="java.lang.String" itemvalue="el-aside" />
<item index="58" class="java.lang.String" itemvalue="house" />
<item index="59" class="java.lang.String" itemvalue="el-dropdown" />
<item index="60" class="java.lang.String" itemvalue="arrowdown" />
<item index="61" class="java.lang.String" itemvalue="el-dropdown-menu" />
<item index="62" class="java.lang.String" itemvalue="caretleft" />
<item index="63" class="java.lang.String" itemvalue="close" />
<item index="64" class="java.lang.String" itemvalue="fold" />
<item index="65" class="java.lang.String" itemvalue="expand" />
<item index="66" class="java.lang.String" itemvalue="caretright" />
<item index="67" class="java.lang.String" itemvalue="upload" />
<item index="68" class="java.lang.String" itemvalue="el-cascader" />
<item index="69" class="java.lang.String" itemvalue="u-search" />
<item index="70" class="java.lang.String" itemvalue="u-scroll-list" />
<item index="71" class="java.lang.String" itemvalue="u-navbar" />
<item index="72" class="java.lang.String" itemvalue="u-loadmore" />
<item index="73" class="java.lang.String" itemvalue="u-parse" />
<item index="74" class="java.lang.String" itemvalue="u-number-box" />
<item index="75" class="java.lang.String" itemvalue="u-tag" />
<item index="76" class="java.lang.String" itemvalue="u-checkbox" />
<item index="77" class="java.lang.String" itemvalue="u-checkbox-group" />
<item index="78" class="java.lang.String" itemvalue="checkbox" />
<item index="79" class="java.lang.String" itemvalue="u-action-sheet" />
<item index="80" class="java.lang.String" itemvalue="u-line" />
<item index="81" class="java.lang.String" itemvalue="u-count-to" />
<item index="82" class="java.lang.String" itemvalue="u-swipe-action" />
<item index="83" class="java.lang.String" itemvalue="u-swipe-action-item" />
<item index="84" class="java.lang.String" itemvalue="u-form" />
<item index="85" class="java.lang.String" itemvalue="u-form-item" />
<item index="86" class="java.lang.String" itemvalue="u-input" />
<item index="87" class="java.lang.String" itemvalue="u-switch" />
<item index="88" class="java.lang.String" itemvalue="u-textarea" />
<item index="89" class="java.lang.String" itemvalue="u-picker" />
<item index="90" class="java.lang.String" itemvalue="u-sticky" />
<item index="91" class="java.lang.String" itemvalue="el-popover" />
<item index="92" class="java.lang.String" itemvalue="plus" />
<item index="93" class="java.lang.String" itemvalue="el-input-number" />
<item index="94" class="java.lang.String" itemvalue="el-checkbox" />
<item index="95" class="java.lang.String" itemvalue="el-checkbox-group" />
<item index="96" class="java.lang.String" itemvalue="el-radio-group" />
<item index="97" class="java.lang.String" itemvalue="el-radio-button" />
<item index="98" class="java.lang.String" itemvalue="el-collapse" />
<item index="99" class="java.lang.String" itemvalue="el-collapse-item" />
</list>
</value>
</option>
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
</profile>
</component>

View File

@@ -0,0 +1,76 @@
<template>
<div class="container footer-main">
<div class="footer-left">
<p>扫一扫公众号</p>
<p>了解我们的动态</p>
<img src="/images/qrcode-mp-official.jpg" alt="公众号二维码" />
</div>
<div class="footer-center">
<h3>广西决策咨询网</h3>
<p>Guangxi Decision-Making Consulting Network</p>
<p>运营方广西决策咨询网有限公司</p>
<p>地址广西壮族自治区南宁市中柬路XX号 XX楼 XX号房</p>
</div>
</div>
</template>
<style scoped>
.container {
width: min(1200px, calc(100% - 32px));
margin: 0 auto;
}
.footer-main {
display: flex;
justify-content: space-between;
gap: 60px;
padding: 28px 0 52px;
}
.footer-left {
color: #4f5c67;
font-size: 13px;
}
.footer-left p {
margin: 0 0 8px;
}
.footer-left img {
width: 100px;
height: 100px;
margin-top: 10px;
object-fit: contain;
}
.footer-center {
padding-top: 10px;
color: #697680;
text-align: left;
}
.footer-center h3 {
margin: 0 0 8px;
color: #27313a;
font-size: 30px;
font-weight: 700;
}
.footer-center p {
margin: 0 0 8px;
font-size: 13px;
}
@media (max-width: 1100px) {
.footer-main {
flex-direction: column;
gap: 24px;
}
}
@media (max-width: 768px) {
.container {
width: min(100%, calc(100% - 24px));
}
}
</style>

View File

@@ -0,0 +1,355 @@
<template>
<div>
<div class="topbar">
<div class="container topbar-inner">
<div>{{ currentDateText }}</div>
<div class="topbar-right">
<div class="top-search">
<input v-model="keyword" type="text" placeholder="请输入关键字" />
<button type="button" @click="goSearch">搜索</button>
</div>
<div class="topbar-links">
<NuxtLink to="/about">网站简介</NuxtLink>
<NuxtLink to="/contact">联系我们</NuxtLink>
</div>
</div>
</div>
</div>
<header class="hero" aria-label="广西决策咨询网横幅" />
<nav class="main-nav">
<div class="container nav-inner">
<div
v-for="item in navItems"
:key="item.to"
class="nav-item-group"
>
<NuxtLink
:to="item.to"
class="nav-item"
:class="{ active: isActive(item.to) }"
>
{{ item.label }}
</NuxtLink>
<div v-if="item.children?.length" class="nav-submenu">
<div class="nav-submenu-title">{{ item.label }}</div>
<NuxtLink
v-for="child in item.children"
:key="child.to"
:to="child.to"
class="nav-submenu-item"
>
{{ child.label }}
</NuxtLink>
</div>
</div>
</div>
</nav>
</div>
</template>
<script setup lang="ts">
type NavChild = {
label: string
to: string
}
type NavItem = {
label: string
to: string
children?: NavChild[]
}
const props = defineProps<{
activePath?: string
}>()
const route = useRoute()
const keyword = ref('')
const navItems: NavItem[] = [
{ label: '首 页', to: '/' },
{
label: '政策要闻',
to: '/news',
children: [
{ label: '党中央国务院信息', to: '/news?type=central' },
{ label: '自治区党委政府信息', to: '/news?type=region' },
{ label: '其他(厅委办)信息', to: '/news?type=department' },
{ label: '最新发布', to: '/news?type=latest' }
]
},
{
label: '决策参考',
to: '/reference',
children: [
{ label: '政策原文', to: '/reference?type=policy' },
{ label: '深度解读', to: '/reference?type=analysis' },
{ label: '东盟研究', to: '/reference?type=asean' },
{ label: '数据服务', to: '/reference?type=data' }
]
},
{
label: '决策咨询',
to: '/consultation',
children: [
{ label: '市县决策', to: '/consultation?type=city' },
{ label: '前沿观察', to: '/consultation?type=frontier' },
{ label: '行业资讯', to: '/consultation?type=industry' },
{ label: '企业动态', to: '/consultation?type=enterprise' }
]
},
{
label: '专家资讯',
to: '/expert',
children: [
{ label: '专家视点', to: '/expert?type=view' },
{ label: '专家动态', to: '/expert?type=dynamic' },
{ label: '专家申请', to: '/expert/apply' }
]
},
{
label: '智库观察',
to: '/think-tank',
children: [
{ label: '智库介绍', to: '/think-tank?type=intro' },
{ label: '智库视角', to: '/think-tank?type=view' }
]
},
{ label: '建言献策', to: '/suggestions' },
{
label: '会员服务',
to: '/membership',
children: [
{ label: '企业咨询', to: '/membership?type=consult' },
{ label: '专项服务', to: '/membership?type=service' }
]
},
{ label: '翰墨文谈', to: '/hanmo' },
{
label: '关于我们',
to: '/about',
children: [
{ label: '学会简介', to: '/about' },
{ label: '组织机构', to: '/about/organization' },
{ label: '学会章程', to: '/about/charter' },
{ label: '咨询服务', to: '/about/consultation' }
]
}
]
const currentDateText = computed(() => {
const now = new Date()
return `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}`
})
const resolvedActivePath = computed(() => props.activePath || route.path)
function isActive(path: string) {
const currentPath = resolvedActivePath.value
return currentPath === path || (path !== '/' && currentPath.startsWith(`${path}/`))
}
function goSearch() {
const value = keyword.value.trim()
navigateTo(value ? `/articles?keyword=${encodeURIComponent(value)}` : '/articles')
}
</script>
<style scoped>
.container {
width: min(1200px, calc(100% - 32px));
margin: 0 auto;
}
.topbar {
height: 34px;
border-bottom: 1px solid #e8e8e8;
background: #fafafa;
color: #777;
font-size: 12px;
}
.topbar-inner {
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.topbar-links {
display: flex;
gap: 16px;
}
.topbar-right {
display: flex;
align-items: center;
gap: 18px;
}
.top-search {
display: flex;
align-items: center;
height: 26px;
overflow: hidden;
border: 1px solid #d8d8d8;
background: #fff;
}
.top-search input {
width: 180px;
height: 100%;
border: 0;
padding: 0 8px;
color: #555;
font-size: 12px;
outline: none;
}
.top-search button {
height: 100%;
border: 0;
border-left: 1px solid #e6e6e6;
background: #f5f5f5;
color: #666;
font-size: 12px;
padding: 0 10px;
cursor: pointer;
}
.topbar a {
color: #777;
}
.hero {
position: relative;
height: 320px;
overflow: hidden;
background: url('/images/banner.png') center center / cover no-repeat;
}
.main-nav {
background: linear-gradient(180deg, #1773c2 0%, #0e63b1 100%);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.28);
}
.nav-inner {
display: grid;
grid-template-columns: repeat(10, 1fr);
position: relative;
z-index: 5;
}
.nav-item-group {
position: relative;
}
.nav-item {
position: relative;
display: flex;
align-items: center;
justify-content: center;
min-height: 46px;
color: #fff;
font-size: 15px;
border-right: 1px solid rgba(255, 255, 255, 0.18);
}
.nav-item-group:first-child .nav-item {
border-left: 1px solid rgba(255, 255, 255, 0.18);
}
.nav-item.active,
.nav-item:hover,
.nav-item-group:hover .nav-item {
background: rgba(6, 38, 78, 0.22);
}
.nav-submenu {
position: absolute;
top: 100%;
left: 0;
min-width: 100%;
padding: 0;
background: rgba(20, 90, 150, 0.96);
box-shadow: 0 10px 20px rgba(13, 45, 82, 0.16);
opacity: 0;
visibility: hidden;
transform: translateY(6px);
transition: all 0.18s ease;
}
.nav-item-group:hover .nav-submenu {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.nav-submenu-title {
padding: 12px 16px;
color: #fff;
font-size: 15px;
font-weight: 700;
background: rgba(8, 56, 97, 0.45);
}
.nav-submenu-item {
display: block;
padding: 11px 16px;
color: #fff;
font-size: 13px;
line-height: 1.5;
border-top: 1px solid rgba(255, 255, 255, 0.12);
white-space: nowrap;
}
.nav-submenu-item:hover {
background: rgba(8, 56, 97, 0.44);
}
@media (max-width: 1100px) {
.nav-inner {
grid-template-columns: repeat(5, 1fr);
}
.nav-submenu {
min-width: 180px;
}
}
@media (max-width: 768px) {
.topbar {
height: auto;
padding: 8px 0;
}
.topbar-inner,
.topbar-right {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.top-search input {
width: 150px;
}
.container {
width: min(100%, calc(100% - 24px));
}
.hero {
height: 220px;
}
.nav-inner {
grid-template-columns: repeat(2, 1fr);
}
.nav-submenu {
display: none;
}
}
</style>

View File

@@ -29,7 +29,7 @@
<NuxtLink v-else :to="sub.to">{{ sub.label }}</NuxtLink>
</a-menu-item>
</a-sub-menu>
<a-menu-item v-else :key="item.to">
<a-menu-item v-else :key="item.key">
<NuxtLink :to="item.to" class="nav-item-wrapper">
<span>{{ item.label }}</span>
<span v-if="item.badge" :class="getBadgeClass(item.badge)">{{ item.badge }}</span>
@@ -94,7 +94,7 @@
<span v-else @click="onNav(sub.to)">{{ sub.label }}</span>
</a-menu-item>
</a-sub-menu>
<a-menu-item v-else :key="item.to" @click="onNav(item.to)">
<a-menu-item v-else :key="item.key" @click="onNav(item.to)">
<NuxtLink :to="item.to" class="nav-item-wrapper">
<span>{{ item.label }}</span>
<span v-if="item.badge" :class="getBadgeClass(item.badge)">{{ item.badge }}</span>
@@ -120,7 +120,6 @@ import type { User } from '@/api/system/user/model'
import { getToken, removeToken } from '@/utils/token-util'
import { clearAuthz, setAuthzFromUser } from '@/utils/permission'
import { UserOutlined, ProfileOutlined, MessageOutlined } from '@ant-design/icons-vue'
import { message } from 'ant-design-vue'
const nav = computed(() => mainNav)
@@ -128,18 +127,18 @@ const route = useRoute()
const open = ref(false)
const selectedKeys = computed(() => {
const hit = nav.value.find((n) => n.to === route.path)
if (hit) return [hit.to]
if (route.path.startsWith('/news')) return ['/news']
if (route.path.startsWith('/consultation')) return ['/consultation']
if (route.path.startsWith('/reference')) return ['/reference']
if (route.path.startsWith('/expert')) return ['/expert']
if (route.path.startsWith('/think-tank')) return ['/think-tank']
if (route.path.startsWith('/suggestions')) return ['/suggestions']
if (route.path.startsWith('/membership')) return ['/membership']
if (route.path.startsWith('/hanmo')) return ['/hanmo']
if (route.path.startsWith('/about')) return ['/about']
return ['/']
const currentPath = route.path
const exactHit = nav.value.find((item) => item.to === currentPath)
if (exactHit) return [exactHit.key]
const prefixHit = nav.value.find((item) => item.to !== '/' && currentPath.startsWith(`${item.to}/`))
if (prefixHit) return [prefixHit.key]
const sectionHit = nav.value.find((item) => item.to !== '/' && currentPath.startsWith(item.to))
if (sectionHit) return [sectionHit.key]
return ['home']
})
// 获取 badge 样式类
@@ -154,8 +153,6 @@ function getBadgeClass(badge: string) {
return `${baseClass} bg-gray-500 text-white`
}
const siteName = ref('广西决策咨询网')
const token = ref('')
const user = ref<User | null>(null)
const isAuthed = computed(() => !!token.value)
@@ -195,12 +192,6 @@ async function refreshAuth() {
}
}
function goConsoleCenter() {
if (!isAuthed.value) return navigateTo('/login')
open.value = false
navigateTo('/profile')
}
function logout() {
removeToken()
try {
@@ -238,8 +229,9 @@ onUnmounted(() => {
<style scoped>
.header {
background: #111827;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
background: linear-gradient(180deg, #1773c2 0%, #0e63b1 100%);
border-bottom: 0;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.28);
padding: 0;
height: 64px;
}
@@ -249,11 +241,14 @@ onUnmounted(() => {
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
}
.nav-left {
display: flex;
align-items: center;
gap: 5rem; /* logo 与菜单的间距 */
flex: 1;
min-width: 0;
gap: 2rem;
}
.nav-right {
display: flex;
@@ -264,18 +259,21 @@ onUnmounted(() => {
background: transparent;
border-bottom: none;
min-width: 0;
flex: 1;
}
.logo-link {
text-decoration: none;
display: flex;
align-items: center;
min-height: 64px;
padding-right: 8px;
}
.logo-text {
color: #fff;
font-family: 'Alimama FangYuanTi VF,sans-serif', sans-serif;
font-size: 20px;
font-size: 22px;
font-weight: 700;
letter-spacing: 0.04em;
letter-spacing: 0.08em;
white-space: nowrap;
line-height: 1;
background: linear-gradient(135deg, #ffffff 0%, #a5c8ff 100%);
@@ -290,48 +288,76 @@ onUnmounted(() => {
.nav-item-wrapper {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
min-height: 62px;
padding: 0 4px;
}
/* ── 统一去掉选中/展开/hover 时的蓝色,改为白色文字 + 底部橙线 ── */
:deep(.ant-menu-dark.ant-menu-horizontal) {
background: transparent !important;
border-bottom: 0 !important;
line-height: 64px;
}
:deep(.ant-menu-dark.ant-menu-horizontal > .ant-menu-item),
:deep(.ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu) {
top: 0;
margin: 0 !important;
padding: 0 16px !important;
border-right: 1px solid rgba(255, 255, 255, 0.18);
}
:deep(.ant-menu-dark.ant-menu-horizontal > .ant-menu-item:first-child),
:deep(.ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu:first-child) {
border-left: 1px solid rgba(255, 255, 255, 0.18);
}
/* 选中项背景去掉 */
:deep(.ant-menu-dark .ant-menu-item-selected),
:deep(.ant-menu-dark .ant-menu-submenu-selected > .ant-menu-submenu-title),
:deep(.ant-menu-dark.ant-menu-horizontal > .ant-menu-item-selected),
:deep(.ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu-selected > .ant-menu-submenu-title) {
background-color: transparent !important;
background-color: rgba(6, 38, 78, 0.22) !important;
color: #fff !important;
}
/* 选中时底部橙线 */
:deep(.ant-menu-dark.ant-menu-horizontal > .ant-menu-item:hover),
:deep(.ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu:hover),
:deep(.ant-menu-dark.ant-menu-horizontal > .ant-menu-item-active),
:deep(.ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu-active),
:deep(.ant-menu-dark.ant-menu-horizontal > .ant-menu-item-selected),
:deep(.ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu-selected) {
background-color: transparent !important;
border-bottom: 2px solid #f97316 !important;
background-color: rgba(6, 38, 78, 0.22) !important;
}
:deep(.ant-menu-dark.ant-menu-horizontal > .ant-menu-item::after),
:deep(.ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu::after) {
border-bottom: none !important;
}
/* sub-menu 展开/hover 时标题不变蓝 */
:deep(.ant-menu-dark .ant-menu-submenu-open > .ant-menu-submenu-title),
:deep(.ant-menu-dark .ant-menu-submenu-active > .ant-menu-submenu-title),
:deep(.ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu-open),
:deep(.ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu-active) {
background-color: transparent !important;
background-color: rgba(6, 38, 78, 0.22) !important;
color: #fff !important;
}
:deep(.ant-menu-dark .ant-menu-submenu-open > .ant-menu-submenu-title > .ant-menu-title-content),
:deep(.ant-menu-dark .ant-menu-submenu-active > .ant-menu-submenu-title > .ant-menu-title-content),
:deep(.ant-menu-dark .ant-menu-submenu-selected > .ant-menu-submenu-title > .ant-menu-title-content),
:deep(.ant-menu-dark .ant-menu-submenu-open > .ant-menu-submenu-title > .ant-menu-title-content a),
:deep(.ant-menu-dark .ant-menu-submenu-active > .ant-menu-submenu-title > .ant-menu-title-content a) {
:deep(.ant-menu-dark .ant-menu-submenu-active > .ant-menu-submenu-title > .ant-menu-title-content a),
:deep(.ant-menu-dark .ant-menu-submenu-selected > .ant-menu-submenu-title > .ant-menu-title-content a) {
color: #fff !important;
}
/* 展开箭头不变蓝 */
:deep(.ant-menu-dark .ant-menu-submenu-open > .ant-menu-submenu-title .ant-menu-submenu-arrow),
:deep(.ant-menu-dark .ant-menu-submenu-active > .ant-menu-submenu-title .ant-menu-submenu-arrow) {
color: rgba(255, 255, 255, 0.65) !important;
:deep(.ant-menu-dark .ant-menu-submenu-active > .ant-menu-submenu-title .ant-menu-submenu-arrow),
:deep(.ant-menu-dark .ant-menu-submenu-selected > .ant-menu-submenu-title .ant-menu-submenu-arrow) {
color: rgba(255, 255, 255, 0.75) !important;
}
/* 所有菜单项 hover/active 时文字统一白色 */
:deep(.ant-menu-dark .ant-menu-item-active > .ant-menu-item-content),
:deep(.ant-menu-dark .ant-menu-item-active > .ant-menu-item-content a),
:deep(.ant-menu-dark .ant-menu-item-open > .ant-menu-item-content),
@@ -341,7 +367,15 @@ onUnmounted(() => {
color: #fff !important;
}
/* 所有菜单项默认文字颜色(覆盖 ant-menu-item a 的蓝色) */
:deep(.ant-menu-dark.ant-menu-horizontal > .ant-menu-item),
:deep(.ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu > .ant-menu-submenu-title) {
display: flex;
align-items: center;
min-height: 64px;
color: rgba(255, 255, 255, 0.92) !important;
font-size: 15px;
}
:deep(.ant-menu-dark .ant-menu-item a),
:deep(.ant-menu-dark .ant-menu-submenu-title a) {
color: rgba(255, 255, 255, 0.85) !important;
@@ -351,20 +385,49 @@ onUnmounted(() => {
color: #fff !important;
}
/* 选中项文字强制白色 */
:deep(.ant-menu-dark .ant-menu-item-selected a),
:deep(.ant-menu-dark .ant-menu-submenu-selected .ant-menu-submenu-title a) {
color: #fff !important;
}
/* 控制台按钮:白色边框 + 白色文字hover 加白色背景 */
.console-btn {
background: transparent !important;
border-color: rgba(255, 255, 255, 0.45) !important;
color: #fff !important;
:deep(.ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu .ant-menu-submenu-arrow),
:deep(.ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu .ant-menu-submenu-arrow::before),
:deep(.ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu .ant-menu-submenu-arrow::after) {
color: rgba(255, 255, 255, 0.72) !important;
}
.console-btn:hover {
border-color: #fff !important;
background: rgba(255, 255, 255, 0.1) !important;
:deep(.ant-dropdown .ant-menu),
:deep(.ant-menu-submenu-popup .ant-menu) {
background: rgba(20, 90, 150, 0.96) !important;
box-shadow: 0 10px 20px rgba(13, 45, 82, 0.16) !important;
}
:deep(.ant-menu-submenu-popup .ant-menu-item),
:deep(.ant-dropdown .ant-menu-item) {
color: #fff !important;
font-size: 13px;
line-height: 1.5;
}
:deep(.ant-menu-submenu-popup .ant-menu-item:hover),
:deep(.ant-menu-submenu-popup .ant-menu-item-active),
:deep(.ant-menu-submenu-popup .ant-menu-item-selected) {
background: rgba(8, 56, 97, 0.44) !important;
}
:deep(.ant-drawer .ant-menu-item .nav-item-wrapper) {
min-height: auto;
justify-content: flex-start;
padding: 0;
}
@media (max-width: 1024px) {
.nav-left {
gap: 1rem;
}
.logo-text {
font-size: 20px;
}
}
</style>

View File

@@ -1,23 +1,25 @@
<template>
<div class="article-detail-page">
<div class="mx-auto max-w-screen-xl px-4 py-8">
<a-row :gutter="[32, 0]">
<!-- 主内容区 -->
<a-col :xs="24" :lg="17">
<!-- 面包屑 -->
<a-breadcrumb class="mb-6">
<a-breadcrumb-item><NuxtLink to="/">首页</NuxtLink></a-breadcrumb-item>
<a-breadcrumb-item v-if="article.categoryPath">
<NuxtLink :to="article.categoryPath">{{ article.categoryName }}</NuxtLink>
</a-breadcrumb-item>
<a-breadcrumb-item>{{ article.title || '文章详情' }}</a-breadcrumb-item>
</a-breadcrumb>
<div class="article-page">
<PortalHeader :active-path="article.categoryPath || route.path" />
<div v-if="loading" class="article-loading">
<main class="container article-main">
<div class="article-layout">
<section class="article-primary">
<div class="breadcrumb-bar">
<NuxtLink to="/">首页</NuxtLink>
<span>/</span>
<NuxtLink v-if="article.categoryPath" :to="article.categoryPath">{{ article.categoryName }}</NuxtLink>
<template v-if="article.categoryPath">
<span>/</span>
</template>
<span>{{ article.title || '文章详情' }}</span>
</div>
<div v-if="loading" class="article-state">
<a-skeleton active :paragraph="{ rows: 12 }" />
</div>
<div v-else-if="!article.id" class="article-empty">
<div v-else-if="!article.id" class="article-state article-state-empty">
<a-result status="404" title="文章不存在" sub-title="您查找的文章不存在或已被删除">
<template #extra>
<a-button type="primary" @click="$router.back()">返回上一页</a-button>
@@ -25,46 +27,34 @@
</a-result>
</div>
<article v-else class="article-container">
<!-- 封面图 -->
<article v-else class="article-card">
<div v-if="article.cover" class="article-cover">
<img :src="article.cover" :alt="article.title" />
</div>
<!-- 标题区 -->
<div class="article-header">
<div class="article-category-tag" v-if="article.categoryName">
{{ article.categoryName }}
</div>
<div class="article-content-wrap">
<div class="article-heading">
<span v-if="article.categoryName" class="article-category">{{ article.categoryName }}</span>
<h1 class="article-title">{{ article.title }}</h1>
<div class="article-meta">
<span class="meta-item" v-if="article.source">
<span class="meta-icon">📰</span>来源{{ article.source }}
</span>
<span class="meta-item" v-if="article.author">
<span class="meta-icon"></span>{{ article.author }}
</span>
<span class="meta-item" v-if="article.publishTime">
<span class="meta-icon">🕐</span>{{ article.publishTime }}
</span>
<span class="meta-item" v-if="article.views">
<span class="meta-icon">👁</span>{{ article.views }} 次阅读
</span>
<span v-if="article.source">来源{{ article.source }}</span>
<span v-if="article.author">作者{{ article.author }}</span>
<span v-if="article.publishTime">{{ article.publishTime }}</span>
<span v-if="article.views">{{ article.views }} 次阅读</span>
</div>
</div>
<!-- 摘要 -->
<div v-if="article.summary" class="article-summary">
<div class="summary-label">摘要</div>
<strong>摘要</strong>
<p>{{ article.summary }}</p>
</div>
<!-- 正文 -->
<div class="article-body" v-html="article.content"></div>
<div class="article-body" v-html="article.content" />
<!-- 附件下载 -->
<div v-if="article.attachments && article.attachments.length" class="article-attachments">
<div class="attachments-title">📎 相关附件</div>
<div v-if="article.attachments?.length" class="article-attachments">
<div class="section-bar">
<span>相关附件</span>
</div>
<div class="attachments-list">
<a
v-for="file in article.attachments"
@@ -73,118 +63,157 @@
target="_blank"
class="attachment-item"
>
<span class="attachment-icon">📄</span>
<span class="attachment-name">{{ file.name }}</span>
<span class="attachment-size">{{ file.size }}</span>
</a>
</div>
</div>
<!-- 标签 -->
<div v-if="article.tags && article.tags.length" class="article-tags">
<div v-if="article.tags?.length" class="article-tags">
<span class="tags-label">标签</span>
<a-tag v-for="tag in article.tags" :key="tag" color="blue">{{ tag }}</a-tag>
</div>
<!-- 声明 -->
<div class="article-disclaimer">
<p>声明本文内容仅代表作者本人观点不代表本网站立场如有侵权请联系我们删除</p>
声明本文内容仅代表作者本人观点不代表本网站立场如有侵权请联系我们删除
</div>
<!-- 分享 & 上一篇下一篇 -->
<div class="article-nav">
<div class="nav-prev" v-if="prevArticle" @click="goArticle(prevArticle)">
<span class="nav-dir">« 上一篇</span>
<span class="nav-title">{{ prevArticle.title }}</span>
<div v-if="prevArticle" class="article-nav-item" @click="goArticle(prevArticle)">
<small>上一篇</small>
<span>{{ prevArticle.title }}</span>
</div>
<div v-if="nextArticle" class="article-nav-item article-nav-item-next" @click="goArticle(nextArticle)">
<small>下一篇</small>
<span>{{ nextArticle.title }}</span>
</div>
<div class="nav-next" v-if="nextArticle" @click="goArticle(nextArticle)">
<span class="nav-title">{{ nextArticle.title }}</span>
<span class="nav-dir">下一篇 »</span>
</div>
</div>
</article>
</a-col>
</section>
<!-- 侧栏 -->
<a-col :xs="0" :lg="7" class="hidden lg:block">
<div class="sidebar">
<!-- 相关文章 -->
<div class="sidebar-card">
<div class="sidebar-title">相关文章</div>
<div class="related-list">
<div
<aside class="article-sidebar">
<div class="sidebar-panel">
<div class="panel-heading">
<span>相关文章</span>
</div>
<div class="sidebar-list">
<button
v-for="item in relatedArticles"
:key="item.id"
class="related-item"
type="button"
class="sidebar-link"
@click="goArticle(item)"
>
<span class="related-dot"></span>
<span class="related-title">{{ item.title }}</span>
</div>
<div v-if="!relatedArticles.length" class="related-empty">暂无相关文章</div>
{{ item.title }}
</button>
<div v-if="!relatedArticles.length" class="sidebar-empty">暂无相关文章</div>
</div>
</div>
<!-- 热门推荐 -->
<div class="sidebar-card mt-6">
<div class="sidebar-title">热门推荐</div>
<div class="related-list">
<div
<div class="sidebar-panel">
<div class="panel-heading">
<span>热门推荐</span>
</div>
<div class="sidebar-list">
<button
v-for="item in hotArticles"
:key="item.id"
class="related-item hot-item"
type="button"
class="sidebar-link sidebar-link-rank"
@click="goArticle(item)"
>
<span class="hot-rank">{{ item.rank }}</span>
<span class="related-title">{{ item.title }}</span>
<strong>{{ item.rank }}</strong>
<span>{{ item.title }}</span>
</button>
</div>
</div>
</aside>
</div>
</main>
<footer class="site-footer">
<div class="footer-nav">
<div class="container footer-nav-inner">
<NuxtLink to="/about">友情链接</NuxtLink>
<NuxtLink to="/consultation">咨询服务</NuxtLink>
<NuxtLink to="/reference">决策成果研究文库</NuxtLink>
<NuxtLink to="/expert">政府智库学者库</NuxtLink>
</div>
</div>
</a-col>
</a-row>
</div>
<PortalFooter />
</footer>
</div>
</template>
<script setup lang="ts">
import { message } from 'ant-design-vue'
import { usePageSeo } from '@/composables/usePageSeo'
definePageMeta({
layout: 'blank'
})
type ArticleAttachment = {
name: string
url: string
size: string
}
type ArticleRecord = {
id?: string
title: string
cover?: string
categoryName?: string
categoryPath?: string
source?: string
author?: string
publishTime?: string
views?: number
summary?: string
content?: string
tags?: string[]
attachments?: ArticleAttachment[]
}
type ArticleLink = {
id: number | string
title: string
rank?: number
}
const route = useRoute()
const router = useRouter()
const articleId = computed(() => route.params.id as string)
const loading = ref(true)
const article = ref<any>({})
const prevArticle = ref<any>(null)
const nextArticle = ref<any>(null)
const relatedArticles = ref<any[]>([])
const hotArticles = ref<any[]>([
const article = ref<ArticleRecord>({ title: '' })
const prevArticle = ref<ArticleLink | null>(null)
const nextArticle = ref<ArticleLink | null>(null)
const relatedArticles = ref<ArticleLink[]>([])
const hotArticles = ref<ArticleLink[]>([
{ id: 1, title: '广西数字经济发展报告2024', rank: 1 },
{ id: 2, title: '自治区关于优化营商环境的实施意见', rank: 2 },
{ id: 3, title: '面向东盟的产业合作政策解读', rank: 3 },
{ id: 4, title: '广西乡村振兴战略实施进展报告', rank: 4 },
{ id: 5, title: '北部湾经济区发展最新动态', rank: 5 },
{ id: 5, title: '北部湾经济区发展最新动态', rank: 5 }
])
usePageSeo({
title: '文章详情',
description: '广西决策咨询网文章详情页'
})
useHead({
title: computed(() => `${article.value?.title || '文章详情'} - 决策咨询网`),
title: computed(() => `${article.value?.title || '文章详情'} - 广西决策咨询网`),
meta: [
{ name: 'description', content: computed(() => article.value?.summary || '') },
{ name: 'description', content: computed(() => article.value?.summary || '广西决策咨询网文章详情页') }
]
})
async function loadArticle() {
loading.value = true
try {
// TODO: 接入实际API
// const res = await getArticleDetail(articleId.value)
// article.value = res
// prevArticle.value = res.prev
// nextArticle.value = res.next
// relatedArticles.value = res.related || []
// 模拟数据
article.value = {
id: articleId.value,
title: '广西自治区党委政府关于加快数字经济发展的实施意见',
@@ -219,17 +248,27 @@ async function loadArticle() {
tags: ['数字经济', '政策解读', '广西'],
attachments: [
{ name: '广西数字经济发展实施意见(全文).pdf', url: '#', size: '2.4MB' },
{ name: '附件:实施细则.docx', url: '#', size: '856KB' },
{ name: '附件:实施细则.docx', url: '#', size: '856KB' }
]
}
} catch (e: any) {
prevArticle.value = { id: Number(articleId.value) - 1 || 1, title: '广西人工智能产业发展三年行动计划解读' }
nextArticle.value = { id: Number(articleId.value) + 1 || 2, title: '关于推动平台经济规范健康持续发展的若干措施' }
relatedArticles.value = [
{ id: 11, title: '广西数字政府建设重点任务清单发布' },
{ id: 12, title: '自治区大数据发展战略阶段性成果综述' },
{ id: 13, title: '面向东盟的数据跨境合作政策观察' },
{ id: 14, title: '产业数字化转型标杆案例分析' },
{ id: 15, title: '推动数字基础设施提质增效的实施路径' }
]
} catch {
message.error('加载失败')
} finally {
loading.value = false
}
}
function goArticle(item: any) {
function goArticle(item: ArticleLink) {
router.push(`/article/${item.id}`)
}
@@ -243,124 +282,159 @@ watch(articleId, () => {
</script>
<style scoped>
.article-detail-page {
background: #f5f7fa;
min-height: 60vh;
.article-page {
min-height: 100vh;
background: #ffffff;
color: #1f2d3d;
}
.article-loading,
.article-empty {
background: #fff;
border-radius: 12px;
padding: 40px;
.container {
width: min(1200px, calc(100% - 32px));
margin: 0 auto;
}
.article-container {
.article-main {
padding: 18px 0 40px;
}
.article-layout {
display: grid;
grid-template-columns: minmax(0, 1fr) 300px;
gap: 24px;
align-items: start;
}
.article-primary,
.article-sidebar {
min-width: 0;
}
.breadcrumb-bar {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
min-height: 44px;
padding: 0 18px;
margin-bottom: 16px;
background: #f7fbff;
border: 1px solid #e3edf6;
color: #6c7f92;
font-size: 13px;
}
.breadcrumb-bar a {
color: #2c6eaa;
}
.article-state {
background: #fff;
border-radius: 12px;
padding: 40px;
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
border: 1px solid #e3edf6;
padding: 28px;
}
.article-state-empty {
padding: 12px;
}
.article-card {
background: #fff;
border: 1px solid #e3edf6;
}
.article-cover {
margin: -40px -40px 32px;
border-radius: 12px 12px 0 0;
height: 360px;
overflow: hidden;
height: 320px;
border-bottom: 1px solid #e3edf6;
}
.article-cover img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.article-header {
margin-bottom: 24px;
padding-bottom: 24px;
border-bottom: 1px solid #f0f0f0;
.article-content-wrap {
padding: 28px 34px 36px;
}
.article-category-tag {
.article-heading {
padding-bottom: 20px;
margin-bottom: 22px;
border-bottom: 1px solid #e6eef5;
text-align: center;
}
.article-category {
display: inline-block;
padding: 3px 12px;
background: #1e3a5f;
color: #fff;
padding: 4px 12px;
margin-bottom: 14px;
background: #edf5fc;
color: #2c6eaa;
font-size: 12px;
border-radius: 4px;
margin-bottom: 12px;
border: 1px solid #c9dff1;
}
.article-title {
font-size: 26px;
font-weight: 700;
color: #1f2937;
line-height: 1.5;
margin: 0 0 16px;
color: #203040;
font-size: 32px;
font-weight: 700;
line-height: 1.45;
}
.article-meta {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
.meta-item {
justify-content: center;
gap: 10px 22px;
color: #8a97a4;
font-size: 13px;
color: #9ca3af;
display: flex;
align-items: center;
gap: 4px;
}
.meta-icon {
font-size: 14px;
}
/* 摘要 */
.article-summary {
background: #f8fafc;
border-left: 4px solid #1e3a5f;
border-radius: 0 8px 8px 0;
padding: 16px 20px;
margin-bottom: 32px;
padding: 16px 18px;
margin-bottom: 28px;
background: #f7fbff;
border-left: 4px solid #2c6eaa;
}
.summary-label {
font-size: 13px;
font-weight: 600;
color: #1e3a5f;
.article-summary strong {
display: block;
margin-bottom: 8px;
color: #2c6eaa;
font-size: 14px;
}
.article-summary p {
font-size: 14px;
color: #4b5563;
line-height: 1.8;
margin: 0;
color: #536271;
font-size: 14px;
line-height: 1.9;
}
/* 正文 */
.article-body {
color: #33485c;
font-size: 16px;
color: #374151;
line-height: 2;
}
.article-body :deep(h2) {
font-size: 20px;
font-weight: 700;
color: #1e3a5f;
margin: 32px 0 16px;
padding-bottom: 8px;
border-bottom: 2px solid #e5e7eb;
padding-left: 12px;
border-left: 4px solid #2c6eaa;
color: #214e7f;
font-size: 22px;
font-weight: 700;
line-height: 1.4;
}
.article-body :deep(h3) {
font-size: 17px;
font-weight: 600;
color: #374151;
margin: 24px 0 12px;
color: #2d5f92;
font-size: 18px;
font-weight: 700;
}
.article-body :deep(p) {
@@ -368,65 +442,62 @@ watch(articleId, () => {
text-indent: 2em;
}
.article-body :deep(ul), .article-body :deep(ol) {
.article-body :deep(ul),
.article-body :deep(ol) {
margin: 14px 0;
padding-left: 24px;
margin: 12px 0;
}
.article-body :deep(li) {
margin: 8px 0;
}
/* 附件 */
.article-attachments {
.article-attachments,
.article-tags,
.article-nav {
margin-top: 32px;
padding: 20px;
background: #f9fafb;
border-radius: 8px;
}
.attachments-title {
font-size: 15px;
font-weight: 600;
color: #374151;
margin-bottom: 12px;
.section-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 0 10px;
border-bottom: 1px solid #e6eef5;
}
.section-bar span {
color: #2c6eaa;
font-size: 20px;
font-weight: 700;
}
.attachments-list {
display: flex;
flex-direction: column;
gap: 8px;
padding-top: 10px;
}
.attachment-item {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 14px;
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 6px;
text-decoration: none;
color: #374151;
justify-content: space-between;
gap: 16px;
padding: 14px 0;
border-bottom: 1px dashed #e8edf2;
color: #3e5162;
font-size: 14px;
transition: all 0.2s;
}
.attachment-item:hover {
border-color: #1e3a5f;
color: #1e3a5f;
background: #f0f7ff;
color: #2c6eaa;
}
.attachment-size {
margin-left: auto;
color: #9ca3af;
color: #98a3ac;
font-size: 12px;
flex-shrink: 0;
}
/* 标签 */
.article-tags {
margin-top: 24px;
display: flex;
align-items: center;
gap: 8px;
@@ -434,169 +505,198 @@ watch(articleId, () => {
}
.tags-label {
color: #7e8c98;
font-size: 13px;
color: #9ca3af;
}
/* 声明 */
.article-disclaimer {
margin-top: 32px;
margin-top: 28px;
padding: 12px 16px;
background: #fefce8;
border: 1px solid #fef08a;
border-radius: 6px;
}
.article-disclaimer p {
background: #fbf8ec;
border: 1px solid #eee2b3;
color: #8d7331;
font-size: 12px;
color: #92400e;
margin: 0;
line-height: 1.7;
}
/* 上下篇 */
.article-nav {
margin-top: 32px;
display: flex;
gap: 16px;
padding-top: 20px;
border-top: 1px solid #f0f0f0;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18px;
padding-top: 24px;
border-top: 1px solid #e6eef5;
}
.nav-prev, .nav-next {
flex: 1;
padding: 12px 16px;
background: #f9fafb;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
.article-nav-item {
display: flex;
flex-direction: column;
gap: 4px;
}
.nav-prev:hover, .nav-next:hover {
background: #eff6ff;
}
.nav-next {
text-align: right;
align-items: flex-end;
}
.nav-dir {
font-size: 12px;
color: #9ca3af;
}
.nav-title {
font-size: 14px;
color: #1e3a5f;
font-weight: 500;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* 侧栏 */
.sidebar {
position: sticky;
top: 80px;
}
.sidebar-card {
background: #fff;
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
}
.sidebar-title {
font-size: 15px;
font-weight: 700;
color: #1e3a5f;
margin-bottom: 16px;
padding-bottom: 10px;
border-bottom: 2px solid #1e3a5f;
}
.related-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.related-item {
display: flex;
align-items: flex-start;
gap: 8px;
min-height: 88px;
padding: 16px 18px;
background: #f8fbfd;
border: 1px solid #e3edf6;
cursor: pointer;
padding: 6px 8px;
border-radius: 6px;
transition: background 0.2s;
transition: all 0.2s ease;
}
.related-item:hover {
background: #f3f4f6;
.article-nav-item:hover {
border-color: #b8d4ea;
background: #f1f8fd;
}
.related-dot {
color: #1e3a5f;
font-weight: 600;
flex-shrink: 0;
margin-top: 2px;
}
.related-title {
font-size: 13px;
color: #374151;
line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.related-empty {
font-size: 13px;
color: #9ca3af;
text-align: center;
padding: 12px 0;
}
.hot-item {
align-items: center;
}
.hot-rank {
width: 22px;
height: 22px;
background: #1e3a5f;
color: #fff;
border-radius: 4px;
.article-nav-item small {
color: #97a3ae;
font-size: 12px;
}
.article-nav-item span {
color: #2a3d50;
font-size: 14px;
line-height: 1.7;
}
.article-nav-item-next {
text-align: right;
}
.article-sidebar {
display: flex;
flex-direction: column;
gap: 18px;
}
.sidebar-panel {
background: #fff;
border: 1px solid #e3edf6;
padding: 16px 18px 18px;
}
.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;
}
.sidebar-list {
padding-top: 8px;
}
.sidebar-link {
display: block;
width: 100%;
padding: 12px 0;
border: 0;
border-bottom: 1px dashed #e8edf2;
background: transparent;
color: #32485b;
font-size: 14px;
line-height: 1.7;
text-align: left;
cursor: pointer;
}
.sidebar-link:hover {
color: #2c6eaa;
}
.sidebar-link-rank {
display: grid;
grid-template-columns: 24px 1fr;
gap: 10px;
align-items: start;
}
.sidebar-link-rank strong {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 24px;
height: 24px;
background: #2c6eaa;
color: #fff;
font-size: 12px;
}
.hot-item:nth-child(-n+3) .hot-rank {
background: #dc2626;
.sidebar-empty {
padding: 18px 0 8px;
color: #99a5af;
font-size: 13px;
text-align: center;
}
.site-footer {
margin-top: 8px;
background: #fff;
border-top: 1px solid #e5eef6;
}
.footer-nav {
border-bottom: 1px solid #eef3f7;
}
.footer-nav-inner {
display: flex;
justify-content: center;
gap: 60px;
padding: 18px 0;
font-size: 14px;
}
.footer-nav-inner a {
color: #5c6974;
}
@media (max-width: 1100px) {
.article-layout {
grid-template-columns: 1fr;
}
.article-sidebar {
order: 2;
}
}
@media (max-width: 768px) {
.article-container {
padding: 20px;
.container {
width: min(100%, calc(100% - 24px));
}
.article-main {
padding-top: 14px;
}
.article-cover {
margin: -20px -20px 20px;
height: 220px;
}
.article-content-wrap {
padding: 20px 18px 28px;
}
.article-title {
font-size: 20px;
font-size: 24px;
}
.article-meta {
justify-content: flex-start;
}
.article-nav {
grid-template-columns: 1fr;
}
.footer-nav-inner {
flex-wrap: wrap;
gap: 18px 28px;
padding: 16px 0;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,10 @@
</template>
<script setup lang="ts">
definePageMeta({
layout: 'default'
})
useHead({ title: '政策要闻 - 决策咨询网' })
const pageConfig = {

BIN
public/images/ad.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

BIN
public/images/ad1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

BIN
public/images/banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

BIN
public/images/jczx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

BIN
public/images/zjsq.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

BIN
public/images/zjzx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
public/images/zjzxBg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
public/images/图层 50.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB