Files
tuanwei-uniapp/pages/gxmu/cross-school-board.vue
2026-07-20 11:56:02 +08:00

887 lines
21 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"></common-hero>
<view class="hero-panel">
<view class="hero-copy">
<view class="hero-kicker">跨校活动情报看板</view>
<view class="hero-title">把兄弟高校的活动打法整理成一眼可读的案例板</view>
<view class="hero-desc">
以信息流和案例卡片形式集中展示同类活动的公开线索方便快速判断选题趋势包装方式和传播动作
</view>
<view class="hero-actions">
<view class="hero-btn hero-btn--primary" @tap="quickFilter('高热')">本周热点</view>
<view class="hero-btn" @tap="quickMatchPlanning">拟办活动对标</view>
</view>
</view>
<view class="hero-stats">
<view class="hero-stat hero-stat--primary">
<view class="stat-label">案例总量</view>
<view class="stat-value">{{ totalCount }}</view>
<view class="stat-desc">当前静态示例数据</view>
</view>
<view class="hero-stat">
<view class="stat-label">高热案例</view>
<view class="stat-value">{{ highHeatCount }}</view>
<view class="stat-desc">适合优先拆解传播路径</view>
</view>
<view class="hero-stat">
<view class="stat-label">覆盖类型</view>
<view class="stat-value">{{ categoryCount }}</view>
<view class="stat-desc">品牌活动 / 实践 / 科创 / 文体</view>
</view>
<view class="hero-stat">
<view class="stat-label">来源院校</view>
<view class="stat-value">{{ schoolCount }}</view>
<view class="stat-desc">按院校公开渠道聚合</view>
</view>
</view>
</view>
<view class="filter-panel">
<view class="filter-group">
<view class="filter-label">活动类型</view>
<scroll-view scroll-x class="chip-scroll" show-scrollbar="false">
<view class="chip-row">
<view
v-for="item in categoryOptions"
:key="item"
class="filter-chip"
:class="{ 'filter-chip--active': selectedCategory === item }"
@tap="selectedCategory = item"
>
{{ item }}
</view>
</view>
</scroll-view>
</view>
<view class="filter-group">
<view class="filter-label">热度等级</view>
<scroll-view scroll-x class="chip-scroll" show-scrollbar="false">
<view class="chip-row">
<view
v-for="item in heatOptions"
:key="item"
class="filter-chip"
:class="{ 'filter-chip--active': selectedHeat === item }"
@tap="selectedHeat = item"
>
{{ item }}
</view>
</view>
</scroll-view>
</view>
<view class="search-box">
<uv-input
v-model.trim="keyword"
class="search-input"
placeholder="搜索标题、院校、标签"
confirm-type="search"
:maxlength="-1"
border="none"
/>
<view v-if="keyword" class="search-clear" @tap="keyword = ''">清空</view>
</view>
</view>
<view class="section-grid">
<view class="stream-card">
<view class="section-title">情报信息流</view>
<view class="stream-list">
<view
v-for="item in streamItems"
:key="item.id"
class="stream-item"
>
<view class="stream-cover" :class="coverClassMap[item.id % coverClassMap.length]">
<view class="stream-cover-category">{{ item.category }}</view>
<view class="stream-cover-school">{{ item.school }}</view>
</view>
<view class="stream-content">
<view class="stream-top">
<view class="stream-title">{{ item.title }}</view>
<view class="heat-tag" :class="heatClassMap[item.heat]">{{ item.heat }}</view>
</view>
<view class="stream-meta">{{ item.school }} · {{ item.source }} · {{ item.publishAt }}</view>
<view class="stream-summary">{{ item.summary }}</view>
<view class="tag-list">
<view v-for="tag in item.tags" :key="tag" class="tag-chip">{{ tag }}</view>
</view>
</view>
</view>
</view>
</view>
<view class="signal-card">
<view class="section-title">策略信号</view>
<view class="signal-list">
<view v-for="item in insightSignals" :key="item.title" class="signal-item">
<view class="signal-title">{{ item.title }}</view>
<view class="signal-desc">{{ item.desc }}</view>
</view>
</view>
<view class="signal-divider"></view>
<view class="section-title section-title--small">建议关注</view>
<view class="watch-list">
<view v-for="item in watchList" :key="item" class="watch-item">{{ item }}</view>
</view>
</view>
</view>
<view id="cases-section" class="cases-card">
<view class="cases-head">
<view class="section-title">案例卡片墙</view>
<view class="case-count"> {{ filteredCases.length }} </view>
</view>
<view v-if="filteredCases.length" class="cases-list">
<view
v-for="item in filteredCases"
:key="item.id"
class="case-card"
>
<view class="case-cover" :class="coverClassMap[item.id % coverClassMap.length]">
<view class="case-school">{{ item.school }}</view>
<view class="case-cover-badge">{{ item.highlight }}</view>
</view>
<view class="case-body">
<view class="case-head">
<view class="case-tag">{{ item.category }}</view>
<view class="heat-tag" :class="heatClassMap[item.heat]">{{ item.heat }}</view>
</view>
<view class="case-title">{{ item.title }}</view>
<view class="case-summary">{{ item.summary }}</view>
<view class="case-info">来源{{ item.source }}</view>
<view class="case-info">发布时间{{ item.publishAt }}</view>
<view class="tag-list">
<view v-for="tag in item.tags" :key="tag" class="tag-chip"># {{ tag }}</view>
</view>
<view class="case-footer">
<view class="signal-badge">
<view class="signal-badge-label">可借鉴动作</view>
<view class="signal-badge-value">{{ item.highlight }}</view>
</view>
<view class="origin-link" @tap="handleOpenLink(item)">查看原文</view>
</view>
</view>
</view>
</view>
<view v-else class="empty-card">暂无匹配的案例</view>
</view>
</scroll-view>
</view>
</template>
<script>
import CommonHero from '../../components/common-hero/common-hero.vue'
const CASES = [
{
id: 1,
title: '青年宣讲训练营联合短视频挑战赛',
school: '华东某高校团委',
category: '品牌活动',
heat: '高热',
source: '校团委公众号',
publishAt: '03-26 18:20',
summary:
'以“训练营 + 路演 + 短视频共创”三段式推进,先做骨干培训,再通过挑战赛扩散校园参与度,适合复制到主题教育和品牌宣讲场景。',
tags: ['宣讲', '短视频', '路演', '骨干培训'],
link: 'https://example.com/cross-school/case-01',
highlight: '训练营 + 二创传播'
},
{
id: 2,
title: '劳动教育月采用主会场直播联动分会场打卡',
school: '华南某高校学生会',
category: '实践活动',
heat: '跟进',
source: '青春校园网',
publishAt: '03-25 09:40',
summary:
'通过主会场直播统一调性,再让各学院自带话题完成线下打卡,既保留主视觉控制力,也扩大二级学院参与面。',
tags: ['劳动教育', '直播', '学院联动'],
link: 'https://example.com/cross-school/case-02',
highlight: '直播总控 + 分会场扩散'
},
{
id: 3,
title: '科创竞赛经验周报做成案例地图与备赛清单',
school: '华中某高校创新中心',
category: '科创竞赛',
heat: '高热',
source: '双创平台',
publishAt: '03-24 21:15',
summary:
'不只发布赛事通知,而是补充往届项目地图、导师方向、材料清单和时间线,显著降低学生获取备赛信息的门槛。',
tags: ['挑战杯', '项目库', '备赛', '知识沉淀'],
link: 'https://example.com/cross-school/case-03',
highlight: '赛事通知升级为攻略型内容'
},
{
id: 4,
title: '校园歌会从报名表单升级为城市主题内容企划',
school: '西南某高校团委',
category: '文体活动',
heat: '观察',
source: '学校新闻网',
publishAt: '03-23 16:05',
summary:
'围绕城市记忆与青年表达设置主题分场,通过海报、话题征集和观众投票提前积累内容资产,活动尚未开始就形成讨论度。',
tags: ['歌会', '主题策划', '投票互动'],
link: 'https://example.com/cross-school/case-04',
highlight: '先做内容议题再做活动报名'
},
{
id: 5,
title: '社会实践出征仪式叠加成果展和队伍名片墙',
school: '西北某高校实践中心',
category: '实践活动',
heat: '跟进',
source: '实践育人专栏',
publishAt: '03-22 11:30',
summary:
'将传统仪式升级为“成果展 + 队伍画像 + 媒体素材包”组合,便于后续持续发布实践过程稿和回访报道。',
tags: ['社会实践', '成果展', '队伍画像'],
link: 'https://example.com/cross-school/case-05',
highlight: '出征现场即完成素材采集'
},
{
id: 6,
title: '青马工程读书班采用任务制和周主题海报并行',
school: '东北某高校马克思主义学院',
category: '品牌活动',
heat: '高热',
source: '学院团学平台',
publishAt: '03-21 20:10',
summary:
'将阅读、研讨、海报共创和阶段展示拆成清晰任务包,持续释放可视化成果,比单次讲座更容易维持项目关注度。',
tags: ['青马工程', '阅读打卡', '任务制'],
link: 'https://example.com/cross-school/case-06',
highlight: '过程任务化,成果可视化'
}
]
const INSIGHT_SIGNALS = [
{
title: '“活动报名”正在转向“内容预热”',
desc: '高热案例普遍会在报名期前先释放主题海报、人物故事或任务线索,先做认知再做转化。'
},
{
title: '学院联动成为扩大参与面的主流方式',
desc: '不少活动不再单点发通知,而是设计统一主视觉和话题标签,把执行动作分发给二级学院。'
},
{
title: '成果展示正在前置',
desc: '从出征仪式到训练营,多数案例会在启动阶段同步准备成果墙、名片墙或可转发素材。'
}
]
const WATCH_LIST = [
'活动是否配置统一 hashtag 与二创任务',
'是否提供报名后自动领取的素材包',
'是否能把案例沉淀为往届经验库',
'是否区分预热、爆发、回顾三个传播阶段'
]
export default {
components: {
CommonHero
},
data() {
return {
categoryOptions: ['全部', '品牌活动', '实践活动', '科创竞赛', '文体活动'],
heatOptions: ['全部', '高热', '跟进', '观察'],
selectedCategory: '全部',
selectedHeat: '全部',
keyword: '',
cases: CASES,
insightSignals: INSIGHT_SIGNALS,
watchList: WATCH_LIST,
coverClassMap: ['cover-a', 'cover-b', 'cover-c', 'cover-d'],
heatClassMap: {
高热: 'heat-tag--hot',
跟进: 'heat-tag--follow',
观察: 'heat-tag--watch'
}
}
},
computed: {
filteredCases() {
const text = String(this.keyword || '').trim().toLowerCase()
return this.cases.filter((item) => {
const matchCategory = this.selectedCategory === '全部' || item.category === this.selectedCategory
const matchHeat = this.selectedHeat === '全部' || item.heat === this.selectedHeat
const searchText = [item.title, item.school, item.summary, item.tags.join(' ')].join(' ').toLowerCase()
return matchCategory && matchHeat && (!text || searchText.includes(text))
})
},
totalCount() {
return this.cases.length
},
highHeatCount() {
return this.cases.filter((item) => item.heat === '高热').length
},
categoryCount() {
return new Set(this.cases.map((item) => item.category)).size
},
schoolCount() {
return new Set(this.cases.map((item) => item.school)).size
},
streamItems() {
return this.filteredCases.slice(0, 4)
}
},
methods: {
quickFilter(heat) {
this.selectedHeat = heat
},
quickMatchPlanning() {
this.selectedCategory = '品牌活动'
this.selectedHeat = '高热'
uni.showToast({
title: '已切换到高热品牌活动案例',
icon: 'none'
})
},
handleOpenLink(item) {
if (!item || !item.link) {
uni.showToast({
title: '原文链接待接入',
icon: 'none'
})
return
}
uni.setClipboardData({
data: item.link,
success: () => {
uni.showToast({
title: '原文链接已复制',
icon: 'none'
})
}
})
}
}
}
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background:
radial-gradient(circle at top left, rgba(80, 180, 255, 0.18), transparent 28%),
linear-gradient(180deg, #f5fbff 0%, #f5fbff 36%, #fff 100%);
}
.page-scroll {
height: 100vh;
//padding: 28rpx 24rpx 40rpx;
box-sizing: border-box;
}
.hero-panel {
padding: 30rpx;
border-radius: 30rpx;
background:
linear-gradient(135deg, rgba(255, 255, 255, 0.94), rgba(245, 251, 255, 0.94)),
repeating-linear-gradient(
90deg,
rgba(21, 144, 255, 0.04) 0,
rgba(21, 144, 255, 0.04) 2rpx,
transparent 2rpx,
transparent 30rpx
);
box-shadow: 0 16rpx 48rpx rgba(21, 144, 255, 0.1);
border: 1rpx solid rgba(21, 144, 255, 0.14);
}
.hero-kicker {
display: inline-flex;
align-items: center;
padding: 10rpx 18rpx;
border-radius: 999rpx;
background: rgba(21, 144, 255, 0.08);
color: #1496f2;
font-size: 22rpx;
font-weight: 600;
letter-spacing: 1rpx;
}
.hero-title {
margin-top: 22rpx;
color: #1f2d3d;
font-size: 44rpx;
line-height: 1.3;
font-weight: 700;
}
.hero-desc {
margin-top: 16rpx;
color: #5f7f9f;
font-size: 25rpx;
line-height: 1.8;
}
.hero-actions {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
margin-top: 24rpx;
}
.hero-btn {
padding: 16rpx 24rpx;
border-radius: 999rpx;
background: #f3f9ff;
border: 1rpx solid rgba(21, 144, 255, 0.16);
color: #1496f2;
font-size: 24rpx;
font-weight: 600;
}
.hero-btn--primary {
background: linear-gradient(135deg, #32bfff, #1496f2);
color: #fff;
border-color: transparent;
box-shadow: 0 10rpx 26rpx rgba(21, 144, 255, 0.22);
}
.hero-stats {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16rpx;
margin-top: 26rpx;
}
.hero-stat {
padding: 22rpx;
border-radius: 24rpx;
background: rgba(255, 255, 255, 0.92);
border: 1rpx solid rgba(21, 144, 255, 0.12);
box-shadow: 0 10rpx 28rpx rgba(21, 144, 255, 0.07);
}
.hero-stat--primary {
background: linear-gradient(160deg, #32bfff 0%, #1496f2 100%);
color: #fff;
}
.hero-stat--primary .stat-label,
.hero-stat--primary .stat-desc {
color: rgba(255, 255, 255, 0.84);
}
.stat-label {
color: #5f7f9f;
font-size: 22rpx;
}
.stat-value {
margin: 10rpx 0 8rpx;
font-size: 48rpx;
line-height: 1;
font-weight: 700;
}
.stat-desc {
color: #5f7f9f;
font-size: 21rpx;
line-height: 1.6;
}
.demo-alert,
.filter-panel,
.stream-card,
.signal-card,
.cases-card {
margin-top: 20rpx;
padding: 24rpx;
border-radius: 28rpx;
background: rgba(255, 255, 255, 0.92);
box-shadow: 0 16rpx 40rpx rgba(21, 144, 255, 0.08);
}
.demo-alert {
border: 1rpx solid rgba(21, 144, 255, 0.18);
background: rgba(255, 251, 248, 0.95);
}
.demo-alert-title {
font-size: 26rpx;
font-weight: 700;
color: #1f2d3d;
}
.demo-alert-desc {
margin-top: 10rpx;
font-size: 23rpx;
line-height: 1.7;
color: #5f7f9f;
}
.filter-group + .filter-group {
margin-top: 18rpx;
}
.filter-label {
color: #5f7f9f;
font-size: 22rpx;
font-weight: 600;
}
.chip-scroll {
width: 100%;
margin-top: 14rpx;
white-space: nowrap;
}
.chip-row {
display: inline-flex;
gap: 12rpx;
padding-right: 8rpx;
}
.filter-chip {
display: inline-flex;
align-items: center;
padding: 10rpx 20rpx;
border: 1rpx solid rgba(21, 144, 255, 0.16);
border-radius: 999rpx;
background: #f5fbff;
color: #1496f2;
font-size: 22rpx;
}
.filter-chip--active {
color: #fff;
border-color: transparent;
background: linear-gradient(135deg, #32bfff, #1496f2);
box-shadow: 0 8rpx 20rpx rgba(21, 144, 255, 0.22);
}
.search-box {
display: flex;
align-items: center;
gap: 14rpx;
margin-top: 18rpx;
padding: 0 18rpx;
height: 80rpx;
border-radius: 999rpx;
background: #f8fbff;
border: 1rpx solid rgba(21, 144, 255, 0.12);
}
:deep(.search-input.uv-input) {
flex: 1;
height: 100%;
padding: 0 !important;
background: transparent !important;
border: 0 !important;
}
:deep(.search-input.uv-input .uv-input__content__field-wrapper__field) {
font-size: 24rpx;
color: #1f2d3d;
}
.search-clear {
flex-shrink: 0;
font-size: 22rpx;
color: #1496f2;
}
.section-grid {
margin-top: 20rpx;
}
.section-title {
font-size: 30rpx;
font-weight: 700;
color: #1f2d3d;
}
.section-title--small {
font-size: 26rpx;
}
.stream-list,
.signal-list,
.watch-list,
.cases-list {
margin-top: 18rpx;
}
.stream-item,
.signal-item,
.case-card,
.watch-item {
border: 1rpx solid rgba(21, 144, 255, 0.1);
background: linear-gradient(180deg, #f5fbff, #fff);
}
.stream-item {
padding: 18rpx;
border-radius: 24rpx;
}
.stream-item + .stream-item,
.signal-item + .signal-item,
.watch-item + .watch-item,
.case-card + .case-card {
margin-top: 16rpx;
}
.stream-cover,
.case-cover {
position: relative;
overflow: hidden;
border-radius: 20rpx;
}
.stream-cover {
height: 180rpx;
padding: 18rpx;
}
.case-cover {
height: 220rpx;
padding: 20rpx;
}
.cover-a {
background: linear-gradient(135deg, #9be7ff 0%, #32bfff 52%, #1496f2 100%);
}
.cover-b {
background: linear-gradient(135deg, #c7f9cc 0%, #57cc99 52%, #38a3a5 100%);
}
.cover-c {
background: linear-gradient(135deg, #ffd6a5 0%, #ffadad 50%, #ff7b7b 100%);
}
.cover-d {
background: linear-gradient(135deg, #cdb4db 0%, #a2d2ff 52%, #8ecae6 100%);
}
.stream-cover::after,
.case-cover::after {
content: '';
position: absolute;
right: -40rpx;
top: -40rpx;
width: 180rpx;
height: 180rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.18);
}
.stream-cover-category,
.stream-cover-school,
.case-school,
.case-cover-badge {
position: relative;
z-index: 1;
color: #fff;
}
.stream-cover-category,
.case-cover-badge {
display: inline-flex;
padding: 8rpx 14rpx;
border-radius: 999rpx;
background: rgba(255, 255, 255, 0.18);
font-size: 20rpx;
font-weight: 600;
}
.stream-cover-school,
.case-school {
margin-top: 18rpx;
font-size: 30rpx;
font-weight: 700;
line-height: 1.4;
}
.stream-content,
.case-body {
margin-top: 16rpx;
}
.stream-top,
.case-head,
.cases-head,
.case-footer {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16rpx;
}
.stream-title,
.case-title,
.signal-title {
color: #1f2d3d;
font-weight: 700;
line-height: 1.5;
}
.stream-title,
.case-title {
font-size: 29rpx;
}
.stream-meta,
.case-info,
.signal-desc,
.watch-item,
.stream-summary,
.case-summary {
color: #5f7f9f;
font-size: 23rpx;
line-height: 1.75;
}
.stream-meta {
margin-top: 8rpx;
}
.stream-summary,
.case-summary {
margin-top: 12rpx;
color: #1f2d3d;
}
.tag-list {
display: flex;
flex-wrap: wrap;
gap: 10rpx;
margin-top: 14rpx;
}
.tag-chip,
.case-tag,
.heat-tag {
display: inline-flex;
align-items: center;
padding: 6rpx 14rpx;
border-radius: 999rpx;
font-size: 20rpx;
}
.tag-chip,
.case-tag {
background: #e8f4ff;
color: #1496f2;
}
.heat-tag {
flex-shrink: 0;
font-weight: 600;
}
.heat-tag--hot {
background: #fff1f0;
color: #e34d59;
}
.heat-tag--follow {
background: #eef6ff;
color: #2563eb;
}
.heat-tag--watch {
background: #f3f4f6;
color: #6b7280;
}
.signal-item,
.watch-item {
padding: 18rpx;
border-radius: 22rpx;
}
.signal-desc {
margin-top: 8rpx;
}
.signal-divider {
height: 1rpx;
margin: 20rpx 0;
background: rgba(21, 144, 255, 0.1);
}
.case-count {
flex-shrink: 0;
font-size: 22rpx;
color: #5f7f9f;
}
.case-card {
border-radius: 26rpx;
overflow: hidden;
}
.case-cover-badge {
position: absolute;
left: 20rpx;
bottom: 20rpx;
}
.case-body {
padding: 0 20rpx 20rpx;
}
.case-title {
margin-top: 14rpx;
}
.case-info {
margin-top: 10rpx;
}
.signal-badge {
flex: 1;
min-width: 0;
padding: 14rpx 18rpx;
border-radius: 20rpx;
background: #f7fbff;
}
.signal-badge-label {
font-size: 20rpx;
color: #7b8794;
}
.signal-badge-value {
margin-top: 8rpx;
font-size: 24rpx;
font-weight: 700;
color: #1f2d3d;
line-height: 1.5;
}
.origin-link {
flex-shrink: 0;
padding-top: 18rpx;
font-size: 24rpx;
font-weight: 600;
color: #1496f2;
}
.empty-card {
margin-top: 18rpx;
padding: 30rpx 24rpx;
border-radius: 24rpx;
background: #f8fbff;
font-size: 24rpx;
color: #90a0b7;
text-align: center;
}
</style>