Apply stashed changes and resolve conflicts
This commit is contained in:
parent
3ad56d0250
commit
d32801cb5b
|
|
@ -127,7 +127,10 @@ Page({
|
|||
|
||||
// 免费畅聊相关
|
||||
freeTime: null,
|
||||
countdownText: ''
|
||||
countdownText: '',
|
||||
|
||||
// 聊天模式 (ai | human)
|
||||
chatMode: 'ai'
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
|
|
@ -140,6 +143,10 @@ Page({
|
|||
this.messageTimer = null
|
||||
this.isProcessing = false
|
||||
|
||||
// 获取当前用户ID
|
||||
const userId = app.globalData.userId || wx.getStorageSync(config.STORAGE_KEYS.USER_ID)
|
||||
this.setData({ userId })
|
||||
|
||||
// 获取参数
|
||||
const characterId = options.id || ''
|
||||
const conversationId = options.conversationId || ''
|
||||
|
|
@ -177,10 +184,20 @@ Page({
|
|||
if (!this.data.loading) {
|
||||
this.loadQuotaStatus()
|
||||
}
|
||||
|
||||
// 开启消息轮询
|
||||
this.startPolling()
|
||||
},
|
||||
|
||||
onHide() {
|
||||
// 页面隐藏时停止轮询
|
||||
this.stopPolling()
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
// 页面卸载时清理
|
||||
this.stopPolling()
|
||||
|
||||
// 清除消息处理定时器
|
||||
if (this.messageTimer) {
|
||||
clearTimeout(this.messageTimer)
|
||||
|
|
@ -201,6 +218,156 @@ Page({
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 开启消息轮询 (智能自适应模式)
|
||||
* 策略:
|
||||
* 1. 默认状态:低频轮询 (每4秒),降低性能损耗
|
||||
* 2. 交互状态:当用户发送消息后,进入"高速模式" (每1.5秒),持续60秒,确保及时收到回复
|
||||
*/
|
||||
startPolling() {
|
||||
this.stopPolling()
|
||||
|
||||
// 如果处于高速模式,使用短间隔
|
||||
const interval = this.isFastPolling ? 1500 : 4000
|
||||
|
||||
console.log(`[chat-detail] 开启消息轮询 (模式: ${this.isFastPolling ? '高速' : '省流'}, 间隔: ${interval}ms)`)
|
||||
|
||||
this.pollingTimer = setTimeout(() => {
|
||||
this.checkNewMessages().finally(() => {
|
||||
// 递归调用,确保上一次请求结束后才开始下一次计时
|
||||
// 页面未卸载且未停止轮询时继续
|
||||
if (this.data.conversationId) {
|
||||
this.startPolling()
|
||||
}
|
||||
})
|
||||
}, interval)
|
||||
},
|
||||
|
||||
/**
|
||||
* 触发高速轮询模式
|
||||
* 在用户发送消息后调用
|
||||
*/
|
||||
triggerFastPolling() {
|
||||
console.log('[chat-detail] 激活高速轮询模式')
|
||||
this.isFastPolling = true
|
||||
this.startPolling() // 立即重启轮询以应用新间隔
|
||||
|
||||
// 清除旧的定时器
|
||||
if (this.fastPollingTimeout) clearTimeout(this.fastPollingTimeout)
|
||||
|
||||
// 60秒后自动恢复普通模式
|
||||
this.fastPollingTimeout = setTimeout(() => {
|
||||
console.log('[chat-detail] 高速模式结束,恢复省流模式')
|
||||
this.isFastPolling = false
|
||||
// 不需要立即重启,下次轮询会自动使用新间隔
|
||||
}, 60000)
|
||||
},
|
||||
|
||||
/**
|
||||
* 停止消息轮询
|
||||
*/
|
||||
stopPolling() {
|
||||
if (this.pollingTimer) {
|
||||
clearTimeout(this.pollingTimer)
|
||||
this.pollingTimer = null
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查新消息
|
||||
*/
|
||||
async checkNewMessages() {
|
||||
// 如果正在加载中、正在发送消息或页面不可见,跳过
|
||||
if (this.data.loading || this.data.loadingMore || this.data.isSending) return
|
||||
|
||||
const { characterId, messages, conversationId } = this.data
|
||||
if (!characterId) return
|
||||
|
||||
try {
|
||||
// 1. 获取最新的一页消息
|
||||
// 添加时间戳防止缓存
|
||||
const res = await api.chat.getChatHistoryByCharacter(characterId, {
|
||||
limit: 20,
|
||||
page: 1,
|
||||
_t: Date.now()
|
||||
})
|
||||
|
||||
if (res.success && res.data && res.data.length > 0) {
|
||||
// 转换消息格式
|
||||
const latestMessages = res.data.map(msg => this.transformMessage(msg))
|
||||
|
||||
// 筛选出本地不存在的新消息
|
||||
// 重点:只筛选对方发来的消息(isMe: false),避免本地已存在的用户消息重复显示
|
||||
// 用户自己的消息(isMe: true)由本地乐观更新处理,轮询时不重复添加
|
||||
const newMessages = latestMessages.filter(msg => {
|
||||
// 2. 本地必须不存在该ID
|
||||
const exists = messages.some(m => m.id === msg.id)
|
||||
return !exists
|
||||
})
|
||||
|
||||
if (newMessages.length > 0) {
|
||||
console.log('[chat-detail] 轮询发现新消息:', newMessages.length, '条')
|
||||
|
||||
// 将新消息追加到列表末尾
|
||||
// 按时间排序确保顺序正确
|
||||
newMessages.sort((a, b) => new Date(a.time) - new Date(b.time))
|
||||
|
||||
const updatedMessages = [...messages, ...newMessages]
|
||||
|
||||
this.setData({
|
||||
messages: updatedMessages
|
||||
}, () => {
|
||||
this.scrollToBottom()
|
||||
|
||||
// 如果有新消息,标记会话已读
|
||||
if (this.data.conversationId) {
|
||||
this.markConversationAsRead(this.data.conversationId)
|
||||
}
|
||||
|
||||
// 收到新消息时,如果是对方发的,也可以触发一下高速模式,以便快速接收连续回复
|
||||
this.triggerFastPolling()
|
||||
|
||||
// 更新本地缓存
|
||||
this.saveMessagesToCache(updatedMessages)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 检查会话模式状态
|
||||
if (conversationId) {
|
||||
// 降低频率:只有在高速模式或者每5次轮询检查一次会话状态
|
||||
// 简单起见,这里每次轮询都检查(因为是 silent 请求,且频率不高)
|
||||
this.checkConversationStatus(conversationId)
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
// 静默失败,不打印过多日志
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查会话状态 (AI/Human 模式)
|
||||
*/
|
||||
async checkConversationStatus(conversationId) {
|
||||
try {
|
||||
const res = await api.chat.getConversationDetail(conversationId)
|
||||
if (res && res.success && res.data) {
|
||||
const mode = res.data.currentMode || res.data.mode || 'ai'
|
||||
|
||||
// 如果模式发生变化,或者首次获取
|
||||
if (mode !== this.data.chatMode) {
|
||||
console.log('[chat-detail] 会话模式变更为:', mode)
|
||||
this.setData({ chatMode: mode })
|
||||
|
||||
// 移除人工模式的显式提示,保持沉浸感
|
||||
// 让用户感觉不到是真人在接管
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// 静默失败
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 标记会话已读
|
||||
* 进入聊天详情页时调用,清除未读数
|
||||
|
|
@ -450,68 +617,101 @@ Page({
|
|||
|
||||
/**
|
||||
* 加载聊天历史(首次加载最近20条)
|
||||
* 优化策略:优先从本地缓存加载,实现"无感"体验,防止后端切换模式导致消息丢失
|
||||
*/
|
||||
async loadChatHistory() {
|
||||
const { characterId, pageSize } = this.data
|
||||
|
||||
if (!characterId) {
|
||||
const welcomeMsg = {
|
||||
id: 'welcome',
|
||||
text: `你好!我是${this.data.character.name},很高兴认识你~`,
|
||||
isMe: false,
|
||||
time: util.formatTime(new Date(), 'HH:mm'),
|
||||
type: 'text'
|
||||
}
|
||||
this.setData({
|
||||
messages: [welcomeMsg],
|
||||
isFirstLoad: false,
|
||||
hasMore: false
|
||||
})
|
||||
this.showWelcomeMessage()
|
||||
return
|
||||
}
|
||||
|
||||
// 1. 先尝试从本地缓存加载,确保秒开且不丢失历史
|
||||
const cachedMessages = wx.getStorageSync(`chat_history_${characterId}`) || []
|
||||
if (cachedMessages.length > 0) {
|
||||
console.log(`[chat-detail] 从本地缓存加载了 ${cachedMessages.length} 条消息`)
|
||||
this.setData({
|
||||
messages: cachedMessages,
|
||||
isFirstLoad: false,
|
||||
hasMore: true // 假设还有更多,允许下拉
|
||||
}, () => {
|
||||
this.scrollToBottom()
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('[chat-detail] 开始加载聊天历史, characterId:', characterId)
|
||||
console.log('[chat-detail] 开始从API同步聊天历史, characterId:', characterId)
|
||||
|
||||
// 首次只加载最近20条消息
|
||||
const res = await api.chat.getChatHistoryByCharacter(characterId, {
|
||||
limit: pageSize,
|
||||
page: 1
|
||||
page: 1,
|
||||
_t: Date.now() // 防缓存
|
||||
})
|
||||
|
||||
console.log('[chat-detail] API响应:', JSON.stringify(res).substring(0, 200))
|
||||
|
||||
if (res.success && res.data && res.data.length > 0) {
|
||||
console.log('[chat-detail] 收到历史消息数量:', res.data.length)
|
||||
console.log('[chat-detail] API返回消息数量:', res.data.length)
|
||||
|
||||
const messages = res.data.map(msg => this.transformMessage(msg))
|
||||
const apiMessages = res.data.map(msg => this.transformMessage(msg))
|
||||
|
||||
// 合并策略:以API数据为准更新,但保留API未返回的本地旧数据(如果有)
|
||||
// 简单起见,如果本地为空,直接用API;如果本地有值,做去重合并
|
||||
|
||||
let finalMessages = []
|
||||
if (this.data.messages.length === 0) {
|
||||
finalMessages = apiMessages
|
||||
} else {
|
||||
// 合并逻辑:将API返回的新消息合并到现有列表中
|
||||
// 1. 创建现有消息的ID映射
|
||||
const existingIds = new Set(this.data.messages.map(m => m.id))
|
||||
|
||||
// 2. 找出API返回中本地没有的消息
|
||||
const newApiMessages = apiMessages.filter(m => !existingIds.has(m.id))
|
||||
|
||||
// 3. 将新消息追加进来 (注意顺序)
|
||||
// API通常返回按时间排序好的,或者我们需要重排
|
||||
finalMessages = [...this.data.messages, ...newApiMessages]
|
||||
|
||||
// 4. 按时间重新排序确保正确
|
||||
finalMessages.sort((a, b) => new Date(a.time) - new Date(b.time))
|
||||
}
|
||||
|
||||
// 只有当消息列表真的发生变化时才更新,避免闪烁
|
||||
if (finalMessages.length !== this.data.messages.length) {
|
||||
this.setData({
|
||||
messages,
|
||||
messages: finalMessages,
|
||||
hasMore: res.data.length >= pageSize,
|
||||
page: 1,
|
||||
isFirstLoad: false
|
||||
}, () => {
|
||||
this.scrollToBottom()
|
||||
// 更新缓存
|
||||
this.saveMessagesToCache(finalMessages)
|
||||
})
|
||||
|
||||
console.log('[chat-detail] 消息已设置, 当前数量:', this.data.messages.length)
|
||||
console.log('[chat-detail] 首次加载完成,不自动滚动到底部')
|
||||
} else {
|
||||
console.log('[chat-detail] 没有历史记录,显示欢迎消息')
|
||||
const welcomeMsg = {
|
||||
id: 'welcome',
|
||||
text: `你好!我是${this.data.character.name},很高兴认识你~`,
|
||||
isMe: false,
|
||||
time: util.formatTime(new Date(), 'HH:mm'),
|
||||
type: 'text'
|
||||
console.log('[chat-detail] 消息列表无变化,跳过更新')
|
||||
this.setData({ isFirstLoad: false })
|
||||
}
|
||||
|
||||
} else {
|
||||
console.log('[chat-detail] API未返回历史记录')
|
||||
if (this.data.messages.length === 0) {
|
||||
this.showWelcomeMessage()
|
||||
}
|
||||
this.setData({
|
||||
messages: [welcomeMsg],
|
||||
isFirstLoad: false,
|
||||
hasMore: false
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('加载聊天历史失败:', err)
|
||||
if (this.data.messages.length === 0) {
|
||||
this.showWelcomeMessage()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 显示欢迎消息
|
||||
*/
|
||||
showWelcomeMessage() {
|
||||
const welcomeMsg = {
|
||||
id: 'welcome',
|
||||
text: `你好!我是${this.data.character.name},很高兴认识你~`,
|
||||
|
|
@ -524,7 +724,16 @@ Page({
|
|||
isFirstLoad: false,
|
||||
hasMore: false
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 将消息保存到本地缓存
|
||||
*/
|
||||
saveMessagesToCache(messages) {
|
||||
if (!this.data.characterId || !messages || messages.length === 0) return
|
||||
// 只缓存最近100条,避免Storage爆满
|
||||
const messagesToSave = messages.slice(-100)
|
||||
wx.setStorageSync(`chat_history_${this.data.characterId}`, messagesToSave)
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -591,10 +800,35 @@ Page({
|
|||
* 转换消息格式
|
||||
*/
|
||||
transformMessage(msg) {
|
||||
// 调试日志:打印原始消息结构,帮助排查 role/sender_id 问题
|
||||
console.log('[chat-detail] transformMessage raw:', msg)
|
||||
|
||||
let isMe = false
|
||||
const currentUserId = this.data.userId || app.globalData.userId || wx.getStorageSync(config.STORAGE_KEYS.USER_ID)
|
||||
|
||||
// 1. 优先使用 sender_id 判断 (最准确)
|
||||
if (msg.sender_id && currentUserId) {
|
||||
isMe = String(msg.sender_id) === String(currentUserId)
|
||||
}
|
||||
// 2. 其次使用 role 判断
|
||||
else if (msg.role) {
|
||||
// 只有明确是 user 且不是 assistant/system 时才认为是自己
|
||||
isMe = msg.role === 'user'
|
||||
}
|
||||
// 3. 最后尝试 sender_type
|
||||
else if (msg.sender_type) {
|
||||
isMe = msg.sender_type === 'user'
|
||||
}
|
||||
|
||||
// 4. 强制修正:如果 role 是 assistant 或 system,绝对不是我
|
||||
if (msg.role === 'assistant' || msg.role === 'system') {
|
||||
isMe = false
|
||||
}
|
||||
|
||||
const baseMessage = {
|
||||
id: msg.id,
|
||||
text: msg.content,
|
||||
isMe: msg.role === 'user',
|
||||
isMe: isMe,
|
||||
time: util.formatTime(msg.created_at || msg.timestamp, 'HH:mm'),
|
||||
type: msg.message_type || 'text'
|
||||
}
|
||||
|
|
@ -744,6 +978,9 @@ Page({
|
|||
// 只检查输入是否为空
|
||||
if (!inputText.trim()) return
|
||||
|
||||
// 触发高速轮询模式,确保及时收到回复
|
||||
this.triggerFastPolling()
|
||||
|
||||
// 检查登录
|
||||
if (app.checkNeedLogin()) return
|
||||
|
||||
|
|
@ -842,8 +1079,10 @@ Page({
|
|||
console.log('[chat-detail] 合并处理消息:', messagesToProcess.length, '条')
|
||||
console.log('[chat-detail] 合并后内容:', combinedMessage)
|
||||
|
||||
// 显示AI正在输入
|
||||
// 显示AI正在输入(仅在AI模式下)
|
||||
if (this.data.chatMode === 'ai') {
|
||||
this.setData({ isTyping: true })
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建对话历史(最近10条消息,只包含文字消息)
|
||||
|
|
@ -918,10 +1157,14 @@ Page({
|
|||
})
|
||||
}
|
||||
|
||||
// 只有当有回复内容时才添加AI消息
|
||||
// 如果是人工模式(human),后端可能只返回成功但不返回content,或者content为空
|
||||
const content = res.data.content || res.data.message
|
||||
if (content) {
|
||||
// 添加AI回复
|
||||
const aiMessage = {
|
||||
id: res.data.id || util.generateId(),
|
||||
text: res.data.content || res.data.message,
|
||||
text: content,
|
||||
isMe: false,
|
||||
time: util.formatTime(new Date(), 'HH:mm'),
|
||||
audioUrl: res.data.audio_url,
|
||||
|
|
@ -934,6 +1177,9 @@ Page({
|
|||
// AI回复后滚动到底部
|
||||
this.scrollToBottom()
|
||||
})
|
||||
} else {
|
||||
console.log('[chat-detail] 收到空回复,不添加消息气泡 (可能是Human模式)')
|
||||
}
|
||||
} else {
|
||||
throw new Error(res.error || res.message || '发送失败')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,9 +49,9 @@
|
|||
<image src="{{item.image}}" class="activity-image" mode="aspectFill"></image>
|
||||
<!-- 位置徽章 -->
|
||||
<view class="location-badge">
|
||||
<image src="/images/icon-location-white.png" class="location-icon" mode="aspectFit"></image>
|
||||
<image src="/images/icon-location.png" class="location-icon" mode="aspectFit"></image>
|
||||
<text>{{item.venue}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 活动信息 -->
|
||||
|
|
|
|||
|
|
@ -121,50 +121,32 @@ Page({
|
|||
|
||||
/**
|
||||
* 加载功能入口图标
|
||||
* 从后台素材管理API加载 (group=entries)
|
||||
* 从后台素材管理API加载
|
||||
*/
|
||||
async loadEntries() {
|
||||
// 暂时禁用API加载,使用本地配置的图标
|
||||
console.log('使用本地配置的功能入口图标')
|
||||
return;
|
||||
/*
|
||||
try {
|
||||
const res = await api.pageAssets.getAssets('entries')
|
||||
console.log('功能入口 API响应:', res)
|
||||
const res = await api.pageAssets.getEntertainmentCategories()
|
||||
console.log('娱乐页分类图标 API响应:', res)
|
||||
|
||||
if (res.success && res.data) {
|
||||
const icons = res.data
|
||||
const { categoryList } = this.data
|
||||
if (res.success && res.data && res.data.length > 0) {
|
||||
// 映射API数据到前端格式
|
||||
const categoryList = res.data.map(item => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
icon: this.processImageUrl(item.iconUrl || item.icon),
|
||||
url: item.pagePath || item.url || '',
|
||||
sort: item.sort || 0
|
||||
})).sort((a, b) => a.sort - b.sort) // 按sort字段排序
|
||||
|
||||
// 映射图标:搭子(id=1), 同城(id=2), 户外(id=3), 定制(id=4), 学堂(id=5), 传递(id=6)
|
||||
const idMap = {
|
||||
1: 'entry_1', // 兴趣搭子
|
||||
2: 'entry_2', // 同城活动
|
||||
3: 'entry_3', // 户外郊游
|
||||
4: 'entry_4', // 定制主题
|
||||
5: 'entry_5', // 快乐学堂
|
||||
6: 'entry_6' // 爱心传递
|
||||
}
|
||||
|
||||
const updatedCategoryList = categoryList.map(item => {
|
||||
const assetKey = idMap[item.id]
|
||||
const iconUrl = icons[assetKey]
|
||||
if (iconUrl) {
|
||||
return {
|
||||
...item,
|
||||
icon: this.processImageUrl(iconUrl)
|
||||
}
|
||||
}
|
||||
return item
|
||||
})
|
||||
|
||||
this.setData({ categoryList: updatedCategoryList })
|
||||
console.log('已更新娱乐页功能入口图标')
|
||||
this.setData({ categoryList })
|
||||
console.log(`加载了 ${categoryList.length} 个娱乐页分类图标`)
|
||||
} else {
|
||||
console.log('娱乐页分类图标API返回为空,使用本地默认配置')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('加载功能入口失败', err)
|
||||
console.error('加载功能入口图标失败', err)
|
||||
// 失败时保持使用 data 初始化时的本地配置,无需额外操作
|
||||
}
|
||||
*/
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -523,7 +505,13 @@ Page({
|
|||
onCategoryTap(e) {
|
||||
const { id, name, url } = e.currentTarget.dataset
|
||||
|
||||
// 从版本配置中获取分类信息
|
||||
// 优先跳转配置的 URL
|
||||
if (url) {
|
||||
wx.navigateTo({ url: url })
|
||||
return
|
||||
}
|
||||
|
||||
// 从版本配置中获取分类信息(作为兜底)
|
||||
const categoryList = versionConfig.getCategoryList()
|
||||
const category = categoryList.find(item => item.id === id)
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ Page({
|
|||
async loadBanner() {
|
||||
try {
|
||||
// 尝试获取 interest_partner 分组的素材
|
||||
// 后端已修复:请求 group=interest_partner 时,会自动返回 interest_partner_banners 的数据,并封装成 { banners: [] } 格式
|
||||
const res = await api.pageAssets.getAssets('interest_partner')
|
||||
console.log('[兴趣搭子] Banner API响应:', res)
|
||||
|
||||
|
|
@ -66,12 +67,14 @@ Page({
|
|||
|
||||
// 如果 banners 为空,尝试读取单图字段
|
||||
if (!banners || banners.length === 0) {
|
||||
const singleBanner = res.data.banner || res.data.interest_banner || res.data.top_banner
|
||||
const singleBanner = res.data.banner || res.data.interest_banner || res.data.top_banner || (Array.isArray(res.data) ? res.data : [])
|
||||
if (singleBanner) {
|
||||
// 支持逗号分隔的多图字符串
|
||||
if (typeof singleBanner === 'string' && singleBanner.includes(',')) {
|
||||
banners = singleBanner.split(',').map(s => s.trim()).filter(s => s)
|
||||
} else {
|
||||
} else if (Array.isArray(singleBanner)) {
|
||||
banners = singleBanner
|
||||
} else if (typeof singleBanner === 'string') {
|
||||
banners = [singleBanner]
|
||||
}
|
||||
}
|
||||
|
|
@ -79,8 +82,16 @@ Page({
|
|||
|
||||
if (banners && banners.length > 0) {
|
||||
// 处理图片URL
|
||||
const bannerList = banners.map(url => {
|
||||
if (typeof url !== 'string') return ''
|
||||
const bannerList = banners.map(item => {
|
||||
let url = ''
|
||||
if (typeof item === 'string') {
|
||||
url = item
|
||||
} else if (item && typeof item === 'object') {
|
||||
url = item.asset_url || item.url || item.imageUrl || ''
|
||||
}
|
||||
|
||||
if (!url) return ''
|
||||
|
||||
let fullUrl = url
|
||||
if (fullUrl.startsWith('/')) {
|
||||
fullUrl = config.API_BASE_URL.replace('/api', '') + fullUrl
|
||||
|
|
@ -138,10 +149,21 @@ Page({
|
|||
|
||||
console.log('[兴趣搭子] API原始响应:', JSON.stringify(res).substring(0, 500))
|
||||
|
||||
// 线上API返回格式:{ success: true, data: [...] } 或 { success: true, data: { list: [...] } }
|
||||
// 线上API返回格式:{ success: true, data: [...] } 或 { success: true, data: { list: [...] } } 或 { success: true, data: { data: [...] } }
|
||||
if (res.success && res.data) {
|
||||
// 兼容两种返回格式
|
||||
let partnerList = Array.isArray(res.data) ? res.data : (res.data.list || [])
|
||||
// 兼容多种返回格式
|
||||
// 1. res.data 是数组
|
||||
// 2. res.data.data 是数组 (Laravel分页或标准包装)
|
||||
// 3. res.data.list 是数组 (自定义列表包装)
|
||||
let partnerList = []
|
||||
|
||||
if (Array.isArray(res.data)) {
|
||||
partnerList = res.data
|
||||
} else if (res.data.data && Array.isArray(res.data.data)) {
|
||||
partnerList = res.data.data
|
||||
} else if (res.data.list && Array.isArray(res.data.list)) {
|
||||
partnerList = res.data.list
|
||||
}
|
||||
|
||||
console.log('[兴趣搭子] 解析后的列表数量:', partnerList.length)
|
||||
if (partnerList.length > 0) {
|
||||
|
|
|
|||
|
|
@ -28,15 +28,6 @@
|
|||
<image src="{{item}}" class="banner-image" mode="aspectFill"></image>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
|
||||
<!-- 遮罩层 -->
|
||||
<view class="hero-overlay"></view>
|
||||
|
||||
<!-- 文字内容 -->
|
||||
<view class="hero-content">
|
||||
<view class="hero-title">寻找志同道合的伙伴</view>
|
||||
<view class="hero-subtitle">加入感兴趣的社群,开启精彩退休生活</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 兴趣分类列表 -->
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ page {
|
|||
/* 顶部Banner */
|
||||
.hero-banner {
|
||||
margin: 32rpx 32rpx 48rpx;
|
||||
height: 240rpx;
|
||||
height: 400rpx;
|
||||
border-radius: 32rpx;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
|
|
|||
|
|
@ -164,13 +164,31 @@ Page({
|
|||
// 优先使用API返回的中文等级名称
|
||||
const userLevel = record.fromUserRoleName || record.userRoleName || roleMap[record.fromUserRole] || roleMap[record.userRole] || record.levelText || '普通用户';
|
||||
|
||||
const type = record.orderType || record.type;
|
||||
let productName = this.getOrderTypeText(type);
|
||||
|
||||
// 后端已把年卡会员改成分销商等级,不显示 VIP会员
|
||||
if (type === 'vip') {
|
||||
productName = '';
|
||||
}
|
||||
|
||||
// Determine level class
|
||||
const rawRole = record.fromUserRole || record.userRole || record.distributorRole || '';
|
||||
let levelClass = '';
|
||||
if (rawRole.includes('soulmate')) levelClass = 'tag-soulmate';
|
||||
else if (rawRole.includes('guardian')) levelClass = 'tag-guardian';
|
||||
else if (rawRole.includes('companion')) levelClass = 'tag-companion';
|
||||
else if (rawRole.includes('listener')) levelClass = 'tag-listener';
|
||||
else if (rawRole.includes('partner')) levelClass = 'tag-partner';
|
||||
|
||||
return {
|
||||
id: record.id,
|
||||
orderNo: record.orderNo || record.order_no || record.id || '---',
|
||||
userName: record.fromUserName || record.userName || '匿名用户',
|
||||
userAvatar: avatar || this.data.defaultAvatar,
|
||||
productName: this.getOrderTypeText(record.orderType || record.type),
|
||||
productName: productName,
|
||||
userLevel: userLevel,
|
||||
levelClass: levelClass,
|
||||
orderAmount: record.orderAmount ? Number(record.orderAmount).toFixed(2) : (record.amount ? Number(record.amount).toFixed(2) : '0.00'),
|
||||
time: fmtTime
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,10 @@
|
|||
<image class="user-avatar" src="{{item.userAvatar || defaultAvatar}}" mode="aspectFill" binderror="onAvatarError" data-index="{{index}}" />
|
||||
<view class="order-info">
|
||||
<view class="user-name">{{item.userName}}</view>
|
||||
<view class="product-info">{{item.productName}} · {{item.userLevel}}</view>
|
||||
<view class="product-info">
|
||||
{{item.productName ? item.productName + ' · ' : ''}}
|
||||
<text class="tag-badge {{item.levelClass}}">{{item.userLevel}}</text>
|
||||
</view>
|
||||
<view class="order-time">时间: {{item.time}}</view>
|
||||
<view class="order-no">单号: {{item.orderNo}}</view>
|
||||
</view>
|
||||
|
|
|
|||
|
|
@ -191,6 +191,43 @@
|
|||
color: #B06AB3;
|
||||
}
|
||||
|
||||
/* Tag Styles */
|
||||
.tag-badge {
|
||||
display: inline-block;
|
||||
font-size: 20rpx;
|
||||
font-weight: 700;
|
||||
padding: 2rpx 10rpx;
|
||||
border-radius: 999rpx;
|
||||
margin-left: 8rpx;
|
||||
background: #E5E7EB;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.tag-soulmate {
|
||||
background: #7C3AED;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.tag-guardian {
|
||||
background: #3B82F6;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.tag-companion {
|
||||
background: #10B981;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.tag-listener {
|
||||
background: #F59E0B;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.tag-partner {
|
||||
background: #EF4444;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 底部提示 */
|
||||
.bottom-tip {
|
||||
text-align: center;
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ Page({
|
|||
const roleMap = {
|
||||
'soulmate': { text: '心伴会员', class: 'vip-soulmate' },
|
||||
'guardian': { text: '守护会员', class: 'vip-guardian' },
|
||||
'companion': { text: '心伴会员', class: 'vip-companion' },
|
||||
'companion': { text: '陪伴会员', class: 'vip-companion' },
|
||||
'listener': { text: '倾听会员', class: 'vip-listener' },
|
||||
'partner': { text: '城市合伙人', class: 'vip-partner' }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -138,12 +138,22 @@ Page({
|
|||
roleMap[x.role] || roleMap[user.role] ||
|
||||
'普通用户';
|
||||
|
||||
// Determine level class
|
||||
const rawRole = x.userRole || user.userRole || x.distributorRole || user.distributorRole || x.role || user.role || '';
|
||||
let levelClass = '';
|
||||
if (rawRole.includes('soulmate')) levelClass = 'tag-soulmate';
|
||||
else if (rawRole.includes('guardian')) levelClass = 'tag-guardian';
|
||||
else if (rawRole.includes('companion')) levelClass = 'tag-companion';
|
||||
else if (rawRole.includes('listener')) levelClass = 'tag-listener';
|
||||
else if (rawRole.includes('partner')) levelClass = 'tag-partner';
|
||||
|
||||
return {
|
||||
...x,
|
||||
userId: x.userId || x.id,
|
||||
userAvatar: avatar || this.data.defaultAvatar,
|
||||
userName: name,
|
||||
levelText: levelText,
|
||||
levelClass: levelClass,
|
||||
totalContribution: contribution,
|
||||
boundAtText: this.formatDate(new Date(dateStr))
|
||||
};
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@
|
|||
<view class="member-info">
|
||||
<view class="name-row">
|
||||
<text class="member-name">{{item.userName}}</text>
|
||||
<view class="tag-badge">{{item.levelText}}</view>
|
||||
<view class="tag-badge {{item.levelClass}}">{{item.levelText}}</view>
|
||||
</view>
|
||||
<text class="member-meta">{{item.boundAtText}}</text>
|
||||
</view>
|
||||
|
|
|
|||
|
|
@ -150,14 +150,39 @@
|
|||
}
|
||||
|
||||
.tag-badge {
|
||||
background: #B06AB3;
|
||||
color: #ffffff;
|
||||
background: #E5E7EB;
|
||||
color: #374151;
|
||||
font-size: 20rpx;
|
||||
font-weight: 700;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
|
||||
.tag-soulmate {
|
||||
background: #7C3AED;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.tag-guardian {
|
||||
background: #3B82F6;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.tag-companion {
|
||||
background: #10B981;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.tag-listener {
|
||||
background: #F59E0B;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.tag-partner {
|
||||
background: #EF4444;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.member-meta {
|
||||
font-size: 24rpx;
|
||||
color: #6B7280;
|
||||
|
|
|
|||
11
utils/api.js
11
utils/api.js
|
|
@ -402,6 +402,12 @@ const chat = {
|
|||
*/
|
||||
getConversations: () => request('/conversations', { silent: true }),
|
||||
|
||||
/**
|
||||
* 获取会话详情
|
||||
* @param {string} id - 会话ID
|
||||
*/
|
||||
getConversationDetail: (id) => request(`/conversations/${id}`, { silent: true }),
|
||||
|
||||
/**
|
||||
* 删除会话
|
||||
* @param {string} conversationId - 会话ID
|
||||
|
|
@ -910,6 +916,11 @@ const pageAssets = {
|
|||
*/
|
||||
getEntertainmentBanners: () => request('/page-assets/entertainment-banners'),
|
||||
|
||||
/**
|
||||
* 获取娱乐页分类图标列表
|
||||
*/
|
||||
getEntertainmentCategories: () => request('/page-assets/entertainment-categories'),
|
||||
|
||||
/**
|
||||
* 获取合作入驻页在线Banner列表
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user