Browse Source
- 新增通用分页组件pagination,支持空状态、加载中、无更多数据状态 - 重构活动列表页面,添加分页逻辑、分类筛选、动态数据渲染 - 完善活动详情页面,支持动态数据展示、签到报名逻辑 - 优化活动创建页面,调整上传组件与表单逻辑 - 重构登录与请求逻辑,统一鉴权处理 - 简化课表页面代码,移除旧的双向滚动逻辑 - 优化上传组件,移除预览删除功能,仅保留上传核心逻辑 - 新增活动结果页面,完善报名成功后的展示与推荐活动master
32 changed files with 2224 additions and 981 deletions
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
/* global getApp, Component */ |
||||
const app = getApp() |
||||
|
||||
Component({ |
||||
externalClasses: ['external-class'], |
||||
properties: { |
||||
pagination: { |
||||
type: Object, |
||||
value() { |
||||
return {} |
||||
}, |
||||
}, |
||||
customEmpty: { |
||||
tyep: Boolean, |
||||
value: false, |
||||
}, |
||||
}, |
||||
data: { |
||||
imageUrl: app.globalData.imageUrl, |
||||
}, |
||||
methods: { |
||||
handleTouchmove() { |
||||
return false |
||||
}, |
||||
}, |
||||
}) |
||||
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
{ |
||||
"component": true, |
||||
"usingComponents": { |
||||
"van-divider": "@vant/weapp/divider/index" |
||||
} |
||||
} |
||||
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
/* components/pagination/index.wxss */ |
||||
.none { |
||||
display: block; |
||||
margin: 30rpx auto; |
||||
width: 80%; |
||||
} |
||||
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
<block wx:if="{{pagination.count==0}}"> |
||||
<slot wx:if="{{customEmpty}}"></slot> |
||||
<image wx:else class="none external-class" src="{{imageUrl}}/none.png?t={{Timestamp}}"></image> |
||||
</block> |
||||
<van-divider contentPosition="center" wx:elif="{{pagination.page<pagination.pages}}"> |
||||
<van-loading /> |
||||
加载中... |
||||
</van-divider> |
||||
<van-divider contentPosition="center" wx:elif="{{pagination.page>=pagination.pages}}">没有更多了</van-divider> |
||||
@ -1,92 +1,20 @@
@@ -1,92 +1,20 @@
|
||||
<!-- |
||||
Upload 上传组件 |
||||
支持图片/视频/文件上传,进度条,预览,删除,重试 |
||||
支持图片/视频/文件上传 |
||||
仅保留上传功能,不显示文件列表 |
||||
--> |
||||
<view class="upload"> |
||||
<!-- 文件列表 --> |
||||
<view class="upload-list"> |
||||
<!-- 单个文件项 --> |
||||
<view |
||||
wx:for="{{_fileList}}" |
||||
wx:key="uid" |
||||
class="upload-item upload-item--{{item.type}}" |
||||
> |
||||
<!-- 图片预览 --> |
||||
<view |
||||
wx:if="{{item.type === 'image'}}" |
||||
class="upload-preview" |
||||
data-uid="{{item.uid}}" |
||||
bindtap="onPreviewImage" |
||||
> |
||||
<image class="upload-preview-media" src="{{item.url}}" mode="aspectFill" /> |
||||
</view> |
||||
|
||||
<!-- 视频预览 --> |
||||
<view |
||||
wx:elif="{{item.type === 'video'}}" |
||||
class="upload-preview" |
||||
data-uid="{{item.uid}}" |
||||
bindtap="onPreviewVideo" |
||||
> |
||||
<video |
||||
class="upload-preview-media" |
||||
src="{{item.url}}" |
||||
controls="{{false}}" |
||||
object-fit="cover" |
||||
/> |
||||
<view class="upload-preview-play"> |
||||
<view class="upload-preview-play-icon"></view> |
||||
</view> |
||||
</view> |
||||
|
||||
<!-- 文件预览 --> |
||||
<view wx:elif="{{item.type === 'file'}}" class="upload-preview upload-preview--file"> |
||||
<view class="upload-file-icon"> |
||||
<view class="upload-file-icon-corner"></view> |
||||
<text class="upload-file-icon-text">{{item.name}}</text> |
||||
</view> |
||||
<text class="upload-file-name">{{item.name}}</text> |
||||
</view> |
||||
|
||||
<!-- 上传中遮罩 + 进度条 --> |
||||
<view wx:if="{{item.status === 'uploading'}}" class="upload-mask"> |
||||
<view class="upload-progress"> |
||||
<view class="upload-progress-bar" style="width: {{item.progress}}%"></view> |
||||
</view> |
||||
<text class="upload-progress-text">{{item.progress}}%</text> |
||||
</view> |
||||
|
||||
<!-- 上传失败遮罩 + 重试 --> |
||||
<view wx:if="{{item.status === 'error'}}" class="upload-mask upload-mask--error"> |
||||
<text class="upload-error-text">上传失败</text> |
||||
<view class="upload-retry" data-uid="{{item.uid}}" catchtap="onRetry"> |
||||
<text class="upload-retry-text">重试</text> |
||||
</view> |
||||
</view> |
||||
|
||||
<!-- 删除按钮 --> |
||||
<view |
||||
wx:if="{{!readonly}}" |
||||
class="upload-remove" |
||||
data-uid="{{item.uid}}" |
||||
catchtap="onRemove" |
||||
> |
||||
<view class="upload-remove-icon"></view> |
||||
</view> |
||||
</view> |
||||
|
||||
<!-- 默认上传按钮(未达到最大数量且非只读时显示) --> |
||||
<view |
||||
wx:if="{{!readonly && _fileList.length < maxCount}}" |
||||
class="upload-trigger {{useSlot ? 'upload-trigger--slot' : ''}}" |
||||
bindtap="onChooseFile" |
||||
> |
||||
<!-- 具名插槽:自定义上传区域 --> |
||||
<slot name="upload-area"></slot> |
||||
<!-- 默认上传占位 --> |
||||
<view wx:if="{{!useSlot}}" class="upload-trigger-default"> |
||||
<view class="upload-trigger-icon"></view> |
||||
</view> |
||||
<!-- 默认上传按钮 --> |
||||
<view |
||||
wx:if="{{!readonly && fileList.length < maxCount}}" |
||||
class="upload-trigger {{useSlot ? 'upload-trigger--slot' : ''}}" |
||||
bindtap="onChooseFile" |
||||
> |
||||
<!-- 具名插槽:自定义上传区域 --> |
||||
<slot name="upload-area"></slot> |
||||
<!-- 默认上传占位 --> |
||||
<view wx:if="{{!useSlot}}" class="upload-trigger-default"> |
||||
<view class="upload-trigger-icon"></view> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
|
||||
|
After Width: | Height: | Size: 77 KiB |
@ -1,52 +1,489 @@
@@ -1,52 +1,489 @@
|
||||
const _app = getApp<IAppOption>() |
||||
const app = getApp<IAppOption>() |
||||
|
||||
interface ILevelItem { |
||||
id: number |
||||
name: string |
||||
code: string |
||||
sort: number |
||||
} |
||||
|
||||
interface ICategoryItem { |
||||
id: number |
||||
name: string |
||||
code: string |
||||
icon: string |
||||
sort: number |
||||
isEnabled: number |
||||
} |
||||
|
||||
interface IActivityItem { |
||||
id: number |
||||
name: string |
||||
type: number |
||||
typeOther: string |
||||
mainImages: string[] |
||||
summary: string |
||||
description: string |
||||
regType: number |
||||
regCondition: string |
||||
contactName: string |
||||
contactPhone: string |
||||
startAt: string |
||||
endAt: string |
||||
location: string |
||||
status: string |
||||
} |
||||
|
||||
interface IPagination { |
||||
page: number |
||||
pageSize: number |
||||
pages: number |
||||
count: number |
||||
} |
||||
|
||||
Page({ |
||||
data: { |
||||
filterShow: true, |
||||
filterShow: false, |
||||
// 活动等级列表
|
||||
levelList: [] as ILevelItem[], |
||||
// 等级 Tab 列表(包含"全部等级")
|
||||
levelTabs: [ |
||||
{ |
||||
id: 0, |
||||
name: '全部', |
||||
}, |
||||
] as Array<{ id: number; name: string }>, |
||||
// 当前选中的等级索引
|
||||
currentLevelIndex: 0, |
||||
// 活动分类列表
|
||||
categoryList: [] as ICategoryItem[], |
||||
// 分类 Tab 列表(包含"全部分类")
|
||||
typeList: [ |
||||
{ |
||||
id: 0, |
||||
name: '全部分类', |
||||
icon: '5', |
||||
iconActive: '6', |
||||
icon: '/images/icon5.png', |
||||
iconActive: '/images/icon6.png', |
||||
isSelected: true, |
||||
}, |
||||
{ |
||||
name: '学术科技', |
||||
icon: '7', |
||||
iconActive: '8', |
||||
] as Array<{ id: number; name: string; icon: string; iconActive: string; isSelected: boolean }>, |
||||
// 当前选中的分类 ID 数组(支持多选)
|
||||
selectedCategoryIds: [] as number[], |
||||
// 活动列表
|
||||
activityList: [] as IActivityItem[], |
||||
// 分页信息
|
||||
pagination: { |
||||
page: 1, |
||||
pageSize: 20, |
||||
pages: 0, |
||||
count: 0, |
||||
} as IPagination, |
||||
// 加载状态
|
||||
loading: false, |
||||
// 筛选参数
|
||||
filters: { |
||||
status: '', |
||||
keyword: '', |
||||
levelId: 0, |
||||
categoryIds: [] as number[], |
||||
startTime: '', |
||||
endTime: '', |
||||
}, |
||||
// 时间快捷选项
|
||||
timeOptions: [ |
||||
{ id: 0, name: '全部时间' }, |
||||
{ id: 1, name: '今天' }, |
||||
{ id: 2, name: '本周' }, |
||||
{ id: 3, name: '本月' }, |
||||
{ id: 4, name: '自定义时间' }, |
||||
], |
||||
// 当前选中的时间选项索引
|
||||
selectedTimeIndex: 0, |
||||
// 自定义时间范围(用于 picker 显示)
|
||||
customStartTime: '', |
||||
customEndTime: '', |
||||
}, |
||||
|
||||
onLoad() { |
||||
// 在 waitLogin 回调中请求接口
|
||||
app.waitLogin({ type: 1 }).then(() => { |
||||
this.fetchLevelList() |
||||
this.fetchCategoryList() |
||||
this.fetchActivityList() |
||||
}) |
||||
}, |
||||
|
||||
// 获取活动等级列表
|
||||
async fetchLevelList() { |
||||
try { |
||||
const res = await wx.ajax({ |
||||
url: '/activity-level/list', |
||||
method: 'GET', |
||||
data: {}, |
||||
}) |
||||
if (res && res.list) { |
||||
// 构建 levelTabs,在开头添加"全部"
|
||||
const levelTabs = [ |
||||
{ |
||||
id: 0, |
||||
name: '全部', |
||||
}, |
||||
...res.list.map((item: ILevelItem) => ({ |
||||
id: item.id, |
||||
name: item.name, |
||||
})), |
||||
] |
||||
this.setData({ |
||||
levelList: res.list, |
||||
levelTabs, |
||||
}) |
||||
} |
||||
} catch (err) { |
||||
console.error('获取活动等级列表失败:', err) |
||||
} |
||||
}, |
||||
|
||||
// 获取活动分类列表
|
||||
async fetchCategoryList() { |
||||
try { |
||||
const res = await wx.ajax({ |
||||
url: '/activity-category/list', |
||||
method: 'GET', |
||||
data: {}, |
||||
}) |
||||
if (res && res.list) { |
||||
const { selectedCategoryIds } = this.data |
||||
// 构建 typeList,在开头添加"全部分类",并计算选中状态
|
||||
const typeList = [ |
||||
{ |
||||
id: 0, |
||||
name: '全部分类', |
||||
icon: '/images/icon5.png', |
||||
iconActive: '/images/icon6.png', |
||||
isSelected: selectedCategoryIds.length === 0, |
||||
}, |
||||
...res.list.map((item: ICategoryItem) => ({ |
||||
id: item.id, |
||||
name: item.name, |
||||
icon: item.icon || '/images/icon5.png', |
||||
iconActive: item.icon || '/images/icon6.png', |
||||
isSelected: selectedCategoryIds.includes(item.id), |
||||
})), |
||||
] |
||||
this.setData({ |
||||
categoryList: res.list, |
||||
typeList, |
||||
}) |
||||
} |
||||
} catch (err) { |
||||
console.error('获取活动分类列表失败:', err) |
||||
} |
||||
}, |
||||
|
||||
// 获取活动列表
|
||||
async fetchActivityList(isRefresh = false) { |
||||
if (this.data.loading) return |
||||
|
||||
const { pagination, filters, selectedCategoryIds, currentLevelIndex, levelTabs } = this.data |
||||
const page = isRefresh ? 1 : pagination.page |
||||
|
||||
this.setData({ loading: true }) |
||||
|
||||
try { |
||||
// 构建请求参数
|
||||
const params: Record<string, any> = { |
||||
page, |
||||
pageSize: pagination.pageSize, |
||||
} |
||||
|
||||
// 添加筛选参数
|
||||
if (filters.status) params.status = filters.status |
||||
if (filters.keyword) params.keyword = filters.keyword |
||||
if (filters.startTime) params.startTime = filters.startTime |
||||
if (filters.endTime) params.endTime = filters.endTime |
||||
|
||||
// 等级筛选:当前选中的等级(非"全部等级")
|
||||
if (currentLevelIndex > 0 && levelTabs[currentLevelIndex]) { |
||||
params.levelId = levelTabs[currentLevelIndex].id |
||||
} |
||||
|
||||
// 分类筛选:选中的分类 ID 数组(非空时传值)
|
||||
if (selectedCategoryIds.length > 0) { |
||||
params.categoryIds = selectedCategoryIds |
||||
} |
||||
|
||||
const res = await wx.ajax({ |
||||
url: '/activity/list', |
||||
method: 'GET', |
||||
data: params, |
||||
}) |
||||
|
||||
if (res) { |
||||
const newList = isRefresh ? res.list : [...this.data.activityList, ...res.list] |
||||
this.setData({ |
||||
activityList: newList, |
||||
pagination: { |
||||
page: res.page || page, |
||||
pageSize: res.pageSize || pagination.pageSize, |
||||
pages: res.pages || 0, |
||||
count: res.count || 0, |
||||
}, |
||||
}) |
||||
} |
||||
} catch (err) { |
||||
console.error('获取活动列表失败:', err) |
||||
} finally { |
||||
this.setData({ loading: false }) |
||||
} |
||||
}, |
||||
|
||||
// 切换分类(支持多选)
|
||||
handleTypeChange(e: WechatMiniprogram.TouchEvent) { |
||||
const id = e.currentTarget.dataset.id |
||||
let { selectedCategoryIds, typeList } = this.data |
||||
|
||||
// 点击"全部分类"(id=0)时,清空所有选中
|
||||
if (id === 0) { |
||||
selectedCategoryIds = [] |
||||
} else { |
||||
// 点击其他分类时
|
||||
const index = selectedCategoryIds.indexOf(id) |
||||
if (index > -1) { |
||||
// 已选中,取消选中
|
||||
selectedCategoryIds = selectedCategoryIds.filter((item) => item !== id) |
||||
} else { |
||||
// 未选中,添加选中
|
||||
selectedCategoryIds = [...selectedCategoryIds, id] |
||||
} |
||||
} |
||||
|
||||
// 更新 typeList 的选中状态
|
||||
typeList = typeList.map((item) => ({ |
||||
...item, |
||||
isSelected: item.id === 0 ? selectedCategoryIds.length === 0 : selectedCategoryIds.includes(item.id), |
||||
})) |
||||
|
||||
this.setData({ |
||||
selectedCategoryIds, |
||||
typeList, |
||||
activityList: [], |
||||
pagination: { |
||||
page: 1, |
||||
pageSize: 20, |
||||
pages: 0, |
||||
count: 0, |
||||
}, |
||||
{ |
||||
name: '文体艺术', |
||||
icon: '9', |
||||
iconActive: '10', |
||||
}) |
||||
this.fetchActivityList(true) |
||||
}, |
||||
|
||||
// 切换等级 Tab
|
||||
handleLevelChange(e: WechatMiniprogram.TouchEvent) { |
||||
const index = e.currentTarget.dataset.index |
||||
if (index === this.data.currentLevelIndex) return |
||||
|
||||
this.setData({ |
||||
currentLevelIndex: index, |
||||
activityList: [], |
||||
pagination: { |
||||
page: 1, |
||||
pageSize: 20, |
||||
pages: 0, |
||||
count: 0, |
||||
}, |
||||
{ |
||||
name: '志愿公益', |
||||
icon: '11', |
||||
iconActive: '12', |
||||
}) |
||||
this.fetchActivityList(true) |
||||
}, |
||||
|
||||
// 下拉刷新
|
||||
onPullDownRefresh() { |
||||
this.setData({ |
||||
activityList: [], |
||||
pagination: { |
||||
page: 1, |
||||
pageSize: 20, |
||||
pages: 0, |
||||
count: 0, |
||||
}, |
||||
{ |
||||
name: '创新创业', |
||||
icon: '13', |
||||
iconActive: '14', |
||||
}) |
||||
this.fetchActivityList(true).then(() => { |
||||
wx.stopPullDownRefresh() |
||||
}) |
||||
}, |
||||
|
||||
// 上拉加载更多
|
||||
onReachBottom() { |
||||
const { pagination, loading } = this.data |
||||
if (loading || pagination.page >= pagination.pages) return |
||||
|
||||
this.setData({ |
||||
pagination: { |
||||
...pagination, |
||||
page: pagination.page + 1, |
||||
}, |
||||
], |
||||
}) |
||||
this.fetchActivityList() |
||||
}, |
||||
onLoad() {}, |
||||
|
||||
// 关闭筛选弹窗
|
||||
handlePopupClose() { |
||||
this.setData({ |
||||
filterShow: false, |
||||
}) |
||||
}, |
||||
|
||||
// 打开筛选弹窗
|
||||
handleFilterOpen() { |
||||
this.setData({ |
||||
filterShow: true, |
||||
}) |
||||
}, |
||||
|
||||
// 选择时间快捷选项
|
||||
handleTimeOptionChange(e: WechatMiniprogram.TouchEvent) { |
||||
const index = e.currentTarget.dataset.index |
||||
this.setData({ |
||||
selectedTimeIndex: index, |
||||
}) |
||||
|
||||
// 根据选项设置时间范围
|
||||
const today = new Date() |
||||
const formatDate = (date: Date) => { |
||||
const year = date.getFullYear() |
||||
const month = String(date.getMonth() + 1).padStart(2, '0') |
||||
const day = String(date.getDate()).padStart(2, '0') |
||||
return `${year}-${month}-${day}` |
||||
} |
||||
|
||||
let startTime = '' |
||||
let endTime = '' |
||||
|
||||
switch (index) { |
||||
case 0: // 全部时间
|
||||
startTime = '' |
||||
endTime = '' |
||||
break |
||||
case 1: // 今天
|
||||
startTime = formatDate(today) |
||||
endTime = formatDate(today) |
||||
break |
||||
case 2: { // 本周
|
||||
const weekStart = new Date(today) |
||||
weekStart.setDate(today.getDate() - today.getDay() + 1) |
||||
const weekEnd = new Date(weekStart) |
||||
weekEnd.setDate(weekStart.getDate() + 6) |
||||
startTime = formatDate(weekStart) |
||||
endTime = formatDate(weekEnd) |
||||
break |
||||
} |
||||
case 3: { // 本月
|
||||
const monthStart = new Date(today.getFullYear(), today.getMonth(), 1) |
||||
const monthEnd = new Date(today.getFullYear(), today.getMonth() + 1, 0) |
||||
startTime = formatDate(monthStart) |
||||
endTime = formatDate(monthEnd) |
||||
break |
||||
} |
||||
case 4: // 自定义时间
|
||||
// 使用已选择的自定义时间,或清空
|
||||
startTime = this.data.customStartTime |
||||
endTime = this.data.customEndTime |
||||
break |
||||
} |
||||
|
||||
this.setData({ |
||||
filters: { |
||||
...this.data.filters, |
||||
startTime, |
||||
endTime, |
||||
}, |
||||
}) |
||||
}, |
||||
|
||||
// 选择开始时间
|
||||
handleStartTimeChange(e: WechatMiniprogram.PickerChange) { |
||||
const date = e.detail.value as string |
||||
this.setData({ |
||||
customStartTime: date, |
||||
selectedTimeIndex: 4, // 自动切换到自定义时间
|
||||
filters: { |
||||
...this.data.filters, |
||||
startTime: date, |
||||
}, |
||||
}) |
||||
}, |
||||
|
||||
// 选择结束时间
|
||||
handleEndTimeChange(e: WechatMiniprogram.PickerChange) { |
||||
const date = e.detail.value as string |
||||
this.setData({ |
||||
customEndTime: date, |
||||
selectedTimeIndex: 4, // 自动切换到自定义时间
|
||||
filters: { |
||||
...this.data.filters, |
||||
endTime: date, |
||||
}, |
||||
}) |
||||
}, |
||||
|
||||
// 取消筛选
|
||||
handleFilterCancel() { |
||||
// 重置筛选条件
|
||||
this.setData({ |
||||
filterShow: false, |
||||
selectedTimeIndex: 0, |
||||
customStartTime: '', |
||||
customEndTime: '', |
||||
filters: { |
||||
status: '', |
||||
keyword: '', |
||||
levelId: 0, |
||||
categoryIds: [], |
||||
startTime: '', |
||||
endTime: '', |
||||
}, |
||||
}) |
||||
this.fetchActivityList(true) |
||||
}, |
||||
|
||||
// 确定筛选
|
||||
handleFilterConfirm() { |
||||
this.setData({ |
||||
filterShow: false, |
||||
activityList: [], |
||||
pagination: { |
||||
page: 1, |
||||
pageSize: 20, |
||||
pages: 0, |
||||
count: 0, |
||||
}, |
||||
}) |
||||
this.fetchActivityList(true) |
||||
}, |
||||
|
||||
// 申请活动
|
||||
handleApply() { |
||||
wx.navigateTo({ |
||||
url: '/pages/actAdd/index', |
||||
}) |
||||
}, |
||||
handleDetail() { |
||||
|
||||
// 查看活动详情
|
||||
handleDetail(e: WechatMiniprogram.TouchEvent) { |
||||
const id = e.currentTarget.dataset.id |
||||
wx.navigateTo({ |
||||
url: '/pages/actDetail/index', |
||||
url: `/pages/actDetail/index?id=${id}`, |
||||
}) |
||||
}, |
||||
|
||||
// 获取活动状态文本
|
||||
getStatusText(status: string): string { |
||||
const statusMap: Record<string, string> = { |
||||
registering: '报名中', |
||||
running: '进行中', |
||||
ended: '已结束', |
||||
} |
||||
return statusMap[status] || status |
||||
}, |
||||
}) |
||||
|
||||
export {} |
||||
|
||||
@ -1,8 +1,52 @@
@@ -1,8 +1,52 @@
|
||||
const _app = getApp<IAppOption>(); |
||||
const app = getApp<IAppOption>() |
||||
|
||||
Page({ |
||||
data: {}, |
||||
onLoad() {}, |
||||
}); |
||||
data: { |
||||
activityId: 0, |
||||
status: '', // pending | draft
|
||||
qrCodeUrl: '', // 公众号二维码 URL
|
||||
}, |
||||
|
||||
onLoad(options: { id?: string; status?: string }) { |
||||
const activityId = options.id ? Number(options.id) : 0 |
||||
const status = options.status || '' |
||||
|
||||
this.setData({ |
||||
activityId, |
||||
status, |
||||
}) |
||||
|
||||
app.waitLogin({ type: 1 }).then(() => { |
||||
// 获取用户信息,包括公众号二维码
|
||||
this.getUserProfile() |
||||
}) |
||||
}, |
||||
|
||||
// 获取用户信息
|
||||
async getUserProfile() { |
||||
const res = await wx.ajax({ |
||||
url: '/me/profile', |
||||
method: 'GET', |
||||
}) |
||||
|
||||
this.setData({ |
||||
qrCodeUrl: res.wechatSubscribe.qrCodeUrl, |
||||
}) |
||||
}, |
||||
|
||||
// 继续发布
|
||||
handleContinue() { |
||||
wx.redirectTo({ |
||||
url: '/pages/actAdd/index', |
||||
}) |
||||
}, |
||||
|
||||
// 返回活动页
|
||||
handleBack() { |
||||
wx.switchTab({ |
||||
url: '/pages/act/index', |
||||
}) |
||||
}, |
||||
}) |
||||
|
||||
export {} |
||||
|
||||
@ -1,33 +1,510 @@
@@ -1,33 +1,510 @@
|
||||
const _app = getApp<IAppOption>() |
||||
const app = getApp<IAppOption>() |
||||
|
||||
interface IActivityDetail { |
||||
id: number |
||||
name: string |
||||
type: number |
||||
typeOther: string |
||||
summary: string |
||||
description: string |
||||
regType: number |
||||
regCondition: string |
||||
contactName: string |
||||
contactPhone: string |
||||
mainImages: string[] |
||||
detailImages: string[] |
||||
regStartAt: string |
||||
regEndAt: string |
||||
startAt: string |
||||
endAt: string |
||||
location: string |
||||
organizer: string |
||||
status: string |
||||
checkinType: number |
||||
checkinStartAt: string |
||||
checkinEndAt: string |
||||
quota: number |
||||
regCount: number |
||||
viewUserCount: number |
||||
viewCount: number |
||||
checkinCount: number |
||||
commentCount: number |
||||
shareCount: number |
||||
collectCount: number |
||||
tags: string[] |
||||
levelId: number |
||||
levelName: string |
||||
categoryIds: number[] |
||||
categoryNames: string[] |
||||
isRegistered: boolean |
||||
isCheckedIn: boolean |
||||
isFavorited: boolean |
||||
isReviewed: boolean |
||||
countdownSeconds: number |
||||
registrationList: Array<{ |
||||
userId: number |
||||
avatarUrl: string |
||||
nickname: string |
||||
realName: string |
||||
registeredAt: string |
||||
}> |
||||
agendas: Array<{ |
||||
id: number |
||||
agendaDate: string |
||||
agendaTime: string |
||||
title: string |
||||
description: string |
||||
sort: number |
||||
}> |
||||
} |
||||
|
||||
interface IReviewItem { |
||||
id: number |
||||
userId: number |
||||
nickname: string |
||||
avatarUrl: string |
||||
rating: string |
||||
content: string |
||||
images: string[] |
||||
isAnonymous: boolean |
||||
likeCount: number |
||||
isLiked: boolean |
||||
createdAt: string |
||||
auditStatus: string |
||||
} |
||||
|
||||
interface IPagination { |
||||
page: number |
||||
pageSize: number |
||||
total: number |
||||
totalPages: number |
||||
} |
||||
|
||||
Page({ |
||||
data: { |
||||
activityId: 0, |
||||
detail: null as IActivityDetail | null, |
||||
loading: true, |
||||
|
||||
// 倒计时
|
||||
countdownSeconds: 0, |
||||
timeData: { days: 0, hours: 0, minutes: 0, seconds: 0 }, |
||||
|
||||
// 弹窗
|
||||
popupShow: false, |
||||
popupType: 'popup1', // 签到成功弹窗
|
||||
popupType: 'popup1', |
||||
popupParams: {} as any, |
||||
|
||||
// 评论
|
||||
commentShow: false, |
||||
commentRating: 5, |
||||
commentContent: '', |
||||
commentImages: [] as Array<{ |
||||
uid: string |
||||
url: string |
||||
type: string |
||||
name: string |
||||
size: number |
||||
status: 'pending' | 'uploading' | 'success' | 'error' |
||||
progress: number |
||||
}>, |
||||
commentAnonymous: false, |
||||
|
||||
// 评价列表
|
||||
reviewList: [] as IReviewItem[], |
||||
reviewPagination: { |
||||
page: 1, |
||||
pageSize: 10, |
||||
total: 0, |
||||
totalPages: 0, |
||||
} as IPagination, |
||||
reviewLoading: false, |
||||
}, |
||||
onLoad() {}, |
||||
handlePopupOk() { |
||||
const { popupType } = this.data |
||||
if (popupType === 'argument') { |
||||
|
||||
onLoad(options: { id?: string }) { |
||||
const activityId = options.id ? Number(options.id) : 0 |
||||
if (!activityId) { |
||||
wx.showToast({ title: '活动不存在', icon: 'error' }) |
||||
setTimeout(() => wx.navigateBack(), 1500) |
||||
return |
||||
} |
||||
|
||||
this.setData({ activityId }) |
||||
|
||||
app.waitLogin({ type: 1 }).then(() => { |
||||
this.fetchActivityDetail() |
||||
this.fetchReviewList() |
||||
}) |
||||
}, |
||||
handlePopupCancel() { |
||||
const { popupType } = this.data |
||||
if (popupType === 'conformBindDoctorConform') { |
||||
|
||||
// 获取活动详情
|
||||
async fetchActivityDetail() { |
||||
try { |
||||
const res = await wx.ajax({ |
||||
url: `/activity/detail?id=${this.data.activityId}`, |
||||
method: 'GET', |
||||
data: {}, |
||||
}) |
||||
if (res) { |
||||
this.setData({ |
||||
detail: res, |
||||
countdownSeconds: res.countdownSeconds || 0, |
||||
loading: false, |
||||
}) |
||||
} |
||||
} catch (err) { |
||||
console.error('获取活动详情失败:', err) |
||||
this.setData({ loading: false }) |
||||
} |
||||
}, |
||||
|
||||
// 获取评价列表
|
||||
async fetchReviewList(isRefresh = false) { |
||||
if (this.data.reviewLoading) return |
||||
|
||||
const { reviewPagination } = this.data |
||||
const page = isRefresh ? 1 : reviewPagination.page |
||||
|
||||
this.setData({ reviewLoading: true }) |
||||
|
||||
try { |
||||
const res = await wx.ajax({ |
||||
url: `/activity/review-list?activityId=${this.data.activityId}`, |
||||
method: 'GET', |
||||
data: { page, pageSize: reviewPagination.pageSize }, |
||||
}) |
||||
if (res) { |
||||
const newList = isRefresh ? res.list : [...this.data.reviewList, ...res.list] |
||||
this.setData({ |
||||
reviewList: newList, |
||||
reviewPagination: { |
||||
page: res.pagination?.page || page, |
||||
pageSize: res.pagination?.pageSize || reviewPagination.pageSize, |
||||
total: res.pagination?.total || 0, |
||||
totalPages: res.pagination?.totalPages || 0, |
||||
}, |
||||
}) |
||||
} |
||||
} catch (err) { |
||||
console.error('获取评价列表失败:', err) |
||||
} finally { |
||||
this.setData({ reviewLoading: false }) |
||||
} |
||||
}, |
||||
|
||||
// 倒计时变化
|
||||
handleTimeChange(e: WechatMiniprogram.CustomEvent) { |
||||
this.setData({ timeData: e.detail }) |
||||
}, |
||||
|
||||
// 倒计时结束
|
||||
handleTimeFinish() { |
||||
this.setData({ countdownSeconds: 0 }) |
||||
this.fetchActivityDetail() |
||||
}, |
||||
|
||||
// 一键报名
|
||||
async handleRegister() { |
||||
const { detail } = this.data |
||||
if (!detail) return |
||||
|
||||
// 检查登录状态
|
||||
const accessToken = app.globalData.accessToken |
||||
if (!accessToken) { |
||||
wx.showToast({ title: '请先登录', icon: 'error' }) |
||||
return |
||||
} |
||||
|
||||
try { |
||||
wx.showLoading({ title: '报名中...' }) |
||||
const res = await wx.ajax({ |
||||
url: `/activity/register?id=${this.data.activityId}`, |
||||
method: 'POST', |
||||
data: {}, |
||||
}) |
||||
wx.hideLoading() |
||||
|
||||
if (res) { |
||||
wx.showToast({ title: '报名成功', icon: 'success' }) |
||||
// 更新状态
|
||||
this.setData({ |
||||
detail: { |
||||
...detail, |
||||
isRegistered: true, |
||||
regCount: detail.regCount + 1, |
||||
}, |
||||
}) |
||||
// 跳转到报名成功页面
|
||||
wx.navigateTo({ |
||||
url: `/pages/actResult/index?id=${this.data.activityId}`, |
||||
}) |
||||
} |
||||
} catch (err: any) { |
||||
wx.hideLoading() |
||||
const message = err?.message || '报名失败' |
||||
wx.showToast({ title: message, icon: 'error' }) |
||||
} |
||||
}, |
||||
|
||||
// 签到
|
||||
async handleCheckin() { |
||||
const { detail } = this.data |
||||
if (!detail) return |
||||
|
||||
// 检查登录状态
|
||||
const accessToken = app.globalData.accessToken |
||||
if (!accessToken) { |
||||
wx.showToast({ title: '请先登录', icon: 'error' }) |
||||
return |
||||
} |
||||
|
||||
try { |
||||
wx.showLoading({ title: '签到中...' }) |
||||
const res = await wx.ajax({ |
||||
url: `/activity/checkin?id=${this.data.activityId}`, |
||||
method: 'POST', |
||||
data: {}, |
||||
}) |
||||
wx.hideLoading() |
||||
|
||||
if (res) { |
||||
// 显示签到成功弹窗
|
||||
this.setData({ |
||||
popupShow: true, |
||||
popupType: 'checkinSuccess', |
||||
popupParams: { checkedAt: res.checkedAt }, |
||||
}) |
||||
// 更新状态
|
||||
this.setData({ |
||||
detail: { |
||||
...detail, |
||||
isCheckedIn: true, |
||||
checkinCount: detail.checkinCount + 1, |
||||
}, |
||||
}) |
||||
} |
||||
} catch (err: any) { |
||||
wx.hideLoading() |
||||
const message = err?.message || '签到失败' |
||||
wx.showToast({ title: message, icon: 'error' }) |
||||
} |
||||
}, |
||||
|
||||
// 分享
|
||||
async handleShare() { |
||||
const { detail } = this.data |
||||
if (!detail) return |
||||
|
||||
// 上报分享
|
||||
try { |
||||
await wx.ajax({ |
||||
url: `/activity/share?id=${this.data.activityId}`, |
||||
method: 'POST', |
||||
data: { channel: 'friend' }, |
||||
}) |
||||
} catch (err) { |
||||
console.error('上报分享失败:', err) |
||||
} |
||||
}, |
||||
|
||||
// 打开评论弹窗
|
||||
handleOpenComment() { |
||||
this.setData({ |
||||
popupShow: false, |
||||
popupType: 'i', |
||||
commentShow: true, |
||||
commentRating: 5, |
||||
commentContent: '', |
||||
commentImages: [], |
||||
commentAnonymous: false, |
||||
}) |
||||
}, |
||||
|
||||
// 评论评分变化
|
||||
onCommentRatingChange(e: WechatMiniprogram.CustomEvent) { |
||||
this.setData({ commentRating: e.detail }) |
||||
}, |
||||
|
||||
// 评论内容变化
|
||||
onCommentContentChange(e: WechatMiniprogram.TextareaInput) { |
||||
this.setData({ commentContent: e.detail.value }) |
||||
}, |
||||
|
||||
// 评论图片上传成功
|
||||
onCommentImageSuccess(e: WechatMiniprogram.CustomEvent) { |
||||
const { file } = e.detail |
||||
console.log('上传成功', file) |
||||
// 添加上传成功的图片到列表
|
||||
this.setData({ |
||||
commentImages: [...this.data.commentImages, file], |
||||
}) |
||||
}, |
||||
|
||||
// 评论图片上传失败
|
||||
onCommentImageError(e: WechatMiniprogram.CustomEvent) { |
||||
const { file, error } = e.detail |
||||
console.log('上传失败', file, error) |
||||
wx.showToast({ title: '图片上传失败', icon: 'none' }) |
||||
}, |
||||
|
||||
// 删除评论图片
|
||||
onRemoveCommentImage(e: WechatMiniprogram.TouchEvent) { |
||||
const index = e.currentTarget.dataset.index |
||||
const commentImages = [...this.data.commentImages] |
||||
commentImages.splice(index, 1) |
||||
this.setData({ commentImages }) |
||||
}, |
||||
|
||||
// 评论匿名切换
|
||||
onCommentAnonymousChange(e: WechatMiniprogram.CustomEvent) { |
||||
this.setData({ commentAnonymous: e.detail.value }) |
||||
}, |
||||
|
||||
// 提交评论
|
||||
async handleSubmitComment() { |
||||
const { commentRating, commentContent, commentImages, commentAnonymous } = this.data |
||||
|
||||
if (!commentContent.trim()) { |
||||
wx.showToast({ title: '请输入评价内容', icon: 'error' }) |
||||
return |
||||
} |
||||
|
||||
try { |
||||
wx.showLoading({ title: '提交中...' }) |
||||
const res = await wx.ajax({ |
||||
url: `/activity/submit-review?id=${this.data.activityId}`, |
||||
method: 'POST', |
||||
data: { |
||||
activityId: this.data.activityId, |
||||
rating: commentRating, |
||||
content: commentContent, |
||||
images: commentImages.map((img) => img.url), |
||||
isAnonymous: commentAnonymous, |
||||
}, |
||||
}) |
||||
wx.hideLoading() |
||||
|
||||
if (res) { |
||||
wx.showToast({ title: '评价成功', icon: 'success' }) |
||||
this.setData({ |
||||
commentShow: false, |
||||
detail: { |
||||
...this.data.detail!, |
||||
isReviewed: true, |
||||
commentCount: this.data.detail!.commentCount + 1, |
||||
}, |
||||
}) |
||||
// 刷新评价列表
|
||||
this.fetchReviewList(true) |
||||
} |
||||
} catch (err: any) { |
||||
wx.hideLoading() |
||||
const message = err?.message || '评价失败' |
||||
wx.showToast({ title: message, icon: 'error' }) |
||||
} |
||||
}, |
||||
|
||||
// 关闭评论弹窗
|
||||
onCommentClose() { |
||||
this.setData({ commentShow: false }) |
||||
}, |
||||
|
||||
// 点赞评价
|
||||
async handleLikeReview(e: WechatMiniprogram.TouchEvent) { |
||||
const reviewId = e.currentTarget.dataset.id |
||||
const { reviewList } = this.data |
||||
|
||||
// 检查登录状态
|
||||
const accessToken = app.globalData.accessToken |
||||
if (!accessToken) { |
||||
wx.showToast({ title: '请先登录', icon: 'error' }) |
||||
return |
||||
} |
||||
|
||||
try { |
||||
const res = await wx.ajax({ |
||||
url: `/activity/toggle-review-like`, |
||||
method: 'POST', |
||||
data: { |
||||
reviewId, |
||||
}, |
||||
}) |
||||
|
||||
if (res) { |
||||
// 更新评价列表中的点赞状态
|
||||
const updatedList = reviewList.map((item) => { |
||||
if (item.id === reviewId) { |
||||
return { |
||||
...item, |
||||
isLiked: res.isLiked, |
||||
likeCount: res.likeCount, |
||||
} |
||||
} |
||||
return item |
||||
}) |
||||
this.setData({ reviewList: updatedList }) |
||||
} |
||||
} catch (err: any) { |
||||
const message = err?.message || '操作失败' |
||||
wx.showToast({ title: message, icon: 'error' }) |
||||
} |
||||
}, |
||||
|
||||
// 弹窗确认
|
||||
handlePopupOk() { |
||||
this.setData({ |
||||
popupShow: false, |
||||
popupType: 'popup1', |
||||
}) |
||||
}, |
||||
|
||||
// 弹窗取消
|
||||
handlePopupCancel() { |
||||
this.setData({ |
||||
commentShow: false, |
||||
popupShow: false, |
||||
popupType: 'popup1', |
||||
}) |
||||
}, |
||||
|
||||
// 分享给朋友
|
||||
onShareAppMessage() { |
||||
const { detail } = this.data |
||||
if (!detail) return {} |
||||
|
||||
// 上报分享
|
||||
this.handleShare() |
||||
|
||||
return { |
||||
title: detail.name, |
||||
path: `/pages/actDetail/index?id=${this.data.activityId}`, |
||||
imageUrl: detail.mainImages[0] || '', |
||||
} |
||||
}, |
||||
|
||||
// 获取活动状态文本
|
||||
getStatusText(status: string): string { |
||||
const statusMap: Record<string, string> = { |
||||
draft: '草稿', |
||||
pending: '待审核', |
||||
approved: '已通过', |
||||
registering: '报名中', |
||||
running: '进行中', |
||||
ended: '已结束', |
||||
cancelled: '已取消', |
||||
rejected: '已拒绝', |
||||
} |
||||
return statusMap[status] || status |
||||
}, |
||||
|
||||
// 获取评分文本
|
||||
getRatingText(rating: number): string { |
||||
if (rating >= 4.5) return '非常满意' |
||||
if (rating >= 4) return '满意' |
||||
if (rating >= 3) return '一般' |
||||
if (rating >= 2) return '不满意' |
||||
return '非常不满意' |
||||
}, |
||||
handleBack() { |
||||
wx.navigateBack() |
||||
}, |
||||
}) |
||||
|
||||
export {} |
||||
|
||||
@ -1,8 +1,83 @@
@@ -1,8 +1,83 @@
|
||||
const _app = getApp<IAppOption>(); |
||||
const app = getApp<IAppOption>() |
||||
|
||||
interface IActivityItem { |
||||
id: number |
||||
name: string |
||||
mainImages: string[] |
||||
startAt: string |
||||
endAt: string |
||||
location: string |
||||
status: string |
||||
regCount: number |
||||
} |
||||
|
||||
Page({ |
||||
data: {}, |
||||
onLoad() {}, |
||||
}); |
||||
data: { |
||||
activityId: 0, |
||||
detail: null as any, |
||||
recommendList: [] as IActivityItem[], |
||||
}, |
||||
|
||||
onLoad(options: { id?: string }) { |
||||
const activityId = options.id ? Number(options.id) : 0 |
||||
this.setData({ activityId }) |
||||
|
||||
app.waitLogin({ type: 0 }).then(() => { |
||||
this.fetchActivityDetail() |
||||
this.fetchRecommendList() |
||||
}) |
||||
}, |
||||
|
||||
// 获取活动详情
|
||||
async fetchActivityDetail() { |
||||
try { |
||||
const res = await wx.ajax({ |
||||
url: `/activity/detail?id=${this.data.activityId}`, |
||||
method: 'GET', |
||||
data: {}, |
||||
}) |
||||
if (res) { |
||||
this.setData({ detail: res }) |
||||
} |
||||
} catch (err) { |
||||
console.error('获取活动详情失败:', err) |
||||
} |
||||
}, |
||||
|
||||
// 获取推荐活动列表
|
||||
async fetchRecommendList() { |
||||
try { |
||||
const res = await wx.ajax({ |
||||
url: '/activity/list', |
||||
method: 'GET', |
||||
data: { |
||||
page: 1, |
||||
pageSize: 3, |
||||
isRecommended: true, |
||||
}, |
||||
}) |
||||
if (res && res.list) { |
||||
this.setData({ recommendList: res.list }) |
||||
} |
||||
} catch (err) { |
||||
console.error('获取推荐活动列表失败:', err) |
||||
} |
||||
}, |
||||
|
||||
// 返回活动页
|
||||
handleBack() { |
||||
wx.switchTab({ |
||||
url: '/pages/act/index', |
||||
}) |
||||
}, |
||||
|
||||
// 查看活动详情
|
||||
handleDetail(e: WechatMiniprogram.TouchEvent) { |
||||
const id = e.currentTarget.dataset.id |
||||
wx.navigateTo({ |
||||
url: `/pages/actDetail/index?id=${id}`, |
||||
}) |
||||
}, |
||||
}) |
||||
|
||||
export {} |
||||
export {} |
||||
Loading…
Reference in new issue