Files
tuanwei-uniapp/pages/index/index.vue
2026-07-20 11:56:02 +08:00

569 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="page">
<scroll-view scroll-y class="page-scroll">
<common-hero title="首页" :use-image-bg="true">
<view class="search-bar" @click="handleSearch">
<view class="search-left">
<view class="search-icon"></view>
<text class="search-placeholder">关键字搜索</text>
</view>
<text class="search-action">搜索</text>
</view>
<swiper
class="hero-swiper"
circular
autoplay
indicator-dots
indicator-color="rgba(72, 88, 104, 0.28)"
indicator-active-color="#5f6570"
interval="3600"
duration="500"
>
<swiper-item v-for="item in banners" :key="item.image">
<image class="banner-image" :src="item.image" mode="aspectFill"></image>
</swiper-item>
</swiper>
<view class="notice-card" @click="handleInfoClick">
<view class="notice-text">
<text class="notice-label">资讯:</text>
<text class="notice-content">{{ notice.title }}</text>
</view>
<view class="notice-arrow"></view>
</view>
</common-hero>
<view class="section">
<view class="section-title">核心业务</view>
<view class="business-list">
<view
v-for="item in featuredModules"
:key="item.title"
class="business-card"
@click="handleFeatureClick(item)"
>
<view class="business-card-top">
<view class="business-icon">{{ item.icon }}</view>
<view class="business-tag">{{ item.tag }}</view>
</view>
<view class="business-title">{{ item.title }}</view>
<view class="business-desc">{{ item.desc }}</view>
</view>
</view>
</view>
<view class="section">
<view class="section-headline">
<view>
<view class="section-title">申报系统</view>
<view class="section-subtitle">当前开放的申报项目入口</view>
</view>
<view class="section-link" @click="goDeclareSystem">查看全部</view>
</view>
<view class="declare-entry-card" @click="goDeclareSystem">
<view class="declare-entry-top">
<view class="declare-entry-action">进入系统</view>
</view>
<view v-if="declareLoading" class="declare-state">正在加载申报项目...</view>
<view v-else-if="declareError" class="declare-state">{{ declareError }}</view>
<view v-else-if="!declarePreviewList.length" class="declare-state">当前暂无可申报项目</view>
<view v-else class="declare-preview-list">
<view
v-for="item in declarePreviewList"
:key="item.id || `${item.module}-${item.year}`"
class="declare-preview-item"
@click.stop="goDeclareSystem"
>
<view class="declare-preview-main">
<view class="declare-preview-name">{{ getDeclareTitle(item) }}</view>
<view class="declare-preview-meta">
{{ getDeclareGroup(item.module) }} · {{ item.year ? `${item.year}` : '未设置年度' }}
</view>
</view>
<view class="declare-preview-tag">可申报</view>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import CommonHero from '../../components/common-hero/common-hero.vue'
import { getFormConfig, getModuleMeta } from '../../utils/gxmu/config'
import { listDeclare } from '../../utils/gxmu/declare-service'
export default {
components: {
CommonHero
},
data() {
return {
banners: [
{
image: '/static/swiper.jpg'
}
],
notice: {
title: '文稿、长图、排版和素材能力统一沉淀'
},
declareLoading: false,
declareError: '',
declareList: [],
featuredModules: [
{
title: '团员档案管理',
desc: '成员信息、组织关系、成长记录',
icon: '档',
tag: '组织',
route: '/pages/tygl/index'
},
{
title: '数据统计与报表中心',
desc: '查看挑战杯项目的年度趋势、结构分布与学校排行。',
icon: '数',
tag: '报表',
route: '/pages/gxmu/stats'
},
{
title: '跨校活动情报看板',
desc: '聚合兄弟高校案例线索,快速对标活动策划与传播打法。',
icon: '情',
tag: '案例',
route: '/pages/gxmu/cross-school-board'
},
{
title: '创新竞赛历史项目数据库',
desc: '进入挑战杯项目库与人才库,检索历史案例、人才与详情链接。',
icon: '库',
tag: '数据',
route: '/pages/gxmu/tzb-database'
}
]
}
},
computed: {
declarePreviewList() {
return this.declareList.slice(0, 2)
}
},
onLoad() {
this.loadDeclareList()
},
methods: {
getModuleMetaSafe(code) {
return getModuleMeta(code) || {
title: code || '未命名模块',
group: '申报项目'
}
},
getDeclareTitle(item) {
const title = String((item && item.title) || '').trim()
if (title) {
return title
}
return this.getModuleMetaSafe(item && item.module).title
},
getDeclareGroup(module) {
return this.getModuleMetaSafe(module).group
},
getTimestamp(value) {
if (!value) {
return 0
}
const time = new Date(String(value).replace(/-/g, '/')).getTime()
return Number.isNaN(time) ? 0 : time
},
async loadDeclareList() {
if (this.declareLoading) {
return
}
this.declareLoading = true
this.declareError = ''
try {
const result = await listDeclare({ usable: true })
const list = Array.isArray(result) ? result : []
this.declareList = list
.sort((left, right) => {
const yearDiff = Number(right.year || 0) - Number(left.year || 0)
if (yearDiff !== 0) {
return yearDiff
}
return this.getTimestamp(right.startTime || right.createTime) - this.getTimestamp(left.startTime || left.createTime)
})
} catch (error) {
this.declareError = (error && error.message) || '申报项目加载失败'
} finally {
this.declareLoading = false
}
},
handleSearch() {
uni.showToast({
title: '搜索功能开发中',
icon: 'none'
})
},
handleInfoClick() {
uni.showToast({
title: this.notice.title,
icon: 'none'
})
},
handleFeatureClick(item) {
if (item.route) {
uni.navigateTo({
url: item.route
})
return
}
uni.showToast({
title: `${item.title}待接入`,
icon: 'none'
})
},
goDeclareSystem() {
uni.navigateTo({
url: '/pages/gxmu/index'
})
}
}
}
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background: linear-gradient(180deg, #f8fbff 0%, #edf3f8 32%, #f7f7f8 100%);
}
.page-scroll {
height: 100vh;
}
.search-bar {
position: absolute;
top: 170rpx;
z-index: 9999;
width: calc(100vw - 56rpx);
left: 28rpx;
padding: 0 26rpx 0 30rpx;
height: 70rpx;
border-radius: 999rpx;
background: rgba(255, 255, 255, 0.97);
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 16rpx 40rpx rgba(97, 151, 193, 0.12);
}
.search-left {
display: flex;
align-items: center;
}
.search-icon {
width: 26rpx;
height: 26rpx;
border: 4rpx solid #c7c7c7;
border-radius: 50%;
position: relative;
}
.search-icon::after {
content: '';
position: absolute;
right: -10rpx;
bottom: -8rpx;
width: 14rpx;
height: 4rpx;
background: #c7c7c7;
border-radius: 999rpx;
transform: rotate(45deg);
transform-origin: center;
}
.search-placeholder {
margin-left: 20rpx;
font-size: 32rpx;
color: #cbcbcb;
}
.search-action {
font-size: 28rpx;
font-weight: 700;
color: #111;
}
.hero-swiper {
position: relative;
z-index: 2;
height: 338rpx;
margin-top: 40rpx;
}
.banner-image {
display: block;
width: 100%;
height: 338rpx;
}
.notice-card {
margin: 18rpx 28rpx 0;
padding: 0 20rpx 0 28rpx;
height: 86rpx;
border-radius: 999rpx;
background: rgba(255, 255, 255, 0.98);
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 12rpx 32rpx rgba(55, 95, 130, 0.08);
}
.notice-text {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
font-size: 28rpx;
color: #111;
}
.notice-label {
flex-shrink: 0;
margin-right: 12rpx;
font-weight: 700;
color: #1197ff;
}
.notice-content {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.notice-arrow {
width: 42rpx;
height: 42rpx;
border-radius: 50%;
border: 2rpx solid #4ca7ff;
display: flex;
align-items: center;
justify-content: center;
font-size: 30rpx;
color: #4ca7ff;
}
.section {
padding: 20rpx 28rpx 20rpx;
}
.section-headline {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 16rpx;
}
.section-title {
font-size: 46rpx;
font-weight: 700;
color: #15a0ff;
}
.section-subtitle {
margin-top: 8rpx;
font-size: 24rpx;
line-height: 1.5;
color: #7c8a96;
}
.section-link {
flex-shrink: 0;
font-size: 24rpx;
font-weight: 700;
color: #1496f2;
}
.business-list {
margin-top: 24rpx;
display: flex;
flex-direction: column;
}
.business-card + .business-card {
margin-top: 26rpx;
}
.business-card {
padding: 24rpx 22rpx 26rpx;
border-radius: 28rpx;
background: rgba(255, 255, 255, 0.98);
box-shadow: 0 20rpx 48rpx rgba(79, 98, 117, 0.08);
}
.business-card-top {
display: flex;
align-items: center;
justify-content: space-between;
}
.business-icon {
min-width: 88rpx;
height: 58rpx;
padding: 0 20rpx;
border-radius: 999rpx;
background: linear-gradient(135deg, #16b2ff 0%, #1496f2 100%);
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 26rpx;
font-weight: 700;
color: #fff;
}
.business-tag {
min-width: 90rpx;
height: 42rpx;
padding: 0 20rpx;
border-radius: 999rpx;
border: 2rpx solid #22a4ff;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
font-weight: 500;
color: #22a4ff;
}
.business-title {
margin-top: 22rpx;
font-size: 26rpx;
font-weight: 700;
line-height: 1.35;
color: #111;
}
.business-desc {
margin-top: 10rpx;
font-size: 24rpx;
line-height: 1.5;
color: #7c7c7c;
}
.declare-entry-card {
margin-top: 24rpx;
padding: 28rpx 24rpx;
border-radius: 28rpx;
background:
linear-gradient(180deg, rgba(240, 247, 255, 0.96) 0%, rgba(255, 255, 255, 0.98) 44%),
#ffffff;
box-shadow: 0 20rpx 48rpx rgba(79, 98, 117, 0.08);
}
.declare-entry-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16rpx;
}
.declare-entry-badge {
display: inline-flex;
align-items: center;
height: 46rpx;
padding: 0 18rpx;
border-radius: 999rpx;
background: #eaf4ff;
color: #1554ad;
font-size: 20rpx;
font-weight: 700;
letter-spacing: 1rpx;
}
.declare-entry-action {
font-size: 24rpx;
font-weight: 700;
color: #1496f2;
}
.declare-entry-title {
margin-top: 18rpx;
font-size: 30rpx;
font-weight: 700;
line-height: 1.45;
color: #1f2329;
}
.declare-entry-desc {
margin-top: 12rpx;
font-size: 24rpx;
line-height: 1.7;
color: #607080;
}
.declare-state {
margin-top: 22rpx;
padding: 20rpx 22rpx;
border-radius: 20rpx;
background: rgba(246, 248, 250, 0.9);
font-size: 24rpx;
line-height: 1.6;
color: #7d8a97;
}
.declare-preview-list {
margin-top: 22rpx;
display: flex;
flex-direction: column;
gap: 16rpx;
}
.declare-preview-item {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16rpx;
padding: 20rpx;
border-radius: 22rpx;
background: rgba(255, 255, 255, 0.96);
border: 1rpx solid rgba(20, 150, 242, 0.08);
}
.declare-preview-main {
flex: 1;
min-width: 0;
}
.declare-preview-name {
font-size: 26rpx;
font-weight: 700;
line-height: 1.45;
color: #1f2329;
}
.declare-preview-meta {
margin-top: 8rpx;
font-size: 22rpx;
line-height: 1.5;
color: #7c8a96;
}
.declare-preview-tag {
flex-shrink: 0;
height: 42rpx;
padding: 0 18rpx;
border-radius: 999rpx;
background: #eef5ff;
color: #1554ad;
font-size: 20rpx;
font-weight: 700;
line-height: 42rpx;
}
</style>