topfans/frontend/pages/components/FriendsContent.vue
2026-04-21 15:18:55 +08:00

1441 lines
33 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="friends-content">
<!-- 背景图片 - 使用mainpage -->
<image class="background-image" src="/static/background/mainpage.png" mode="aspectFill"></image>
<!-- 蒙层 -->
<view class="background-overlay"></view>
<!-- 页面内容 -->
<view class="content-wrapper">
<!-- Tab标签页 -->
<view class="tab-container">
<view
class="tab-item"
:class="{ 'active': activeTab === 0 }"
:style="{ zIndex: activeTab === 0 ? 4 : 2 }"
@click="switchTab(0)"
>
<text class="tab-text">好友列表</text>
</view>
<view
class="tab-item"
:class="{ 'active': activeTab === 1 }"
:style="{ zIndex: activeTab === 1 ? 4 : 2 }"
@click="switchTab(1)"
>
<text class="tab-text">添加好友</text>
</view>
<view
class="tab-item"
:class="{ 'active': activeTab === 2 }"
:style="{ zIndex: activeTab === 2 ? 4 : 1 }"
@click="switchTab(2)"
>
<text class="tab-text">好友请求</text>
</view>
</view>
<!-- 好友列表Tab内容 -->
<view v-show="activeTab === 0" class="friend-list-container">
<!-- 大卡片容器 -->
<view class="friend-list-card">
<!-- 大卡片背景 -->
<image class="card-background" src="/static/background/friend-list-bg.png" mode="aspectFill"></image>
<!-- 大卡片蒙层 -->
<view class="card-overlay"></view>
<!-- 卡片内容 -->
<view class="card-content">
<!-- 好友总数 -->
<view class="friend-count">
<text class="count-text">好友列表 ({{ friendList.length }}/{{ totalCount }})</text>
</view>
<scroll-view
class="scroll-view"
scroll-y
:show-scrollbar="false"
:lower-threshold="50"
@scrolltolower="onScrollToLower"
>
<view class="friend-list">
<view
v-for="friend in friendList"
:key="friend.user_id"
class="friend-card"
>
<!-- 好友头像 -->
<view class="friend-avatar">
<Avatar
:userId="friend.user_id"
:nickname="friend.nickname"
:avatarUrl="friend.avatar_url"
:size="100"
:borderWidth="4"
:showLevel="true"
:level="friend.fan_level"
:enableCache="false"
/>
</view>
<!-- 好友信息 -->
<view class="friend-info">
<text class="friend-nickname">{{ friend.nickname }}</text>
<text class="friend-uid">UID: {{ friend.user_id }}</text>
</view>
<!-- 操作按钮 -->
<view class="friend-actions">
<view class="action-item">
<view class="action-btn delete-btn" @click="handleDelete(friend)">
<image class="action-icon" src="/static/icon/reject.png" mode="aspectFit"></image>
</view>
<text class="action-label">删除好友</text>
</view>
</view>
</view>
<!-- 加载状态 -->
<view v-if="loading" class="loading-more">
<text class="loading-text">加载中...</text>
</view>
<view v-if="!hasMore && friendList.length > 0" class="no-more">
<text class="no-more-text">没有更多数据了</text>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
<!-- 添加好友Tab内容 -->
<view v-show="activeTab === 1" class="add-friend-container">
<!-- 大卡片容器 -->
<view class="friend-list-card">
<!-- 大卡片背景 -->
<image class="card-background" src="/static/background/friend-list-bg.png" mode="aspectFill"></image>
<!-- 大卡片蒙层 -->
<view class="card-overlay"></view>
<!-- 卡片内容 -->
<view class="card-content add-friend-card-content">
<!-- 搜索框 -->
<view class="search-wrapper">
<image class="search-icon" src="/static/icon/search.png" mode="aspectFit"></image>
<input
class="search-input"
v-model="searchUid"
placeholder="输入uid进行用户搜索"
type="number"
/>
<view
v-show="searchUid"
class="clear-btn"
@click="clearSearch"
>
<image class="clear-icon" src="/static/icon/cancel.png" mode="aspectFit"></image>
</view>
</view>
<!-- 搜索结果区域 -->
<view class="search-result-area" v-if="searchUid">
<!-- 搜索中 -->
<view v-if="isSearching" class="search-status">
<text class="status-text">搜索中...</text>
</view>
<!-- 搜索错误 -->
<view v-else-if="searchError" class="search-status">
<text class="error-text">{{ searchError }}</text>
</view>
<!-- 搜索结果卡片 -->
<view v-else-if="searchResult" class="friend-card">
<view class="friend-avatar">
<Avatar
:userId="searchResult.user_id"
:nickname="searchResult.nickname"
:avatarUrl="searchResult.avatar_url"
:size="100"
:borderWidth="4"
:showLevel="true"
:level="searchResult.fan_level"
:enableCache="false"
/>
</view>
<view class="friend-info">
<text class="friend-nickname">{{ searchResult.nickname }}</text>
<text class="friend-uid">UID: {{ searchResult.user_id }}</text>
</view>
<view class="friend-actions">
<button class="add-friend-btn" @click="handleAddFriend(searchResult.user_id, searchResult.nickname)">
添加好友
</button>
</view>
</view>
</view>
<!-- 分隔线 -->
<view class="divider"></view>
<!-- 已发送的好友请求 -->
<view class="sent-requests-section">
<view class="section-header">
<text class="section-title">我发出的好友请求</text>
</view>
<!-- 加载中 -->
<view v-if="loadingSentRequests" class="loading-status">
<text class="status-text">加载中...</text>
</view>
<!-- 空状态 -->
<view v-else-if="sentRequests.length === 0" class="empty-status">
<text class="empty-text">暂无已发送的好友请求</text>
</view>
<!-- 请求列表 -->
<view v-else class="request-list">
<view
v-for="request in sentRequests"
:key="request.to_user_id"
class="request-card"
>
<view class="friend-avatar">
<Avatar
:userId="request.to_user_id"
:nickname="request.to_user_nickname"
:avatarUrl="request.to_user_avatar_url"
:size="100"
:borderWidth="4"
:showLevel="true"
:level="request.to_user_fan_level"
:enableCache="false"
/>
</view>
<view class="request-info">
<text class="friend-nickname">{{ request.to_user_nickname }}</text>
<text class="friend-uid">UID: {{ request.to_user_id }}</text>
<text v-if="request.message" class="request-message">留言: {{ request.message }}</text>
</view>
<view class="status-badge" :class="'status-' + request.status">
<text class="status-text">{{ getStatusText(request.status) }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 好友请求Tab内容 -->
<view v-show="activeTab === 2" class="friend-request-container">
<!-- 大卡片容器 -->
<view class="friend-list-card">
<!-- 大卡片背景 -->
<image class="card-background" src="/static/background/friend-list-bg.png" mode="aspectFill"></image>
<!-- 大卡片蒙层 -->
<view class="card-overlay"></view>
<!-- 卡片内容 -->
<view class="card-content add-friend-card-content">
<!-- 加载中 -->
<view v-if="loadingReceivedRequests && receivedRequests.length === 0" class="loading-status">
<text class="status-text">加载中...</text>
</view>
<!-- 空状态 -->
<view v-else-if="receivedRequests.length === 0 && !loadingReceivedRequests" class="empty-status">
<text class="empty-text">暂无收到的好友请求</text>
</view>
<!-- 请求列表 -->
<scroll-view
v-else
class="scroll-view"
scroll-y
:show-scrollbar="false"
:lower-threshold="50"
@scrolltolower="loadMoreReceivedRequests"
>
<view class="request-list">
<view
v-for="request in receivedRequests"
:key="request.request_id || request.from_user_id"
class="request-card"
>
<view class="friend-avatar">
<Avatar
:userId="request.from_user_id"
:nickname="request.from_user_nickname"
:avatarUrl="request.from_user_avatar_url"
:size="100"
:borderWidth="4"
:showLevel="true"
:level="request.from_user_fan_level"
:enableCache="false"
/>
</view>
<view class="request-info">
<text class="friend-nickname">{{ request.from_user_nickname }}</text>
<text class="friend-uid">UID: {{ request.from_user_id }}</text>
<text v-if="request.message" class="request-message">留言: {{ request.message }}</text>
</view>
<view class="request-actions">
<view class="action-btn accept-btn" @click="handleAcceptRequest(request)">
<image class="action-icon" src="/static/icon/accept.png" mode="aspectFit"></image>
</view>
<view class="action-btn reject-btn" @click="handleRejectRequest(request)">
<image class="action-icon" src="/static/icon/reject.png" mode="aspectFit"></image>
</view>
</view>
</view>
<!-- 加载更多 -->
<view v-if="loadingReceivedRequests" class="loading-more">
<text class="loading-text">加载中...</text>
</view>
<view v-if="!hasMoreReceivedRequests && receivedRequests.length > 0" class="no-more">
<text class="no-more-text">没有更多数据了</text>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
<!-- 添加好友确认弹窗 -->
<view class="custom-modal" v-if="showAddFriendModal" @click="cancelAddFriend">
<view class="modal-content" @click.stop>
<view class="modal-title">添加好友</view>
<view class="modal-message">
确定要添加{{ pendingAddUserNickname ? ` "${pendingAddUserNickname}" ` : '该用户' }}为好友吗?
</view>
<view class="modal-buttons">
<button class="modal-btn modal-btn-cancel" @click="cancelAddFriend">取消</button>
<button class="modal-btn modal-btn-confirm" @click="confirmAddFriend">确认</button>
</view>
</view>
</view>
<!-- 删除好友确认弹窗 -->
<view class="custom-modal" v-if="showDeleteFriendModal" @click="cancelDeleteFriend">
<view class="modal-content" @click.stop>
<view class="modal-title">确认删除</view>
<view class="modal-message">
确定要删除好友"{{ pendingDeleteFriend?.nickname }}"吗?
</view>
<view class="modal-buttons">
<button class="modal-btn modal-btn-cancel" @click="cancelDeleteFriend">取消</button>
<button class="modal-btn modal-btn-confirm" @click="confirmDeleteFriend">确认</button>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
import Avatar from './Avatar.vue';
import { friendListApi, searchUserApi, getSentFriendRequestsApi, sendFriendRequestApi, getReceivedFriendRequestsApi, handleFriendRequestApi, deleteFriendApi } from '@/utils/api';
import { getFriendAvatarRealUrl } from '@/utils/assetImageHelper.js';
// Tab状态
const activeTab = ref(0);
// 好友列表数据
const friendList = ref([]);
const totalCount = ref(0);
const currentPage = ref(1);
const pageSize = ref(10);
const loading = ref(false);
const hasMore = ref(true);
// 搜索UID
const searchUid = ref('');
// 搜索相关
const searchResult = ref(null); // 搜索结果用户
const searchError = ref(''); // 搜索错误信息
const isSearching = ref(false); // 搜索中状态
let searchTimer = null; // 搜索防抖定时器
// 已发送的好友请求
const sentRequests = ref([]); // 已发送请求列表
const loadingSentRequests = ref(false);
// 收到的好友请求
const receivedRequests = ref([]);
const receivedRequestsPage = ref(1);
const receivedRequestsPageSize = ref(10);
const loadingReceivedRequests = ref(false);
const hasMoreReceivedRequests = ref(true);
// 添加好友确认弹窗
const showAddFriendModal = ref(false);
const pendingAddUserId = ref(null);
const pendingAddUserNickname = ref('');
// 删除好友确认弹窗
const showDeleteFriendModal = ref(false);
const pendingDeleteFriend = ref(null);
// 切换Tab
const switchTab = (index) => {
activeTab.value = index;
};
// 加载好友列表
const loadFriendList = async (page) => {
if (loading.value) return;
loading.value = true;
try {
const res = await friendListApi(page, pageSize.value);
if (res.code === 200 && res.data) {
// 映射字段并解析头像URL
const friendsPromises = (res.data.items || []).map(async item => {
const realAvatarUrl = item.friend_avatar
? await getFriendAvatarRealUrl(item.friend_avatar)
: '';
return {
user_id: item.friend_id,
nickname: item.friend_nickname,
fan_level: item.friend_fan_level,
avatar_url: realAvatarUrl,
_loading: false // 标记加载完成
};
});
// 先设置占位数据(用于显示加载状态)
if (page === 1) {
const placeholders = (res.data.items || []).map(item => ({
user_id: item.friend_id,
nickname: item.friend_nickname,
fan_level: item.friend_fan_level,
avatar_url: '',
_loading: true // 标记正在加载
}));
friendList.value = placeholders;
totalCount.value = res.data.total || 0;
}
// 等待所有头像解析完成
const mappedList = await Promise.all(friendsPromises);
if (page === 1) {
// 第一页,替换数据
friendList.value = mappedList;
} else {
// 后续页,追加数据
friendList.value = [...friendList.value, ...mappedList];
}
// 判断是否还有更多数据
hasMore.value = mappedList.length === pageSize.value &&
(page * pageSize.value) < totalCount.value;
currentPage.value = page;
}
} catch (error) {
console.error('加载好友列表失败:', error);
uni.showToast({
title: '加载失败,请重试',
icon: 'none'
});
} finally {
loading.value = false;
}
};
// 加载更多
const loadMore = () => {
if (!hasMore.value || loading.value) return;
loadFriendList(currentPage.value + 1);
};
// 滚动到底部
const onScrollToLower = () => {
if (activeTab.value === 0) {
loadMore();
}
};
// 删除好友
const handleDelete = (friend) => {
pendingDeleteFriend.value = friend;
showDeleteFriendModal.value = true;
};
// 取消删除好友
const cancelDeleteFriend = () => {
showDeleteFriendModal.value = false;
pendingDeleteFriend.value = null;
};
// 确认删除好友
const confirmDeleteFriend = async () => {
if (!pendingDeleteFriend.value) return;
const friend = pendingDeleteFriend.value;
const friendUserId = friend.user_id; // 使用user_id作为friend_user_id
try {
uni.showLoading({ title: '删除中...', mask: true });
await deleteFriendApi(friendUserId);
uni.hideLoading();
// 关闭弹窗
showDeleteFriendModal.value = false;
// 从列表中移除
const index = friendList.value.findIndex(item => item.user_id === friend.user_id);
if (index !== -1) {
friendList.value.splice(index, 1);
totalCount.value = Math.max(0, totalCount.value - 1);
}
uni.showToast({ title: '删除成功', icon: 'success' });
// 重置状态
pendingDeleteFriend.value = null;
} catch (error) {
uni.hideLoading();
uni.showToast({
title: error.message || '删除失败',
icon: 'none'
});
}
};
// 清空搜索框
const clearSearch = () => {
searchUid.value = '';
};
// 监听搜索框变化,实现自动搜索
watch(searchUid, async (newValue) => {
// 清除之前的定时器
if (searchTimer) {
clearTimeout(searchTimer);
}
// 清空之前的结果
searchResult.value = null;
searchError.value = '';
// 如果搜索框为空,直接返回
if (!newValue || newValue.trim() === '') {
return;
}
// 设置新的定时器500ms 后执行搜索
searchTimer = setTimeout(async () => {
try {
isSearching.value = true;
const res = await searchUserApi(newValue.trim());
if (res.code === 200 && res.data) {
// 解析头像URL
const realAvatarUrl = res.data.avatar_url
? await getFriendAvatarRealUrl(res.data.avatar_url)
: '';
searchResult.value = {
...res.data,
avatar_url: realAvatarUrl
};
searchError.value = '';
}
} catch (error) {
// 处理 404 错误
if (error.statusCode === 404 || error.message.includes('404')) {
searchError.value = '用户不存在';
searchResult.value = null;
} else {
searchError.value = error.message || '搜索失败';
searchResult.value = null;
}
} finally {
isSearching.value = false;
}
}, 500);
});
// 添加好友
const handleAddFriend = (userId, nickname) => {
pendingAddUserId.value = userId;
pendingAddUserNickname.value = nickname || '';
showAddFriendModal.value = true;
};
// 取消添加好友
const cancelAddFriend = () => {
showAddFriendModal.value = false;
pendingAddUserId.value = null;
pendingAddUserNickname.value = '';
};
// 确认添加好友
const confirmAddFriend = async () => {
if (!pendingAddUserId.value) return;
try {
uni.showLoading({ title: '发送中...', mask: true });
await sendFriendRequestApi(pendingAddUserId.value);
uni.hideLoading();
// 关闭弹窗
showAddFriendModal.value = false;
uni.showToast({ title: '好友请求已发送', icon: 'success' });
// 刷新已发送列表
await fetchSentRequests();
// 清空搜索
searchUid.value = '';
// 重置状态
pendingAddUserId.value = null;
pendingAddUserNickname.value = '';
} catch (error) {
uni.hideLoading();
uni.showToast({
title: error.message || '发送失败',
icon: 'none'
});
}
};
// 获取已发送的好友请求列表
const fetchSentRequests = async () => {
try {
loadingSentRequests.value = true;
const res = await getSentFriendRequestsApi(1, 10);
if (res.code === 200) {
// 解析头像URL
const requestsPromises = (res.data.items || []).map(async item => {
const realAvatarUrl = item.to_user_avatar_url
? await getFriendAvatarRealUrl(item.to_user_avatar_url)
: '';
return {
...item,
to_user_avatar_url: realAvatarUrl
};
});
sentRequests.value = await Promise.all(requestsPromises);
}
} catch (error) {
console.error('获取已发送请求失败:', error);
uni.showToast({
title: error.message || '获取失败',
icon: 'none'
});
} finally {
loadingSentRequests.value = false;
}
};
// 获取状态文本
const getStatusText = (status) => {
const statusMap = {
'pending': '待处理',
'accepted': '已接受',
'rejected': '已拒绝',
'cancelled': '已取消'
};
return statusMap[status] || status;
};
// 加载收到的好友请求
const loadReceivedRequests = async (page) => {
if (loadingReceivedRequests.value) return;
loadingReceivedRequests.value = true;
try {
const res = await getReceivedFriendRequestsApi(page, receivedRequestsPageSize.value);
if (res.code === 200 && res.data) {
// 解析头像URL
const requestsPromises = (res.data.items || []).map(async item => {
const realAvatarUrl = item.from_user_avatar_url
? await getFriendAvatarRealUrl(item.from_user_avatar_url)
: '';
return {
...item,
from_user_avatar_url: realAvatarUrl
};
});
const items = await Promise.all(requestsPromises);
if (page === 1) {
receivedRequests.value = items;
} else {
receivedRequests.value = [...receivedRequests.value, ...items];
}
// 判断是否还有更多
hasMoreReceivedRequests.value = items.length === receivedRequestsPageSize.value;
receivedRequestsPage.value = page;
}
} catch (error) {
console.error('获取收到的好友请求失败:', error);
uni.showToast({
title: error.message || '获取失败',
icon: 'none'
});
} finally {
loadingReceivedRequests.value = false;
}
};
// 加载更多收到的好友请求
const loadMoreReceivedRequests = () => {
if (!hasMoreReceivedRequests.value || loadingReceivedRequests.value) return;
loadReceivedRequests(receivedRequestsPage.value + 1);
};
// 处理接受好友请求
const handleAcceptRequest = async (request) => {
if (!request.request_id) {
uni.showToast({ title: '请求ID不存在', icon: 'none' });
return;
}
try {
uni.showLoading({ title: '处理中...', mask: true });
await handleFriendRequestApi(request.request_id, 'accept');
uni.hideLoading();
uni.showToast({ title: '已接受好友请求', icon: 'success' });
// 从列表中移除该请求
const index = receivedRequests.value.findIndex(item =>
(item.request_id || item.from_user_id) === (request.request_id || request.from_user_id)
);
if (index !== -1) {
receivedRequests.value.splice(index, 1);
}
} catch (error) {
uni.hideLoading();
uni.showToast({
title: error.message || '处理失败',
icon: 'none'
});
}
};
// 处理拒绝好友请求
const handleRejectRequest = async (request) => {
if (!request.request_id) {
uni.showToast({ title: '请求ID不存在', icon: 'none' });
return;
}
try {
uni.showLoading({ title: '处理中...', mask: true });
await handleFriendRequestApi(request.request_id, 'reject');
uni.hideLoading();
uni.showToast({ title: '已拒绝好友请求', icon: 'success' });
// 从列表中移除该请求
const index = receivedRequests.value.findIndex(item =>
(item.request_id || item.from_user_id) === (request.request_id || request.from_user_id)
);
if (index !== -1) {
receivedRequests.value.splice(index, 1);
}
} catch (error) {
uni.hideLoading();
uni.showToast({
title: error.message || '处理失败',
icon: 'none'
});
}
};
// 监听 activeTab 变化,切换到添加好友 tab 时刷新
watch(activeTab, (newTab) => {
if (newTab === 0) {
// 切换到好友列表tab时重新加载
loadFriendList(1);
} else if (newTab === 1) {
fetchSentRequests();
} else if (newTab === 2) {
// 切换到好友请求tab时加载
if (receivedRequests.value.length === 0) {
loadReceivedRequests(1);
}
}
});
// 页面加载时获取第一页数据
onMounted(() => {
loadFriendList(1);
// 根据初始tab加载对应数据
if (activeTab.value === 1) {
fetchSentRequests();
} else if (activeTab.value === 2) {
loadReceivedRequests(1);
}
});
</script>
<style scoped>
.friends-content {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: 1;
overflow: hidden;
}
.background-image {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: 0;
object-fit: cover;
min-width: 100%;
min-height: 100%;
filter: blur(20rpx);
transform: scale(1.1);
}
.background-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: 0;
background: rgba(0, 0, 0, 0.3);
}
.content-wrapper {
position: relative;
width: 100%;
height: 100%;
z-index: 1;
display: flex;
flex-direction: column;
padding-top: 192rpx;
padding-left: 40rpx;
padding-right: 40rpx;
padding-bottom: 160rpx;
box-sizing: border-box;
}
/* Tab标签页样式 */
.tab-container {
display: flex;
justify-content: center;
gap: 0;
margin-bottom: -20rpx;
align-items: flex-end;
position: relative;
z-index: 1;
}
.tab-item {
padding: 20rpx 40rpx;
cursor: pointer;
border-radius: 30rpx 30rpx 0 0;
background: linear-gradient(to bottom, rgb(232, 180, 217) 0%, rgb(180, 232, 232) 100%);
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.2);
transition: all 0.3s ease;
position: relative;
min-width: 140rpx;
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
margin-left: -20rpx;
transform: translateY(-10rpx);
}
.tab-item:first-child {
margin-left: 0;
}
.tab-item.active {
background: linear-gradient(to bottom, #E8B4D9 0%, #B4E8E8 100%);
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.3);
transform: translateY(-15rpx);
padding-bottom: 40rpx;
min-width: 160rpx;
}
.tab-item:active {
transform: translateY(-10rpx) scale(0.95);
}
.tab-item.active:active {
transform: translateY(-15rpx) scale(0.98);
}
.tab-text {
font-size: 32rpx;
color: rgba(255, 255, 255, 0.8);
font-weight: bold;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
transition: all 0.3s ease;
}
.tab-item.active .tab-text {
color: #e6e6e6;
font-weight: bold;
font-size: 36rpx;
text-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.3);
}
/* 好友列表容器 */
.friend-list-container {
flex: 1;
width: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
border-radius: 30rpx;
}
/* 大卡片容器 */
.friend-list-card {
position: relative;
width: 100%;
flex: 1;
border-radius: 30rpx;
overflow: hidden;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.3);
z-index: 10;
}
.card-background {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: 0;
object-fit: cover;
border-radius: 30rpx;
}
.card-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: 0;
background: rgba(0, 0, 0, 0.4);
border-radius: 30rpx;
}
.card-content {
position: relative;
width: 100%;
height: 100%;
z-index: 1;
display: flex;
flex-direction: column;
padding: 30rpx;
box-sizing: border-box;
border-radius: 30rpx;
}
.friend-count {
width: 100%;
padding: 20rpx 0;
text-align: center;
margin-bottom: 20rpx;
}
.count-text {
font-size: 32rpx;
color: rgba(255, 255, 255, 0.9);
font-weight: bold;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3);
}
.scroll-view {
flex: 1;
width: 100%;
height: 0;
}
.friend-list {
width: 100%;
padding-bottom: 40rpx;
}
/* 好友卡片样式 */
.friend-card {
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.9);
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}
.friend-avatar {
margin-right: 30rpx;
}
.friend-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 10rpx;
}
.friend-nickname {
font-size: 36rpx;
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
color: #333333;
font-weight: normal;
}
.friend-uid {
font-size: 24rpx;
color: #999999;
margin-top: 4rpx;
}
.friend-actions {
display: flex;
align-items: center;
gap: 10rpx;
margin-left: auto;
}
.action-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 6rpx;
}
.action-btn {
width: 100rpx;
height: 100rpx;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: transform 0.2s ease;
flex-shrink: 0;
}
.action-btn:active {
transform: scale(0.9);
}
.visit-btn {
background: transparent;
}
.action-icon {
width: 60rpx;
height: 60rpx;
}
.delete-btn {
background: transparent;
}
.action-label {
font-size: 20rpx;
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
text-align: center;
white-space: nowrap;
}
/* 加载状态 */
.loading-more,
.no-more {
width: 100%;
padding: 40rpx 0;
text-align: center;
}
.loading-text,
.no-more-text {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.6);
}
/* 添加好友容器 */
.add-friend-container {
flex: 1;
width: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
border-radius: 30rpx;
}
.add-friend-card-content {
justify-content: flex-start;
align-items: flex-start;
padding-top: 30rpx;
overflow-y: auto;
}
.search-wrapper {
width: 100%;
padding: 0;
margin-bottom: 20rpx;
display: flex;
align-items: center;
background: rgba(0, 0, 0, 0.6);
border-radius: 20rpx;
padding: 0 30rpx;
box-sizing: border-box;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.2);
}
.search-icon {
width: 40rpx;
height: 40rpx;
margin-right: 20rpx;
flex-shrink: 0;
}
.search-input {
flex: 1;
height: 88rpx;
background: transparent;
border: none;
font-size: 32rpx;
color: #e6e6e6;
box-sizing: border-box;
}
.search-input::placeholder {
color: rgba(255, 255, 255, 0.6);
}
.clear-btn {
width: 40rpx;
height: 40rpx;
margin-left: 20rpx;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
cursor: pointer;
transition: transform 0.2s ease;
}
.clear-btn:active {
transform: scale(0.9);
}
.clear-icon {
width: 40rpx;
height: 40rpx;
}
/* 好友请求容器 */
.friend-request-container {
flex: 1;
width: 100%;
display: flex;
flex-direction: column;
position: relative;
}
.request-placeholder {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 100rpx 0;
}
.placeholder-text {
font-size: 32rpx;
color: rgba(255, 255, 255, 0.6);
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3);
}
/* 搜索结果区域 */
.search-result-area {
width: 100%;
margin-top: 20rpx;
}
.search-status {
text-align: center;
padding: 40rpx 0;
}
.status-text {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.6);
}
.error-text {
font-size: 28rpx;
color: #ff6b9d;
}
.add-friend-btn {
background: linear-gradient(165deg, #F0E4B1 0%, #F08399 50%, #B94E73 90%, #834B9E 100%);
color: #e6e6e6;
border: none;
border-radius: 50rpx;
padding: 0 32rpx;
height: 90rpx;
font-size: 32rpx;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
}
.add-friend-btn::after {
border: none;
}
.add-friend-btn:active {
opacity: 0.8;
}
/* 分隔线 */
.divider {
width: 100%;
height: 2rpx;
background: rgba(255, 255, 255, 0.2);
margin: 40rpx 0;
}
/* 已发送请求区域 */
.sent-requests-section {
width: 100%;
display: flex;
flex-direction: column;
}
.section-header {
margin-bottom: 20rpx;
}
.section-title {
font-size: 32rpx;
color: rgba(255, 255, 255, 0.9);
font-weight: bold;
}
.loading-status,
.empty-status {
text-align: center;
padding: 40rpx 0;
}
.empty-text {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.5);
}
.request-list {
width: 100%;
}
.request-card {
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.9);
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}
.request-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 8rpx;
margin-right: 20rpx;
}
.request-message {
font-size: 26rpx;
color: #666;
margin-top: 8rpx;
}
.status-badge {
padding: 10rpx 24rpx;
border-radius: 30rpx;
font-size: 24rpx;
font-weight: bold;
white-space: nowrap;
flex-shrink: 0;
}
.status-pending {
background: linear-gradient(135deg, #FFA726 0%, #FB8C00 100%);
color: #e6e6e6;
}
.status-accepted {
background: linear-gradient(135deg, #66BB6A 0%, #43A047 100%);
color: #e6e6e6;
}
.status-rejected {
background: linear-gradient(135deg, #EF5350 0%, #E53935 100%);
color: #e6e6e6;
}
.status-cancelled {
background: linear-gradient(135deg, #BDBDBD 0%, #9E9E9E 100%);
color: #e6e6e6;
}
.status-text {
font-size: 24rpx;
}
/* 收到的好友请求操作按钮 */
.request-actions {
display: flex;
align-items: center;
gap: 10rpx;
flex-shrink: 0;
}
.request-actions .action-btn {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.2s ease;
}
.request-actions .action-btn:active {
transform: scale(0.9);
}
.request-actions .accept-btn {
background: transparent;
}
.request-actions .reject-btn {
background: transparent;
}
.request-actions .action-icon {
width: 80rpx;
height: 80rpx;
}
/* 自定义添加好友弹窗 */
.custom-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.modal-content {
width: 580rpx;
background: #e6e6e6;
border-radius: 30rpx;
padding: 50rpx 40rpx 40rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.modal-title {
font-size: 38rpx;
font-weight: bold;
color: #333333;
margin-bottom: 30rpx;
}
.modal-message {
font-size: 30rpx;
color: #666666;
text-align: center;
line-height: 1.6;
margin-bottom: 40rpx;
}
.modal-buttons {
width: 100%;
display: flex;
gap: 20rpx;
}
.modal-btn {
flex: 1;
height: 90rpx;
border-radius: 50rpx;
font-size: 32rpx;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
border: none;
}
.modal-btn::after {
border: none;
}
.modal-btn-cancel {
background: #f5f5f5;
color: #666666;
}
.modal-btn-confirm {
background: linear-gradient(165deg, #F0E4B1 0%, #F08399 50%, #B94E73 90%, #834B9E 100%);
color: #e6e6e6;
}
.modal-btn:active {
opacity: 0.8;
}
</style>