feat: Phase 1-3 全部完成 — 沙盒增强、学情分析、学习路径

This commit is contained in:
yuzhiran-dev
2026-05-18 09:48:51 +08:00
commit 11bb86854c
277 changed files with 37755 additions and 0 deletions
+100
View File
@@ -0,0 +1,100 @@
<template>
<view class="post-detail">
<view class="post" v-if="post.id">
<text class="title">{{ post.title }}</text>
<view class="meta">
<text class="author">{{ post.user?.nickname || '匿名' }}</text>
<text class="time">{{ post.createdAt?.slice(0, 10) }}</text>
</view>
<view class="content">
<text>{{ post.content }}</text>
</view>
<view class="actions">
<text class="action" @tap="toggleLike">{{ liked ? '' : '🤍' }} {{ post.likeCount }}</text>
</view>
</view>
<view class="comments-section">
<text class="section-title">评论 ({{ post.comments?.length || 0 }})</text>
<view class="comment-list">
<view class="comment" v-for="c in post.comments" :key="c.id">
<text class="comment-author">{{ c.user?.nickname || '匿名' }}</text>
<text class="comment-content">{{ c.content }}</text>
</view>
</view>
<view class="comment-input-area">
<input class="comment-input" v-model="commentText" placeholder="写评论..." />
<button class="btn-submit" @tap="submitComment">发送</button>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { api } from '../../api/index'
import { useUserStore } from '../../store/user'
const userStore = useUserStore()
const post = ref<any>({})
const liked = ref(false)
const commentText = ref('')
onMounted(async () => {
const id = Number((uni as any).getCurrentInstance()?.router?.params?.id || '')
try {
const [detail, likeStatus] = await Promise.all([
api.community.postDetail(id),
userStore.isLoggedIn ? api.community.checkLike(id) : Promise.resolve({ liked: false }),
])
post.value = detail
liked.value = (likeStatus as any)?.liked || false
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
}
})
async function toggleLike() {
if (!userStore.isLoggedIn) { uni.navigateTo({ url: '/pages/login/index' }); return }
try {
await api.community.toggleLike(post.value.id)
liked.value = !liked.value
post.value.likeCount += liked.value ? 1 : -1
} catch (e) { /* ignore */ }
}
async function submitComment() {
if (!userStore.isLoggedIn) { uni.navigateTo({ url: '/pages/login/index' }); return }
if (!commentText.value) { uni.showToast({ title: '请输入评论内容', icon: 'none' }); return }
try {
await api.community.addComment(post.value.id, commentText.value)
commentText.value = ''
uni.showToast({ title: '评论成功', icon: 'success' })
const detail: any = await api.community.postDetail(post.value.id)
post.value = detail
} catch (e: any) {
uni.showToast({ title: e.message || '评论失败', icon: 'none' })
}
}
</script>
<style scoped>
.post-detail { padding: 30rpx; }
.post { background: #fff; border-radius: 16rpx; padding: 30rpx; }
.title { font-size: 36rpx; font-weight: bold; display: block; margin-bottom: 16rpx; }
.meta { display: flex; justify-content: space-between; margin-bottom: 24rpx; }
.author { font-size: 24rpx; color: #4f8cff; }
.time { font-size: 22rpx; color: #ccc; }
.content { font-size: 28rpx; color: #333; line-height: 1.8; margin-bottom: 24rpx; }
.actions { }
.action { font-size: 26rpx; color: #666; }
.comments-section { margin-top: 30rpx; }
.section-title { font-size: 30rpx; font-weight: bold; display: block; margin-bottom: 20rpx; }
.comment-list { background: #fff; border-radius: 16rpx; padding: 20rpx; }
.comment { padding: 16rpx 0; border-bottom: 1rpx solid #f5f5f5; }
.comment:last-child { border-bottom: none; }
.comment-author { font-size: 24rpx; color: #4f8cff; display: block; margin-bottom: 8rpx; }
.comment-content { font-size: 26rpx; color: #333; }
.comment-input-area { display: flex; align-items: center; gap: 16rpx; margin-top: 24rpx; }
.comment-input { flex: 1; background: #fff; border-radius: 12rpx; padding: 20rpx 24rpx; font-size: 26rpx; border: 1rpx solid #e8e8e8; }
.btn-submit { background: #4f8cff; color: #fff; border-radius: 10rpx; padding: 16rpx 32rpx; font-size: 26rpx; border: none; }
</style>