You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

674 lines
15 KiB

<script setup>
import { ref, onMounted, onUnmounted, nextTick, computed } from 'vue'
import { getHomeConfig } from '../services/api'
import { showToast } from 'vant'
import SubmitAchievementForm from '../components/SubmitAchievementForm.vue'
import SubmitDemandForm from '../components/SubmitDemandForm.vue'
import AchievementList from '../components/AchievementList.vue'
import DemandList from '../components/DemandList.vue'
// 定义数据
const config = ref({
title: '',
introduction: '',
serviceHotline: [],
emails: [],
officialSite: []
})
const loading = ref(true)
// 将简介文本按换行符分割成段落
const introductionParagraphs = computed(() => {
if (!config.value.introduction) return []
return config.value.introduction.split(/\r\n|\r|\n/).filter(paragraph => paragraph.trim())
})
// 切换标签数据<van-icon name="balance-list" /><van-icon name="add-square" />
const tabs = [
{ key: 'achievement', name: '成果清单', icon: 'balance-list', color: '#2196f3', activeColor: '#1976d2' },
{ key: 'demand', name: '需求清单', icon: 'award', color: '#9c27b0', activeColor: '#7b1fa2' },
{ key: 'submit-achievement', name: '成果填报', icon: 'add', color: '#4caf50', activeColor: '#388e3c' },
{ key: 'submit-demand', name: '需求填报', icon: 'add-square', color: '#f44336', activeColor: '#d32f2f' }
]
// 当前激活的标签
const activeTab = ref('achievement')
// 处理标签点击
const handleTabClick = (tab) => {
// 更新激活状态
activeTab.value = tab.key
}
// 是否固定标签栏
const isTabsFixed = ref(false)
// 标签栏DOM引用
const tabsContainer = ref(null)
// 标签栏初始top值
const tabsInitialTop = ref(0)
// 滚动监听处理函数
const handleScroll = () => {
if (tabsInitialTop.value === 0) return
const scrollTop = window.scrollY
isTabsFixed.value = scrollTop >= tabsInitialTop.value
}
// 十六进制颜色转RGB值函数
const hexToRgb = (hex) => {
// 移除#号
hex = hex.replace(/^#/, '')
// 解析十六进制值
let r = 0, g = 0, b = 0
if (hex.length === 3) {
// 简写形式,如 #RGB
r = parseInt(hex[0] + hex[0], 16)
g = parseInt(hex[1] + hex[1], 16)
b = parseInt(hex[2] + hex[2], 16)
} else if (hex.length === 6) {
// 完整形式,如 #RRGGBB
r = parseInt(hex.substring(0, 2), 16)
g = parseInt(hex.substring(2, 4), 16)
b = parseInt(hex.substring(4, 6), 16)
}
return `${r}, ${g}, ${b}`
}
// 页面挂载时获取数据和添加滚动监听
onMounted(async () => {
// 获取数据
try {
loading.value = true
const response = await getHomeConfig()
if (response.code === 1) {
config.value = response.data
// 设置浏览器标题
if (response.data?.siteTitle) {
document.title = response.data.siteTitle
}
} else {
showToast('获取数据失败')
}
} catch (error) {
console.error('请求失败:', error)
showToast('网络错误,请稍后重试')
} finally {
loading.value = false
}
// 等待DOM更新后获取初始位置
nextTick(() => {
if (tabsContainer.value) {
tabsInitialTop.value = tabsContainer.value.offsetTop
}
})
// 添加滚动监听
window.addEventListener('scroll', handleScroll)
})
// 页面卸载时移除滚动监听
onUnmounted(() => {
window.removeEventListener('scroll', handleScroll)
})
</script>
<template>
<div class="home">
<!-- 顶部标题区域 -->
<div class="header">
<h1 class="title">{{ config.title }}</h1>
<p class="subtitle">{{ config.subTitle }}</p>
</div>
<!-- 内容区 -->
<div class="content">
<!-- 简介区域 -->
<div class="card intro-card">
<!-- <h2 class="section-title">平台简介</h2> -->
<div v-if="loading" class="loading-container">
<van-skeleton title :row="3" animated />
</div>
<div v-else class="intro-content">
<p v-for="(paragraph, index) in introductionParagraphs" :key="index" class="intro-paragraph">
{{ paragraph }}
</p>
</div>
</div>
<!-- 切换按钮区域 -->
<div class="tabs-container" ref="tabsContainer" :class="{ 'tabs-fixed': isTabsFixed }">
<div class="tabs-wrapper">
<div
v-for="tab in tabs"
:key="tab.key"
class="tab-item"
:style="{
'--tab-color': activeTab === tab.key ? tab.activeColor : tab.color,
'--tab-color-rgb': hexToRgb(activeTab === tab.key ? tab.activeColor : tab.color)
}"
:class="{ active: activeTab === tab.key }"
@click="handleTabClick(tab)"
>
<van-icon :name="tab.icon" size="18" />
<span class="tab-text">{{ tab.name }}</span>
</div>
</div>
</div>
<!-- 切换内容区域 -->
<div class="tab-content">
<!-- 成果清单 -->
<div v-if="activeTab === 'achievement'" class="tab-panel">
<AchievementList :config="config" />
</div>
<!-- 需求清单 -->
<div v-else-if="activeTab === 'demand'" class="tab-panel">
<DemandList :config="config" />
</div>
<!-- 成果填报 -->
<div v-else-if="activeTab === 'submit-achievement'" class="tab-panel">
<SubmitAchievementForm />
</div>
<!-- 需求填报 -->
<div v-else-if="activeTab === 'submit-demand'" class="tab-panel">
<SubmitDemandForm />
</div>
</div>
</div>
<!-- 底部咨询对接区域 -->
<div class="contact-section">
<div class="contact-card">
<div class="contact-title-container">
<van-icon name="chat-o" size="20" color="#4caf50" />
<h2 class="contact-title">咨询对接</h2>
</div>
<!-- 服务热线 -->
<div class="contact-item-card phone-card">
<div class="contact-item-header">
<van-icon name="service-o" size="18" color="#2196f3" />
<span class="contact-item-label">服务热线</span>
</div>
<div class="contact-item-content">
<div v-for="(item, index) in config.serviceHotline" :key="index" class="contact-person">
<span class="person-name">{{ item.name }}</span>
<a :href="`tel:${item.phone}`" class="person-phone">
<van-icon name="phone" size="14" color="#2196f3" />
<span>{{ item.phone }}</span>
</a>
</div>
</div>
</div>
<!-- 邮箱咨询 -->
<div class="contact-item-card email-card">
<div class="contact-item-header">
<van-icon name="envelop-o" size="18" color="#9c27b0" />
<span class="contact-item-label">邮箱咨询</span>
</div>
<div class="contact-item-content">
<a v-for="(email, index) in config.emails" :key="index" :href="`mailto:${email}`" class="contact-email">
{{ email }}
</a>
</div>
</div>
<!-- 协会官网 -->
<div class="contact-item-card official-site-card">
<div class="contact-item-header">
<van-icon name="link-o" size="18" color="#4caf50" />
<span class="contact-item-label">协会官网</span>
</div>
<div class="contact-item-content">
<a v-for="(site, index) in config.officialSite" :key="index" :href="site" class="contact-official-site" target="_blank">
{{ site }}
</a>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
/* 全局样式重置和基础设置 */
.home {
min-height: 100vh;
background-color: #f5f5f5;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
/* 顶部标题区域 */
.header {
background: linear-gradient(135deg, #2563eb 0%, #7e22ce 100%);
color: white;
padding: 30px 20px;
text-align: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.title {
font-size: 22px;
font-weight: 700;
margin: 0;
line-height: 1.4;
word-break: break-word;
}
/* 副标题样式 */
.subtitle {
font-size: 16px;
font-weight: 500;
margin: 8px 0 0 0;
line-height: 1.3;
word-break: break-word;
opacity: 0.9;
}
/* 内容区 */
.content {
padding: 15px;
}
/* 卡片样式 */
.card {
background-color: white;
border-radius: 12px;
padding: 15px;
margin-bottom: 15px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card:active {
transform: translateY(1px);
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.05);
}
/* 标题样式 */
.section-title {
font-size: 18px;
font-weight: 600;
color: #333;
margin: 0 0 15px 0;
padding-bottom: 8px;
border-bottom: 2px solid #1e88e5;
}
/* 加载状态 */
.loading-container {
padding: 10px 0;
}
/* 简介内容容器 */
.intro-content {
display: block;
width: 100%;
box-sizing: border-box;
}
/* 简介段落 */
.intro-paragraph {
font-size: 14px;
line-height: 1.7;
color: #555;
text-align: justify;
text-indent: 2em;
margin: 0 0 12px 0;
word-wrap: break-word;
}
.intro-paragraph:last-child {
margin-bottom: 0;
}
/* 正文占位符 */
.content-placeholder {
text-align: center;
padding: 30px 15px;
color: #999;
font-size: 15px;
background-color: #fafafa;
border-radius: 8px;
margin-top: 5px;
}
/* 底部咨询对接区域 */
.contact-section {
background-color: #f5f5f5;
padding: 20px 15px;
margin-top: auto;
}
/* 整体咨询卡片 */
.contact-card {
background-color: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
overflow: hidden;
}
/* 咨询标题容器 */
.contact-title-container {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
margin-bottom: 20px;
}
/* 咨询标题 */
.contact-title {
font-size: 18px;
font-weight: 600;
color: #333;
margin: 0;
}
/* 单个咨询项卡片 */
.contact-item-card {
border-radius: 10px;
padding: 15px;
margin-bottom: 15px;
background-color: #fafafa;
transition: all 0.2s ease;
}
.contact-item-card:last-child {
margin-bottom: 0;
}
/* 服务热线卡片样式 */
.phone-card {
background-color: rgba(33, 150, 243, 0.05);
border-left: 4px solid #2196f3;
}
.phone-card .contact-item-label {
color: #2196f3;
}
.phone-card .person-phone {
color: #2196f3;
}
/* 邮箱咨询卡片样式 */
.email-card {
background-color: rgba(156, 39, 176, 0.05);
border-left: 4px solid #9c27b0;
}
.email-card .contact-item-label {
color: #9c27b0;
}
.email-card .contact-email {
color: #9c27b0;
}
/* 协会官网卡片样式 */
.official-site-card {
background-color: rgba(76, 175, 80, 0.05);
border-left: 4px solid #4caf50;
}
.official-site-card .contact-item-label {
color: #4caf50;
}
.official-site-card .contact-official-site {
color: #4caf50;
}
/* 咨询项头部 */
.contact-item-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
font-size: 16px;
font-weight: 500;
}
/* 咨询项标签 */
.contact-item-label {
font-weight: 500;
}
/* 咨询项内容 */
.contact-item-content {
padding-left: 26px;
}
/* 联系人信息 */
.contact-person {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 12px;
font-size: 14px;
}
.contact-person:last-child {
margin-bottom: 0;
}
/* 联系人姓名 */
.person-name {
color: #666;
font-weight: 500;
margin-right: 12px;
}
/* 联系人电话 */
.person-phone {
display: flex;
align-items: center;
gap: 6px;
font-weight: 500;
text-decoration: none;
}
/* 联系邮箱 */
.contact-email {
display: block;
font-size: 14px;
text-decoration: none;
font-weight: 500;
}
/* 协会官网链接 */
.contact-official-site {
display: block;
font-size: 14px;
font-weight: 500;
line-height: 1.6;
text-decoration: none;
word-break: break-all;
}
.contact-official-site:hover {
text-decoration: underline;
}
/* 切换按钮区域 */
.tabs-container {
margin: 0;
background-color: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
z-index: 10;
transition: all 0.3s ease;
width: 100%;
box-sizing: border-box;
}
/* 固定状态 */
.tabs-fixed {
position: fixed;
top: 0;
left: 0;
right: 0;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
/* 切换按钮容器 */
.tabs-wrapper {
display: flex;
width: 100%;
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* 确保所有状态下按钮容器的宽度正确 */
.tabs-container {
width: 100%;
box-sizing: border-box;
}
/* 固定状态下的按钮容器 */
.tabs-fixed .tabs-wrapper {
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
}
/* 确保固定状态下按钮容器不超出视口 */
.tabs-fixed {
width: 100%;
box-sizing: border-box;
}
/* 切换按钮项 */
.tab-item {
flex: 1 1 25%;
width: 25%;
padding: 12px 0;
text-align: center;
font-size: 13px;
font-weight: 500;
color: var(--tab-color);
cursor: pointer;
position: relative;
transition: all 0.2s ease;
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
border-left: 1px solid #e5e5e5;
background-color: white;
box-sizing: border-box;
min-width: 0;
}
.tab-item:first-child {
border-left: none;
}
.tab-item:active {
opacity: 0.9;
}
/* 激活状态 */
.tab-item.active {
font-weight: 600;
/* 使用对应颜色的深色背景作为选中效果 */
background-color: var(--tab-color);
}
/* 激活状态的图标 */
.tab-item.active .van-icon {
color: white;
}
/* 激活状态的文字 */
.tab-item.active .tab-text {
color: white;
}
/* 非激活状态的图标 */
.tab-item:not(.active) .van-icon {
color: var(--tab-color);
font-size: 18px;
}
/* 非激活状态的文字 */
.tab-item:not(.active) .tab-text {
color: var(--tab-color);
font-size: 13px;
}
/* 切换内容区域 */
.tab-content {
margin-top: 15px;
}
/* 切换面板 */
.tab-panel {
background-color: white;
border-radius: 12px;
padding: 15px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
min-height: 400px;
}
/* 成果填报表单容器 */
.tab-panel :deep(.submit-achievement-form) {
padding: 0;
}
/* 适配小屏幕 */
@media (max-width: 375px) {
.tab-panel {
padding: 12px;
}
}
/* 适配小屏幕 */
@media (max-width: 375px) {
.header {
padding: 25px 15px;
}
.title {
font-size: 20px;
}
.content {
padding: 12px;
}
.section-title {
font-size: 17px;
}
.contact-section {
padding: 15px 12px;
}
/* 小屏幕切换按钮 */
.tab-item {
font-size: 14px;
padding: 12px 0;
}
.tab-panel {
padding: 15px;
min-height: 300px;
}
}
</style>