fix: youhua

This commit is contained in:
zzc
2026-06-10 01:24:41 +08:00
parent 1e5bbbbf9e
commit aa88eef08e
3 changed files with 164 additions and 26 deletions

View File

@@ -20,10 +20,13 @@
class="comment-input" class="comment-input"
type="text" type="text"
v-model="commentText" v-model="commentText"
:disabled="sending"
placeholder="说点什么吧..." placeholder="说点什么吧..."
placeholder-class="input-placeholder" placeholder-class="input-placeholder"
/> />
<button class="send-btn" @tap="handleSend">发送</button> <button class="send-btn" :class="{ 'is-disabled': sending }" :disabled="sending" @tap="handleSend">
{{ sending ? '发送中...' : '发送' }}
</button>
</view> </view>
</view> </view>
</template> </template>
@@ -35,6 +38,10 @@ const props = defineProps({
initialAction: { initialAction: {
type: String, type: String,
default: '' default: ''
},
sending: {
type: Boolean,
default: false
} }
}); });
@@ -62,19 +69,28 @@ const handleAction = (action) => {
}; };
const handleSend = () => { const handleSend = () => {
if (!commentText.value.trim()) { const content = commentText.value.trim();
if (!content) {
uni.showToast({ uni.showToast({
title: '请输入评论内容', title: '请输入评论内容',
icon: 'none' icon: 'none'
}); });
return; return;
} }
if (props.sending) return;
emit('send-comment', { emit('send-comment', {
action: currentAction.value, action: currentAction.value,
content: commentText.value content
}); });
commentText.value = ''; // 发送后清空
}; };
const resetComment = () => {
commentText.value = '';
};
defineExpose({
resetComment
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -162,6 +178,10 @@ const handleSend = () => {
margin: 0; margin: 0;
} }
.send-btn.is-disabled {
opacity: 0.7;
}
.send-btn::after { .send-btn::after {
border: none; border: none;
} }

View File

@@ -37,7 +37,7 @@ const props = defineProps({
} }
}); });
const emit = defineEmits(['need-login']); const emit = defineEmits(['need-login', 'like-change']);
const userStore = useUserStore(); const userStore = useUserStore();
const isLoggedIn = computed(() => !!userStore.token || !!userStore.userInfo?.nickName); const isLoggedIn = computed(() => !!userStore.token || !!userStore.userInfo?.nickName);
@@ -70,6 +70,11 @@ const handleLike = async () => {
} else { } else {
currentLikes.value -= 1; currentLikes.value -= 1;
} }
emit('like-change', {
id: props.comment.id,
isLiked: isLiked.value,
likes: currentLikes.value
});
try { try {
// 调用点赞接口 (点赞和取消点赞为同一个接口) // 调用点赞接口 (点赞和取消点赞为同一个接口)
@@ -85,7 +90,6 @@ const handleLike = async () => {
page: 'rating_detail' page: 'rating_detail'
} }
}); });
uni.showToast({ title: '操作成功', icon: 'success' });
} catch (error) { } catch (error) {
// 接口调用失败,回滚 UI 状态 // 接口调用失败,回滚 UI 状态
isLiked.value = !isLiked.value; isLiked.value = !isLiked.value;
@@ -94,6 +98,11 @@ const handleLike = async () => {
} else { } else {
currentLikes.value -= 1; currentLikes.value -= 1;
} }
emit('like-change', {
id: props.comment.id,
isLiked: isLiked.value,
likes: currentLikes.value
});
uni.showToast({ title: '操作失败', icon: 'none' }); uni.showToast({ title: '操作失败', icon: 'none' });
} finally { } finally {
isLiking = false; isLiking = false;

View File

@@ -47,7 +47,9 @@
<!-- 你的态度 --> <!-- 你的态度 -->
<AttitudePanel <AttitudePanel
ref="attitudePanelRef"
:initial-action="currentAction" :initial-action="currentAction"
:sending="isSendingComment"
@action-change="handleActionChange" @action-change="handleActionChange"
@send-comment="handleSendComment" @send-comment="handleSendComment"
/> />
@@ -89,18 +91,32 @@
<!-- <text class="filter-icon"></text> --> <!-- <text class="filter-icon"></text> -->
</view> </view>
<view class="comment-list" style="min-height: 400rpx;"> <view class="comment-list">
<view v-if="commentLoading && !comments.length" class="comment-state">
<text class="comment-state-title">评论加载中...</text>
<text class="comment-state-desc">正在拉取大家的锐评</text>
</view>
<view v-else-if="commentError && !comments.length" class="comment-state">
<text class="comment-state-title">评论加载失败</text>
<text class="comment-state-desc">{{ commentError }}</text>
<button class="comment-state-btn" @tap="retryLoadComments">重新加载</button>
</view>
<view v-else-if="!comments.length" class="comment-state">
<text class="comment-state-title">还没有评论</text>
<text class="comment-state-desc">来发第一条锐评带动一下讨论气氛</text>
</view>
<CommentItem <CommentItem
v-for="(comment, index) in comments" v-for="(comment, index) in comments"
:key="comment.id" :key="comment.id"
:comment="comment" :comment="comment"
@need-login="openLoginPopup" @need-login="openLoginPopup"
@like-change="handleCommentLikeChange"
/> />
</view> </view>
<!-- 加载更多 --> <!-- 加载更多 -->
<view class="load-more"> <view v-if="comments.length" class="load-more">
<text>{{ loading ? '加载中...' : (hasMore ? '上拉加载更多' : '没有更多了') }}</text> <text>{{ commentLoadText }}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -127,14 +143,22 @@ import { getShareReward } from "@/api/system";
import { useUserStore } from "@/stores/user"; import { useUserStore } from "@/stores/user";
const userStore = useUserStore(); const userStore = useUserStore();
const statusBarHeight = ref(getStatusBarHeight() || 44); const statusBarHeight = ref(getStatusBarHeight() || 44);
const loading = ref(false);
const hasMore = ref(true); const hasMore = ref(true);
const currentPage = ref(1); const currentPage = ref(1);
const currentAction = ref(''); const currentAction = ref('');
const successPopupRef = ref(null); const successPopupRef = ref(null);
const loginPopupRef = ref(null); const loginPopupRef = ref(null);
const attitudePanelRef = ref(null);
const currentTab = ref('hot'); // 'hot' 或 'new' const currentTab = ref('hot'); // 'hot' 或 'new'
const isLoggedIn = computed(() => !!userStore.token || !!userStore.userInfo?.nickName); const isLoggedIn = computed(() => !!userStore.token || !!userStore.userInfo?.nickName);
const commentLoading = ref(false);
const isSendingComment = ref(false);
const commentError = ref('');
const commentLoadText = computed(() => {
if (commentLoading.value && comments.value.length) return '加载中...';
if (!hasMore.value) return '没有更多了';
return '上拉加载更多';
});
const goBack = () => { const goBack = () => {
uni.navigateBack(); uni.navigateBack();
@@ -215,9 +239,10 @@ const handleSendComment = async (payload) => {
return; return;
} }
if (!detailData.value.topicId || !itemId.value) return; if (!detailData.value.topicId || !itemId.value || isSendingComment.value) return;
try { try {
isSendingComment.value = true;
uni.showLoading({ title: '发送中...' }); uni.showLoading({ title: '发送中...' });
await topicItemComment({ await topicItemComment({
topicId: detailData.value.topicId, topicId: detailData.value.topicId,
@@ -237,7 +262,6 @@ const handleSendComment = async (payload) => {
// 刷新页面数据以获取最新评论 // 刷新页面数据以获取最新评论
currentPage.value = 1; currentPage.value = 1;
hasMore.value = true; hasMore.value = true;
comments.value = [];
uni.$trackRecord({ uni.$trackRecord({
eventName: 'comment', eventName: 'comment',
eventType: 'comment', eventType: 'comment',
@@ -248,13 +272,16 @@ const handleSendComment = async (payload) => {
page: 'rating_detail' page: 'rating_detail'
} }
}); });
getCommentsData(); attitudePanelRef.value?.resetComment?.();
await getCommentsData({ replace: true });
} catch (error) { } catch (error) {
uni.hideLoading(); uni.hideLoading();
uni.showToast({ uni.showToast({
title: '发送失败', title: '发送失败',
icon: 'none' icon: 'none'
}); });
} finally {
isSendingComment.value = false;
} }
}; };
@@ -263,16 +290,34 @@ const switchTab = (tab) => {
currentTab.value = tab; currentTab.value = tab;
currentPage.value = 1; currentPage.value = 1;
hasMore.value = true; hasMore.value = true;
// comments.value = []; // 移除这行,避免数据清空导致高度塌陷 commentError.value = '';
getCommentsData(true); // 传入标识表示是切换榜单 getCommentsData({ replace: true });
}; };
const getCommentsData = async (isSwitchTab = false) => { const resolveCommentHasMore = (res, list) => {
if (!detailData.value.topicId || !itemId.value || loading.value) return; const data = res?.data || res || {};
const hasNext = data?.hasNext ?? res?.hasNext;
if (typeof hasNext === 'boolean') return hasNext;
const pages = Number(data?.pages ?? res?.pages ?? 0);
if (pages > 0) return currentPage.value < pages;
const total = Number(data?.total ?? res?.total ?? 0);
if (total > 0) {
const size = Number(data?.size ?? data?.pageSize ?? res?.size ?? res?.pageSize ?? list.length || 1);
return currentPage.value * size < total;
}
return list.length > 0;
};
const getCommentsData = async ({ replace = false } = {}) => {
if (!detailData.value.topicId || !itemId.value || commentLoading.value) return;
try { try {
loading.value = true; commentLoading.value = true;
const orderBy = currentTab.value; commentError.value = '';
const orderBy = currentTab.value === 'hot' ? 1 : 2;
const res = await topicItemCommentList(detailData.value.topicId, itemId.value, currentPage.value, orderBy); const res = await topicItemCommentList(detailData.value.topicId, itemId.value, currentPage.value, orderBy);
const rawList = const rawList =
res?.data?.records || res?.data?.records ||
@@ -297,19 +342,40 @@ const getCommentsData = async (isSwitchTab = false) => {
userAction: item.userAction || "" // 夯/顶级/人上人/NPC/拉 userAction: item.userAction || "" // 夯/顶级/人上人/NPC/拉
})); }));
if (currentPage.value === 1 || isSwitchTab) { if (currentPage.value === 1 || replace) {
comments.value = formattedList; comments.value = formattedList;
} else { } else {
comments.value = [...comments.value, ...formattedList]; comments.value = [...comments.value, ...formattedList];
} }
// 假设每页10条如果返回的少于10条则没有更多了 hasMore.value = resolveCommentHasMore(res, list);
hasMore.value = list.length >= 6;
} catch (error) { } catch (error) {
console.error('获取评论列表失败:', error); console.error('获取评论列表失败:', error);
} finally { commentError.value = '请检查网络后重试';
loading.value = false; if (currentPage.value > 1) {
currentPage.value -= 1;
} }
} finally {
commentLoading.value = false;
}
};
const retryLoadComments = () => {
if (!comments.value.length) {
currentPage.value = 1;
}
getCommentsData({ replace: currentPage.value === 1 });
};
const handleCommentLikeChange = ({ id, isLiked, likes }) => {
comments.value = comments.value.map((item) => {
if (item.id !== id) return item;
return {
...item,
isLiked,
likes
};
});
}; };
const getDetailData = async () => { const getDetailData = async () => {
@@ -344,7 +410,7 @@ const getDetailData = async () => {
// 拉取真实的评论列表 // 拉取真实的评论列表
if (currentPage.value === 1) { if (currentPage.value === 1) {
getCommentsData(); getCommentsData({ replace: true });
} }
} catch (error) { } catch (error) {
console.error('获取详情失败:', error); console.error('获取详情失败:', error);
@@ -377,6 +443,7 @@ const handleLoginSuccess = () => {
currentPage.value = 1; currentPage.value = 1;
hasMore.value = true; hasMore.value = true;
comments.value = []; comments.value = [];
commentError.value = '';
getDetailData(); getDetailData();
}; };
@@ -405,11 +472,12 @@ onPullDownRefresh(() => {
currentPage.value = 1; currentPage.value = 1;
hasMore.value = true; hasMore.value = true;
comments.value = []; comments.value = [];
commentError.value = '';
getDetailData(); getDetailData();
}); });
onReachBottom(() => { onReachBottom(() => {
if (!hasMore.value || loading.value) return; if (!hasMore.value || commentLoading.value) return;
currentPage.value += 1; currentPage.value += 1;
getCommentsData(); getCommentsData();
}); });
@@ -749,6 +817,47 @@ onReachBottom(() => {
.comment-list { .comment-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 400rpx;
}
.comment-state {
min-height: 400rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
color: #999;
padding: 40rpx 32rpx;
box-sizing: border-box;
}
.comment-state-title {
font-size: 30rpx;
color: #222;
font-weight: 700;
margin-bottom: 12rpx;
}
.comment-state-desc {
font-size: 24rpx;
color: #9aa0ae;
line-height: 1.6;
}
.comment-state-btn {
margin-top: 24rpx;
height: 72rpx;
line-height: 72rpx;
padding: 0 32rpx;
border-radius: 999rpx;
background: #4d44f1;
color: #fff;
font-size: 26rpx;
}
.comment-state-btn::after {
border: none;
} }
.load-more { .load-more {