1
0
mirror of https://github.com/ialley-workshop-open/uni-halo.git synced 2026-07-27 04:20:43 +08:00

feat: 新增点赞、游客评论、分享等核心功能,重构页面逻辑

1. 新增全局点赞状态管理,使用metadata.name做key并持久化存储
2. 新增游客信息存储与评论面板,支持游客评论互动
3. 新增全局分享hook,为多个页面配置分享功能
4. 重构登录页面,支持登录/注册切换与游客访问模式
5. 合并注册页到登录页,简化路由结构
6. 优化剪贴板工具函数,支持自定义提示文本
7. 重构个人中心页面,支持游客信息管理
8. 修复相册页面标题与样式问题
9. 移除无用的备份文件与测试代码
This commit is contained in:
小莫唐尼
2026-06-16 00:43:48 +08:00
parent 1ba71ff43f
commit a9e649e110
19 changed files with 1077 additions and 597 deletions
@@ -2,8 +2,11 @@
import { createComment, createReply, listCommentReplies, listComments } from '@/api/halo-base/comment'
import { avatar } from '@/utils/imageHelper'
import { timeFrom } from '@/utils/base/timeFrom'
import { useGuestStore } from '@/store/guest'
import { useUpvoteStore } from '@/store/upvote'
import type { CommentV1alpha1PublicApiListCommentRepliesRequest, CommentV1alpha1PublicApiListComments1Request, CommentWithReplyVo, ReplyVo } from '@halo-dev/api-client'
import type { IHaloListResponseBase } from '@/api/types/halo'
import UhGuestInfoPanel from './uh-guest-info-panel.vue'
interface IProps {
postName: string
@@ -20,6 +23,9 @@ const emits = defineEmits<{
(e: 'commented'): void
}>()
const guestStore = useGuestStore()
const upvoteStore = useUpvoteStore()
// --- 评论列表 ---
const commentList = ref<CommentWithReplyVo[]>([])
const commentPage = ref(1)
@@ -44,6 +50,26 @@ const replyLoadingMap = ref<Record<string, boolean>>({})
const replyHasNextMap = ref<Record<string, boolean>>({})
const replyPageMap = ref<Record<string, number>>({})
// --- 游客信息面板 ---
const showGuestPanel = ref(false)
// --- 点赞(切换) ---
async function handleUpvote(name: string) {
const nowUpvoted = await upvoteStore.toggle(name, 'comments')
// 更新本地计数
for (const comment of commentList.value) {
if (comment.metadata.name === name) {
comment.stats.upvote = Math.max(0, (comment.stats.upvote ?? 0) + (nowUpvoted ? 1 : -1))
break
}
const reply = comment.replies?.items?.find(r => r.metadata.name === name)
if (reply) {
reply.stats.upvote = Math.max(0, (reply.stats.upvote ?? 0) + (nowUpvoted ? 1 : -1))
break
}
}
}
// --- 获取评论列表 ---
async function fetchComments(page = 1, append = false) {
commentLoading.value = true
@@ -152,13 +178,29 @@ function handleCancelReply() {
}
// --- 提交评论/回复 ---
async function handleSubmit() {
function handleSubmit() {
const text = inputText.value.trim()
if (!text || submitLoading.value)
return
// 检查游客信息,未设置直接弹出面板
if (!guestStore.isComplete) {
showGuestPanel.value = true
return
}
doSubmit(text)
}
async function doSubmit(text: string) {
submitLoading.value = true
try {
const owner = {
displayName: guestStore.info.displayName,
email: guestStore.info.email,
website: guestStore.info.website || undefined,
}
if (replyTarget.value) {
await createReply({
name: replyTarget.value.commentName,
@@ -167,6 +209,7 @@ async function handleSubmit() {
raw: text,
quoteReply: replyTarget.value.replyName || undefined,
allowNotification: true,
owner,
},
} as any)
}
@@ -182,6 +225,8 @@ async function handleSubmit() {
version: props.commentSubjectVersion,
},
allowNotification: true,
hidden: false,
owner,
},
} as any)
}
@@ -201,6 +246,16 @@ async function handleSubmit() {
}
}
// --- 游客信息保存后自动提交 ---
function handleGuestSaved() {
showGuestPanel.value = false
// 保存后如果有输入内容,自动提交
const text = inputText.value.trim()
if (text) {
doSubmit(text)
}
}
// --- 查找被引用的回复 ---
function findQuotedReply(comment: CommentWithReplyVo, quoteReplyName: string): ReplyVo | undefined {
return comment.replies?.items?.find(r => r.metadata.name === quoteReplyName)
@@ -291,6 +346,24 @@ onMounted(() => {
<!-- 操作栏 -->
<view class="flex items-center gap-x-4">
<!-- 点赞 -->
<view
class="flex items-center gap-x-0.5"
@click="handleUpvote(comment.metadata.name)"
>
<wd-icon
name="thumb-up"
:color="upvoteStore.isUpvoted(comment.metadata.name) ? '#EF4444' : '#9CA3AF'"
:size="14"
/>
<text
class="text-[11px]"
:class="upvoteStore.isUpvoted(comment.metadata.name) ? 'text-red-500' : 'text-gray-400'"
>
{{ comment.stats.upvote || '' }}
</text>
</view>
<!-- 回复 -->
<view
class="flex items-center gap-x-1"
@click="handleReply(comment.metadata.name, undefined, comment.owner.displayName || '匿名用户', comment.spec.raw)"
@@ -298,6 +371,7 @@ onMounted(() => {
<wd-icon name="message" color="#9CA3AF" :size="14" />
<text class="text-[11px] text-gray-400">回复</text>
</view>
<!-- 展开回复 -->
<view
v-if="(comment.status?.replyCount ?? 0) > 0"
class="flex items-center gap-x-1"
@@ -345,12 +419,33 @@ onMounted(() => {
<rich-text :nodes="reply.spec.raw" />
</text>
<view
class="flex items-center gap-x-1"
@click="handleReply(comment.metadata.name, reply.metadata.name, reply.owner.displayName || '匿名用户', reply.spec.raw)"
>
<wd-icon name="message" color="#9CA3AF" :size="12" />
<text class="text-[10px] text-gray-400">回复</text>
<!-- 回复操作栏 -->
<view class="flex items-center gap-x-3">
<!-- 点赞 -->
<view
class="flex items-center gap-x-0.5"
@click="handleUpvote(reply.metadata.name)"
>
<wd-icon
name="thumb-up"
:color="upvoteStore.isUpvoted(reply.metadata.name) ? '#EF4444' : '#9CA3AF'"
:size="12"
/>
<text
class="text-[10px]"
:class="upvoteStore.isUpvoted(reply.metadata.name) ? 'text-red-500' : 'text-gray-400'"
>
{{ reply.stats.upvote || '' }}
</text>
</view>
<!-- 回复 -->
<view
class="flex items-center gap-x-1"
@click="handleReply(comment.metadata.name, reply.metadata.name, reply.owner.displayName || '匿名用户', reply.spec.raw)"
>
<wd-icon name="message" color="#9CA3AF" :size="12" />
<text class="text-[10px] text-gray-400">回复</text>
</view>
</view>
</view>
</view>
@@ -422,4 +517,11 @@ onMounted(() => {
</view>
</wd-transition>
</view>
<!-- 游客信息面板 -->
<uh-guest-info-panel
v-if="showGuestPanel"
@close="showGuestPanel = false"
@saved="handleGuestSaved"
/>
</template>