Apply stashed changes and resolve conflicts

This commit is contained in:
zhiyun 2026-02-05 01:07:28 +08:00
parent 3ad56d0250
commit d32801cb5b
14 changed files with 473 additions and 122 deletions

View File

@ -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))
this.setData({
messages,
hasMore: res.data.length >= pageSize,
page: 1,
isFirstLoad: false
})
// 合并策略以API数据为准更新但保留API未返回的本地旧数据(如果有)
// 简单起见如果本地为空直接用API如果本地有值做去重合并
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'
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: finalMessages,
hasMore: res.data.length >= pageSize,
page: 1,
isFirstLoad: false
}, () => {
this.scrollToBottom()
// 更新缓存
this.saveMessagesToCache(finalMessages)
})
} else {
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正在输入
this.setData({ isTyping: true })
// 显示AI正在输入仅在AI模式下
if (this.data.chatMode === 'ai') {
this.setData({ isTyping: true })
}
try {
// 构建对话历史最近10条消息只包含文字消息
@ -918,22 +1157,29 @@ Page({
})
}
// 添加AI回复
const aiMessage = {
id: res.data.id || util.generateId(),
text: res.data.content || res.data.message,
isMe: false,
time: util.formatTime(new Date(), 'HH:mm'),
audioUrl: res.data.audio_url,
type: 'text' // 标记为文字消息
}
// 只有当有回复内容时才添加AI消息
// 如果是人工模式(human)后端可能只返回成功但不返回content或者content为空
const content = res.data.content || res.data.message
if (content) {
// 添加AI回复
const aiMessage = {
id: res.data.id || util.generateId(),
text: content,
isMe: false,
time: util.formatTime(new Date(), 'HH:mm'),
audioUrl: res.data.audio_url,
type: 'text' // 标记为文字消息
}
this.setData({
messages: [...this.data.messages, aiMessage]
}, () => {
// AI回复后滚动到底部
this.scrollToBottom()
})
this.setData({
messages: [...this.data.messages, aiMessage]
}, () => {
// AI回复后滚动到底部
this.scrollToBottom()
})
} else {
console.log('[chat-detail] 收到空回复,不添加消息气泡 (可能是Human模式)')
}
} else {
throw new Error(res.error || res.message || '发送失败')
}

View File

@ -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>
<text>{{item.venue}}</text>
</view>
<image src="/images/icon-location.png" class="location-icon" mode="aspectFit"></image>
<text>{{item.venue}}</text>
</view>
</view>
<!-- 活动信息 -->

View File

@ -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)

View File

@ -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) {

View File

@ -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>
<!-- 兴趣分类列表 -->

View File

@ -19,7 +19,7 @@ page {
/* 顶部Banner */
.hero-banner {
margin: 32rpx 32rpx 48rpx;
height: 240rpx;
height: 400rpx;
border-radius: 32rpx;
position: relative;
overflow: hidden;

View File

@ -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
}

View File

@ -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>

View File

@ -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;

View File

@ -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' }
};

View File

@ -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))
};

View File

@ -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>

View File

@ -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;

View File

@ -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列表
*/