批量推送
This commit is contained in:
@@ -0,0 +1,374 @@
|
||||
<template>
|
||||
<div class="detail-page">
|
||||
<header class="top-nav" :class="topNavClass">
|
||||
<a class="logo" title="返回首页" @click.prevent="goHome">
|
||||
<div class="logo-icon">医</div>
|
||||
<div class="logo-text">
|
||||
<span class="logo-title">北京整合医学学会</span>
|
||||
<span class="logo-subtitle">Beijing Association of Holistic Integrative Medicine</span>
|
||||
</div>
|
||||
</a>
|
||||
<ul class="nav-list">
|
||||
<li class="nav-item"><a class="nav-link" @click.prevent="goHome">年度项目规划</a></li>
|
||||
<li class="nav-item"><a class="nav-link" @click.prevent="goPublicity">项目公示</a></li>
|
||||
</ul>
|
||||
<div class="top-tools">
|
||||
<template v-if="!loggedIn">
|
||||
<span class="login-btn" @click="goLogin">登录</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-dropdown trigger="click" @command="onUserCmd">
|
||||
<a class="user-link" @click.prevent>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
|
||||
<circle cx="12" cy="7" r="4"/>
|
||||
</svg>
|
||||
<span>{{ userName }}</span>
|
||||
</a>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="account">我的主页</el-dropdown-item>
|
||||
<el-dropdown-item command="logout" divided>退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<article class="article-card" v-loading="loading">
|
||||
<header class="article-head">
|
||||
<h1 class="article-title">{{ article.title || (type === 'agreement' ? '用户服务协议' : '隐私政策') }}</h1>
|
||||
<div class="article-meta" v-if="article.updateTime">最后更新: {{ fmt(article.updateTime) }}</div>
|
||||
</header>
|
||||
<div class="article-body" v-html="article.content || '<p class=\'empty\'>暂无内容</p>'" />
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-main">
|
||||
<div class="footer-brand">
|
||||
<div class="brand-row">
|
||||
<div class="footer-logo-icon">医</div>
|
||||
<div>
|
||||
<div class="footer-brand-name">北京整合医学学会</div>
|
||||
<div class="footer-brand-en">Beijing Association of Holistic Integrative Medicine</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="footer-desc">依托"健康中国2030"战略,整合医学资源,推动健康科普与公益事业,助力全民健康素养提升,共建共享健康中国。</p>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4>快速导航</h4>
|
||||
<a @click.prevent="goHome">首页</a>
|
||||
<a @click.prevent="goHome">年度项目规划</a>
|
||||
<a @click.prevent="goPublicity">项目公示</a>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4>学会项目</h4>
|
||||
<a>百姓巡常行</a>
|
||||
<a>专病联盟暨全国专家智库</a>
|
||||
<a>乡村幸福安康</a>
|
||||
<a>临床科研资助计划</a>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4>联系方式</h4>
|
||||
<p>电话: 010-82089470</p>
|
||||
<p>邮箱: contactus@bahim.org.cn</p>
|
||||
<p>地址: 北京市海淀区知春路1号学院国际大厦908-2室</p>
|
||||
<p>邮编: 100083</p>
|
||||
</div>
|
||||
<div class="footer-qr">
|
||||
<div class="qr-image">
|
||||
<svg width="52" height="52" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M3 11h8V3H3v8zm2-6h4v4H5V5zm8-2v8h8V3h-8zm6 6h-4V5h4v4zM3 21h8v-8H3v8zm2-6h4v4H5v-4zm13-2h-2v2h2v-2zm-2 2h-2v2h2v-2zm2 2h-2v2h2v-2zm2-2h-2v2h2v-2zm-2-2h-2v2h2v-2zm-2-2h-2v2h2v-2zm-2-2h-2v2h2v-2zm-2-2h-2v2h2v-2zm0 0h-2v2h2v-2z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="qr-label">公众号二维码</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<span>© 北京整合医学学会 BAHIM</span>
|
||||
<span>京ICP备2020035479号-1 京公网安备11010802034820</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { logout as logoutApi } from '@/api/auth'
|
||||
import request from '@/utils/request'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const isScrolled = ref(false)
|
||||
const topNavClass = computed(() => isScrolled.value ? 'is-scrolled' : '')
|
||||
const loggedIn = computed(() => !!userStore.token)
|
||||
const userName = computed(() => userStore.user?.userName || '用户')
|
||||
|
||||
const article = ref({})
|
||||
const loading = ref(false)
|
||||
const type = ref('')
|
||||
|
||||
async function load() {
|
||||
const t = route.params.type
|
||||
type.value = t
|
||||
if (!t) return
|
||||
loading.value = true
|
||||
try {
|
||||
const r = await request({ url: `/business/public/article/${t}`, method: 'get' })
|
||||
article.value = (r.data && r.data.data) || r.data || {}
|
||||
} catch (e) {
|
||||
article.value = {}
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
function fmt(d) {
|
||||
if (!d) return ''
|
||||
const date = new Date(d)
|
||||
const y = date.getFullYear()
|
||||
const m = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${y}-${m}-${day}`
|
||||
}
|
||||
|
||||
function goHome() { router.push('/') }
|
||||
function goPublicity() { router.push('/publicity') }
|
||||
function goLogin() { router.push('/login') }
|
||||
async function goLogout() { try { await logoutApi() } catch {}; userStore.logout(); router.replace('/login') }
|
||||
async function onUserCmd(cmd) {
|
||||
if (cmd === 'logout') return goLogout()
|
||||
if (cmd === 'account') {
|
||||
const r = (userStore.user?.role || '')
|
||||
const map = { admin: '/leader/home', leader: '/leader/home', manager: '/manager/workbench', doctor: '/doctor/home', executor: '/executor/meetings', sponsor: '/sponsor/home' }
|
||||
router.push(map[r] || '/leader/home')
|
||||
}
|
||||
}
|
||||
|
||||
function handleScroll() { isScrolled.value = window.scrollY > 10 }
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('scroll', handleScroll, { passive: true })
|
||||
load()
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('scroll', handleScroll)
|
||||
})
|
||||
watch(() => route.params.type, load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* ========== 基础 ========== */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
color: #1f2937;
|
||||
background: #f5f6f8;
|
||||
min-width: 1354px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
a { color: inherit; text-decoration: none; }
|
||||
|
||||
/* ========== 顶部导航 ========== */
|
||||
.top-nav {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
height: 72px;
|
||||
padding: 0 60px;
|
||||
background: var(--brand-primary);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
|
||||
transition: box-shadow 0.3s;
|
||||
}
|
||||
.top-nav.is-scrolled { box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25); }
|
||||
|
||||
.logo { display: flex; align-items: center; gap: 12px; }
|
||||
.logo-icon {
|
||||
width: 36px; height: 36px;
|
||||
background: #fff;
|
||||
color: var(--brand-primary);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 18px; font-weight: 600;
|
||||
}
|
||||
.logo-text { display: flex; flex-direction: column; line-height: 1.2; }
|
||||
.logo-title { font-size: 16px; font-weight: 600; color: #fff; letter-spacing: 0.5px; }
|
||||
.logo-subtitle { font-size: 11px; color: rgba(255, 255, 255, 0.6); margin-top: 2px; letter-spacing: 0.3px; }
|
||||
|
||||
.nav-list {
|
||||
flex: 1; display: flex; align-items: center; justify-content: center;
|
||||
gap: 36px; list-style: none;
|
||||
}
|
||||
.nav-item { position: relative; height: 72px; display: flex; align-items: center; }
|
||||
.nav-link {
|
||||
font-size: 15px; font-weight: 500; color: rgba(255, 255, 255, 0.85);
|
||||
cursor: pointer; transition: color 0.25s; position: relative;
|
||||
height: 72px; display: flex; align-items: center;
|
||||
}
|
||||
.nav-link::after {
|
||||
content: ''; position: absolute; left: 50%; bottom: 0;
|
||||
width: 0; height: 2px; background: #fff;
|
||||
transform: translateX(-50%); transition: width 0.3s ease;
|
||||
}
|
||||
.nav-item:hover .nav-link, .nav-link.active { color: #fff; }
|
||||
.nav-item:hover .nav-link::after, .nav-link.active::after { width: 100%; }
|
||||
|
||||
.top-tools { display: flex; align-items: center; gap: 16px; }
|
||||
.login-btn {
|
||||
padding: 7px 20px;
|
||||
background: #fff;
|
||||
color: var(--brand-primary);
|
||||
font-size: 13px; font-weight: 500;
|
||||
cursor: pointer; transition: background 0.2s; letter-spacing: 1px;
|
||||
}
|
||||
.login-btn:hover { background: #f3f4f6; }
|
||||
.user-link {
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
color: #fff; font-size: 14px; cursor: pointer;
|
||||
}
|
||||
|
||||
/* ========== 主体 ========== */
|
||||
.container {
|
||||
max-width: 1354px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 60px 60px; /* 顶部加 40px 留白, 拉开与 sticky navcat 的距离 */
|
||||
}
|
||||
|
||||
.back-link {
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
margin: 28px 0 20px;
|
||||
font-size: 13px; color: #6b7280;
|
||||
cursor: pointer; transition: color 0.2s;
|
||||
}
|
||||
.back-link:hover { color: var(--brand-primary); }
|
||||
|
||||
/* ========== 文章卡片 ========== */
|
||||
.article-card {
|
||||
background: #fff;
|
||||
border: 1px solid #e5e7eb;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
||||
padding: 56px 80px 64px;
|
||||
min-height: 480px;
|
||||
}
|
||||
.article-head {
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #eef0f3;
|
||||
padding-bottom: 28px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.article-title {
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
letter-spacing: 1px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.article-meta {
|
||||
margin-top: 12px;
|
||||
font-size: 13px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
.article-body {
|
||||
font-size: 15px;
|
||||
line-height: 2;
|
||||
color: #374151;
|
||||
}
|
||||
.article-body :deep(h1),
|
||||
.article-body :deep(h2),
|
||||
.article-body :deep(h3) {
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
margin: 24px 0 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.article-body :deep(h1) { font-size: 22px; text-align: center; }
|
||||
.article-body :deep(h2) { font-size: 18px; }
|
||||
.article-body :deep(h3) { font-size: 16px; }
|
||||
.article-body :deep(p) {
|
||||
margin: 10px 0;
|
||||
text-indent: 2em;
|
||||
text-align: justify;
|
||||
}
|
||||
.article-body :deep(ul),
|
||||
.article-body :deep(ol) {
|
||||
margin: 12px 0 12px 2em;
|
||||
}
|
||||
.article-body :deep(li) { margin: 6px 0; }
|
||||
.article-body :deep(.empty) {
|
||||
text-align: center;
|
||||
color: #c0c4cc;
|
||||
text-indent: 0;
|
||||
padding: 60px 0;
|
||||
}
|
||||
|
||||
/* ========== 底部 ========== */
|
||||
.footer {
|
||||
background: var(--brand-primary-darker);
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
padding: 48px 0 0;
|
||||
}
|
||||
.footer-main {
|
||||
display: grid;
|
||||
grid-template-columns: 1.4fr 1fr 1fr 1.2fr auto;
|
||||
gap: 48px;
|
||||
padding-bottom: 36px;
|
||||
}
|
||||
.footer-brand .brand-row { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
|
||||
.footer-logo-icon {
|
||||
width: 36px; height: 36px;
|
||||
background: #fff;
|
||||
color: var(--brand-primary);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 18px; font-weight: 600; flex-shrink: 0;
|
||||
}
|
||||
.footer-brand-name { font-size: 16px; font-weight: 600; color: #fff; letter-spacing: 1px; }
|
||||
.footer-brand-en { font-size: 10px; color: rgba(255, 255, 255, 0.4); letter-spacing: 0.5px; margin-top: 2px; }
|
||||
.footer-desc { font-size: 13px; line-height: 1.9; color: rgba(255, 255, 255, 0.55); }
|
||||
.footer-col h4 {
|
||||
font-size: 14px; font-weight: 600; color: #fff;
|
||||
letter-spacing: 1px; margin-bottom: 16px; padding-bottom: 10px;
|
||||
position: relative;
|
||||
}
|
||||
.footer-col h4::after {
|
||||
content: ''; position: absolute; left: 0; bottom: 0;
|
||||
width: 24px; height: 2px; background: #93c5fd;
|
||||
}
|
||||
.footer-col a, .footer-col p {
|
||||
display: block; font-size: 13px; color: rgba(255, 255, 255, 0.6);
|
||||
line-height: 2.1; transition: color 0.2s;
|
||||
}
|
||||
.footer-col a { cursor: pointer; }
|
||||
.footer-col a:hover { color: #fff; }
|
||||
.footer-qr { text-align: center; }
|
||||
.qr-image {
|
||||
width: 100px; height: 100px;
|
||||
background: #fff;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
color: #1f2937;
|
||||
}
|
||||
.qr-label { font-size: 12px; color: rgba(255, 255, 255, 0.5); margin-top: 10px; }
|
||||
.footer-bottom {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.12);
|
||||
padding: 18px 0;
|
||||
display: flex; justify-content: space-between;
|
||||
font-size: 12px; color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.container { padding: 0 24px 40px; }
|
||||
.article-card { padding: 32px 24px; }
|
||||
}
|
||||
</style>
|
||||
@@ -77,83 +77,16 @@
|
||||
<div class="specialty-inner">
|
||||
<div class="section-title">七大专项计划</div>
|
||||
<div class="specialty-list">
|
||||
<div class="specialty-card">
|
||||
<div class="specialty-no">01</div>
|
||||
<div v-for="(p, i) in specialPlans" :key="p.id" class="specialty-card" @click="openPlan(p.id)">
|
||||
<div class="specialty-no">{{ String(i + 1).padStart(2, '0') }}</div>
|
||||
<div class="specialty-body">
|
||||
<h3 class="specialty-title">《规范化诊疗及医疗质量提升专项计划(2026年)》</h3>
|
||||
<p class="specialty-desc">聚焦临床诊疗规范制定与质量提升,推动各级医疗机构诊疗标准化、同质化。</p>
|
||||
<h3 class="specialty-title">《{{ p.title }}》</h3>
|
||||
<div class="specialty-action">
|
||||
<span class="specialty-link">查看详情</span>
|
||||
<span class="specialty-link">项目提案</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="specialty-card">
|
||||
<div class="specialty-no">02</div>
|
||||
<div class="specialty-body">
|
||||
<h3 class="specialty-title">《科研创新专项行动计划(2026-2030年)》</h3>
|
||||
<p class="specialty-desc">支持医学科研创新,推动整合医学研究成果转化与产业化应用,构建协同创新生态。</p>
|
||||
<div class="specialty-action">
|
||||
<span class="specialty-link">查看详情</span>
|
||||
<span class="specialty-link">项目提案</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="specialty-card">
|
||||
<div class="specialty-no">03</div>
|
||||
<div class="specialty-body">
|
||||
<h3 class="specialty-title">《医疗卫生人才培育专项行动计划(2026-2030年)》</h3>
|
||||
<p class="specialty-desc">建立多层次医学人才培养体系,重点加强基层医疗人才与跨学科复合型人才建设。</p>
|
||||
<div class="specialty-action">
|
||||
<span class="specialty-link">查看详情</span>
|
||||
<span class="specialty-link">项目提案</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="specialty-card">
|
||||
<div class="specialty-no">04</div>
|
||||
<div class="specialty-body">
|
||||
<h3 class="specialty-title">《医院管理及高质量发展促进专项计划(2026-2030年)》</h3>
|
||||
<p class="specialty-desc">推动医院管理现代化,提升运营效率与服务水平,助力公立医院高质量发展。</p>
|
||||
<div class="specialty-action">
|
||||
<span class="specialty-link">查看详情</span>
|
||||
<span class="specialty-link">项目提案</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="specialty-card">
|
||||
<div class="specialty-no">05</div>
|
||||
<div class="specialty-body">
|
||||
<h3 class="specialty-title">《社会公益与可及性提升专项计划(2026-2030年)》</h3>
|
||||
<p class="specialty-desc">聚焦医疗服务的可及性与公平性,开展公益救助、基层帮扶与健康科普活动。</p>
|
||||
<div class="specialty-action">
|
||||
<span class="specialty-link">查看详情</span>
|
||||
<span class="specialty-link">项目提案</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="specialty-card">
|
||||
<div class="specialty-no">06</div>
|
||||
<div class="specialty-body">
|
||||
<h3 class="specialty-title">《政学协作综合项目专项计划(2026-2030年)》</h3>
|
||||
<p class="specialty-desc">搭建政府与学术机构协同平台,推动政策研究与决策支持,促进政产学研用深度融合。</p>
|
||||
<div class="specialty-action">
|
||||
<span class="specialty-link">查看详情</span>
|
||||
<span class="specialty-link">项目提案</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="specialty-card">
|
||||
<div class="specialty-no">07</div>
|
||||
<div class="specialty-body">
|
||||
<h3 class="specialty-title">《组织建设与内部治理专项计划(2026-2030年)》</h3>
|
||||
<p class="specialty-desc">完善学会组织架构,健全内部治理机制,提升规范化、专业化、信息化管理水平。</p>
|
||||
<div class="specialty-action">
|
||||
<span class="specialty-link">查看详情</span>
|
||||
<span class="specialty-link">项目提案</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!specialPlans.length" class="specialty-empty">暂无专项计划</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -215,11 +148,26 @@ import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { logout as logoutApi } from '@/api/auth'
|
||||
import request from '@/utils/request'
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const isScrolled = ref(false)
|
||||
const specialPlans = ref([])
|
||||
|
||||
async function loadSpecialPlans() {
|
||||
try {
|
||||
const r = await request({ url: '/business/public/specialPlan/list', method: 'get' })
|
||||
specialPlans.value = (r.data && r.data.data) || r.data || []
|
||||
} catch (e) {
|
||||
specialPlans.value = []
|
||||
}
|
||||
}
|
||||
|
||||
function openPlan(id) {
|
||||
window.open(`/#/special-plan/${id}`, '_blank')
|
||||
}
|
||||
const topNavClass = computed(() => isScrolled.value ? 'is-scrolled' : '')
|
||||
const loggedIn = computed(() => !!userStore.token)
|
||||
const userName = computed(() => userStore.user?.userName || '用户')
|
||||
@@ -266,12 +214,6 @@ const planSvg = `<svg viewBox="0 150 1200 370" xmlns="http://www.w3.org/2000/svg
|
||||
<g transform="translate(904 362)"><polygon points="-30,0 -15,-26 15,-26 30,0 15,26 -15,26" fill="url(#hexGrad)"/><text y="6" font-size="19">07</text></g>
|
||||
<g transform="translate(986 468)"><polygon points="-34,0 -17,-29 17,-29 34,0 17,29 -17,29" fill="url(#hexGrad)"/><text y="7" font-size="22">08</text></g>
|
||||
</g>
|
||||
<g transform="translate(600 360)">
|
||||
<circle cx="0" cy="0" r="40" fill="#fff" stroke="#e0e7ff" stroke-width="2"/>
|
||||
<circle cx="0" cy="0" r="30" fill="url(#centerGrad)"/>
|
||||
<rect x="-16" y="-5" width="32" height="10" fill="#fff"/>
|
||||
<rect x="-5" y="-16" width="10" height="32" fill="#fff"/>
|
||||
</g>
|
||||
<text x="600" y="430" fill="var(--brand-primary)" text-anchor="middle" font-family="Microsoft YaHei, sans-serif" font-size="32" font-weight="bold" letter-spacing="6">2025-2030年</text>
|
||||
<line x1="540" y1="447" x2="600" y2="447" stroke="var(--brand-primary)" stroke-width="1.5" opacity="0.4"/>
|
||||
<line x1="600" y1="447" x2="660" y2="447" stroke="var(--brand-primary)" stroke-width="1.5" opacity="0.4"/>
|
||||
@@ -291,7 +233,10 @@ function handleScroll() {
|
||||
isScrolled.value = window.scrollY > 10
|
||||
}
|
||||
|
||||
onMounted(() => window.addEventListener('scroll', handleScroll, { passive: true }))
|
||||
onMounted(() => {
|
||||
window.addEventListener('scroll', handleScroll, { passive: true })
|
||||
loadSpecialPlans()
|
||||
})
|
||||
onBeforeUnmount(() => window.removeEventListener('scroll', handleScroll))
|
||||
|
||||
function onHome() { isScrolled.value = false }
|
||||
@@ -632,8 +577,16 @@ a { color: inherit; text-decoration: none; }
|
||||
background: #fff;
|
||||
border: 1px solid #e5e7eb;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s, transform 0.2s;
|
||||
}
|
||||
.specialty-empty {
|
||||
grid-column: 1 / -1;
|
||||
text-align: center;
|
||||
color: #9ca3af;
|
||||
padding: 60px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.specialty-card:hover {
|
||||
border-color: var(--brand-primary);
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<span class="notice-item-no">{{ String((currentPage - 1) * PAGE_SIZE + i + 1).padStart(3, '0') }}</span>
|
||||
<span class="notice-date">{{ fmtDate(n.date) }}</span>
|
||||
<span class="notice-title">
|
||||
<span v-if="n.projectName" style="color:#909399;margin-right:8px;font-size:13px">【{{ n.projectName }}】</span>
|
||||
<span v-if="n.projectNo" style="color:#909399;margin-right:8px;font-size:13px">【{{ n.projectNo }}】</span>
|
||||
{{ n.title }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -172,8 +172,9 @@ async function load() {
|
||||
annId: r.projectId,
|
||||
date: r.publishTime,
|
||||
type,
|
||||
title: r.projectName,
|
||||
projectNo: r.projectNo || '',
|
||||
projectName: r.projectName,
|
||||
title: r.projectName,
|
||||
fileUrl: urlMap[type] || r.invitationUrl || r.supportLetterUrl || r.publishUrl || ''
|
||||
}
|
||||
})
|
||||
|
||||
@@ -156,28 +156,27 @@
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- 分享二维码弹窗 -->
|
||||
<div class="modal-overlay" :class="showQr ? 'active' : ''" @click.self="showQr = false">
|
||||
<div class="modal" @click.stop>
|
||||
<h3 class="modal-title">分享本页</h3>
|
||||
<div class="modal-qr-wrap">
|
||||
<span class="qr-corner qr-corner-tl"></span>
|
||||
<span class="qr-corner qr-corner-tr"></span>
|
||||
<span class="qr-corner qr-corner-bl"></span>
|
||||
<span class="qr-corner qr-corner-br"></span>
|
||||
<div class="modal-qr">
|
||||
<img v-if="qrUrl" :src="qrUrl" alt="分享二维码" class="qr-img" />
|
||||
<span v-else class="qr-loading">
|
||||
<span class="qr-dot"></span><span class="qr-dot"></span><span class="qr-dot"></span>
|
||||
生成中...
|
||||
</span>
|
||||
</div>
|
||||
<div class="qr-brand">北京整合医学学会</div>
|
||||
<!-- 分享二维码弹窗 (el-dialog 自带右上角 X 关闭按钮 + Esc + 遮罩点击) -->
|
||||
<el-dialog v-model="showQr" title="分享本页" width="420px" align-center destroy-on-close>
|
||||
<div class="modal-qr-wrap">
|
||||
<span class="qr-corner qr-corner-tl"></span>
|
||||
<span class="qr-corner qr-corner-tr"></span>
|
||||
<span class="qr-corner qr-corner-bl"></span>
|
||||
<span class="qr-corner qr-corner-br"></span>
|
||||
<div class="modal-qr">
|
||||
<img v-if="qrUrl" :src="qrUrl" alt="分享二维码" class="qr-img" />
|
||||
<span v-else class="qr-loading">
|
||||
<span class="qr-dot"></span><span class="qr-dot"></span><span class="qr-dot"></span>
|
||||
生成中...
|
||||
</span>
|
||||
</div>
|
||||
<p class="modal-tip">扫码查看「{{ ann?.projectName || '项目公示' }}」公示详情</p>
|
||||
<button class="modal-close" @click="showQr = false">关 闭</button>
|
||||
<div class="qr-brand">北京整合医学学会</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="modal-tip">扫码查看「{{ ann?.projectName || '项目公示' }}」公示详情</p>
|
||||
<template #footer>
|
||||
<el-button type="primary" :disabled="!qrUrl" @click="onDownloadQr">下载二维码</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -388,6 +387,17 @@ async function onSignup() {
|
||||
function onKeydown(e) { if (e.key === 'Escape') showQr.value = false }
|
||||
function handleScroll() { isScrolled.value = window.scrollY > 10 }
|
||||
|
||||
// 下载二维码: qrUrl 是 QRCode.toDataURL 生成的 data:image/png;base64,... 直接 <a download> 即可
|
||||
function onDownloadQr() {
|
||||
if (!qrUrl.value) return
|
||||
const a = document.createElement('a')
|
||||
a.href = qrUrl.value
|
||||
a.download = `${(ann.value?.projectName || '公示')}_分享二维码.png`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('scroll', handleScroll, { passive: true })
|
||||
window.addEventListener('keydown', onKeydown)
|
||||
@@ -981,39 +991,11 @@ a { color: inherit; text-decoration: none; }
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== 二维码弹窗 ========== */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
.modal-overlay.active { display: flex; }
|
||||
|
||||
.modal {
|
||||
background: #fff;
|
||||
padding: 32px 36px;
|
||||
width: 320px;
|
||||
text-align: center;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin-bottom: 20px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/* ========== 二维码弹窗 (el-dialog 自带标题/X/footer, 这里只写内部 QR 样式) ========== */
|
||||
.modal-qr-wrap {
|
||||
position: relative;
|
||||
width: 220px;
|
||||
margin: 0 auto 20px;
|
||||
margin: 0 auto 16px;
|
||||
padding: 12px 12px 8px;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f7faff 100%);
|
||||
border-radius: 12px;
|
||||
@@ -1076,22 +1058,10 @@ a { color: inherit; text-decoration: none; }
|
||||
.modal-tip {
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
margin-bottom: 20px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
width: 100%;
|
||||
height: 38px;
|
||||
background: var(--brand-primary);
|
||||
color: #fff;
|
||||
border: none;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.modal-close:hover { background: var(--brand-primary-deep); }
|
||||
|
||||
/* ========== 底部 ========== */
|
||||
.footer {
|
||||
background: var(--brand-primary-darker);
|
||||
|
||||
@@ -0,0 +1,341 @@
|
||||
<template>
|
||||
<div class="detail-page">
|
||||
<header class="top-nav" :class="topNavClass">
|
||||
<a class="logo" title="返回首页" @click.prevent="goHome">
|
||||
<div class="logo-icon">医</div>
|
||||
<div class="logo-text">
|
||||
<span class="logo-title">北京整合医学学会</span>
|
||||
<span class="logo-subtitle">Beijing Association of Holistic Integrative Medicine</span>
|
||||
</div>
|
||||
</a>
|
||||
<ul class="nav-list">
|
||||
<li class="nav-item"><a class="nav-link" @click.prevent="goHome">年度项目规划</a></li>
|
||||
<li class="nav-item"><a class="nav-link" @click.prevent="goPublicity">项目公示</a></li>
|
||||
</ul>
|
||||
<div class="top-tools">
|
||||
<template v-if="!loggedIn">
|
||||
<span class="login-btn" @click="goLogin">登录</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-dropdown trigger="click" @command="onUserCmd">
|
||||
<a class="user-link" @click.prevent>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
|
||||
<circle cx="12" cy="7" r="4"/>
|
||||
</svg>
|
||||
<span>{{ userName }}</span>
|
||||
</a>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="account">我的主页</el-dropdown-item>
|
||||
<el-dropdown-item command="logout" divided>退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<article class="plan-card" v-loading="loading">
|
||||
<header class="plan-head">
|
||||
<h1 class="plan-title">{{ plan.title || '专项计划' }}</h1>
|
||||
<div class="plan-meta" v-if="plan.updateTime">最后更新: {{ fmt(plan.updateTime) }}</div>
|
||||
</header>
|
||||
|
||||
<!-- 富文本内容 -->
|
||||
<div v-if="!plan.contentType || plan.contentType === 'rich'"
|
||||
class="plan-body plan-body-rich"
|
||||
v-html="plan.content || '<p class=\'empty\'>暂无内容</p>'" />
|
||||
|
||||
<!-- 上传文件: PDF / PNG -->
|
||||
<div v-else class="plan-body plan-body-file">
|
||||
<iframe v-if="isPdf(plan.fileUrl)"
|
||||
:src="proxyUrl(plan.fileUrl)"
|
||||
class="content-frame"
|
||||
frameborder="0"></iframe>
|
||||
<img v-else-if="isImg(plan.fileUrl)"
|
||||
:src="proxyUrl(plan.fileUrl)"
|
||||
class="content-img"
|
||||
alt="专项计划文件" />
|
||||
<div v-else class="content-empty">
|
||||
<el-icon size="64"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zM6 20V4h7v5h5v11H6z"/></svg></el-icon>
|
||||
<p>暂无可查看的文件</p>
|
||||
<p class="hint">请上传 PDF 或 PNG 格式</p>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-main">
|
||||
<div class="footer-brand">
|
||||
<div class="brand-row">
|
||||
<div class="footer-logo-icon">医</div>
|
||||
<div>
|
||||
<div class="footer-brand-name">北京整合医学学会</div>
|
||||
<div class="footer-brand-en">Beijing Association of Holistic Integrative Medicine</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="footer-desc">依托"健康中国2030"战略,整合医学资源,推动健康科普与公益事业,助力全民健康素养提升,共建共享健康中国。</p>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4>快速导航</h4>
|
||||
<a @click.prevent="goHome">首页</a>
|
||||
<a @click.prevent="goHome">年度项目规划</a>
|
||||
<a @click.prevent="goPublicity">项目公示</a>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4>学会项目</h4>
|
||||
<a>百姓巡常行</a>
|
||||
<a>专病联盟暨全国专家智库</a>
|
||||
<a>乡村幸福安康</a>
|
||||
<a>临床科研资助计划</a>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4>联系方式</h4>
|
||||
<p>电话: 010-82089470</p>
|
||||
<p>邮箱: contactus@bahim.org.cn</p>
|
||||
<p>地址: 北京市海淀区知春路1号学院国际大厦908-2室</p>
|
||||
<p>邮编: 100083</p>
|
||||
</div>
|
||||
<div class="footer-qr">
|
||||
<div class="qr-image">
|
||||
<svg width="52" height="52" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M3 11h8V3H3v8zm2-6h4v4H5V5zm8-2v8h8V3h-8zm6 6h-4V5h4v4zM3 21h8v-8H3v8zm2-6h4v4H5v-4zm13-2h-2v2h2v-2zm-2 2h-2v2h2v-2zm2 2h-2v2h2v-2zm2-2h-2v2h2v-2zm-2-2h-2v2h2v-2zm-2-2h-2v2h2v-2zm-2-2h-2v2h2v-2zm-2-2h-2v2h2v-2zm0 0h-2v2h2v-2z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="qr-label">公众号二维码</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<span>© 北京整合医学学会 BAHIM</span>
|
||||
<span>京ICP备2020035479号-1 京公网安备11010802034820</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { logout as logoutApi } from '@/api/auth'
|
||||
import request from '@/utils/request'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const isScrolled = ref(false)
|
||||
const topNavClass = computed(() => isScrolled.value ? 'is-scrolled' : '')
|
||||
const loggedIn = computed(() => !!userStore.token)
|
||||
const userName = computed(() => userStore.user?.userName || '用户')
|
||||
|
||||
const plan = ref({})
|
||||
const loading = ref(false)
|
||||
|
||||
async function load() {
|
||||
const id = route.params.id
|
||||
if (!id) return
|
||||
loading.value = true
|
||||
try {
|
||||
const r = await request({ url: `/business/public/specialPlan/${id}`, method: 'get' })
|
||||
plan.value = (r.data && r.data.data) || r.data || {}
|
||||
} catch (e) {
|
||||
plan.value = {}
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
function fmt(d) {
|
||||
if (!d) return ''
|
||||
const date = new Date(d)
|
||||
const y = date.getFullYear()
|
||||
const m = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${y}-${m}-${day}`
|
||||
}
|
||||
|
||||
function isPdf(url) {
|
||||
if (!url) return false
|
||||
return /\.pdf(\?|$)/i.test(url.split('?')[0])
|
||||
}
|
||||
function isImg(url) {
|
||||
if (!url) return false
|
||||
return /\.(png|jpg|jpeg|gif|webp)(\?|$)/i.test(url.split('?')[0])
|
||||
}
|
||||
// OSS bucket 默认 Content-Disposition: attachment, PDF 会被强制下载.
|
||||
// 走 /common/oss/proxy 后端代理改写为 inline.
|
||||
// 参考 PublicityDetail.vue 的 proxyUrl 逻辑
|
||||
function proxyUrl(url) {
|
||||
if (!url) return url
|
||||
if (url.includes('hwtossbamlorgcn.oss-cn-beijing.aliyuncs.com')) {
|
||||
return '/dev-api/common/oss/proxy?url=' + encodeURIComponent(url) + '#toolbar=0&zoom=page-width'
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
function goHome() { router.push('/') }
|
||||
function goPublicity() { router.push('/publicity') }
|
||||
function goLogin() { router.push('/login') }
|
||||
async function goLogout() { try { await logoutApi() } catch {}; userStore.logout(); router.replace('/login') }
|
||||
async function onUserCmd(cmd) {
|
||||
if (cmd === 'logout') return goLogout()
|
||||
if (cmd === 'account') {
|
||||
const r = (userStore.user?.role || '')
|
||||
const map = { admin: '/leader/home', leader: '/leader/home', manager: '/manager/workbench', doctor: '/doctor/home', executor: '/executor/meetings', sponsor: '/sponsor/home' }
|
||||
router.push(map[r] || '/leader/home')
|
||||
}
|
||||
}
|
||||
|
||||
function handleScroll() { isScrolled.value = window.scrollY > 10 }
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('scroll', handleScroll, { passive: true })
|
||||
load()
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('scroll', handleScroll)
|
||||
})
|
||||
watch(() => route.params.id, load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
color: #1f2937;
|
||||
background: #f5f6f8;
|
||||
min-width: 1354px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
a { color: inherit; text-decoration: none; }
|
||||
|
||||
/* ===== 顶部导航 (与 PublicityDetail 一致) ===== */
|
||||
.top-nav {
|
||||
position: sticky; top: 0; z-index: 1000;
|
||||
height: 72px; padding: 0 60px;
|
||||
background: var(--brand-primary);
|
||||
border-bottom: 1px solid rgba(255,255,255,0.08);
|
||||
display: flex; align-items: center;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.15);
|
||||
transition: box-shadow 0.3s;
|
||||
}
|
||||
.top-nav.is-scrolled { box-shadow: 0 4px 20px rgba(0,0,0,0.25); }
|
||||
.logo { display: flex; align-items: center; gap: 12px; }
|
||||
.logo-icon {
|
||||
width: 36px; height: 36px;
|
||||
background: #fff; color: var(--brand-primary);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 18px; font-weight: 600;
|
||||
}
|
||||
.logo-text { display: flex; flex-direction: column; line-height: 1.2; }
|
||||
.logo-title { font-size: 16px; font-weight: 600; color: #fff; letter-spacing: 0.5px; }
|
||||
.logo-subtitle { font-size: 11px; color: rgba(255,255,255,0.6); margin-top: 2px; letter-spacing: 0.3px; }
|
||||
.nav-list { flex: 1; display: flex; align-items: center; justify-content: center; gap: 36px; list-style: none; }
|
||||
.nav-item { position: relative; height: 72px; display: flex; align-items: center; }
|
||||
.nav-link {
|
||||
font-size: 15px; font-weight: 500; color: rgba(255,255,255,0.85);
|
||||
cursor: pointer; transition: color 0.25s; position: relative;
|
||||
height: 72px; display: flex; align-items: center;
|
||||
}
|
||||
.nav-link::after {
|
||||
content: ''; position: absolute; left: 50%; bottom: 0;
|
||||
width: 0; height: 2px; background: #fff;
|
||||
transform: translateX(-50%); transition: width 0.3s ease;
|
||||
}
|
||||
.nav-item:hover .nav-link, .nav-link.active { color: #fff; }
|
||||
.nav-item:hover .nav-link::after, .nav-link.active::after { width: 100%; }
|
||||
.top-tools { display: flex; align-items: center; gap: 16px; }
|
||||
.login-btn {
|
||||
padding: 7px 20px; background: #fff; color: var(--brand-primary);
|
||||
font-size: 13px; font-weight: 500;
|
||||
cursor: pointer; transition: background 0.2s; letter-spacing: 1px;
|
||||
}
|
||||
.login-btn:hover { background: #f3f4f6; }
|
||||
.user-link { display: inline-flex; align-items: center; gap: 6px; color: #fff; font-size: 14px; cursor: pointer; }
|
||||
|
||||
/* ===== 主体 ===== */
|
||||
.container { max-width: 1354px; margin: 0 auto; padding: 28px 60px 60px; }
|
||||
|
||||
.plan-card {
|
||||
background: #fff;
|
||||
border: 1px solid #e5e7eb;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.04);
|
||||
padding: 56px 80px 64px;
|
||||
min-height: 480px;
|
||||
}
|
||||
.plan-head { text-align: center; border-bottom: 1px solid #eef0f3; padding-bottom: 28px; margin-bottom: 32px; }
|
||||
.plan-title { font-size: 26px; font-weight: 700; color: #1f2937; letter-spacing: 1px; line-height: 1.4; }
|
||||
.plan-meta { margin-top: 12px; font-size: 13px; color: #9ca3af; }
|
||||
|
||||
/* 富文本 (与 ArticleView 一致) */
|
||||
.plan-body-rich {
|
||||
font-size: 15px; line-height: 2; color: #374151;
|
||||
}
|
||||
.plan-body-rich :deep(h1),
|
||||
.plan-body-rich :deep(h2),
|
||||
.plan-body-rich :deep(h3) { font-weight: 700; color: #1f2937; margin: 24px 0 14px; line-height: 1.5; }
|
||||
.plan-body-rich :deep(h1) { font-size: 22px; text-align: center; }
|
||||
.plan-body-rich :deep(h2) { font-size: 18px; }
|
||||
.plan-body-rich :deep(h3) { font-size: 16px; }
|
||||
.plan-body-rich :deep(p) { margin: 10px 0; text-indent: 2em; text-align: justify; }
|
||||
.plan-body-rich :deep(ul),
|
||||
.plan-body-rich :deep(ol) { margin: 12px 0 12px 2em; }
|
||||
.plan-body-rich :deep(li) { margin: 6px 0; }
|
||||
.plan-body-rich :deep(.empty) { text-align: center; color: #c0c4cc; text-indent: 0; padding: 60px 0; }
|
||||
|
||||
/* 文件预览 */
|
||||
.plan-body-file { padding: 0; min-height: 600px; }
|
||||
.content-frame { width: 100%; height: 700px; display: block; border: 0; }
|
||||
.content-img { width: 100%; max-width: 100%; display: block; margin: 0 auto; }
|
||||
.content-empty { padding: 80px 20px; text-align: center; color: #c0c4cc; }
|
||||
.content-empty p { margin: 8px 0 0; }
|
||||
.content-empty .hint { font-size: 12px; }
|
||||
|
||||
/* ===== 底部 ===== */
|
||||
.footer { background: var(--brand-primary-darker); color: rgba(255,255,255,0.65); padding: 48px 0 0; }
|
||||
.footer-main { display: grid; grid-template-columns: 1.4fr 1fr 1fr 1.2fr auto; gap: 48px; padding-bottom: 36px; }
|
||||
.footer-brand .brand-row { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
|
||||
.footer-logo-icon {
|
||||
width: 36px; height: 36px;
|
||||
background: #fff; color: var(--brand-primary);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 18px; font-weight: 600; flex-shrink: 0;
|
||||
}
|
||||
.footer-brand-name { font-size: 16px; font-weight: 600; color: #fff; letter-spacing: 1px; }
|
||||
.footer-brand-en { font-size: 10px; color: rgba(255,255,255,0.4); letter-spacing: 0.5px; margin-top: 2px; }
|
||||
.footer-desc { font-size: 13px; line-height: 1.9; color: rgba(255,255,255,0.55); }
|
||||
.footer-col h4 {
|
||||
font-size: 14px; font-weight: 600; color: #fff;
|
||||
letter-spacing: 1px; margin-bottom: 16px; padding-bottom: 10px; position: relative;
|
||||
}
|
||||
.footer-col h4::after { content: ''; position: absolute; left: 0; bottom: 0; width: 24px; height: 2px; background: #93c5fd; }
|
||||
.footer-col a, .footer-col p {
|
||||
display: block; font-size: 13px; color: rgba(255,255,255,0.6);
|
||||
line-height: 2.1; transition: color 0.2s;
|
||||
}
|
||||
.footer-col a { cursor: pointer; }
|
||||
.footer-col a:hover { color: #fff; }
|
||||
.footer-qr { text-align: center; }
|
||||
.qr-image {
|
||||
width: 100px; height: 100px;
|
||||
background: #fff;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
color: #1f2937;
|
||||
}
|
||||
.qr-label { font-size: 12px; color: rgba(255,255,255,0.5); margin-top: 10px; }
|
||||
.footer-bottom {
|
||||
border-top: 1px solid rgba(255,255,255,0.12);
|
||||
padding: 18px 0;
|
||||
display: flex; justify-content: space-between;
|
||||
font-size: 12px; color: rgba(255,255,255,0.4);
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.container { padding: 0 24px 40px; }
|
||||
.plan-card { padding: 32px 24px; }
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user