support guard price

This commit is contained in:
2024-03-24 10:30:04 +08:00
parent f71420511f
commit 2c6bf5beac
9 changed files with 376 additions and 62 deletions

View File

@@ -1,11 +1,11 @@
<script setup lang="ts">
import { useAccount } from '@/api/account'
import { ForumCommentModel, ForumTopicModel } from '@/api/models/forum'
import { ForumCommentModel, ForumModel, ForumTopicModel } from '@/api/models/forum'
import { VTSURU_API_URL } from '@/data/constants'
import { useForumStore } from '@/store/useForumStore'
import { ArrowReply16Filled } from '@vicons/fluent'
import { Heart, HeartOutline } from '@vicons/ionicons5'
import { NAvatar, NButton, NCard, NDivider, NFlex, NIcon, NText, NTime, NTooltip } from 'naive-ui'
import { ArrowHookUpLeft24Filled, ArrowReply16Filled, Delete24Filled } from '@vicons/fluent'
import { Heart, HeartOutline, SyncCircleSharp } from '@vicons/ionicons5'
import { NAvatar, NButton, NCard, NDivider, NFlex, NIcon, NPopconfirm, NTag, NText, NTime, NTooltip } from 'naive-ui'
import ForumReplyItem from './ForumReplyItem.vue'
import { computed } from 'vue'
@@ -20,6 +20,32 @@ const accountInfo = useAccount()
const canOprate = computed(() => {
return !props.topic.isLocked && accountInfo.value.id > 0
})
const emits = defineEmits<{
(e: 'delete', id: number): void
}>()
function delComment(id: number) {
useForum.DelComment(id).then((success) => {
if (success) {
emits('delete', id)
}
})
}
function restoreComment(id: number) {
useForum.RestoreComment(id).then((success) => {
if (success) {
props.item.isDeleted = false
}
})
}
function delReply(id: number) {
useForum.DelReply(id).then((success) => {
if (success) {
props.item.replies = props.item.replies.filter((reply) => reply.id !== id)
}
})
}
</script>
<template>
@@ -29,7 +55,8 @@ const canOprate = computed(() => {
:img-props="{ referrerpolicy: 'no-referrer' }"
/>
<NFlex vertical style="flex: 1" :size="2">
<NFlex>
<NFlex align="center">
<NTag v-if="item.isDeleted" type="warning" :bordered="false"> 已删除 </NTag>
<NText>
{{ item.user.name }}
</NText>
@@ -43,7 +70,22 @@ const canOprate = computed(() => {
</NText>
</NFlex>
<div class="editor-content-view" v-html="item.content"></div>
<NDivider style="margin: 0" />
<NCard v-if="item.replies.length > 0" size="small" style="margin-bottom: 10px">
<NFlex vertical>
<ForumReplyItem
v-for="reply in item.replies"
:key="reply.id"
:item="reply"
:comment="item"
:topic="topic"
showReplyButton
:reply-to="reply.replyTo ? item.replies.find((r) => r.id === reply.replyTo) : undefined"
:reply-to-id="reply.replyTo"
@delete="delReply"
/>
</NFlex>
</NCard>
<NFlex>
<NTooltip>
<template #trigger>
@@ -71,12 +113,7 @@ const canOprate = computed(() => {
</NTooltip>
<NTooltip>
<template #trigger>
<NButton
size="small"
@click="useForum.SetReplyingComment(item)"
text
:disabled="!canOprate"
>
<NButton size="small" @click="useForum.SetReplyingComment(item)" text :disabled="!canOprate">
<template #icon>
<NIcon :component="ArrowReply16Filled" />
</template>
@@ -85,20 +122,42 @@ const canOprate = computed(() => {
</template>
回复
</NTooltip>
</NFlex>
<NCard v-if="item.replies.length > 0" size="small">
<NFlex vertical>
<ForumReplyItem
v-for="reply in item.replies"
:key="reply.id"
:item="reply"
:comment="item"
:topic="topic"
showReplyButton
:reply-to="reply.replyTo ? item.replies.find((r) => r.id === reply.replyTo) : undefined"
/>
<NFlex style="flex: 1" justify="end">
<NTooltip v-if="item.user.id === accountInfo.id || topic.isAdmin">
<template #trigger>
<NPopconfirm @positive-click="delComment(item.id)">
<template #trigger>
<NButton size="small" text :disabled="!canOprate">
<template #icon>
<NIcon
:component="Delete24Filled"
:color="item.isDeleted || topic.isAdmin ? '#dd484f' : '#7f7f7f'"
/>
</template>
</NButton>
</template>
{{ item.isDeleted ? '确定完全删除这条评论吗? 这将无法恢复' : '确定删除这条评论吗' }}
</NPopconfirm>
</template>
{{ item.isDeleted || topic.isAdmin ? '完全' : '' }}删除
</NTooltip>
<NTooltip v-if="item.isDeleted && topic.isAdmin">
<template #trigger>
<NPopconfirm @positive-click="restoreComment(item.id)">
<template #trigger>
<NButton size="small" text :disabled="!canOprate">
<template #icon>
<NIcon :component="SyncCircleSharp" color="#7f7f7f" />
</template>
</NButton>
</template>
要恢复这条评论吗?
</NPopconfirm>
</template>
恢复
</NTooltip>
</NFlex>
</NCard>
</NFlex>
</NFlex>
</NFlex>
</template>

View File

@@ -1,8 +1,11 @@
<script setup lang="ts">
import { useAccount } from '@/api/account'
import { ForumModel, ForumTopicBaseModel } from '@/api/models/forum'
import { useForumStore } from '@/store/useForumStore'
import { ArrowReply24Filled, Chat24Regular, MoreVertical24Filled, Star24Filled } from '@vicons/fluent'
import { ArrowReply24Filled, Chat24Regular, Delete24Filled, MoreVertical24Filled, Star24Filled } from '@vicons/fluent'
import { SyncCircleSharp } from '@vicons/ionicons5'
import { NButton, NDropdown, NFlex, NIcon, NTag, NText, NTime, NTooltip, useDialog } from 'naive-ui'
import { h } from 'vue'
const props = defineProps<{
item: ForumTopicBaseModel
@@ -11,6 +14,7 @@ const props = defineProps<{
const useForum = useForumStore()
const dialog = useDialog()
const accountInfo = useAccount()
function onDropdownSelect(key: string) {
switch (key) {
@@ -31,6 +35,21 @@ function onDropdownSelect(key: string) {
},
})
break
case 'delete':
dialog.warning({
title: '问问',
content: '确定要恢复这条话题吗?',
positiveText: '确定',
negativeText: '再想想',
onPositiveClick: () => {
useForum.RestoreTopic(props.item.id).then((success) => {
if (success) {
props.item.isDeleted = false
}
})
},
})
break
case 'top':
dialog.info({
title: '问',
@@ -54,7 +73,8 @@ function onDropdownSelect(key: string) {
<template>
<NFlex align="center">
<NFlex align="center" :wrap="false">
<NTag v-if="item.isPinned" size="small" round>
<NTag v-if="item.isDeleted" size="small" round :bordered="false"> 已删除 </NTag>
<NTag v-if="item.isPinned" size="small" round :bordered="false">
<NIcon :component="Star24Filled" color="#dba913" />
</NTag>
<NTag size="small" style="color: gray">
@@ -63,7 +83,10 @@ function onDropdownSelect(key: string) {
</template>
{{ item.commentCount }}
</NTag>
<NText style="font-size: large">
<NText
:style="{ fontSize: 'large', color: item.user?.id == accountInfo?.id ? '#5f877d' : '' }"
:depth="item.isDeleted ? 3 : 1"
>
{{ item.title }}
</NText>
</NFlex>
@@ -84,8 +107,18 @@ function onDropdownSelect(key: string) {
<NDropdown
v-if="forum.isAdmin"
:options="[
{ label: '删除', key: 'delete' },
{ label: item.isPinned ? '取消置顶' : '置顶', key: 'top' },
{
label: item.isPinned ? '取消置顶' : '置顶',
key: 'top',
icon: () => h(NIcon, { component: Star24Filled }),
type: 'info',
},
{
label: item.isDeleted ? '恢复' : '删除',
key: item.isDeleted ? 'restore' : 'delete',
icon: () => h(NIcon, { component: item.isDeleted ? SyncCircleSharp : Delete24Filled }),
type: 'error',
},
]"
trigger="hover"
@select="onDropdownSelect"

View File

@@ -3,13 +3,14 @@ import { getUserAvatarUrl } from '@/Utils'
import { useAccount } from '@/api/account'
import { ForumCommentModel, ForumReplyModel, ForumTopicModel } from '@/api/models/forum'
import { useForumStore } from '@/store/useForumStore'
import { ArrowReply16Filled } from '@vicons/fluent'
import { NAvatar, NButton, NCard, NFlex, NIcon, NText, NTime, NTooltip } from 'naive-ui'
import { ArrowReply16Filled, Delete24Filled } from '@vicons/fluent'
import { NAvatar, NButton, NCard, NFlex, NIcon, NPopconfirm, NText, NTime, NTooltip } from 'naive-ui'
import { computed } from 'vue'
const props = defineProps<{
item: ForumReplyModel
replyTo?: ForumReplyModel
replyToId?: number
comment: ForumCommentModel
topic: ForumTopicModel
showReplyButton?: boolean
@@ -21,6 +22,9 @@ const accountInfo = useAccount()
const canOprate = computed(() => {
return !props.topic.isLocked && accountInfo.value.id > 0
})
const emits = defineEmits<{
(e: 'delete', id: number): void
}>()
</script>
<template>
@@ -69,6 +73,29 @@ const canOprate = computed(() => {
</template>
回复这条回复
</NTooltip>
<NPopconfirm
v-if="(item.user.id === accountInfo.id || topic.isAdmin) && showReplyButton"
@positive-click="emits('delete', item.id)"
>
<template #trigger>
<NTooltip v-if="showReplyButton">
<template #trigger>
<NButton
size="tiny"
round
secondary
:disabled="!canOprate"
>
<template #icon>
<NIcon :component="Delete24Filled" />
</template>
</NButton>
</template>
删除
</NTooltip>
</template>
确定删除这条回复吗
</NPopconfirm>
</NFlex>
</NFlex>
</template>

View File

@@ -1,14 +1,20 @@
<script setup lang="ts">
import { getUserAvatarUrl } from '@/Utils'
import { UserInfo } from '@/api/api-models'
import { PaginationResponse, UserInfo } from '@/api/api-models'
import { ForumCommentModel, ForumCommentSortTypes, ForumTopicModel } from '@/api/models/forum'
import '@/assets/forumContentStyle.css'
import TurnstileVerify from '@/components/TurnstileVerify.vue'
import VEditor from '@/components/VEditor.vue'
import { VTSURU_API_URL } from '@/data/constants'
import { useForumStore } from '@/store/useForumStore'
import { ArrowCircleLeft12Filled, ArrowCircleLeft12Regular, Comment24Regular, Eye24Regular } from '@vicons/fluent'
import { Heart, HeartOutline } from '@vicons/ionicons5'
import {
ArrowCircleLeft12Filled,
ArrowCircleLeft12Regular,
Comment24Regular,
Delete24Filled,
Eye24Regular,
} from '@vicons/fluent'
import { Heart, HeartOutline, SyncCircleSharp } from '@vicons/ionicons5'
import {
NAvatar,
NAvatarGroup,
@@ -25,6 +31,9 @@ import {
NList,
NListItem,
NModal,
NPagination,
NPopconfirm,
NTag,
NText,
NTime,
NTooltip,
@@ -35,6 +44,7 @@ import { useRoute } from 'vue-router'
import ForumCommentItem from './ForumCommentItem.vue'
import ForumReplyItem from './ForumReplyItem.vue'
import { useAccount } from '@/api/account'
import router from '@/router'
type PostCommentModel = {
content: string
@@ -69,9 +79,10 @@ const currentCommentContent = ref<PostCommentModel>({} as PostCommentModel)
const currentReplyContent = ref<PostReplyModel>({} as PostReplyModel)
const topic = ref<ForumTopicModel>({ id: -1 } as ForumTopicModel)
const comments = ref<ForumCommentModel[]>([])
const comments = ref<PaginationResponse<ForumCommentModel[]>>()
const ps = ref(20)
const pn = ref(0)
const total = ref(0)
const sort = ref(ForumCommentSortTypes.Time)
const canOprate = computed(() => {
@@ -89,7 +100,9 @@ async function postComment() {
.PostComment(currentCommentContent.value, token.value)
.then(async (comment) => {
if (comment) {
comments.value = (await useForum.GetComments(topic.value.id, pn.value, ps.value, sort.value)) ?? []
setTimeout(async () => {
refreshComments()
}, 1000)
currentCommentContent.value = {} as PostCommentModel
showCommentModal.value = false
}
@@ -110,7 +123,7 @@ async function postReply() {
.PostReply(currentReplyContent.value, token.value)
.then(async (comment) => {
if (comment) {
comments.value = (await useForum.GetComments(topic.value.id, pn.value, ps.value, sort.value)) ?? []
refreshComments()
currentReplyContent.value = {} as PostReplyModel
useForum.SetReplyingComment()
}
@@ -119,12 +132,38 @@ async function postReply() {
turnstile.value?.reset()
})
}
async function refreshComments() {
comments.value = await useForum.GetComments(topic.value.id, pn.value, ps.value, sort.value)
}
function onDeleteComment(id: number) {
if (comments.value) {
comments.value.data = comments.value.data.filter((c) => c.id !== id)
}
}
async function delTopic(topicId: number) {
useForum.DelTopic(topicId).then((success) => {
if (success) {
setTimeout(() => {
router.push({ name: 'user-forum', params: { id: userInfo?.name } })
})
}
})
}
async function restoreTopic(topicId: number) {
useForum.RestoreTopic(topicId).then((success) => {
if (success) {
setTimeout(() => {
topic.value.isDeleted = false
})
}
})
}
onMounted(async () => {
if (route.params.topicId) {
topicId.value = route.params.topicId as unknown as number
topic.value = (await useForum.GetTopicDetail(topicId.value)) ?? ({ id: -1 } as ForumTopicModel)
comments.value = (await useForum.GetComments(topicId.value, pn.value, ps.value, sort.value)) ?? []
refreshComments()
}
})
</script>
@@ -136,11 +175,14 @@ onMounted(async () => {
<NBackTop />
<NBadge class="back-forum-badge" style="width: 100%; left: 0" type="info" :offset="[3, 3]">
<NCard size="small">
<NText style="font-size: large; font-weight: bold; text-align: center; width: 100%">
<NEllipsis style="width: 100%">
{{ topic.title }}
</NEllipsis>
</NText>
<NFlex align="center" :wrap="false">
<NTag v-if="topic.isDeleted" type="warning" :bordered="false"> 已删除 </NTag>
<NText style="font-size: large; font-weight: bold; text-align: center; width: 100%">
<NEllipsis style="width: 100%">
{{ topic.title }}
</NEllipsis>
</NText>
</NFlex>
</NCard>
<template #value>
<NTooltip>
@@ -232,6 +274,41 @@ onMounted(async () => {
</template>
评论
</NTooltip>
<NFlex style="flex: 1" justify="end">
<NTooltip v-if="topic?.user?.id === accountInfo.id || topic.isAdmin">
<template #trigger>
<NPopconfirm @positive-click="delTopic(topic.id)">
<template #trigger>
<NButton size="small" text :disabled="!canOprate">
<template #icon>
<NIcon
:component="Delete24Filled"
:color="topic.isDeleted || topic.isAdmin ? '#dd484f' : '#7f7f7f'"
/>
</template>
</NButton>
</template>
{{ topic.isDeleted ? '确定完全删除这个话题吗? 这将无法恢复' : '确定删除这个话题吗' }}
</NPopconfirm>
</template>
{{ topic.isDeleted || topic.isAdmin ? '完全' : '' }}删除
</NTooltip>
<NTooltip v-if="topic.isDeleted && topic.isAdmin">
<template #trigger>
<NPopconfirm @positive-click="restoreTopic(topic.id)">
<template #trigger>
<NButton size="small" text :disabled="!canOprate">
<template #icon>
<NIcon :component="SyncCircleSharp" color="#7f7f7f" />
</template>
</NButton>
</template>
要恢复这个话题吗?
</NPopconfirm>
</template>
恢复
</NTooltip>
</NFlex>
</NFlex>
</template>
<div class="editor-content-view" v-html="topic.content"></div>
@@ -239,12 +316,34 @@ onMounted(async () => {
<NDivider>
<NButton @click="showCommentModal = true" type="primary" :disabled="!canOprate">发送评论</NButton>
</NDivider>
<NEmpty v-if="comments.length === 0" description="暂无评论" />
<NFlex align="center" justify="center">
<NPagination
v-if="comments && (comments?.data.length ?? 0) > 0"
v-model:page="pn"
:item-count="comments?.data.length ?? 0"
:page-size="ps"
show-quick-jumper
@update:page="refreshComments"
/>
</NFlex>
<br />
<NEmpty v-if="!comments || comments.data.length === 0" description="暂无评论" />
<NList v-else hoverable bordered size="small">
<NListItem v-for="item in comments" :key="item.id">
<ForumCommentItem :item="item" :topic="topic" />
<NListItem v-for="item in comments.data" :key="item.id">
<ForumCommentItem :item="item" :topic="topic" @delete="onDeleteComment" />
</NListItem>
</NList>
<br />
<NFlex v-if="(comments?.data.length ?? 0) > 5" align="center" justify="center">
<NPagination
v-if="comments && (comments?.data.length ?? 0) > 0"
v-model:page="pn"
:item-count="comments?.data.length ?? 0"
:page-size="ps"
show-quick-jumper
@update:page="refreshComments"
/>
</NFlex>
<NDivider />
</div>
</template>