mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-06 23:21:16 +08:00
fix: setup 迁移完成
This commit is contained in:
@@ -230,7 +230,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'
|
||||
import VueEasyLightbox from 'vue-easy-lightbox'
|
||||
import { useRoute } from 'vue-router'
|
||||
@@ -243,7 +243,7 @@ import ReactionsGroup from '~/components/ReactionsGroup.vue'
|
||||
import DropdownMenu from '~/components/DropdownMenu.vue'
|
||||
import { renderMarkdown, handleMarkdownClick, stripMarkdownLength } from '~/utils/markdown'
|
||||
import { getMedalTitle } from '~/utils/medal'
|
||||
import { API_BASE_URL, toast } from '~/main'
|
||||
import { toast } from '~/main'
|
||||
import { getToken, authState } from '~/utils/auth'
|
||||
import TimeManager from '~/utils/time'
|
||||
import { useRouter } from 'vue-router'
|
||||
@@ -251,52 +251,38 @@ import { useIsMobile } from '~/utils/screen'
|
||||
import Dropdown from '~/components/Dropdown.vue'
|
||||
import { ClientOnly } from '#components'
|
||||
|
||||
export default {
|
||||
name: 'PostPageView',
|
||||
components: {
|
||||
CommentItem,
|
||||
CommentEditor,
|
||||
BaseTimeline,
|
||||
ArticleTags,
|
||||
ArticleCategory,
|
||||
ReactionsGroup,
|
||||
DropdownMenu,
|
||||
VueEasyLightbox,
|
||||
Dropdown,
|
||||
ClientOnly,
|
||||
},
|
||||
async setup() {
|
||||
const route = useRoute()
|
||||
const postId = route.params.id
|
||||
const router = useRouter()
|
||||
const config = useRuntimeConfig()
|
||||
const API_BASE_URL = config.public.apiBaseUrl
|
||||
|
||||
const title = ref('')
|
||||
const author = ref('')
|
||||
const postContent = ref('')
|
||||
const category = ref('')
|
||||
const tags = ref([])
|
||||
const postReactions = ref([])
|
||||
const comments = ref([])
|
||||
const status = ref('PUBLISHED')
|
||||
const pinnedAt = ref(null)
|
||||
const isWaitingFetchingPost = ref(false)
|
||||
const isWaitingPostingComment = ref(false)
|
||||
const postTime = ref('')
|
||||
const postItems = ref([])
|
||||
const mainContainer = ref(null)
|
||||
const currentIndex = ref(1)
|
||||
const subscribed = ref(false)
|
||||
const commentSort = ref('NEWEST')
|
||||
const isFetchingComments = ref(false)
|
||||
const isMobile = useIsMobile()
|
||||
const route = useRoute()
|
||||
const postId = route.params.id
|
||||
const router = useRouter()
|
||||
|
||||
const headerHeight = process.client
|
||||
? parseFloat(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--header-height'),
|
||||
) || 0
|
||||
const title = ref('')
|
||||
const author = ref('')
|
||||
const postContent = ref('')
|
||||
const category = ref('')
|
||||
const tags = ref([])
|
||||
const postReactions = ref([])
|
||||
const comments = ref([])
|
||||
const status = ref('PUBLISHED')
|
||||
const pinnedAt = ref(null)
|
||||
const isWaitingFetchingPost = ref(false)
|
||||
const isWaitingPostingComment = ref(false)
|
||||
const postTime = ref('')
|
||||
const postItems = ref([])
|
||||
const mainContainer = ref(null)
|
||||
const currentIndex = ref(1)
|
||||
const subscribed = ref(false)
|
||||
const commentSort = ref('NEWEST')
|
||||
const isFetchingComments = ref(false)
|
||||
const isMobile = useIsMobile()
|
||||
|
||||
const headerHeight = process.client
|
||||
? parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--header-height')) || 0
|
||||
: 0
|
||||
|
||||
useHead(() => ({
|
||||
useHead(() => ({
|
||||
title: title.value ? `OpenIsle - ${title.value}` : 'OpenIsle',
|
||||
meta: [
|
||||
{
|
||||
@@ -304,35 +290,35 @@ export default {
|
||||
content: stripMarkdownLength(postContent.value, 400),
|
||||
},
|
||||
],
|
||||
}))
|
||||
}))
|
||||
|
||||
if (process.client) {
|
||||
if (process.client) {
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('scroll', updateCurrentIndex)
|
||||
if (countdownTimer) clearInterval(countdownTimer)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const lightboxVisible = ref(false)
|
||||
const lightboxIndex = ref(0)
|
||||
const lightboxImgs = ref([])
|
||||
const loggedIn = computed(() => authState.loggedIn)
|
||||
const isAdmin = computed(() => authState.role === 'ADMIN')
|
||||
const isAuthor = computed(() => authState.username === author.value.username)
|
||||
const lottery = ref(null)
|
||||
const countdown = ref('00:00:00')
|
||||
let countdownTimer = null
|
||||
const lotteryParticipants = computed(() => lottery.value?.participants || [])
|
||||
const lotteryWinners = computed(() => lottery.value?.winners || [])
|
||||
const lotteryEnded = computed(() => {
|
||||
const lightboxVisible = ref(false)
|
||||
const lightboxIndex = ref(0)
|
||||
const lightboxImgs = ref([])
|
||||
const loggedIn = computed(() => authState.loggedIn)
|
||||
const isAdmin = computed(() => authState.role === 'ADMIN')
|
||||
const isAuthor = computed(() => authState.username === author.value.username)
|
||||
const lottery = ref(null)
|
||||
const countdown = ref('00:00:00')
|
||||
let countdownTimer = null
|
||||
const lotteryParticipants = computed(() => lottery.value?.participants || [])
|
||||
const lotteryWinners = computed(() => lottery.value?.winners || [])
|
||||
const lotteryEnded = computed(() => {
|
||||
if (!lottery.value || !lottery.value.endTime) return false
|
||||
return new Date(lottery.value.endTime).getTime() <= Date.now()
|
||||
})
|
||||
const hasJoined = computed(() => {
|
||||
})
|
||||
const hasJoined = computed(() => {
|
||||
if (!loggedIn.value) return false
|
||||
return lotteryParticipants.value.some((p) => p.id === Number(authState.userId))
|
||||
})
|
||||
const updateCountdown = () => {
|
||||
})
|
||||
const updateCountdown = () => {
|
||||
if (!lottery.value || !lottery.value.endTime) {
|
||||
countdown.value = '00:00:00'
|
||||
return
|
||||
@@ -350,15 +336,15 @@ export default {
|
||||
const m = String(Math.floor((diff % 3600000) / 60000)).padStart(2, '0')
|
||||
const s = String(Math.floor((diff % 60000) / 1000)).padStart(2, '0')
|
||||
countdown.value = `${h}:${m}:${s}`
|
||||
}
|
||||
const startCountdown = () => {
|
||||
}
|
||||
const startCountdown = () => {
|
||||
if (!process.client) return
|
||||
if (countdownTimer) clearInterval(countdownTimer)
|
||||
updateCountdown()
|
||||
countdownTimer = setInterval(updateCountdown, 1000)
|
||||
}
|
||||
const gotoUser = (id) => router.push(`/users/${id}`)
|
||||
const articleMenuItems = computed(() => {
|
||||
}
|
||||
const gotoUser = (id) => router.push(`/users/${id}`)
|
||||
const articleMenuItems = computed(() => {
|
||||
const items = []
|
||||
if (isAuthor.value || isAdmin.value) {
|
||||
items.push({ text: '编辑文章', onClick: () => editPost() })
|
||||
@@ -376,9 +362,9 @@ export default {
|
||||
items.push({ text: '驳回', color: 'red', onClick: () => rejectPost() })
|
||||
}
|
||||
return items
|
||||
})
|
||||
})
|
||||
|
||||
const gatherPostItems = () => {
|
||||
const gatherPostItems = () => {
|
||||
const items = []
|
||||
if (mainContainer.value) {
|
||||
const main = mainContainer.value.querySelector('.info-content-container')
|
||||
@@ -394,9 +380,9 @@ export default {
|
||||
items.sort((a, b) => a.top - b.top)
|
||||
postItems.value = items.map((i) => i.el)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mapComment = (c, parentUserName = '', level = 0) => ({
|
||||
const mapComment = (c, parentUserName = '', level = 0) => ({
|
||||
id: c.id,
|
||||
userName: c.author.username,
|
||||
medal: c.author.displayMedal,
|
||||
@@ -410,13 +396,13 @@ export default {
|
||||
src: c.author.avatar,
|
||||
iconClick: () => router.push(`/users/${c.author.id}`),
|
||||
parentUserName: parentUserName,
|
||||
})
|
||||
})
|
||||
|
||||
const getTop = (el) => {
|
||||
const getTop = (el) => {
|
||||
return el.getBoundingClientRect().top + window.scrollY
|
||||
}
|
||||
}
|
||||
|
||||
const findCommentPath = (id, list) => {
|
||||
const findCommentPath = (id, list) => {
|
||||
for (const item of list) {
|
||||
if (item.id === Number(id) || item.id === id) {
|
||||
return [item]
|
||||
@@ -427,17 +413,17 @@ export default {
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const expandCommentPath = (id) => {
|
||||
const expandCommentPath = (id) => {
|
||||
const path = findCommentPath(id, comments.value)
|
||||
if (!path) return
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
path[i].openReplies = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const removeCommentFromList = (id, list) => {
|
||||
const removeCommentFromList = (id, list) => {
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const item = list[i]
|
||||
if (item.id === id) {
|
||||
@@ -449,9 +435,9 @@ export default {
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const handleContentClick = (e) => {
|
||||
const handleContentClick = (e) => {
|
||||
handleMarkdownClick(e)
|
||||
if (e.target.tagName === 'IMG') {
|
||||
const container = e.target.parentNode
|
||||
@@ -460,14 +446,14 @@ export default {
|
||||
lightboxIndex.value = imgs.indexOf(e.target.src)
|
||||
lightboxVisible.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onCommentDeleted = (id) => {
|
||||
const onCommentDeleted = (id) => {
|
||||
removeCommentFromList(Number(id), comments.value)
|
||||
fetchComments()
|
||||
}
|
||||
}
|
||||
|
||||
const fetchPost = async () => {
|
||||
const fetchPost = async () => {
|
||||
try {
|
||||
isWaitingFetchingPost.value = true
|
||||
const token = getToken()
|
||||
@@ -498,29 +484,29 @@ export default {
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const totalPosts = computed(() => comments.value.length + 1)
|
||||
const lastReplyTime = computed(() =>
|
||||
const totalPosts = computed(() => comments.value.length + 1)
|
||||
const lastReplyTime = computed(() =>
|
||||
comments.value.length ? comments.value[comments.value.length - 1].time : postTime.value,
|
||||
)
|
||||
const firstReplyTime = computed(() =>
|
||||
)
|
||||
const firstReplyTime = computed(() =>
|
||||
comments.value.length ? comments.value[0].time : postTime.value,
|
||||
)
|
||||
const scrollerTopTime = computed(() =>
|
||||
)
|
||||
const scrollerTopTime = computed(() =>
|
||||
commentSort.value === 'OLDEST' ? postTime.value : firstReplyTime.value,
|
||||
)
|
||||
)
|
||||
|
||||
watch(
|
||||
watch(
|
||||
() => comments.value.length,
|
||||
async () => {
|
||||
await nextTick()
|
||||
gatherPostItems()
|
||||
updateCurrentIndex()
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
const updateCurrentIndex = () => {
|
||||
const updateCurrentIndex = () => {
|
||||
const scrollTop = window.scrollY
|
||||
|
||||
for (let i = 0; i < postItems.value.length; i++) {
|
||||
@@ -533,9 +519,9 @@ export default {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onSliderInput = (e) => {
|
||||
const onSliderInput = (e) => {
|
||||
const index = Number(e.target.value)
|
||||
currentIndex.value = index
|
||||
const target = postItems.value[index - 1]
|
||||
@@ -543,9 +529,9 @@ export default {
|
||||
const top = getTop(target) - headerHeight - 20 // 20 for beauty
|
||||
window.scrollTo({ top, behavior: 'auto' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const postComment = async (parentUserName, text, clear) => {
|
||||
const postComment = async (parentUserName, text, clear) => {
|
||||
if (!text.trim()) return
|
||||
console.debug('Posting comment', { postId, text })
|
||||
isWaitingPostingComment.value = true
|
||||
@@ -583,15 +569,15 @@ export default {
|
||||
} finally {
|
||||
isWaitingPostingComment.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const copyPostLink = () => {
|
||||
const copyPostLink = () => {
|
||||
navigator.clipboard.writeText(location.href.split('#')[0]).then(() => {
|
||||
toast.success('已复制')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const subscribePost = async () => {
|
||||
const subscribePost = async () => {
|
||||
const token = getToken()
|
||||
if (!token) {
|
||||
toast.error('请先登录')
|
||||
@@ -607,9 +593,9 @@ export default {
|
||||
} else {
|
||||
toast.error('操作失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const approvePost = async () => {
|
||||
const approvePost = async () => {
|
||||
const token = getToken()
|
||||
if (!token) return
|
||||
const res = await fetch(`${API_BASE_URL}/api/admin/posts/${postId}/approve`, {
|
||||
@@ -622,9 +608,9 @@ export default {
|
||||
} else {
|
||||
toast.error('操作失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const pinPost = async () => {
|
||||
const pinPost = async () => {
|
||||
const token = getToken()
|
||||
if (!token) return
|
||||
const res = await fetch(`${API_BASE_URL}/api/admin/posts/${postId}/pin`, {
|
||||
@@ -637,9 +623,9 @@ export default {
|
||||
} else {
|
||||
toast.error('操作失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const unpinPost = async () => {
|
||||
const unpinPost = async () => {
|
||||
const token = getToken()
|
||||
if (!token) return
|
||||
const res = await fetch(`${API_BASE_URL}/api/admin/posts/${postId}/unpin`, {
|
||||
@@ -652,13 +638,13 @@ export default {
|
||||
} else {
|
||||
toast.error('操作失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const editPost = () => {
|
||||
const editPost = () => {
|
||||
router.push(`/posts/${postId}/edit`)
|
||||
}
|
||||
}
|
||||
|
||||
const deletePost = async () => {
|
||||
const deletePost = async () => {
|
||||
const token = getToken()
|
||||
if (!token) {
|
||||
toast.error('请先登录')
|
||||
@@ -674,9 +660,9 @@ export default {
|
||||
} else {
|
||||
toast.error('操作失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rejectPost = async () => {
|
||||
const rejectPost = async () => {
|
||||
const token = getToken()
|
||||
if (!token) return
|
||||
const res = await fetch(`${API_BASE_URL}/api/admin/posts/${postId}/reject`, {
|
||||
@@ -689,8 +675,8 @@ export default {
|
||||
} else {
|
||||
toast.error('操作失败')
|
||||
}
|
||||
}
|
||||
const unsubscribePost = async () => {
|
||||
}
|
||||
const unsubscribePost = async () => {
|
||||
const token = getToken()
|
||||
if (!token) {
|
||||
toast.error('请先登录')
|
||||
@@ -707,9 +693,9 @@ export default {
|
||||
} else {
|
||||
toast.error('操作失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const joinLottery = async () => {
|
||||
const joinLottery = async () => {
|
||||
const token = getToken()
|
||||
if (!token) {
|
||||
toast.error('请先登录')
|
||||
@@ -725,17 +711,17 @@ export default {
|
||||
} else {
|
||||
toast.error('操作失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fetchCommentSorts = () => {
|
||||
const fetchCommentSorts = () => {
|
||||
return Promise.resolve([
|
||||
{ id: 'NEWEST', name: '最新', icon: 'fas fa-clock' },
|
||||
{ id: 'OLDEST', name: '最旧', icon: 'fas fa-hourglass-start' },
|
||||
// { id: 'MOST_INTERACTIONS', name: '最多互动', icon: 'fas fa-fire' }
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
const fetchComments = async () => {
|
||||
const fetchComments = async () => {
|
||||
isFetchingComments.value = true
|
||||
console.debug('Fetching comments', { postId, sort: commentSort.value })
|
||||
try {
|
||||
@@ -760,11 +746,11 @@ export default {
|
||||
} finally {
|
||||
isFetchingComments.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watch(commentSort, fetchComments)
|
||||
watch(commentSort, fetchComments)
|
||||
|
||||
const jumpToHashComment = async () => {
|
||||
const jumpToHashComment = async () => {
|
||||
const hash = location.hash
|
||||
if (hash.startsWith('#comment-')) {
|
||||
const id = hash.substring('#comment-'.length)
|
||||
@@ -777,13 +763,13 @@ export default {
|
||||
setTimeout(() => el.classList.remove('comment-highlight'), 4000)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const gotoProfile = () => {
|
||||
const gotoProfile = () => {
|
||||
router.push(`/users/${author.value.id}`)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
onMounted(async () => {
|
||||
await fetchComments()
|
||||
const hash = location.hash
|
||||
const id = hash.startsWith('#comment-') ? hash.substring('#comment-'.length) : null
|
||||
@@ -791,69 +777,9 @@ export default {
|
||||
updateCurrentIndex()
|
||||
window.addEventListener('scroll', updateCurrentIndex)
|
||||
jumpToHashComment()
|
||||
})
|
||||
})
|
||||
|
||||
await fetchPost()
|
||||
|
||||
return {
|
||||
postContent,
|
||||
author,
|
||||
title,
|
||||
category,
|
||||
tags,
|
||||
comments,
|
||||
postTime,
|
||||
scrollerTopTime,
|
||||
lastReplyTime,
|
||||
postItems,
|
||||
mainContainer,
|
||||
currentIndex,
|
||||
totalPosts,
|
||||
postReactions,
|
||||
articleMenuItems,
|
||||
postId,
|
||||
postComment,
|
||||
onSliderInput,
|
||||
copyPostLink,
|
||||
subscribePost,
|
||||
unsubscribePost,
|
||||
joinLottery,
|
||||
renderMarkdown,
|
||||
isWaitingFetchingPost,
|
||||
isWaitingPostingComment,
|
||||
gotoProfile,
|
||||
gotoUser,
|
||||
subscribed,
|
||||
loggedIn,
|
||||
isAuthor,
|
||||
status,
|
||||
isAdmin,
|
||||
approvePost,
|
||||
editPost,
|
||||
onCommentDeleted,
|
||||
deletePost,
|
||||
pinPost,
|
||||
unpinPost,
|
||||
rejectPost,
|
||||
lightboxVisible,
|
||||
lightboxIndex,
|
||||
lightboxImgs,
|
||||
handleContentClick,
|
||||
isMobile,
|
||||
pinnedAt,
|
||||
commentSort,
|
||||
fetchCommentSorts,
|
||||
isFetchingComments,
|
||||
getMedalTitle,
|
||||
lottery,
|
||||
countdown,
|
||||
lotteryParticipants,
|
||||
lotteryWinners,
|
||||
lotteryEnded,
|
||||
hasJoined,
|
||||
}
|
||||
},
|
||||
}
|
||||
await fetchPost()
|
||||
</script>
|
||||
<style>
|
||||
.post-page-container {
|
||||
|
||||
@@ -296,7 +296,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import AchievementList from '~/components/AchievementList.vue'
|
||||
@@ -304,42 +304,40 @@ import BasePlaceholder from '~/components/BasePlaceholder.vue'
|
||||
import BaseTimeline from '~/components/BaseTimeline.vue'
|
||||
import LevelProgress from '~/components/LevelProgress.vue'
|
||||
import UserList from '~/components/UserList.vue'
|
||||
import { API_BASE_URL, toast } from '~/main'
|
||||
import { toast } from '~/main'
|
||||
import { authState, getToken } from '~/utils/auth'
|
||||
import { prevLevelExp } from '~/utils/level'
|
||||
import { stripMarkdown, stripMarkdownLength } from '~/utils/markdown'
|
||||
import TimeManager from '~/utils/time'
|
||||
const config = useRuntimeConfig()
|
||||
const API_BASE_URL = config.public.apiBaseUrl
|
||||
|
||||
export default {
|
||||
name: 'ProfileView',
|
||||
components: { BaseTimeline, UserList, BasePlaceholder, LevelProgress, AchievementList },
|
||||
setup() {
|
||||
definePageMeta({
|
||||
definePageMeta({
|
||||
alias: ['/users/:id/'],
|
||||
})
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const username = route.params.id
|
||||
})
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const username = route.params.id
|
||||
|
||||
const user = ref({})
|
||||
const hotPosts = ref([])
|
||||
const hotReplies = ref([])
|
||||
const hotTags = ref([])
|
||||
const timelineItems = ref([])
|
||||
const followers = ref([])
|
||||
const followings = ref([])
|
||||
const medals = ref([])
|
||||
const subscribed = ref(false)
|
||||
const isLoading = ref(true)
|
||||
const tabLoading = ref(false)
|
||||
const selectedTab = ref(
|
||||
const user = ref({})
|
||||
const hotPosts = ref([])
|
||||
const hotReplies = ref([])
|
||||
const hotTags = ref([])
|
||||
const timelineItems = ref([])
|
||||
const followers = ref([])
|
||||
const followings = ref([])
|
||||
const medals = ref([])
|
||||
const subscribed = ref(false)
|
||||
const isLoading = ref(true)
|
||||
const tabLoading = ref(false)
|
||||
const selectedTab = ref(
|
||||
['summary', 'timeline', 'following', 'achievements'].includes(route.query.tab)
|
||||
? route.query.tab
|
||||
: 'summary',
|
||||
)
|
||||
const followTab = ref('followers')
|
||||
)
|
||||
const followTab = ref('followers')
|
||||
|
||||
const levelInfo = computed(() => {
|
||||
const levelInfo = computed(() => {
|
||||
const exp = user.value.experience || 0
|
||||
const currentLevel = user.value.currentLevel || 0
|
||||
const nextExp = user.value.nextLevelExp || 0
|
||||
@@ -348,20 +346,20 @@ export default {
|
||||
const ratio = total > 0 ? (exp - prevExp) / total : 1
|
||||
const percent = Math.max(0, Math.min(1, ratio)) * 100
|
||||
return { exp, currentLevel, nextExp, percent }
|
||||
})
|
||||
})
|
||||
|
||||
const isMine = computed(function () {
|
||||
const isMine = computed(function () {
|
||||
const mine = authState.username === username || String(authState.userId) === username
|
||||
console.log(mine)
|
||||
return mine
|
||||
})
|
||||
})
|
||||
|
||||
const formatDate = (d) => {
|
||||
const formatDate = (d) => {
|
||||
if (!d) return ''
|
||||
return TimeManager.format(d)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchUser = async () => {
|
||||
const fetchUser = async () => {
|
||||
const token = getToken()
|
||||
const headers = token ? { Authorization: `Bearer ${token}` } : {}
|
||||
const res = await fetch(`${API_BASE_URL}/api/users/${username}`, { headers })
|
||||
@@ -372,9 +370,9 @@ export default {
|
||||
} else if (res.status === 404) {
|
||||
router.replace('/404')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fetchSummary = async () => {
|
||||
const fetchSummary = async () => {
|
||||
const postsRes = await fetch(`${API_BASE_URL}/api/users/${username}/hot-posts`)
|
||||
if (postsRes.ok) {
|
||||
const data = await postsRes.json()
|
||||
@@ -392,9 +390,9 @@ export default {
|
||||
const data = await tagsRes.json()
|
||||
hotTags.value = data.map((t) => ({ icon: 'fas fa-tag', tag: t }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fetchTimeline = async () => {
|
||||
const fetchTimeline = async () => {
|
||||
const [postsRes, repliesRes, tagsRes] = await Promise.all([
|
||||
fetch(`${API_BASE_URL}/api/users/${username}/posts?limit=50`),
|
||||
fetch(`${API_BASE_URL}/api/users/${username}/replies?limit=50`),
|
||||
@@ -425,36 +423,36 @@ export default {
|
||||
]
|
||||
mapped.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
|
||||
timelineItems.value = mapped
|
||||
}
|
||||
}
|
||||
|
||||
const fetchFollowUsers = async () => {
|
||||
const fetchFollowUsers = async () => {
|
||||
const [followerRes, followingRes] = await Promise.all([
|
||||
fetch(`${API_BASE_URL}/api/users/${username}/followers`),
|
||||
fetch(`${API_BASE_URL}/api/users/${username}/following`),
|
||||
])
|
||||
followers.value = followerRes.ok ? await followerRes.json() : []
|
||||
followings.value = followingRes.ok ? await followingRes.json() : []
|
||||
}
|
||||
}
|
||||
|
||||
const loadSummary = async () => {
|
||||
const loadSummary = async () => {
|
||||
tabLoading.value = true
|
||||
await fetchSummary()
|
||||
tabLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadTimeline = async () => {
|
||||
const loadTimeline = async () => {
|
||||
tabLoading.value = true
|
||||
await fetchTimeline()
|
||||
tabLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadFollow = async () => {
|
||||
const loadFollow = async () => {
|
||||
tabLoading.value = true
|
||||
await fetchFollowUsers()
|
||||
tabLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const fetchAchievements = async () => {
|
||||
const fetchAchievements = async () => {
|
||||
const res = await fetch(`${API_BASE_URL}/api/medals?userId=${user.value.id}`)
|
||||
if (res.ok) {
|
||||
medals.value = await res.json()
|
||||
@@ -462,15 +460,15 @@ export default {
|
||||
medals.value = []
|
||||
toast.error('获取成就失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const loadAchievements = async () => {
|
||||
const loadAchievements = async () => {
|
||||
tabLoading.value = true
|
||||
await fetchAchievements()
|
||||
tabLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const subscribeUser = async () => {
|
||||
const subscribeUser = async () => {
|
||||
const token = getToken()
|
||||
if (!token) {
|
||||
toast.error('请先登录')
|
||||
@@ -486,9 +484,9 @@ export default {
|
||||
} else {
|
||||
toast.error('操作失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const unsubscribeUser = async () => {
|
||||
const unsubscribeUser = async () => {
|
||||
const token = getToken()
|
||||
if (!token) {
|
||||
toast.error('请先登录')
|
||||
@@ -504,14 +502,14 @@ export default {
|
||||
} else {
|
||||
toast.error('操作失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const gotoTag = (tag) => {
|
||||
const gotoTag = (tag) => {
|
||||
const value = encodeURIComponent(tag.id ?? tag.name)
|
||||
router.push({ path: '/', query: { tags: value } })
|
||||
}
|
||||
}
|
||||
|
||||
const init = async () => {
|
||||
const init = async () => {
|
||||
try {
|
||||
await fetchUser()
|
||||
if (selectedTab.value === 'summary') {
|
||||
@@ -528,54 +526,20 @@ export default {
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(init)
|
||||
onMounted(init)
|
||||
|
||||
watch(selectedTab, async (val) => {
|
||||
watch(selectedTab, async (val) => {
|
||||
// router.replace({ query: { ...route.query, tab: val } })
|
||||
if (val === 'timeline' && timelineItems.value.length === 0) {
|
||||
await loadTimeline()
|
||||
} else if (
|
||||
val === 'following' &&
|
||||
followers.value.length === 0 &&
|
||||
followings.value.length === 0
|
||||
) {
|
||||
} else if (val === 'following' && followers.value.length === 0 && followings.value.length === 0) {
|
||||
await loadFollow()
|
||||
} else if (val === 'achievements' && medals.value.length === 0) {
|
||||
await loadAchievements()
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
user,
|
||||
hotPosts,
|
||||
hotReplies,
|
||||
timelineItems,
|
||||
followers,
|
||||
followings,
|
||||
medals,
|
||||
subscribed,
|
||||
isMine,
|
||||
isLoading,
|
||||
tabLoading,
|
||||
selectedTab,
|
||||
followTab,
|
||||
formatDate,
|
||||
stripMarkdown,
|
||||
stripMarkdownLength,
|
||||
loadTimeline,
|
||||
loadFollow,
|
||||
loadAchievements,
|
||||
loadSummary,
|
||||
subscribeUser,
|
||||
unsubscribeUser,
|
||||
gotoTag,
|
||||
hotTags,
|
||||
levelInfo,
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { API_BASE_URL } from '~/main'
|
||||
import { reactive } from 'vue'
|
||||
|
||||
const TOKEN_KEY = 'token'
|
||||
@@ -65,6 +64,8 @@ export function clearUserInfo() {
|
||||
}
|
||||
|
||||
export async function fetchCurrentUser() {
|
||||
const config = useRuntimeConfig()
|
||||
const API_BASE_URL = config.public.apiBaseUrl
|
||||
const token = getToken()
|
||||
if (!token) return null
|
||||
try {
|
||||
@@ -91,6 +92,8 @@ export function isLogin() {
|
||||
}
|
||||
|
||||
export async function checkToken() {
|
||||
const config = useRuntimeConfig()
|
||||
const API_BASE_URL = config.public.apiBaseUrl
|
||||
const token = getToken()
|
||||
if (!token) return false
|
||||
try {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { API_BASE_URL, DISCORD_CLIENT_ID, toast } from '../main'
|
||||
import { DISCORD_CLIENT_ID, toast } from '../main'
|
||||
import { WEBSITE_BASE_URL } from '../constants'
|
||||
import { setToken, loadCurrentUser } from './auth'
|
||||
import { registerPush } from './push'
|
||||
@@ -15,6 +15,8 @@ export function discordAuthorize(state = '') {
|
||||
|
||||
export async function discordExchange(code, state, reason) {
|
||||
try {
|
||||
const config = useRuntimeConfig()
|
||||
const API_BASE_URL = config.public.apiBaseUrl
|
||||
const res = await fetch(`${API_BASE_URL}/api/auth/discord`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { API_BASE_URL, GITHUB_CLIENT_ID, toast } from '../main'
|
||||
import { GITHUB_CLIENT_ID, toast } from '../main'
|
||||
import { setToken, loadCurrentUser } from './auth'
|
||||
import { WEBSITE_BASE_URL } from '../constants'
|
||||
import { registerPush } from './push'
|
||||
@@ -15,6 +15,8 @@ export function githubAuthorize(state = '') {
|
||||
|
||||
export async function githubExchange(code, state, reason) {
|
||||
try {
|
||||
const config = useRuntimeConfig()
|
||||
const API_BASE_URL = config.public.apiBaseUrl
|
||||
const res = await fetch(`${API_BASE_URL}/api/auth/github`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { API_BASE_URL, GOOGLE_CLIENT_ID, toast } from '../main'
|
||||
import { GOOGLE_CLIENT_ID, toast } from '../main'
|
||||
import { setToken, loadCurrentUser } from './auth'
|
||||
import { registerPush } from './push'
|
||||
import { WEBSITE_BASE_URL } from '../constants'
|
||||
@@ -32,6 +32,8 @@ export function googleAuthorize() {
|
||||
|
||||
export async function googleAuthWithToken(idToken, redirect_success, redirect_not_approved) {
|
||||
try {
|
||||
const config = useRuntimeConfig()
|
||||
const API_BASE_URL = config.public.apiBaseUrl
|
||||
const res = await fetch(`${API_BASE_URL}/api/auth/google`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { API_BASE_URL } from '~/main'
|
||||
import { getToken } from './auth'
|
||||
import { reactive } from 'vue'
|
||||
|
||||
@@ -7,6 +6,8 @@ export const notificationState = reactive({
|
||||
})
|
||||
|
||||
export async function fetchUnreadCount() {
|
||||
const config = useRuntimeConfig()
|
||||
const API_BASE_URL = config.public.apiBaseUrl
|
||||
try {
|
||||
const token = getToken()
|
||||
if (!token) {
|
||||
@@ -31,6 +32,9 @@ export async function fetchUnreadCount() {
|
||||
|
||||
export async function markNotificationsRead(ids) {
|
||||
try {
|
||||
const config = useRuntimeConfig()
|
||||
const API_BASE_URL = config.public.apiBaseUrl
|
||||
|
||||
const token = getToken()
|
||||
if (!token || !ids || ids.length === 0) return false
|
||||
const res = await fetch(`${API_BASE_URL}/api/notifications/read`, {
|
||||
@@ -49,6 +53,9 @@ export async function markNotificationsRead(ids) {
|
||||
|
||||
export async function fetchNotificationPreferences() {
|
||||
try {
|
||||
const config = useRuntimeConfig()
|
||||
const API_BASE_URL = config.public.apiBaseUrl
|
||||
|
||||
const token = getToken()
|
||||
if (!token) return []
|
||||
const res = await fetch(`${API_BASE_URL}/api/notifications/prefs`, {
|
||||
@@ -63,6 +70,8 @@ export async function fetchNotificationPreferences() {
|
||||
|
||||
export async function updateNotificationPreference(type, enabled) {
|
||||
try {
|
||||
const config = useRuntimeConfig()
|
||||
const API_BASE_URL = config.public.apiBaseUrl
|
||||
const token = getToken()
|
||||
if (!token) return false
|
||||
const res = await fetch(`${API_BASE_URL}/api/notifications/prefs`, {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { API_BASE_URL } from '../main'
|
||||
import { getToken } from './auth'
|
||||
|
||||
function urlBase64ToUint8Array(base64String) {
|
||||
@@ -21,6 +20,8 @@ function arrayBufferToBase64(buffer) {
|
||||
|
||||
export async function registerPush() {
|
||||
if (!('serviceWorker' in navigator) || !('PushManager' in window)) return
|
||||
const config = useRuntimeConfig()
|
||||
const API_BASE_URL = config.public.apiBaseUrl
|
||||
try {
|
||||
const reg = await navigator.serviceWorker.register('/notifications-sw.js')
|
||||
const res = await fetch(`${API_BASE_URL}/api/push/public-key`)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { API_BASE_URL, TWITTER_CLIENT_ID, toast } from '../main'
|
||||
import { TWITTER_CLIENT_ID, toast } from '../main'
|
||||
import { WEBSITE_BASE_URL } from '../constants'
|
||||
import { setToken, loadCurrentUser } from './auth'
|
||||
import { registerPush } from './push'
|
||||
@@ -42,6 +42,8 @@ export async function twitterAuthorize(state = '') {
|
||||
|
||||
export async function twitterExchange(code, state, reason) {
|
||||
try {
|
||||
const config = useRuntimeConfig()
|
||||
const API_BASE_URL = config.public.apiBaseUrl
|
||||
const codeVerifier = sessionStorage.getItem('twitter_code_verifier')
|
||||
sessionStorage.removeItem('twitter_code_verifier')
|
||||
const res = await fetch(`${API_BASE_URL}/api/auth/twitter`, {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { API_BASE_URL } from '../main'
|
||||
|
||||
export async function fetchFollowings(username) {
|
||||
if (!username) return []
|
||||
const config = useRuntimeConfig()
|
||||
const API_BASE_URL = config.public.apiBaseUrl
|
||||
try {
|
||||
const res = await fetch(`${API_BASE_URL}/api/users/${username}/following`)
|
||||
return res.ok ? await res.json() : []
|
||||
@@ -11,6 +11,8 @@ export async function fetchFollowings(username) {
|
||||
}
|
||||
|
||||
export async function fetchAdmins() {
|
||||
const config = useRuntimeConfig()
|
||||
const API_BASE_URL = config.public.apiBaseUrl
|
||||
try {
|
||||
const res = await fetch(`${API_BASE_URL}/api/users/admins`)
|
||||
return res.ok ? await res.json() : []
|
||||
@@ -21,6 +23,8 @@ export async function fetchAdmins() {
|
||||
|
||||
export async function searchUsers(keyword) {
|
||||
if (!keyword) return []
|
||||
const config = useRuntimeConfig()
|
||||
const API_BASE_URL = config.public.apiBaseUrl
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/search/users?keyword=${encodeURIComponent(keyword)}`,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Vditor from 'vditor'
|
||||
import { API_BASE_URL } from '../main'
|
||||
import { getToken, authState } from './auth'
|
||||
import { searchUsers, fetchFollowings, fetchAdmins } from './user'
|
||||
import { tiebaEmoji } from './tiebaEmoji'
|
||||
@@ -14,6 +13,8 @@ export function getPreviewTheme() {
|
||||
|
||||
export function createVditor(editorId, options = {}) {
|
||||
const { placeholder = '', preview = {}, input, after } = options
|
||||
const config = useRuntimeConfig()
|
||||
const API_BASE_URL = config.public.apiBaseUrl
|
||||
|
||||
const fetchMentions = async (value) => {
|
||||
if (!value) {
|
||||
|
||||
Reference in New Issue
Block a user