From 8b03809c7a3a999442344d581417bf910a5d3e28 Mon Sep 17 00:00:00 2001 From: zerosaturation Date: Wed, 13 May 2026 14:41:17 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E6=B4=BB=E5=8A=A8?= =?UTF-8?q?=E5=B0=8F=E6=8C=89=E9=92=AE=E6=94=B9=E4=B8=BA=E5=8A=A8=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gateway/controller/activity_controller.go | 1 + backend/pkg/models/activity.go | 1 + backend/pkg/proto/activity/activity.pb.go | 13 ++- backend/proto/activity.proto | 1 + .../migrate_add_icon_to_activities.sql | 2 + frontend/pages/components/Header.vue | 103 +++++++----------- .../pages/square/components/WaterfallGrid.vue | 15 +-- .../pages/square/composables/useBanner.js | 5 +- 8 files changed, 66 insertions(+), 75 deletions(-) create mode 100644 backend/scripts/migrate_add_icon_to_activities.sql diff --git a/backend/gateway/controller/activity_controller.go b/backend/gateway/controller/activity_controller.go index 7017661..d310d26 100644 --- a/backend/gateway/controller/activity_controller.go +++ b/backend/gateway/controller/activity_controller.go @@ -559,6 +559,7 @@ func convertActivityListResponse(resp *pbActivity.GetActivityListResponse) map[s "banner_image": activity.BannerImage, "current_stage_background": activity.CurrentStageBackground, "current_stage_title": activity.CurrentStageTitle, + "icon": activity.Icon, }) } diff --git a/backend/pkg/models/activity.go b/backend/pkg/models/activity.go index 4a8d5b7..44d5ea0 100644 --- a/backend/pkg/models/activity.go +++ b/backend/pkg/models/activity.go @@ -20,6 +20,7 @@ type Activity struct { CurrentProgress int64 `json:"current_progress" gorm:"default:0"` Status string `json:"status" gorm:"size:20;default:pending"` // pending/active/completed/expired StageConfigs json.RawMessage `json:"stage_configs" gorm:"type:jsonb"` // 阶段配置 + Icon string `json:"icon" gorm:"size:500"` // 活动图标 CreatedAt int64 `json:"created_at" gorm:"not null"` UpdatedAt int64 `json:"updated_at" gorm:"not null"` diff --git a/backend/pkg/proto/activity/activity.pb.go b/backend/pkg/proto/activity/activity.pb.go index 4f7a0ed..164623c 100644 --- a/backend/pkg/proto/activity/activity.pb.go +++ b/backend/pkg/proto/activity/activity.pb.go @@ -44,6 +44,7 @@ type Activity struct { CurrentStageBackground string `protobuf:"bytes,15,opt,name=current_stage_background,json=currentStageBackground,proto3" json:"current_stage_background,omitempty"` // 当前阶段背景图 CurrentStageTitle string `protobuf:"bytes,16,opt,name=current_stage_title,json=currentStageTitle,proto3" json:"current_stage_title,omitempty"` // 当前阶段标题 OverallEndTime int64 `protobuf:"varint,18,opt,name=overall_end_time,json=overallEndTime,proto3" json:"overall_end_time,omitempty"` // 整体活动结束时间 + Icon string `protobuf:"bytes,19,opt,name=icon,proto3" json:"icon,omitempty"` // 活动图标 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -204,6 +205,13 @@ func (x *Activity) GetOverallEndTime() int64 { return 0 } +func (x *Activity) GetIcon() string { + if x != nil { + return x.Icon + } + return "" +} + // 活动道具 type ActivityItem struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1355,7 +1363,7 @@ var File_activity_proto protoreflect.FileDescriptor const file_activity_proto_rawDesc = "" + "\n" + - "\x0eactivity.proto\x12\x10topfans.activity\x1a\x12proto/common.proto\x1a\x1cgoogle/api/annotations.proto\"\xff\x04\n" + + "\x0eactivity.proto\x12\x10topfans.activity\x1a\x12proto/common.proto\x1a\x1cgoogle/api/annotations.proto\"\x93\x05\n" + "\bActivity\x12\x0e\n" + "\x02id\x18\x01 \x01(\x03R\x02id\x12#\n" + "\ractivity_type\x18\x02 \x01(\tR\factivityType\x12\x14\n" + @@ -1377,7 +1385,8 @@ const file_activity_proto_rawDesc = "" + "\fbanner_image\x18\x0e \x01(\tR\vbannerImage\x128\n" + "\x18current_stage_background\x18\x0f \x01(\tR\x16currentStageBackground\x12.\n" + "\x13current_stage_title\x18\x10 \x01(\tR\x11currentStageTitle\x12(\n" + - "\x10overall_end_time\x18\x12 \x01(\x03R\x0eoverallEndTime\"\xc7\x01\n" + + "\x10overall_end_time\x18\x12 \x01(\x03R\x0eoverallEndTime\x12\x12\n" + + "\x04icon\x18\x13 \x01(\tR\x04icon\"\xc7\x01\n" + "\fActivityItem\x12\x0e\n" + "\x02id\x18\x01 \x01(\x03R\x02id\x12\x1b\n" + "\titem_type\x18\x02 \x01(\tR\bitemType\x12\x1b\n" + diff --git a/backend/proto/activity.proto b/backend/proto/activity.proto index 63b1a41..333ae31 100644 --- a/backend/proto/activity.proto +++ b/backend/proto/activity.proto @@ -29,6 +29,7 @@ message Activity { string current_stage_background = 15; // 当前阶段背景图 string current_stage_title = 16; // 当前阶段标题 int64 overall_end_time = 18; // 整体活动结束时间 + string icon = 19; // 活动图标 } // 活动道具 diff --git a/backend/scripts/migrate_add_icon_to_activities.sql b/backend/scripts/migrate_add_icon_to_activities.sql new file mode 100644 index 0000000..40f3569 --- /dev/null +++ b/backend/scripts/migrate_add_icon_to_activities.sql @@ -0,0 +1,2 @@ +-- 添加 icon 字段到 activities 表 +ALTER TABLE activities ADD COLUMN icon VARCHAR(500) DEFAULT '' COMMENT '活动图标'; \ No newline at end of file diff --git a/frontend/pages/components/Header.vue b/frontend/pages/components/Header.vue index 3a9f046..1a79f55 100644 --- a/frontend/pages/components/Header.vue +++ b/frontend/pages/components/Header.vue @@ -35,17 +35,24 @@ --> - - - - - - - + + + + + + + + - - - 星援活动 + + + {{ activity.theme || '星援活动' }} + @@ -94,9 +101,12 @@ import { useStore } from 'vuex'; import Avatar from './Avatar.vue'; import DailyTasks from '@/pages/tasks/daily-tasks.vue'; import GuideModal from '@/pages/tasks/GuideModal.vue'; -import { getActivityListApi } from '@/utils/api.js'; +import { useBanner } from '@/pages/square/composables/useBanner.js'; import { reportEvent } from '@/utils/task-api.js'; +// 获取星援活动数据(复用 square 的 useBanner) +const { bannerActivities, loadBannerActivities } = useBanner(); + // 定义 props const props = defineProps({ showBack: { @@ -226,9 +236,16 @@ function checkAndReportDailyLogin() { } } -// 组件挂载时加载用户信息并监听事件 +// 星援活动列表 - 从 bannerActivities 中获取非 expired 的活动 +const starActivities = computed(() => { + // return bannerActivities.value + return bannerActivities.value.filter(activity => activity.status !== 'expired'); +}); + +// 组件挂载时加载用户信息和星援活动数据 onMounted(() => { loadUserInfo(); + loadBannerActivities(); uni.$on('avatarUpdated', handleAvatarUpdate); uni.$on('userInfoUpdated', handleUserInfoUpdate); uni.$on('balanceUpdated', handleBalanceUpdate); @@ -270,58 +287,10 @@ const handleTaskClick = () => { }; // 处理星援活动图标点击 -const handleStarActivityClick = async () => { - try { - // 从本地存储获取star_id - const starId = uni.getStorageSync('star_id'); - if (!starId) { - uni.showToast({ - title: '无法获取用户信息', - icon: 'none' - }); - return; - } - - // 显示加载提示 - uni.showLoading({ - title: '加载中...' - }); - - // 调用API获取活动列表 - const response = await getActivityListApi(starId, 1, 10); - - uni.hideLoading(); - - // 检查响应数据 - if (response && response.data && response.data.activities) { - const activities = response.data.activities; - - // 查找activity_type为bus的活动 - const busActivity = activities.find(activity => activity.activity_type === 'bus'); - - if (busActivity) { - // 跳转到应援活动页面 - uni.navigateTo({ - url: `/pages/support-activity/index?id=${busActivity.id}` - }); - } else { - uni.showToast({ - title: '暂无巴士应援活动', - icon: 'none' - }); - } - } else { - uni.showToast({ - title: '获取活动列表失败', - icon: 'none' - }); - } - } catch (error) { - uni.hideLoading(); - console.error('获取活动列表失败:', error); - uni.showToast({ - title: error.message || '获取活动列表失败', - icon: 'none' +const handleActivityClick = (activity) => { + if (activity) { + uni.navigateTo({ + url: `/pages/support-activity/index?id=${activity.id}` }); } }; @@ -443,6 +412,12 @@ defineExpose({ margin-left: auto; } +.star-activity-list { + display: flex; + align-items: center; + gap: 8rpx; +} + .daily-task-group { position: relative; /* 必须是相对定位,作为子元素的定位基准 */ diff --git a/frontend/pages/square/components/WaterfallGrid.vue b/frontend/pages/square/components/WaterfallGrid.vue index 1d16553..68aa729 100644 --- a/frontend/pages/square/components/WaterfallGrid.vue +++ b/frontend/pages/square/components/WaterfallGrid.vue @@ -13,7 +13,7 @@ > { }); } else { // 第一次点击,单击跳转 - - cardTapTimers[card.id] = setTimeout(() => { - delete cardTapTimers[card.id]; - uni.navigateTo({ url: `/pages/asset-detail/asset-detail?asset_id=${card.id}` }); - }, 300); + if(card.id){ + cardTapTimers[card.id] = setTimeout(() => { + delete cardTapTimers[card.id]; + uni.navigateTo({ url: `/pages/asset-detail/asset-detail?asset_id=${card.id}` }); + }, 300); + } } } @@ -966,7 +967,7 @@ const loadUsersAndStartScroll = () => { } @keyframes iosAutoScroll { - from { transform: translateX(0); } + /* from { transform: translateX(0); } */ to { transform: translateX(var(--scroll-dist)); } } diff --git a/frontend/pages/square/composables/useBanner.js b/frontend/pages/square/composables/useBanner.js index e321b5a..1822c65 100644 --- a/frontend/pages/square/composables/useBanner.js +++ b/frontend/pages/square/composables/useBanner.js @@ -11,8 +11,9 @@ export function useBanner() { if (res.code === 200 && res.data?.activities) { const activities = res.data.activities - // 直接使用后端返回的图片URL - bannerActivities.value = activities + // 过滤掉已过期的活动 + // bannerActivities.value = activities + bannerActivities.value = activities.filter(item => item.status !== 'expired') } } catch (e) { console.error('[useBanner] 加载 banner 活动失败', e?.message ?? e)