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
+75
View File
@@ -0,0 +1,75 @@
<template>
<view class="circle-detail">
<view class="header" v-if="circle.id">
<text class="name">#{{ circle.name }}</text>
<text class="desc">{{ circle.description }}</text>
<button class="btn-join" v-if="!isMember" @tap="joinCircle">加入圈子</button>
<button class="btn-leave" v-else @tap="leaveCircle">退出圈子</button>
</view>
<view class="post-list">
<view class="post-item" v-for="item in posts" :key="item.id" @tap="goPost(item.id)">
<text class="post-title">{{ item.title }}</text>
<text class="post-meta">{{ item.user?.nickname }} · {{ item.likeCount }}</text>
</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 circle = ref<any>({})
const posts = ref<any[]>([])
const isMember = ref(false)
onMounted(async () => {
const id = Number((uni as any).getCurrentInstance()?.router?.params?.id || '')
try {
const [c, p]: any = await Promise.all([
api.circles.detail(id),
api.circles.posts(id, { pageSize: 20 }),
])
circle.value = c
posts.value = p.items || []
} catch (e) { console.error(e) }
})
async function joinCircle() {
if (!userStore.isLoggedIn) { uni.navigateTo({ url: '/pages/login/index' }); return }
try {
await api.circles.join(circle.value.id)
isMember.value = true
uni.showToast({ title: '已加入', icon: 'success' })
} catch (e: any) {
uni.showToast({ title: e.message || '操作失败', icon: 'none' })
}
}
async function leaveCircle() {
try {
await api.circles.leave(circle.value.id)
isMember.value = false
uni.showToast({ title: '已退出', icon: 'success' })
} catch (e: any) {
uni.showToast({ title: e.message || '操作失败', icon: 'none' })
}
}
const goPost = (id: number) => uni.navigateTo({ url: `/pages/post-detail/index?id=${id}` })
</script>
<style scoped>
.circle-detail { }
.header { padding: 40rpx 30rpx; background: #fff; border-bottom: 1rpx solid #f0f0f0; }
.name { font-size: 40rpx; font-weight: bold; color: #4f8cff; display: block; }
.desc { font-size: 26rpx; color: #666; margin-top: 16rpx; display: block; }
.btn-join { background: #4f8cff; color: #fff; border-radius: 10rpx; padding: 16rpx 40rpx; font-size: 26rpx; margin-top: 20rpx; border: none; }
.btn-leave { background: #fff; color: #ff4d4f; border-radius: 10rpx; padding: 16rpx 40rpx; font-size: 26rpx; margin-top: 20rpx; border: 1rpx solid #ff4d4f; }
.post-list { padding: 20rpx 30rpx; }
.post-item { background: #fff; border-radius: 12rpx; padding: 24rpx; margin-bottom: 16rpx; }
.post-title { font-size: 28rpx; font-weight: 500; display: block; }
.post-meta { font-size: 22rpx; color: #999; margin-top: 8rpx; display: block; }
</style>