You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
paopao-ce/web/src/components/rightbar.vue

260 lines
7.6 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="rightbar-wrap" v-if="!store.state.collapsedRight">
<div class="search-wrap">
<n-input
round
clearable
placeholder="搜一搜..."
v-model:value="keyword"
@keyup.enter.prevent="handleSearch"
>
<template #prefix>
<n-icon :component="Search" />
</template>
</n-input>
</div>
<n-card v-if="showFollowTopics" class="hottopic-wrap" title="关注话题" embedded :bordered="false" size="small">
<n-spin :show="loading">
<div class="hot-tag-item" v-for="tag in followTags" :key="tag.id">
<router-link
class="hash-link"
:to="{
name: 'home',
query: {
q: tag.tag,
t: 'tag',
},
}"
>
#{{ tag.tag }}
</router-link>
<div class="post-num">
{{ formatQuoteNum(tag.quote_num) }}
</div>
</div>
</n-spin>
</n-card>
<n-card class="hottopic-wrap" title="热门话题" embedded :bordered="false" size="small">
<n-spin :show="loading">
<div class="hot-tag-item" v-for="tag in hotTags" :key="tag.id">
<router-link
class="hash-link"
:to="{
name: 'home',
query: {
q: tag.tag,
t: 'tag',
},
}"
>
#{{ tag.tag }}
</router-link>
<div class="post-num">
{{ formatQuoteNum(tag.quote_num) }}
</div>
</div>
</n-spin>
</n-card>
<n-card class="copyright-wrap" embedded :bordered="false" size="small">
<div class="copyright">&copy; {{ copyrightTop }}</div>
<div>
<n-space>
<a
:href="copyrightLeftLink"
target="_blank"
class="hash-link"
>
{{ copyrightLeft }}
</a>
<a
:href="copyrightRightLink"
target="_blank"
class="hash-link"
>
{{ copyrightRight }}
</a>
</n-space>
</div>
<div v-if="store.state.userInfo.is_admin" ref="userInfoElement">
<n-space>
<span class="copyright">{{ registerUserCount }} &nbsp;&nbsp;</span>
<span class="copyright">{{ onlineUserCount }} 线</span>
</n-space>
</div>
</n-card>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed, watch } from 'vue';
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import { getTags } from '@/api/post';
import { getSiteInfo } from '@/api/user';
import { Search } from '@vicons/ionicons5';
const hotTags = ref<Item.TagProps[]>([]);
const followTags = ref<Item.TagProps[]>([]);
const loading = ref(false);
const keyword = ref('');
const store = useStore();
const router = useRouter();
const registerUserCount = ref(0)
const onlineUserCount = ref(0)
const userInfoElement = ref<HTMLElement | null>(null);
const copyrightTop = import.meta.env.VITE_COPYRIGHT_TOP
const copyrightLeft = import.meta.env.VITE_COPYRIGHT_LEFT
const copyrightLeftLink = import.meta.env.VITE_COPYRIGHT_LEFT_LINK
const copyrightRight = import.meta.env.VITE_COPYRIGHT_RIGHT
const copyrightRightLink = import.meta.env.VITE_COPYRIGHT_RIGHT_LINK
const rightFollowTopicMaxSize = Number(import.meta.env.VITE_RIGHT_FOLLOW_TOPIC_MAX_SIZE)
const rightHotTopicMaxSize = Number(import.meta.env.VITE_RIGHT_HOT_TOPIC_MAX_SIZE)
const loadSiteInfo = () => {
getSiteInfo()
.then((res) => {
registerUserCount.value = res.register_user_count;
onlineUserCount.value = res.online_user_count;
})
.catch((_err) => {
// do nothing
});
observer.disconnect()
};
const loadHotTags = () => {
loading.value = true;
getTags({
type: 'hot_extral',
num: rightHotTopicMaxSize,
extral_num: rightFollowTopicMaxSize,
})
.then((res) => {
hotTags.value = res.topics;
followTags.value = res.extral_topics??[];
showFollowTopics.value = true
loading.value = false;
})
.catch((_err) => {
loading.value = false;
});
};
const formatQuoteNum = (num: number) => {
if (num >= 1000) {
return (num / 1000).toFixed(1) + 'k';
}
return num;
};
const handleSearch = () => {
router.push({
name: 'home',
query: {
q: keyword.value,
},
});
};
const showFollowTopics = computed({
get: () => {
return store.state.userLogined && followTags.value.length !==0;
},
set: (newVal) => {
// do nothing
},
});
watch(
() => ({
refreshTopicFollow: store.state.refreshTopicFollow,
userLogined: store.state.userLogined
}),
(to, from) => {
if (to.refreshTopicFollow !== from.refreshTopicFollow || to.userLogined) {
loadHotTags();
}
if (store.state.userInfo.is_admin) {
loadSiteInfo();
}
}
);
const observer = new IntersectionObserver((entries: IntersectionObserverEntry[]) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
loadSiteInfo();
}
});
}, {
root: null,
rootMargin: '0px',
threshold: 1
});
onMounted(() => {
// 不知道为什么 store.state.userInfo.is_admin 在这里就是不起作用f*k所以才用这么一种蹩脚的法子来凑合
if (userInfoElement.value) {
observer.observe(userInfoElement.value);
}
loadHotTags();
});
</script>
<style lang="less" scoped>
.rightbar-wrap::-webkit-scrollbar {
width: 0; /* 隐藏滚动条的宽度 */
height: 0; /* 隐藏滚动条的高度 */
}
.rightbar-wrap {
width: 240px;
position: fixed;
left: calc(50% + var(--content-main) / 2 + 10px);
max-height: calc(100vh); /* 调整高度 */
overflow: auto;
.search-wrap {
margin: 12px 0;
}
.hot-tag-item {
line-height: 2;
position: relative;
.hash-link {
width: calc(100% - 60px);
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
display: block;
}
.post-num {
position: absolute;
right: 0;
top: 0;
width: 60px;
text-align: right;
line-height: 2;
opacity: 0.5;
}
}
.hottopic-wrap {
margin-bottom: 10px;
}
.copyright-wrap {
.copyright {
font-size: 12px;
opacity: 0.75;
}
.hash-link {
font-size: 12px;
}
}
}
.dark {
.hottopic-wrap {
background-color: #18181c;
}
.copyright-wrap {
background-color: #18181c;
}
}
</style>