| | |
| | | // pages/message/message.js |
| | | const app = getApp() |
| | | const { graphqlRequest, formatDate, formatRelativeTime } = require('../../lib/utils') |
| | | |
| | | Page({ |
| | | data: { |
| | | loading: false, |
| | | refreshing: false, |
| | | loadingMore: false, |
| | | hasMore: true, |
| | | |
| | | // 消息列表 |
| | | messages: [], |
| | | |
| | | // 分页参数 |
| | | pagination: { |
| | | page: 1, |
| | | limit: 20, |
| | | total: 0 |
| | | }, |
| | | |
| | | // 筛选条件 |
| | | filter: { |
| | | type: 'ALL', // ALL, SYSTEM, ACTIVITY, JUDGE, ORGANIZER |
| | | status: 'ALL' // ALL, READ, UNREAD |
| | | }, |
| | | |
| | | // 筛选选项 |
| | | typeOptions: [ |
| | | { value: 'ALL', label: '全部消息' }, |
| | | { value: 'SYSTEM', label: '系统通知' }, |
| | | { value: 'ACTIVITY', label: '活动消息' }, |
| | | { value: 'JUDGE', label: '评审消息' }, |
| | | { value: 'ORGANIZER', label: '主办方消息' } |
| | | ], |
| | | |
| | | statusOptions: [ |
| | | { value: 'ALL', label: '全部状态' }, |
| | | { value: 'UNREAD', label: '未读' }, |
| | | { value: 'READ', label: '已读' } |
| | | ], |
| | | |
| | | // 显示筛选面板 |
| | | showFilter: false, |
| | | |
| | | // 统计数据 |
| | | stats: { |
| | | total: 0, |
| | | unread: 0 |
| | | }, |
| | | |
| | | // 选择模式 |
| | | selectMode: false, |
| | | selectedMessages: [], |
| | | |
| | | // 消息类型图标映射 |
| | | typeIcons: { |
| | | 'SYSTEM': '🔔', |
| | | 'ACTIVITY': '🎯', |
| | | 'JUDGE': '⚖️', |
| | | 'ORGANIZER': '👥', |
| | | 'REGISTRATION': '📝', |
| | | 'RESULT': '🏆', |
| | | 'REMINDER': '⏰' |
| | | } |
| | | loading: false |
| | | }, |
| | | |
| | | onLoad() { |
| | | this.loadMessages() |
| | | this.loadMessageStats() |
| | | }, |
| | | |
| | | onShow() { |
| | | // 页面显示时刷新未读数量 |
| | | this.loadMessageStats() |
| | | // 初始化自定义 tabbar |
| | | if (typeof this.getTabBar === 'function' && this.getTabBar()) { |
| | | this.getTabBar().init() |
| | | } |
| | | this.loadMessages() |
| | | }, |
| | | |
| | | onPullDownRefresh() { |
| | | this.refreshMessages() |
| | | }, |
| | | |
| | | onReachBottom() { |
| | | if (this.data.hasMore && !this.data.loadingMore) { |
| | | this.loadMoreMessages() |
| | | } |
| | | this.loadMessages() |
| | | }, |
| | | |
| | | // 加载消息列表 |
| | | async loadMessages(reset = true) { |
| | | try { |
| | | if (reset) { |
| | | this.setData({ |
| | | loading: true, |
| | | pagination: { ...this.data.pagination, page: 1 } |
| | | }) |
| | | } |
| | | |
| | | const { filter, pagination } = this.data |
| | | |
| | | const query = ` |
| | | query GetMessages($input: MessageQueryInput!) { |
| | | messages(input: $input) { |
| | | items { |
| | | id |
| | | type |
| | | title |
| | | content |
| | | isRead |
| | | createdAt |
| | | data |
| | | relatedActivity { |
| | | id |
| | | title |
| | | } |
| | | } |
| | | pagination { |
| | | total |
| | | page |
| | | limit |
| | | hasMore |
| | | } |
| | | } |
| | | } |
| | | ` |
| | | |
| | | const input = { |
| | | type: filter.type === 'ALL' ? null : filter.type, |
| | | status: filter.status === 'ALL' ? null : filter.status, |
| | | page: pagination.page, |
| | | limit: pagination.limit |
| | | } |
| | | |
| | | const result = await graphqlRequest(query, { input }) |
| | | |
| | | if (result && result.messages) { |
| | | const { items, pagination: newPagination } = result.messages |
| | | |
| | | // 为每个消息添加选中状态 |
| | | const processedItems = items.map(item => ({ |
| | | ...item, |
| | | isSelected: this.data.selectedMessages.indexOf(item.id) > -1 |
| | | })) |
| | | |
| | | this.setData({ |
| | | messages: reset ? processedItems : [...this.data.messages, ...processedItems], |
| | | pagination: newPagination, |
| | | hasMore: newPagination.hasMore |
| | | }) |
| | | } |
| | | } catch (error) { |
| | | console.error('加载消息失败:', error) |
| | | loadMessages() { |
| | | // 检查用户是否已登录 |
| | | const userInfo = app.globalData.userInfo |
| | | if (!userInfo || !userInfo.userId) { |
| | | console.error('用户未登录或userId不存在') |
| | | wx.showToast({ |
| | | title: '加载失败', |
| | | icon: 'error' |
| | | }) |
| | | } finally { |
| | | this.setData({ |
| | | loading: false, |
| | | refreshing: false, |
| | | loadingMore: false |
| | | }) |
| | | wx.stopPullDownRefresh() |
| | | } |
| | | }, |
| | | |
| | | // 刷新消息 |
| | | async refreshMessages() { |
| | | this.setData({ refreshing: true }) |
| | | await this.loadMessages(true) |
| | | await this.loadMessageStats() |
| | | }, |
| | | |
| | | // 加载更多消息 |
| | | async loadMoreMessages() { |
| | | if (!this.data.hasMore || this.data.loadingMore) return |
| | | |
| | | this.setData({ |
| | | loadingMore: true, |
| | | pagination: { |
| | | ...this.data.pagination, |
| | | page: this.data.pagination.page + 1 |
| | | } |
| | | }) |
| | | |
| | | await this.loadMessages(false) |
| | | }, |
| | | |
| | | // 加载消息统计 |
| | | async loadMessageStats() { |
| | | try { |
| | | const query = ` |
| | | query GetMessageStats { |
| | | messageStats { |
| | | total |
| | | unread |
| | | } |
| | | } |
| | | ` |
| | | |
| | | const result = await graphqlRequest(query) |
| | | |
| | | if (result && result.messageStats) { |
| | | this.setData({ |
| | | stats: result.messageStats |
| | | }) |
| | | |
| | | // 更新底部导航栏的未读数量 |
| | | if (typeof this.getTabBar === 'function' && this.getTabBar()) { |
| | | this.getTabBar().setData({ |
| | | unreadCount: result.messageStats.unread |
| | | }) |
| | | } |
| | | } |
| | | } catch (error) { |
| | | console.error('加载消息统计失败:', error) |
| | | } |
| | | }, |
| | | |
| | | // 消息点击 |
| | | async onMessageTap(e) { |
| | | const { id, index } = e.currentTarget.dataset |
| | | const message = this.data.messages[index] |
| | | |
| | | // 如果是选择模式,切换选择状态 |
| | | if (this.data.selectMode) { |
| | | this.toggleMessageSelection(id) |
| | | return |
| | | } |
| | | |
| | | // 标记为已读 |
| | | if (!message.isRead) { |
| | | await this.markAsRead(id, index) |
| | | } |
| | | |
| | | // 根据消息类型处理跳转 |
| | | this.handleMessageAction(message) |
| | | }, |
| | | |
| | | // 处理消息动作 |
| | | handleMessageAction(message) { |
| | | const { type, data, relatedActivity } = message |
| | | |
| | | switch (type) { |
| | | case 'ACTIVITY': |
| | | if (relatedActivity && relatedActivity.id) { |
| | | wx.navigateTo({ |
| | | url: `/pages/activity/detail?id=${relatedActivity.id}` |
| | | }) |
| | | } |
| | | break |
| | | case 'REGISTRATION': |
| | | if (data && data.registrationId) { |
| | | wx.navigateTo({ |
| | | url: `/pages/profile/registration-detail?id=${data.registrationId}` |
| | | }) |
| | | } |
| | | break |
| | | case 'JUDGE': |
| | | if (data && data.submissionId) { |
| | | wx.navigateTo({ |
| | | url: `/pages/judge/review?id=${data.submissionId}` |
| | | }) |
| | | } |
| | | break |
| | | case 'ORGANIZER': |
| | | if (relatedActivity && relatedActivity.id) { |
| | | wx.navigateTo({ |
| | | url: `/pages/organizer/activity-detail?id=${relatedActivity.id}` |
| | | }) |
| | | } |
| | | break |
| | | default: |
| | | // 显示消息详情 |
| | | this.showMessageDetail(message) |
| | | break |
| | | } |
| | | }, |
| | | |
| | | // 显示消息详情 |
| | | showMessageDetail(message) { |
| | | wx.showModal({ |
| | | title: message.title, |
| | | content: message.content, |
| | | showCancel: false, |
| | | confirmText: '知道了' |
| | | }) |
| | | }, |
| | | |
| | | // 标记为已读 |
| | | async markAsRead(messageId, index) { |
| | | try { |
| | | const mutation = ` |
| | | mutation MarkMessageAsRead($id: ID!) { |
| | | markMessageAsRead(id: $id) { |
| | | success |
| | | } |
| | | } |
| | | ` |
| | | |
| | | const result = await graphqlRequest(mutation, { id: messageId }) |
| | | |
| | | if (result && result.markMessageAsRead.success) { |
| | | // 更新本地数据 |
| | | const messages = [...this.data.messages] |
| | | messages[index].isRead = true |
| | | |
| | | this.setData({ messages }) |
| | | |
| | | // 更新统计数据 |
| | | this.setData({ |
| | | 'stats.unread': Math.max(0, this.data.stats.unread - 1) |
| | | }) |
| | | } |
| | | } catch (error) { |
| | | console.error('标记已读失败:', error) |
| | | } |
| | | }, |
| | | |
| | | // 批量标记为已读 |
| | | async markAllAsRead() { |
| | | try { |
| | | wx.showLoading({ title: '处理中...' }) |
| | | |
| | | const mutation = ` |
| | | mutation MarkAllMessagesAsRead { |
| | | markAllMessagesAsRead { |
| | | success |
| | | count |
| | | } |
| | | } |
| | | ` |
| | | |
| | | const result = await graphqlRequest(mutation) |
| | | |
| | | if (result && result.markAllMessagesAsRead.success) { |
| | | // 刷新消息列表 |
| | | await this.refreshMessages() |
| | | |
| | | wx.showToast({ |
| | | title: '已全部标记为已读', |
| | | icon: 'success' |
| | | }) |
| | | } |
| | | } catch (error) { |
| | | console.error('批量标记已读失败:', error) |
| | | wx.showToast({ |
| | | title: '操作失败', |
| | | icon: 'error' |
| | | }) |
| | | } finally { |
| | | wx.hideLoading() |
| | | } |
| | | }, |
| | | |
| | | // 删除消息 |
| | | async deleteMessage(messageId) { |
| | | try { |
| | | const mutation = ` |
| | | mutation DeleteMessage($id: ID!) { |
| | | deleteMessage(id: $id) { |
| | | success |
| | | } |
| | | } |
| | | ` |
| | | |
| | | const result = await graphqlRequest(mutation, { id: messageId }) |
| | | |
| | | if (result && result.deleteMessage.success) { |
| | | // 从列表中移除 |
| | | const messages = this.data.messages.filter(msg => msg.id !== messageId) |
| | | this.setData({ messages }) |
| | | |
| | | wx.showToast({ |
| | | title: '删除成功', |
| | | icon: 'success' |
| | | }) |
| | | } |
| | | } catch (error) { |
| | | console.error('删除消息失败:', error) |
| | | wx.showToast({ |
| | | title: '删除失败', |
| | | icon: 'error' |
| | | }) |
| | | } |
| | | }, |
| | | |
| | | // 长按消息 |
| | | onMessageLongPress(e) { |
| | | const { id } = e.currentTarget.dataset |
| | | |
| | | wx.showActionSheet({ |
| | | itemList: ['标记为已读', '删除消息'], |
| | | success: (res) => { |
| | | switch (res.tapIndex) { |
| | | case 0: |
| | | this.markAsRead(id) |
| | | break |
| | | case 1: |
| | | wx.showModal({ |
| | | title: '确认删除', |
| | | content: '确定要删除这条消息吗?', |
| | | success: (modalRes) => { |
| | | if (modalRes.confirm) { |
| | | this.deleteMessage(id) |
| | | } |
| | | } |
| | | }) |
| | | break |
| | | } |
| | | } |
| | | }) |
| | | }, |
| | | |
| | | // 切换筛选面板 |
| | | onToggleFilter() { |
| | | this.setData({ |
| | | showFilter: !this.data.showFilter |
| | | }) |
| | | }, |
| | | |
| | | // 筛选条件改变 |
| | | onFilterChange(e) { |
| | | const { type, value } = e.currentTarget.dataset |
| | | |
| | | this.setData({ |
| | | [`filter.${type}`]: value, |
| | | showFilter: false |
| | | }) |
| | | |
| | | // 重新加载消息 |
| | | this.loadMessages(true) |
| | | }, |
| | | |
| | | // 切换选择模式 |
| | | onToggleSelectMode() { |
| | | this.setData({ |
| | | selectMode: !this.data.selectMode, |
| | | selectedMessages: [] |
| | | }) |
| | | }, |
| | | |
| | | // 切换消息选择状态 |
| | | toggleMessageSelection(messageId) { |
| | | const selectedMessages = [...this.data.selectedMessages] |
| | | const index = selectedMessages.indexOf(messageId) |
| | | |
| | | if (index > -1) { |
| | | selectedMessages.splice(index, 1) |
| | | } else { |
| | | selectedMessages.push(messageId) |
| | | } |
| | | |
| | | // 同时更新消息的isSelected字段 |
| | | const messages = this.data.messages.map(msg => ({ |
| | | ...msg, |
| | | isSelected: selectedMessages.indexOf(msg.id) > -1 |
| | | })) |
| | | |
| | | this.setData({ |
| | | selectedMessages, |
| | | messages |
| | | }) |
| | | }, |
| | | |
| | | // 全选/取消全选 |
| | | onToggleSelectAll() { |
| | | const { messages, selectedMessages } = this.data |
| | | const allSelected = selectedMessages.length === messages.length |
| | | const newSelectedMessages = allSelected ? [] : messages.map(msg => msg.id) |
| | | |
| | | // 同时更新消息的isSelected字段 |
| | | const updatedMessages = messages.map(msg => ({ |
| | | ...msg, |
| | | isSelected: !allSelected |
| | | })) |
| | | |
| | | this.setData({ |
| | | selectedMessages: newSelectedMessages, |
| | | messages: updatedMessages |
| | | }) |
| | | }, |
| | | |
| | | // 批量删除选中消息 |
| | | async onDeleteSelected() { |
| | | const { selectedMessages } = this.data |
| | | |
| | | if (selectedMessages.length === 0) { |
| | | wx.showToast({ |
| | | title: '请选择要删除的消息', |
| | | title: '请先登录', |
| | | icon: 'error' |
| | | }) |
| | | return |
| | | } |
| | | |
| | | this.setData({ loading: true }) |
| | | |
| | | wx.showModal({ |
| | | title: '确认删除', |
| | | content: `确定要删除选中的 ${selectedMessages.length} 条消息吗?`, |
| | | success: async (res) => { |
| | | if (res.confirm) { |
| | | try { |
| | | wx.showLoading({ title: '删除中...' }) |
| | | |
| | | const mutation = ` |
| | | mutation DeleteMessages($ids: [ID!]!) { |
| | | deleteMessages(ids: $ids) { |
| | | success |
| | | count |
| | | } |
| | | } |
| | | ` |
| | | |
| | | const result = await graphqlRequest(mutation, { ids: selectedMessages }) |
| | | |
| | | if (result && result.deleteMessages.success) { |
| | | // 刷新消息列表 |
| | | await this.refreshMessages() |
| | | |
| | | this.setData({ |
| | | selectMode: false, |
| | | selectedMessages: [] |
| | | }) |
| | | |
| | | wx.showToast({ |
| | | title: `已删除 ${result.deleteMessages.count} 条消息`, |
| | | icon: 'success' |
| | | }) |
| | | } |
| | | } catch (error) { |
| | | console.error('批量删除失败:', error) |
| | | wx.showToast({ |
| | | title: '删除失败', |
| | | icon: 'error' |
| | | }) |
| | | } finally { |
| | | wx.hideLoading() |
| | | } |
| | | const query = ` |
| | | query GetMessagesByUserId($userId: Long!) { |
| | | getMessagesByUserId(userId: $userId) { |
| | | id |
| | | userId |
| | | content |
| | | wxMsgSuccess |
| | | wxMsgErrCount |
| | | state |
| | | createTime |
| | | updateTime |
| | | } |
| | | } |
| | | }) |
| | | }, |
| | | ` |
| | | |
| | | // 获取消息类型图标 |
| | | getTypeIcon(type) { |
| | | return this.data.typeIcons[type] || '📨' |
| | | }, |
| | | |
| | | // 获取消息类型文本 |
| | | getTypeText(type) { |
| | | const typeMap = { |
| | | 'SYSTEM': '系统通知', |
| | | 'ACTIVITY': '活动消息', |
| | | 'JUDGE': '评审消息', |
| | | 'ORGANIZER': '主办方消息', |
| | | 'REGISTRATION': '报名消息', |
| | | 'RESULT': '结果通知', |
| | | 'REMINDER': '提醒消息' |
| | | const variables = { |
| | | userId: userInfo.userId |
| | | } |
| | | return typeMap[type] || '消息' |
| | | |
| | | app.graphqlRequest(query, variables) |
| | | .then(data => { |
| | | console.log('消息数据:', data) |
| | | this.setData({ |
| | | messages: data.getMessagesByUserId || [], |
| | | loading: false |
| | | }) |
| | | wx.stopPullDownRefresh() |
| | | }) |
| | | .catch(error => { |
| | | console.error('加载消息失败:', error) |
| | | wx.showToast({ |
| | | title: '加载失败', |
| | | icon: 'error' |
| | | }) |
| | | this.setData({ loading: false }) |
| | | wx.stopPullDownRefresh() |
| | | }) |
| | | }, |
| | | |
| | | // 格式化时间 |
| | | formatTime(dateString) { |
| | | const now = new Date() |
| | | const date = new Date(dateString) |
| | | const diff = now.getTime() - date.getTime() |
| | | // 格式化消息时间 |
| | | formatMessageTime(timeStr) { |
| | | if (!timeStr) return '' |
| | | |
| | | // 一天内显示相对时间 |
| | | if (diff < 24 * 60 * 60 * 1000) { |
| | | return formatRelativeTime(dateString) |
| | | } |
| | | |
| | | // 超过一天显示具体日期 |
| | | return formatDate(dateString, 'MM-DD HH:mm') |
| | | }, |
| | | |
| | | // 分享页面 |
| | | onShareAppMessage() { |
| | | return { |
| | | title: '蓉易创 - 消息中心', |
| | | path: '/pages/index/index' |
| | | try { |
| | | const date = new Date(timeStr) |
| | | const now = new Date() |
| | | const diff = now.getTime() - date.getTime() |
| | | |
| | | // 如果是今天 |
| | | if (diff < 24 * 60 * 60 * 1000) { |
| | | const hours = date.getHours().toString().padStart(2, '0') |
| | | const minutes = date.getMinutes().toString().padStart(2, '0') |
| | | return `${hours}:${minutes}` |
| | | } |
| | | |
| | | // 如果是昨天 |
| | | if (diff < 48 * 60 * 60 * 1000) { |
| | | const hours = date.getHours().toString().padStart(2, '0') |
| | | const minutes = date.getMinutes().toString().padStart(2, '0') |
| | | return `昨天 ${hours}:${minutes}` |
| | | } |
| | | |
| | | // 其他日期 |
| | | const month = (date.getMonth() + 1).toString().padStart(2, '0') |
| | | const day = date.getDate().toString().padStart(2, '0') |
| | | const hours = date.getHours().toString().padStart(2, '0') |
| | | const minutes = date.getMinutes().toString().padStart(2, '0') |
| | | return `${month}-${day} ${hours}:${minutes}` |
| | | } catch (error) { |
| | | console.error('时间格式化失败:', error) |
| | | return timeStr |
| | | } |
| | | } |
| | | }) |