|
|
@@ -8,35 +8,128 @@
|
|
|
|
|
|
<div class="title px-5 py-4 flex items-center relative">
|
|
|
<span class="text-base font-medium">全部评论</span>
|
|
|
- <span class="text-xs text-[#797A8A] ml-[2px]">(1356)</span>
|
|
|
+ <span class="text-xs text-[#797A8A] ml-[2px]">({{ totals }})</span>
|
|
|
<img class="absolute bottom-2 left-[38px] w-[28px]" src="@/assets/png-xian.png" alt="" />
|
|
|
</div>
|
|
|
|
|
|
<div class="overflow-hidden pb-11">
|
|
|
<div
|
|
|
- v-for="(i, index) in 10"
|
|
|
+ v-for="(item, index) in list"
|
|
|
class="comment-box"
|
|
|
:class="{ 'comment-center': showList[index] }"
|
|
|
- :key="i"
|
|
|
+ :key="index"
|
|
|
ref="commentRef"
|
|
|
>
|
|
|
- <comment-info></comment-info>
|
|
|
+ <comment-info :info="item"></comment-info>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="loading flex items-center justify-center opacity-0" ref="loadingRef">加载中</div>
|
|
|
+
|
|
|
+ <div class="empty py-6" v-if="!loading && !totals">
|
|
|
+ <img class="w-3/5 block mx-[auto]" :src="isDark ? emptyDarkImg : emptyImg" alt="" />
|
|
|
+ <div class="text-sm text-[#939599] text-center py-3">暂无评论</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
+
|
|
|
+ <div class="bottom bg-white px-[10px] py-[6px]">
|
|
|
+ <NConfigProvider
|
|
|
+ :theme-overrides="{
|
|
|
+ Button: {
|
|
|
+ fontSizeLarge: '24px',
|
|
|
+ heightLarge: '40px'
|
|
|
+ },
|
|
|
+ Input: {
|
|
|
+ color: '#F5F6F8',
|
|
|
+ colorFocus: '#F5F6F8'
|
|
|
+ }
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <div class="flex">
|
|
|
+ <n-input
|
|
|
+ size="large"
|
|
|
+ v-model:value="msg"
|
|
|
+ type="text"
|
|
|
+ @keydown.enter="send"
|
|
|
+ clearable
|
|
|
+ placeholder="来说点什么…"
|
|
|
+ maxlength="200"
|
|
|
+ />
|
|
|
+ <n-button class="!ml-4" type="primary" size="large" @click="send">
|
|
|
+ <template #icon>
|
|
|
+ <n-icon><telegram-plane /></n-icon>
|
|
|
+ </template>
|
|
|
+ </n-button>
|
|
|
+ </div>
|
|
|
+ </NConfigProvider>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
import { MomentInfo, CommentInfo } from '@/components/common'
|
|
|
import { ref, watch, nextTick } from 'vue'
|
|
|
-import { useWindowScroll, useElementBounding, useWindowSize, useScroll } from '@vueuse/core'
|
|
|
-import { fetchGetMomentsDetail } from '@/api'
|
|
|
+import { useWindowScroll, useElementBounding, useWindowSize, useScroll, useIntersectionObserver } from '@vueuse/core'
|
|
|
+import { fetchGetMomentsDetail, fetchSendComment, fetchComments } from '@/api'
|
|
|
import { useRoute } from 'vue-router'
|
|
|
+import { NInput, NButton, NIcon, NConfigProvider, useMessage, NSpin } from 'naive-ui'
|
|
|
+import { TelegramPlane } from '@vicons/fa'
|
|
|
+import { useUserStore } from '@/store'
|
|
|
+import emptyImg from '@/assets/empty.png'
|
|
|
+import emptyDarkImg from '@/assets/empty-dark.png'
|
|
|
+import { useTheme } from '@/hooks/useTheme'
|
|
|
+
|
|
|
+const { isDark } = useTheme()
|
|
|
|
|
|
+
|
|
|
+const route = useRoute()
|
|
|
+const momentsId = route.query.id
|
|
|
const commentRef = ref([])
|
|
|
const showList = ref([] as any)
|
|
|
const { y } = useWindowScroll()
|
|
|
const { width, height } = useWindowSize()
|
|
|
+const list = ref([] as any)
|
|
|
+const totals = ref(0)
|
|
|
+const page = ref(1)
|
|
|
+const finish = ref(false)
|
|
|
+
|
|
|
+function getComments(isSend = false) {
|
|
|
+ loading.value = true
|
|
|
+ let data = {
|
|
|
+ page: {
|
|
|
+ page: page.value,
|
|
|
+ limit: 20
|
|
|
+ },
|
|
|
+ search: {
|
|
|
+ where: {
|
|
|
+ momentsId: momentsId
|
|
|
+ },
|
|
|
+ order: {
|
|
|
+ createdAt: 'DESC'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (isSend) {
|
|
|
+ data.page = {
|
|
|
+ page: 1,
|
|
|
+ limit: 1
|
|
|
+ }
|
|
|
+ }
|
|
|
+ fetchComments(data).then((res: any) => {
|
|
|
+ loading.value = false
|
|
|
+ if (isSend) {
|
|
|
+ list.value = [...res.items, ...list.value]
|
|
|
+ } else {
|
|
|
+ list.value = [...list.value, ...res.items]
|
|
|
+ }
|
|
|
+ totals.value = res.meta.totalItems
|
|
|
+ if (res.meta.totalPages <= res.meta.currentPage) {
|
|
|
+ finish.value = true
|
|
|
+ } else {
|
|
|
+ finish.value = false
|
|
|
+ page.value = page.value + 1
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
watch(
|
|
|
y,
|
|
|
() => {
|
|
|
@@ -51,11 +144,54 @@ watch(
|
|
|
}
|
|
|
)
|
|
|
|
|
|
-const route = useRoute()
|
|
|
+watch(
|
|
|
+ list,
|
|
|
+ () => {
|
|
|
+ nextTick(() => {
|
|
|
+ showList.value = commentRef.value.map((item: any) => {
|
|
|
+ return useElementBounding(item).top.value < height.value - 30
|
|
|
+ })
|
|
|
+ })
|
|
|
+ },
|
|
|
+ {
|
|
|
+ immediate: true
|
|
|
+ }
|
|
|
+)
|
|
|
+
|
|
|
const moments = ref({})
|
|
|
-fetchGetMomentsDetail(route.query.id).then((res: any) => {
|
|
|
+
|
|
|
+fetchGetMomentsDetail(momentsId).then((res: any) => {
|
|
|
moments.value = res
|
|
|
})
|
|
|
+
|
|
|
+const msg = ref('')
|
|
|
+const message = useMessage()
|
|
|
+const userStore = useUserStore()
|
|
|
+function send() {
|
|
|
+ if (!msg.value) {
|
|
|
+ message.info('评论不能为空')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ fetchSendComment({
|
|
|
+ content: msg.value,
|
|
|
+ userId: userStore.userInfo.id,
|
|
|
+ userName: userStore.userInfo.name,
|
|
|
+ momentsId: momentsId
|
|
|
+ }).then(res => {
|
|
|
+ message.success('发送成功')
|
|
|
+ msg.value = ''
|
|
|
+ getComments(true)
|
|
|
+ window.scrollTo(0, 0)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const loadingRef = ref(null)
|
|
|
+const loading = ref(false)
|
|
|
+useIntersectionObserver(loadingRef, ([{ isIntersecting }]) => {
|
|
|
+ if (isIntersecting && !loading.value && !finish.value) {
|
|
|
+ getComments()
|
|
|
+ }
|
|
|
+})
|
|
|
</script>
|
|
|
|
|
|
<style lang="less" scoped>
|
|
|
@@ -73,4 +209,12 @@ fetchGetMomentsDetail(route.query.id).then((res: any) => {
|
|
|
opacity: 1;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+.bottom {
|
|
|
+ position: fixed;
|
|
|
+ bottom: 0;
|
|
|
+ z-index: 20;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+}
|
|
|
</style>
|