Merge pull request #89 from nagisa77/codex/integrate-real-content-for-homepage,-post,-and-comments

Connect frontend to backend post APIs
This commit is contained in:
Tim
2025-07-06 11:52:55 +08:00
committed by GitHub
3 changed files with 121 additions and 219 deletions

View File

@@ -60,6 +60,8 @@
import { ref } from 'vue'
import CommentEditor from './CommentEditor.vue'
import { renderMarkdown } from '../utils/markdown'
import { API_BASE_URL, toast } from '../main'
import { getToken } from '../utils/auth'
const CommentItem = {
name: 'CommentItem',
props: {
@@ -81,18 +83,44 @@ const CommentItem = {
const toggleEditor = () => {
showEditor.value = !showEditor.value
}
const submitReply = (text) => {
const submitReply = async (text) => {
if (!text.trim()) return
const replyList = props.comment.reply || (props.comment.reply = [])
replyList.push({
id: Date.now(),
userName: '你',
time: new Date().toLocaleDateString('zh-CN', { month: 'numeric', day: 'numeric' }),
avatar: 'https://picsum.photos/200/200',
text,
reply: []
})
showEditor.value = false
const token = getToken()
if (!token) {
toast.error('请先登录')
return
}
try {
const res = await fetch(`${API_BASE_URL}/api/comments/${props.comment.id}/replies`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
body: JSON.stringify({ content: text })
})
if (res.ok) {
const data = await res.json()
const replyList = props.comment.reply || (props.comment.reply = [])
replyList.push({
id: data.id,
userName: data.author,
time: new Date(data.createdAt).toLocaleDateString('zh-CN', { month: 'numeric', day: 'numeric' }),
avatar: 'https://picsum.photos/200/200',
text: data.content,
reply: (data.replies || []).map(r => ({
id: r.id,
userName: r.author,
time: new Date(r.createdAt).toLocaleDateString('zh-CN', { month: 'numeric', day: 'numeric' }),
avatar: 'https://picsum.photos/200/200',
text: r.content,
reply: []
}))
})
showEditor.value = false
} else {
toast.error('回复失败')
}
} catch (e) {
toast.error('回复失败')
}
}
const copyCommentLink = () => {
const link = `${location.origin}${location.pathname}#comment-${props.comment.id}`

View File

@@ -81,8 +81,9 @@
</template>
<script>
import { ref } from 'vue'
import { ref, onMounted } from 'vue'
import { stripMarkdown } from '../utils/markdown'
import { API_BASE_URL } from '../main'
import CategorySelect from '../components/CategorySelect.vue'
import TagSelect from '../components/TagSelect.vue'
@@ -102,98 +103,30 @@ export default {
const topics = ref(['最新', '排行榜', '热门', '类别'])
const selectedTopic = ref('最新')
const baseAvatar = 'https://picsum.photos/200/200'
const articles = ref([
{
id: 1,
title: '各位佬科研项目python语言适合什么ai编程',
description:
'是的L站目前每天都有不少各色各样的佬友加入。对于一个在线社区来说不断壮大和涌入新的血液是一件好事。但我每天都要问问自己这里面有没有问题真的完全是好事吗在这个过程中我嗅到了一丝危险的气息有人试图同质化这里把这里当作互联网上另⋯阅读更多',
category: '开发调优',
tags: ['AI', 'Python', 'Java'],
members: Array(5).fill(baseAvatar),
comments: 139,
views: '2.4k',
time: '23小时前'
},
{
id: 2,
title: '各位佬科研项目python语言适合什么ai编程',
description:
ref(`# 📢 社区公告
const articles = ref([])
是的L 站目前每天都有不少各色各样的佬友加入。对于一个在线社区来说,不断壮大和涌入新的血液是一件好事。
但我每天都要问问自己:**这里面有没有问题?真的完全是好事吗?**
在这个过程中我嗅到了一丝危险的气息——有人试图**同质化**这里,把这里当作互联网上**另一个可以随意发泄情绪**的地方!甚至试图占领舆论高地,把这里堂而皇之地变成**另一个垃圾场**。
> 这是要万分警惕并坚决予以打击的!
L 站的愿景是成为新的**理想型社区**,让每一个一身疲惫的佬友在这里得到放松。哪怕只有一刻能放松手中攥紧的武器,徜徉在和谐的氛围中得到喘息与治愈。
我和管理团队始终**坚定这一点,丝毫不会放松**
千里之堤,溃于蚁穴——如果任由戾气蔓延、争端四起,最终这里的愿景将会完全破产。**有病要医,不是同路人不必强行融合。**任何把戾气带来这里、试图在此建立另一个互联网垃圾场的人,**都是不受欢迎的,都要被驱逐出社区。**
请好好说话,友善交流!我们完全支持并鼓励友好交流与分享,每个人都可以。**键盘**是你与人沟通、互通有无的桥梁,不只是你谋取私利的工具,更不是肆意挥舞用来攻击的武器。
---
## 🚫 自本公告发布之日起,我们将严肃处理以下 3 类发言:
1. **傲慢轻蔑回复**
2. **阴阳怪气回复**
3. **攻击谩骂回复**
如有以上发言,我们将视言论破坏程度采取(但不限于)**删帖、临时封禁、永久封禁**等举措。
> 请各位佬友积极监督,感谢你们为共建美好社区做出的贡献!
> **请一定一定不要把互联网上的戾气带来这里,这里就要做不一样。**
**持续时间:** *直至最后一个不会好好说话的账号持有者被请出社区为止。*`),
category: '开发调优',
tags: ['AI', 'Python', 'Java'],
members: Array(5).fill(baseAvatar),
comments: 139,
views: '2.4k',
time: '23小时前'
},
{
id: 3,
title: '各位佬科研项目python语言适合什么ai编程',
description:
'是的L站目前每天都有不少各色各样的佬友加入。对于一个在线社区来说不断壮大和涌入新的血液是一件好事。但我每天都要问问自己这里面有没有问题真的完全是好事吗在这个过程中我嗅到了一丝危险的气息有人试图同质化这里把这里当作互联网上另⋯阅读更多',
category: '开发调优',
tags: ['AI', 'Python', 'Java'],
members: Array(5).fill(baseAvatar),
comments: 139,
views: '2.4k',
time: '23小时前'
},
{
id: 4,
title: '各位佬科研项目python语言适合什么ai编程',
description:
'rt',
category: '开发调优',
tags: ['AI', 'Python', 'Java'],
members: Array(5).fill(baseAvatar),
comments: 139,
views: '2.4k',
time: '23小时前'
},
{
id: 5,
title: '各位佬科研项目python语言适合什么ai编程',
description:
'是的L站目前每天都有不少各色各样的佬友加入。对于一个在线社区来说不断壮大和涌入新的血液是一件好事。但我每天都要问问自己这里面有没有问题真的完全是好事吗在这个过程中我嗅到了一丝危险的气息有人试图同质化这里把这里当作互联网上另⋯阅读更多',
category: '开发调优',
tags: ['AI', 'Python', 'Java'],
members: Array(5).fill(baseAvatar),
comments: 139,
views: '2.4k',
time: '23小时前'
const fetchPosts = async () => {
try {
const res = await fetch(`${API_BASE_URL}/api/posts`)
if (!res.ok) return
const data = await res.json()
articles.value = data.map(p => ({
id: p.id,
title: p.title,
description: p.content,
category: p.category,
tags: p.tags || [],
members: [],
comments: (p.comments || []).length,
views: p.views,
time: new Date(p.createdAt).toLocaleDateString('zh-CN', { month: 'numeric', day: 'numeric' })
}))
} catch (e) {
console.error(e)
}
])
}
onMounted(fetchPosts)
const sanitizeDescription = (text) => stripMarkdown(text)

View File

@@ -85,128 +85,56 @@
<script>
import { ref, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import CommentItem from '../components/CommentItem.vue'
import CommentEditor from '../components/CommentEditor.vue'
import { renderMarkdown } from '../utils/markdown'
import { API_BASE_URL, toast } from '../main'
import { getToken } from '../utils/auth'
export default {
name: 'PostPageView',
components: { CommentItem, CommentEditor },
setup() {
const postContent = ref(`# 📢 社区公告
const route = useRoute()
const postId = route.params.id
是的L 站目前每天都有不少各色各样的佬友加入。对于一个在线社区来说,不断壮大和涌入新的血液是一件好事。
但我每天都要问问自己:**这里面有没有问题?真的完全是好事吗?**
在这个过程中我嗅到了一丝危险的气息——有人试图**同质化**这里,把这里当作互联网上**另一个可以随意发泄情绪**的地方!甚至试图占领舆论高地,把这里堂而皇之地变成**另一个垃圾场**。
> 这是要万分警惕并坚决予以打击的!
L 站的愿景是成为新的**理想型社区**,让每一个一身疲惫的佬友在这里得到放松。哪怕只有一刻能放松手中攥紧的武器,徜徉在和谐的氛围中得到喘息与治愈。
我和管理团队始终**坚定这一点,丝毫不会放松**
千里之堤,溃于蚁穴——如果任由戾气蔓延、争端四起,最终这里的愿景将会完全破产。**有病要医,不是同路人不必强行融合。**任何把戾气带来这里、试图在此建立另一个互联网垃圾场的人,**都是不受欢迎的,都要被驱逐出社区。**
请好好说话,友善交流!我们完全支持并鼓励友好交流与分享,每个人都可以。**键盘**是你与人沟通、互通有无的桥梁,不只是你谋取私利的工具,更不是肆意挥舞用来攻击的武器。
---
## 🚫 自本公告发布之日起,我们将严肃处理以下 3 类发言:
1. **傲慢轻蔑回复**
2. **阴阳怪气回复**
3. **攻击谩骂回复**
如有以上发言,我们将视言论破坏程度采取(但不限于)**删帖、临时封禁、永久封禁**等举措。
> 请各位佬友积极监督,感谢你们为共建美好社区做出的贡献!
> **请一定一定不要把互联网上的戾气带来这里,这里就要做不一样。**
**持续时间:** *直至最后一个不会好好说话的账号持有者被请出社区为止。*`)
const tags = ref(['AI', 'Python', 'Java'])
const comments = ref([
{
id: 1,
userName: 'Nagisa77',
time: '3月10日',
avatar: 'https://picsum.photos/200/200',
text: '沙发🛋️🛋️🛋️🛋️',
reply: [
{
id: 7,
userName: 'Nagisa77',
time: '3月11日',
avatar: 'https://picsum.photos/200/200',
text: '💩💩💩💩💩',
reply: [
{
id: 9,
userName: 'Nagisa77',
time: '3月11日',
avatar: 'https://picsum.photos/200/200',
text: '发💩干嘛? 我💩你'
},
],
},
{
id: 8,
userName: 'Nagisa77',
time: '3月11日',
avatar: 'https://picsum.photos/200/200',
text: '支持',
reply: [],
},
]
},
{
id: 2,
userName: 'Nagisa77',
time: '3月11日',
avatar: 'https://picsum.photos/200/200',
text: '💩💩💩💩💩',
reply: [],
},
{
id: 3,
userName: 'Nagisa77',
time: '3月12日',
avatar: 'https://picsum.photos/200/200',
text: '是的',
reply: [],
},
{
id: 4,
userName: 'Nagisa77',
time: '3月13日',
avatar: 'https://picsum.photos/200/200',
text: '持续时间至最后一个不会好好说话的账号持有者被请出社区为止。',
reply: [],
},
{
id: 5,
userName: 'Nagisa77',
time: '3月14日',
avatar: 'https://picsum.photos/200/200',
text: '赞同楼主',
reply: [],
},
{
id: 6,
userName: 'Nagisa77',
time: '3月15日',
avatar: 'https://picsum.photos/200/200',
text: '这里面有没有问题?真的完全是好事吗?在这个过程中我嗅到了一丝危险的气息',
reply: [],
}
])
const postTime = ref('3月10日')
const postContent = ref('')
const tags = ref([])
const comments = ref([])
const postTime = ref('')
const postItems = ref([])
const mainContainer = ref(null)
const currentIndex = ref(1)
const mapComment = c => ({
id: c.id,
userName: c.author,
time: new Date(c.createdAt).toLocaleDateString('zh-CN', { month: 'numeric', day: 'numeric' }),
avatar: 'https://picsum.photos/200/200',
text: c.content,
reply: (c.replies || []).map(mapComment)
})
const fetchPost = async () => {
try {
const res = await fetch(`${API_BASE_URL}/api/posts/${postId}`)
if (!res.ok) return
const data = await res.json()
postContent.value = data.content
tags.value = data.tags || []
comments.value = (data.comments || []).map(mapComment)
postTime.value = new Date(data.createdAt).toLocaleDateString('zh-CN', { month: 'numeric', day: 'numeric' })
} catch (e) {
console.error(e)
}
}
const totalPosts = computed(() => comments.value.length + 1)
const lastReplyTime = computed(() =>
comments.value.length ? comments.value[comments.value.length - 1].time : postTime.value
)
const updateCurrentIndex = () => {
const scrollTop = mainContainer.value ? mainContainer.value.scrollTop : 0
for (let i = 0; i < postItems.value.length; i++) {
@@ -225,21 +153,36 @@ L 站的愿景是成为新的**理想型社区**,让每一个一身疲惫的
}
}
const postComment = (text) => {
const postComment = async (text) => {
if (!text.trim()) return
comments.value.push({
id: comments.value.length + 1,
userName: '你',
time: new Date().toLocaleDateString('zh-CN', { month: 'numeric', day: 'numeric' }),
avatar: 'https://picsum.photos/200/200',
text,
reply: []
})
const token = getToken()
if (!token) {
toast.error('请先登录')
return
}
try {
const res = await fetch(`${API_BASE_URL}/api/posts/${postId}/comments`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
body: JSON.stringify({ content: text })
})
if (res.ok) {
const data = await res.json()
comments.value.push(mapComment(data))
} else {
toast.error('评论失败')
}
} catch (e) {
toast.error('评论失败')
}
}
const copyPostLink = () => {
navigator.clipboard.writeText(location.href.split('#')[0])
}
onMounted(() => {
fetchPost()
updateCurrentIndex()
const hash = location.hash
if (hash.startsWith('#comment-')) {
@@ -268,11 +211,9 @@ L 站的愿景是成为新的**理想型社区**,让每一个一身疲惫的
onScroll: updateCurrentIndex,
copyPostLink,
renderMarkdown
}
}
}
</script>
<style>
.post-page-container {
display: flex;