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

@@ -85,16 +85,18 @@ export interface ForumTopicBaseModel {
isLocked?: boolean // Assuming the default value is handled elsewhere isLocked?: boolean // Assuming the default value is handled elsewhere
isPinned?: boolean // Assuming the default value is handled elsewhere isPinned?: boolean // Assuming the default value is handled elsewhere
isHighlighted?: boolean // Assuming the default value is handled elsewhere isHighlighted?: boolean // Assuming the default value is handled elsewhere
isDeleted?: boolean // Assuming the default value is handled elsewhere
} }
export interface ForumTopicModel extends ForumTopicBaseModel { export interface ForumTopicModel extends ForumTopicBaseModel {
isLocked?: boolean // Assuming the default value is handled elsewhere isLocked?: boolean // Assuming the default value is handled elsewhere
isDeleted?: boolean // Assuming the default value is handled elsewhere
isHidden?: boolean // Assuming the default value is handled elsewhere isHidden?: boolean // Assuming the default value is handled elsewhere
type?: ForumTopicTypes // Assuming the default value is handled elsewhere type?: ForumTopicTypes // Assuming the default value is handled elsewhere
extraTypeId?: number | null // Nullable int in C# is optional or null in TS extraTypeId?: number | null // Nullable int in C# is optional or null in TS
likedBy?: number[] // Assuming the default value is handled elsewhere likedBy?: number[] // Assuming the default value is handled elsewhere
isAdmin: boolean
} }
export interface ForumCommentModel { export interface ForumCommentModel {
id: number id: number
@@ -105,6 +107,8 @@ export interface ForumCommentModel {
likeCount: number likeCount: number
isLiked: boolean isLiked: boolean
isDeleted: boolean
} }
export interface ForumReplyModel { export interface ForumReplyModel {
id: number id: number

View File

@@ -2,7 +2,7 @@ import { QueryGetAPI } from '@/api/query'
import { USER_API_URL, apiFail } from '@/data/constants' import { USER_API_URL, apiFail } from '@/data/constants'
import { ref } from 'vue' import { ref } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { APIRoot, UserInfo } from './api-models' import { APIRoot, UserBasicInfo, UserInfo } from './api-models'
export const USERS = ref<{ [id: string]: UserInfo }>({}) export const USERS = ref<{ [id: string]: UserInfo }>({})
@@ -38,6 +38,10 @@ export async function useUserWithUId(id: number) {
} }
return USERS.value[id.toString()] return USERS.value[id.toString()]
} }
export async function getUserBasicInfo(id: string | number | undefined) {
if (!id) return undefined
return (await QueryGetAPI<UserBasicInfo>(`${USER_API_URL}basic/${id}`)).data
}
export async function GetInfo(id: string): Promise<APIRoot<UserInfo>> { export async function GetInfo(id: string): Promise<APIRoot<UserInfo>> {
return QueryGetAPI<UserInfo>(`${USER_API_URL}info`, { return QueryGetAPI<UserInfo>(`${USER_API_URL}info`, {

View File

@@ -30,6 +30,7 @@ function OnClickCover() {
params: { id: live.liveId }, params: { id: live.liveId },
}) })
} }
const guartPriceStartData = new Date(Date.UTC(2024, 2, 24, 10, 0, 0))
watch( watch(
() => live, () => live,
@@ -119,7 +120,7 @@ watch(
<NStatistic tabular-nums> <NStatistic tabular-nums>
<template #label> <template #label>
收益 收益
<NTooltip> <NTooltip v-if="new Date(live.startAt) < guartPriceStartData">
<template #trigger> <template #trigger>
<NIcon :component="Info24Filled" /> <NIcon :component="Info24Filled" />
</template> </template>

View File

@@ -124,7 +124,7 @@ export const useForumStore = defineStore('forum', () => {
sort, sort,
}) })
if (data.code == 200) { if (data.code == 200) {
return data.data return data
} else { } else {
console.error('无法获取数据: ' + data.message) console.error('无法获取数据: ' + data.message)
message?.error('无法获取数据: ' + data.message) message?.error('无法获取数据: ' + data.message)
@@ -317,7 +317,7 @@ export const useForumStore = defineStore('forum', () => {
async function DelComment(comment: number) { async function DelComment(comment: number) {
try { try {
isLoading.value = true isLoading.value = true
const data = await QueryGetAPI(FORUM_API_URL + 'manage/delete-comment', { comment: comment }) const data = await QueryGetAPI(FORUM_API_URL + 'delete-comment', { comment: comment })
if (data.code == 200) { if (data.code == 200) {
message?.success('删除成功') message?.success('删除成功')
return true return true
@@ -354,6 +354,46 @@ export const useForumStore = defineStore('forum', () => {
isLoading.value = false isLoading.value = false
} }
} }
async function RestoreComment(comment: number) {
try {
isLoading.value = true
const data = await QueryGetAPI(FORUM_API_URL + 'manage/restore-comment', { comment: comment })
if (data.code == 200) {
message?.success('恢复成功')
return true
} else {
message?.error('恢复失败: ' + data.message)
console.error('恢复失败: ' + data.message)
return false
}
} catch (err) {
message?.error('恢复失败: ' + err)
console.error('恢复失败: ' + err)
return false
} finally {
isLoading.value = false
}
}
async function RestoreTopic(topic: number) {
try {
isLoading.value = true
const data = await QueryGetAPI(FORUM_API_URL + 'manage/restore-topic', { topic })
if (data.code == 200) {
message?.success('恢复成功')
return true
} else {
message?.error('恢复失败: ' + data.message)
console.error('恢复失败: ' + data.message)
return false
}
} catch (err) {
message?.error('恢复失败: ' + err)
console.error('恢复失败: ' + err)
return false
} finally {
isLoading.value = false
}
}
async function ConfirmApply(owner: number, id: number) { async function ConfirmApply(owner: number, id: number) {
try { try {
isLoading.value = true isLoading.value = true
@@ -393,6 +433,8 @@ export const useForumStore = defineStore('forum', () => {
DelComment, DelComment,
DelReply, DelReply,
ConfirmApply, ConfirmApply,
RestoreComment,
RestoreTopic,
isLoading, isLoading,
isLikeLoading, isLikeLoading,
replyingComment, replyingComment,

View File

@@ -45,6 +45,10 @@ const showAgreement = ref(false)
const create_Name = ref('') const create_Name = ref('')
const create_Description = ref('') const create_Description = ref('')
const showAddAdminModal = ref(false)
const inputUser = ref<UserBasicInfo>({} as UserBasicInfo)
const addAdminName = ref()
const paginationSetting = { defaultPageSize: 20, showSizePicker: true, pageSizes: [20, 50, 100] } const paginationSetting = { defaultPageSize: 20, showSizePicker: true, pageSizes: [20, 50, 100] }
async function createForum() { async function createForum() {
@@ -154,6 +158,46 @@ const banColumns: DataTableColumns<ForumUserModel> = [
}, },
}, },
] ]
const adminColumns: DataTableColumns<ForumUserModel> = [
...defaultColumns,
{
title: '操作',
key: 'action',
render(row) {
return h(
NButton,
{
text: true,
type: 'success',
onClick: () =>
useForum.ConfirmApply(currentForum.value.owner.id, row.id).then((success) => {
if (success) message.success('操作成功')
currentForum.value.applying = currentForum.value.applying.filter((u) => u.id != row.id)
}),
},
{ default: () => '通过申请' },
)
},
},
]
async function addAdmin() {
if (!currentForum.value.id) return
try {
const data = await QueryPostAPI<ForumModel>(FORUM_API_URL + 'manage/add-admin', {
forum: currentForum.value.id,
user: addAdminName.value,
})
if (data.code == 200) {
message.success('操作成功')
} else {
message.error('操作失败: ' + data.message)
}
} catch (err) {
message.error('操作失败: ' + err)
}
}
</script> </script>
<template> <template>
@@ -215,11 +259,12 @@ const banColumns: DataTableColumns<ForumUserModel> = [
</NTabPane> </NTabPane>
<NTabPane tab="成员" name="member"> <NTabPane tab="成员" name="member">
<NDivider> 申请 </NDivider> <NDivider> 申请 </NDivider>
<NDataTable <NDataTable :columns="applyingColumns" :data="currentForum.applying" :pagination="paginationSetting" />
:columns="applyingColumns" <NDivider> 管理员 </NDivider>
:data="currentForum.applying" <NFlex>
:pagination="paginationSetting" <NButton @click="showAddAdminModal = true" size="small" type="primary"> 添加管理员 </NButton>
/> </NFlex>
<NDataTable :columns="adminColumns" :data="currentForum.admins" :pagination="paginationSetting" />
<template v-if="currentForum.settings.requireApply"> <template v-if="currentForum.settings.requireApply">
<NDivider> 成员 </NDivider> <NDivider> 成员 </NDivider>
<NDataTable <NDataTable
@@ -229,11 +274,7 @@ const banColumns: DataTableColumns<ForumUserModel> = [
/> />
</template> </template>
<NDivider> 封禁用户 </NDivider> <NDivider> 封禁用户 </NDivider>
<NDataTable <NDataTable :columns="banColumns" :data="currentForum.blackList" :pagination="paginationSetting" />
:columns="banColumns"
:data="currentForum.blackList"
:pagination="paginationSetting"
/>
</NTabPane> </NTabPane>
</NTabs> </NTabs>
</NSpin> </NSpin>
@@ -246,4 +287,8 @@ const banColumns: DataTableColumns<ForumUserModel> = [
> >
<Agreement /> <Agreement />
</NModal> </NModal>
<NModal v-model:show="showAddAdminModal" preset="card" title="添加管理员" style="width: 600px; max-width: 90vw">
<NInput v-model:value="addAdminName" placeholder="请输入用户名或VTsuruId" />
<NButton @click="addAdmin" type="primary"> 添加 </NButton>
</NModal>
</template> </template>

View File

@@ -1,11 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { useAccount } from '@/api/account' 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 { VTSURU_API_URL } from '@/data/constants'
import { useForumStore } from '@/store/useForumStore' import { useForumStore } from '@/store/useForumStore'
import { ArrowReply16Filled } from '@vicons/fluent' import { ArrowHookUpLeft24Filled, ArrowReply16Filled, Delete24Filled } from '@vicons/fluent'
import { Heart, HeartOutline } from '@vicons/ionicons5' import { Heart, HeartOutline, SyncCircleSharp } from '@vicons/ionicons5'
import { NAvatar, NButton, NCard, NDivider, NFlex, NIcon, NText, NTime, NTooltip } from 'naive-ui' import { NAvatar, NButton, NCard, NDivider, NFlex, NIcon, NPopconfirm, NTag, NText, NTime, NTooltip } from 'naive-ui'
import ForumReplyItem from './ForumReplyItem.vue' import ForumReplyItem from './ForumReplyItem.vue'
import { computed } from 'vue' import { computed } from 'vue'
@@ -20,6 +20,32 @@ const accountInfo = useAccount()
const canOprate = computed(() => { const canOprate = computed(() => {
return !props.topic.isLocked && accountInfo.value.id > 0 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> </script>
<template> <template>
@@ -29,7 +55,8 @@ const canOprate = computed(() => {
:img-props="{ referrerpolicy: 'no-referrer' }" :img-props="{ referrerpolicy: 'no-referrer' }"
/> />
<NFlex vertical style="flex: 1" :size="2"> <NFlex vertical style="flex: 1" :size="2">
<NFlex> <NFlex align="center">
<NTag v-if="item.isDeleted" type="warning" :bordered="false"> 已删除 </NTag>
<NText> <NText>
{{ item.user.name }} {{ item.user.name }}
</NText> </NText>
@@ -43,7 +70,22 @@ const canOprate = computed(() => {
</NText> </NText>
</NFlex> </NFlex>
<div class="editor-content-view" v-html="item.content"></div> <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> <NFlex>
<NTooltip> <NTooltip>
<template #trigger> <template #trigger>
@@ -71,12 +113,7 @@ const canOprate = computed(() => {
</NTooltip> </NTooltip>
<NTooltip> <NTooltip>
<template #trigger> <template #trigger>
<NButton <NButton size="small" @click="useForum.SetReplyingComment(item)" text :disabled="!canOprate">
size="small"
@click="useForum.SetReplyingComment(item)"
text
:disabled="!canOprate"
>
<template #icon> <template #icon>
<NIcon :component="ArrowReply16Filled" /> <NIcon :component="ArrowReply16Filled" />
</template> </template>
@@ -85,20 +122,42 @@ const canOprate = computed(() => {
</template> </template>
回复 回复
</NTooltip> </NTooltip>
</NFlex> <NFlex style="flex: 1" justify="end">
<NCard v-if="item.replies.length > 0" size="small"> <NTooltip v-if="item.user.id === accountInfo.id || topic.isAdmin">
<NFlex vertical> <template #trigger>
<ForumReplyItem <NPopconfirm @positive-click="delComment(item.id)">
v-for="reply in item.replies" <template #trigger>
:key="reply.id" <NButton size="small" text :disabled="!canOprate">
:item="reply" <template #icon>
:comment="item" <NIcon
:topic="topic" :component="Delete24Filled"
showReplyButton :color="item.isDeleted || topic.isAdmin ? '#dd484f' : '#7f7f7f'"
:reply-to="reply.replyTo ? item.replies.find((r) => r.id === reply.replyTo) : undefined"
/> />
</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>
</NFlex> </NFlex>
</NCard>
</NFlex> </NFlex>
</NFlex> </NFlex>
</template> </template>

View File

@@ -1,8 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { useAccount } from '@/api/account'
import { ForumModel, ForumTopicBaseModel } from '@/api/models/forum' import { ForumModel, ForumTopicBaseModel } from '@/api/models/forum'
import { useForumStore } from '@/store/useForumStore' 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 { NButton, NDropdown, NFlex, NIcon, NTag, NText, NTime, NTooltip, useDialog } from 'naive-ui'
import { h } from 'vue'
const props = defineProps<{ const props = defineProps<{
item: ForumTopicBaseModel item: ForumTopicBaseModel
@@ -11,6 +14,7 @@ const props = defineProps<{
const useForum = useForumStore() const useForum = useForumStore()
const dialog = useDialog() const dialog = useDialog()
const accountInfo = useAccount()
function onDropdownSelect(key: string) { function onDropdownSelect(key: string) {
switch (key) { switch (key) {
@@ -31,6 +35,21 @@ function onDropdownSelect(key: string) {
}, },
}) })
break 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': case 'top':
dialog.info({ dialog.info({
title: '问', title: '问',
@@ -54,7 +73,8 @@ function onDropdownSelect(key: string) {
<template> <template>
<NFlex align="center"> <NFlex align="center">
<NFlex align="center" :wrap="false"> <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" /> <NIcon :component="Star24Filled" color="#dba913" />
</NTag> </NTag>
<NTag size="small" style="color: gray"> <NTag size="small" style="color: gray">
@@ -63,7 +83,10 @@ function onDropdownSelect(key: string) {
</template> </template>
{{ item.commentCount }} {{ item.commentCount }}
</NTag> </NTag>
<NText style="font-size: large"> <NText
:style="{ fontSize: 'large', color: item.user?.id == accountInfo?.id ? '#5f877d' : '' }"
:depth="item.isDeleted ? 3 : 1"
>
{{ item.title }} {{ item.title }}
</NText> </NText>
</NFlex> </NFlex>
@@ -84,8 +107,18 @@ function onDropdownSelect(key: string) {
<NDropdown <NDropdown
v-if="forum.isAdmin" v-if="forum.isAdmin"
:options="[ :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" trigger="hover"
@select="onDropdownSelect" @select="onDropdownSelect"

View File

@@ -3,13 +3,14 @@ import { getUserAvatarUrl } from '@/Utils'
import { useAccount } from '@/api/account' import { useAccount } from '@/api/account'
import { ForumCommentModel, ForumReplyModel, ForumTopicModel } from '@/api/models/forum' import { ForumCommentModel, ForumReplyModel, ForumTopicModel } from '@/api/models/forum'
import { useForumStore } from '@/store/useForumStore' import { useForumStore } from '@/store/useForumStore'
import { ArrowReply16Filled } from '@vicons/fluent' import { ArrowReply16Filled, Delete24Filled } from '@vicons/fluent'
import { NAvatar, NButton, NCard, NFlex, NIcon, NText, NTime, NTooltip } from 'naive-ui' import { NAvatar, NButton, NCard, NFlex, NIcon, NPopconfirm, NText, NTime, NTooltip } from 'naive-ui'
import { computed } from 'vue' import { computed } from 'vue'
const props = defineProps<{ const props = defineProps<{
item: ForumReplyModel item: ForumReplyModel
replyTo?: ForumReplyModel replyTo?: ForumReplyModel
replyToId?: number
comment: ForumCommentModel comment: ForumCommentModel
topic: ForumTopicModel topic: ForumTopicModel
showReplyButton?: boolean showReplyButton?: boolean
@@ -21,6 +22,9 @@ const accountInfo = useAccount()
const canOprate = computed(() => { const canOprate = computed(() => {
return !props.topic.isLocked && accountInfo.value.id > 0 return !props.topic.isLocked && accountInfo.value.id > 0
}) })
const emits = defineEmits<{
(e: 'delete', id: number): void
}>()
</script> </script>
<template> <template>
@@ -69,6 +73,29 @@ const canOprate = computed(() => {
</template> </template>
回复这条回复 回复这条回复
</NTooltip> </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>
</NFlex> </NFlex>
</template> </template>

View File

@@ -1,14 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
import { getUserAvatarUrl } from '@/Utils' 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 { ForumCommentModel, ForumCommentSortTypes, ForumTopicModel } from '@/api/models/forum'
import '@/assets/forumContentStyle.css' import '@/assets/forumContentStyle.css'
import TurnstileVerify from '@/components/TurnstileVerify.vue' import TurnstileVerify from '@/components/TurnstileVerify.vue'
import VEditor from '@/components/VEditor.vue' import VEditor from '@/components/VEditor.vue'
import { VTSURU_API_URL } from '@/data/constants' import { VTSURU_API_URL } from '@/data/constants'
import { useForumStore } from '@/store/useForumStore' import { useForumStore } from '@/store/useForumStore'
import { ArrowCircleLeft12Filled, ArrowCircleLeft12Regular, Comment24Regular, Eye24Regular } from '@vicons/fluent' import {
import { Heart, HeartOutline } from '@vicons/ionicons5' ArrowCircleLeft12Filled,
ArrowCircleLeft12Regular,
Comment24Regular,
Delete24Filled,
Eye24Regular,
} from '@vicons/fluent'
import { Heart, HeartOutline, SyncCircleSharp } from '@vicons/ionicons5'
import { import {
NAvatar, NAvatar,
NAvatarGroup, NAvatarGroup,
@@ -25,6 +31,9 @@ import {
NList, NList,
NListItem, NListItem,
NModal, NModal,
NPagination,
NPopconfirm,
NTag,
NText, NText,
NTime, NTime,
NTooltip, NTooltip,
@@ -35,6 +44,7 @@ import { useRoute } from 'vue-router'
import ForumCommentItem from './ForumCommentItem.vue' import ForumCommentItem from './ForumCommentItem.vue'
import ForumReplyItem from './ForumReplyItem.vue' import ForumReplyItem from './ForumReplyItem.vue'
import { useAccount } from '@/api/account' import { useAccount } from '@/api/account'
import router from '@/router'
type PostCommentModel = { type PostCommentModel = {
content: string content: string
@@ -69,9 +79,10 @@ const currentCommentContent = ref<PostCommentModel>({} as PostCommentModel)
const currentReplyContent = ref<PostReplyModel>({} as PostReplyModel) const currentReplyContent = ref<PostReplyModel>({} as PostReplyModel)
const topic = ref<ForumTopicModel>({ id: -1 } as ForumTopicModel) const topic = ref<ForumTopicModel>({ id: -1 } as ForumTopicModel)
const comments = ref<ForumCommentModel[]>([]) const comments = ref<PaginationResponse<ForumCommentModel[]>>()
const ps = ref(20) const ps = ref(20)
const pn = ref(0) const pn = ref(0)
const total = ref(0)
const sort = ref(ForumCommentSortTypes.Time) const sort = ref(ForumCommentSortTypes.Time)
const canOprate = computed(() => { const canOprate = computed(() => {
@@ -89,7 +100,9 @@ async function postComment() {
.PostComment(currentCommentContent.value, token.value) .PostComment(currentCommentContent.value, token.value)
.then(async (comment) => { .then(async (comment) => {
if (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 currentCommentContent.value = {} as PostCommentModel
showCommentModal.value = false showCommentModal.value = false
} }
@@ -110,7 +123,7 @@ async function postReply() {
.PostReply(currentReplyContent.value, token.value) .PostReply(currentReplyContent.value, token.value)
.then(async (comment) => { .then(async (comment) => {
if (comment) { if (comment) {
comments.value = (await useForum.GetComments(topic.value.id, pn.value, ps.value, sort.value)) ?? [] refreshComments()
currentReplyContent.value = {} as PostReplyModel currentReplyContent.value = {} as PostReplyModel
useForum.SetReplyingComment() useForum.SetReplyingComment()
} }
@@ -119,12 +132,38 @@ async function postReply() {
turnstile.value?.reset() 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 () => { onMounted(async () => {
if (route.params.topicId) { if (route.params.topicId) {
topicId.value = route.params.topicId as unknown as number topicId.value = route.params.topicId as unknown as number
topic.value = (await useForum.GetTopicDetail(topicId.value)) ?? ({ id: -1 } as ForumTopicModel) 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> </script>
@@ -136,11 +175,14 @@ onMounted(async () => {
<NBackTop /> <NBackTop />
<NBadge class="back-forum-badge" style="width: 100%; left: 0" type="info" :offset="[3, 3]"> <NBadge class="back-forum-badge" style="width: 100%; left: 0" type="info" :offset="[3, 3]">
<NCard size="small"> <NCard size="small">
<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%"> <NText style="font-size: large; font-weight: bold; text-align: center; width: 100%">
<NEllipsis style="width: 100%"> <NEllipsis style="width: 100%">
{{ topic.title }} {{ topic.title }}
</NEllipsis> </NEllipsis>
</NText> </NText>
</NFlex>
</NCard> </NCard>
<template #value> <template #value>
<NTooltip> <NTooltip>
@@ -232,6 +274,41 @@ onMounted(async () => {
</template> </template>
评论 评论
</NTooltip> </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> </NFlex>
</template> </template>
<div class="editor-content-view" v-html="topic.content"></div> <div class="editor-content-view" v-html="topic.content"></div>
@@ -239,12 +316,34 @@ onMounted(async () => {
<NDivider> <NDivider>
<NButton @click="showCommentModal = true" type="primary" :disabled="!canOprate">发送评论</NButton> <NButton @click="showCommentModal = true" type="primary" :disabled="!canOprate">发送评论</NButton>
</NDivider> </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"> <NList v-else hoverable bordered size="small">
<NListItem v-for="item in comments" :key="item.id"> <NListItem v-for="item in comments.data" :key="item.id">
<ForumCommentItem :item="item" :topic="topic" /> <ForumCommentItem :item="item" :topic="topic" @delete="onDeleteComment" />
</NListItem> </NListItem>
</NList> </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 /> <NDivider />
</div> </div>
</template> </template>