diff --git a/src/components/ChatBox.vue b/src/components/ChatBox.vue index 51ff02b..c101c9c 100644 --- a/src/components/ChatBox.vue +++ b/src/components/ChatBox.vue @@ -1,5 +1,5 @@ @@ -122,6 +137,9 @@ const connectTime = ref(''); const statusBarHeight = ref(0); const navBarHeight = ref(0); +// 下拉刷新相关 +const refresherTriggered = ref(false); + // 获取系统状态栏高度 const getSystemInfo = () => { const systemInfo = uni.getSystemInfoSync(); @@ -139,7 +157,7 @@ onMounted(() => { }); // 刷新设备状态 -const refreshDeviceStatus = () => { +const refreshDeviceStatus = (isRefresh = false) => { if (!userStore.isLoggedIn) { uni.showToast({ title: '请先登录', @@ -149,10 +167,12 @@ const refreshDeviceStatus = () => { } // 暂时注释掉API请求,使用本地模拟数据 - uni.showToast({ - title: '刷新成功(本地模拟)', - icon: 'success' - }); + if (!isRefresh) { + uni.showToast({ + title: '刷新成功(本地模拟)', + icon: 'success' + }); + } /* API请求已注释 uni.showLoading({ @@ -283,15 +303,42 @@ const goToLogin = () => { url: '/pages/mine/mine' }); }; + +// 下拉刷新处理 +const onRefresh = async () => { + console.log('触发下拉刷新'); + refresherTriggered.value = true; + + try { + // 刷新设备状态 + refreshDeviceStatus(true); + + // 延迟显示刷新成功提示 + setTimeout(() => { + uni.showToast({ + title: '刷新成功', + icon: 'success', + duration: 1500 + }); + }, 300); + } finally { + // 刷新完成后,关闭刷新状态 + setTimeout(() => { + refresherTriggered.value = false; + }, 500); + } +}; diff --git a/src/pages/drama/index.vue b/src/pages/drama/index.vue index 8d90e4b..5f35b16 100644 --- a/src/pages/drama/index.vue +++ b/src/pages/drama/index.vue @@ -24,11 +24,13 @@ + @@ -36,13 +38,30 @@ + + + {{ unreadCounts[item.roleId] }} + {{ item.tag }} {{ item.title }} - - + + @@ -54,13 +73,30 @@ + + + {{ unreadCounts[item.roleId] }} + {{ item.tag }} {{ item.title }} - - + + @@ -117,6 +153,13 @@ import { ref, computed, onMounted } from 'vue'; import { useUserStore } from '@/stores/user.js'; import { roleAPI, getResourceUrl } from '@/utils/api.js'; +// 🔴 新增:导入未读消息管理模块 +import { + getAllUnreadCounts, + generateTestMessages, + clearUnreadMessages, + autoGenerateMessagesForRandomRoles +} from '@/utils/unreadMessages.js'; const userStore = useUserStore(); const showLoginModal = ref(false); @@ -125,10 +168,19 @@ const showLoginModal = ref(false); const showDetailModal = ref(false); const selectedItem = ref(null); +// 下拉刷新相关 +const refresherTriggered = ref(false); + // 状态栏高度适配 const statusBarHeight = ref(0); const navBarHeight = ref(0); +// 🔴 新增:未读消息数量统计 +const unreadCounts = ref({}); + +// 🔴 新增:是否首次加载标记 +const isFirstLoad = ref(true); + // 获取系统状态栏高度 const getSystemInfo = () => { const systemInfo = uni.getSystemInfoSync(); @@ -320,11 +372,20 @@ const dramaList = ref([ const leftColumnItems = computed(() => dramaList.value.filter((_, index) => index % 2 === 0)); const rightColumnItems = computed(() => dramaList.value.filter((_, index) => index % 2 === 1)); +// 🔴 新增:加载未读消息数量 +const loadUnreadCounts = () => { + unreadCounts.value = getAllUnreadCounts(); + console.log('📬 未读消息统计:', unreadCounts.value); +}; + // 加载角色列表数据 -const loadDramaList = async () => { +const loadDramaList = async (isRefresh = false) => { try { - uni.showLoading({ title: '加载中...' }); - console.log('开始加载角色列表...'); + // 如果不是下拉刷新,则显示loading提示 + if (!isRefresh) { + uni.showLoading({ title: '加载中...' }); + } + console.log('开始加载角色列表...', isRefresh ? '(下拉刷新)' : ''); const result = await roleAPI.getRoles(); console.log('API返回结果:', result); @@ -379,32 +440,93 @@ const loadDramaList = async () => { })); console.log('角色列表加载成功,共', dramaList.value.length, '个角色'); + + // 如果是下拉刷新,显示成功提示 + if (isRefresh) { + uni.showToast({ + title: '刷新成功', + icon: 'success', + duration: 1500 + }); + } } else { console.error('获取角色列表失败:', result.error); uni.showToast({ - title: '加载失败,请重试', + title: isRefresh ? '刷新失败,请重试' : '加载失败,请重试', icon: 'none' }); } } catch (error) { console.error('加载角色列表异常:', error); uni.showToast({ - title: '加载异常,请重试', + title: isRefresh ? '刷新异常,请重试' : '加载异常,请重试', icon: 'none' }); } finally { - uni.hideLoading(); + if (!isRefresh) { + uni.hideLoading(); + } + // 🔴 新增:加载完角色后刷新未读消息数量 + loadUnreadCounts(); } }; +// 下拉刷新处理 +const onRefresh = async () => { + console.log('触发下拉刷新'); + refresherTriggered.value = true; + + try { + await loadDramaList(true); + } finally { + // 刷新完成后,关闭刷新状态 + setTimeout(() => { + refresherTriggered.value = false; + }, 500); + } +}; + +// 🔴 新增:自动生成随机角色消息 +const autoGenerateRandomMessages = () => { + console.log('🎲 准备自动生成消息,当前角色数量:', dramaList.value.length); + + if (dramaList.value.length === 0) { + console.log('⚠️ 角色列表为空,无法生成消息'); + return 0; + } + + // 随机选择2个角色生成消息 + const count = autoGenerateMessagesForRandomRoles(dramaList.value, 2, 1); + + if (count > 0) { + console.log(`✨ 已为 ${count} 个随机角色生成主动消息`); + // 刷新未读消息数量 + loadUnreadCounts(); + } else { + console.log('⚠️ 未能生成消息'); + } + + return count; +}; + onMounted(() => { getSystemInfo(); // 获取系统信息 userStore.init(); // 调用 API 加载角色列表 loadDramaList(); + console.log('📱 进入角色列表页面'); + +// 刷新未读消息数量 +loadUnreadCounts(); + +// 延迟生成消息(等待角色列表加载完成) +setTimeout(() => { + autoGenerateRandomMessages(); +}, 500); }); + // 方法 const handleUse = (item) => { if (!item || !item.id) { @@ -412,7 +534,7 @@ const handleUse = (item) => { return; } uni.showLoading({ title: '正在设置角色...' }); - + // 构建完整的角色参数,包括模型和模板信息 const params = { characterId: item.id, @@ -428,15 +550,40 @@ const handleUse = (item) => { temperature: item.temperature || '', topP: item.topP || '' }; - + const queryString = Object.keys(params) .map(key => `${key}=${encodeURIComponent(params[key] || '')}`) .join('&'); - + uni.hideLoading(); uni.navigateTo({ url: `/pages/role-chat/role-chat?${queryString}` }); }; +// 🔴 新增:长按角色卡生成测试消息(用于开发测试) +const handleLongPress = (item) => { + uni.showActionSheet({ + itemList: ['为该角色生成1条消息', '为该角色生成3条消息'], + success: (res) => { + const messageCount = res.tapIndex === 0 ? 1 : 3; + + const success = generateTestMessages(item.roleId, item.roleName, messageCount); + if (success) { + loadUnreadCounts(); // 刷新未读消息数量 + uni.showToast({ + title: `✅ 已为"${item.roleName}"生成${messageCount}条消息`, + icon: 'success', + duration: 2000 + }); + } else { + uni.showToast({ + title: '❌ 生成失败', + icon: 'none' + }); + } + } + }); +}; + const showLoginTip = () => { showLoginModal.value = true; }; const showDetail = (item) => { selectedItem.value = item; showDetailModal.value = true; }; const closeDetail = () => { showDetailModal.value = false; selectedItem.value = null; }; @@ -830,4 +977,36 @@ const goToLogin = () => { showLoginModal.value = false; uni.switchTab({ url: '/p flex: 1; padding: 16rpx 32rpx; } + +/* 🔴 新增:未读消息红点样式 */ +.unread-badge { + position: absolute; + top: 12rpx; + right: 12rpx; + min-width: 36rpx; + height: 36rpx; + background: linear-gradient(135deg, #ff4d4f 0%, #ff7875 100%); + color: #ffffff; + font-size: 20rpx; + font-weight: bold; + border-radius: 18rpx; + padding: 0 8rpx; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 4rpx 12rpx rgba(255, 77, 79, 0.6); + z-index: 10; + animation: badge-pulse 2s infinite; +} + +@keyframes badge-pulse { + 0%, 100% { + transform: scale(1); + box-shadow: 0 4rpx 12rpx rgba(255, 77, 79, 0.6); + } + 50% { + transform: scale(1.1); + box-shadow: 0 6rpx 16rpx rgba(255, 77, 79, 0.8); + } +} diff --git a/src/pages/mine/mine.vue b/src/pages/mine/mine.vue index f0846f1..dec9771 100644 --- a/src/pages/mine/mine.vue +++ b/src/pages/mine/mine.vue @@ -29,6 +29,10 @@ :style="{ marginTop: navBarHeight + 'px' }" scroll-y="true" :show-scrollbar="false" + refresher-enabled + :refresher-triggered="refresherTriggered" + @refresherrefresh="onRefresh" + refresher-background="rgba(26, 11, 46, 0.5)" >