fix: setup 迁移完成 v1

This commit is contained in:
Tim
2025-08-14 10:27:01 +08:00
parent 902fce5174
commit 655e8f2a65
33 changed files with 289 additions and 393 deletions

View File

@@ -1,4 +1,5 @@
NUXT_PUBLIC_API_BASE_URL=https://www.open-isle.com
NUXT_PUBLIC_WEBSITE_BASE_URL=https://www.open-isle.com
NUXT_PUBLIC_GOOGLE_CLIENT_ID=777830451304-xxx.apps.googleusercontent.com
NUXT_PUBLIC_GITHUB_CLIENT_ID=Ov23liVkO1NPAX5JyWxJ
NUXT_PUBLIC_DISCORD_CLIENT_ID=1394985417044000779

View File

@@ -11,29 +11,20 @@
</BasePopup>
</template>
<script>
<script setup>
import BasePopup from '~/components/BasePopup.vue'
import { useRouter } from 'vue-router'
export default {
name: 'ActivityPopup',
components: { BasePopup },
props: {
visible: { type: Boolean, default: false },
icon: String,
text: String,
},
emits: ['close'],
setup(props, { emit }) {
const router = useRouter()
const gotoActivity = () => {
emit('close')
router.push('/activities')
}
const close = () => emit('close')
return { gotoActivity, close }
},
const props = defineProps({
visible: { type: Boolean, default: false },
icon: String,
text: String,
})
const emit = defineEmits(['close'])
const gotoActivity = async () => {
emit('close')
await navigateTo('/activities', { replace: true })
}
const close = () => emit('close')
</script>
<style scoped>

View File

@@ -12,25 +12,15 @@
</div>
</template>
<script>
import { useRouter } from 'vue-router'
<script setup>
const props = defineProps({
category: { type: Object, default: null },
})
export default {
name: 'ArticleCategory',
props: {
category: { type: Object, default: null },
},
setup(props) {
const router = useRouter()
const gotoCategory = () => {
if (!props.category) return
const value = encodeURIComponent(props.category.id ?? props.category.name)
router.push({ path: '/', query: { category: value } }).then(() => {
window.location.reload()
})
}
return { gotoCategory }
},
const gotoCategory = async () => {
if (!props.category) return
const value = encodeURIComponent(props.category.id ?? props.category.name)
await navigateTo({ path: '/', query: { category: value } }, { replace: true })
}
</script>

View File

@@ -17,24 +17,14 @@
</div>
</template>
<script>
import { useRouter } from 'vue-router'
<script setup>
defineProps({
tags: { type: Array, default: () => [] },
})
export default {
name: 'ArticleTags',
props: {
tags: { type: Array, default: () => [] },
},
setup() {
const router = useRouter()
const gotoTag = (tag) => {
const value = encodeURIComponent(tag.id ?? tag.name)
router.push({ path: '/', query: { tags: value } }).then(() => {
window.location.reload()
})
}
return { gotoTag }
},
const gotoTag = async (tag) => {
const value = encodeURIComponent(tag.id ?? tag.name)
await navigateTo({ path: '/', query: { tags: value } }, { replace: true })
}
</script>

View File

@@ -91,7 +91,6 @@
<script setup>
import { computed, ref, watch } from 'vue'
import VueEasyLightbox from 'vue-easy-lightbox'
import { useRouter } from 'vue-router'
import { toast } from '~/main'
import { authState, getToken } from '~/utils/auth'
import { handleMarkdownClick, renderMarkdown } from '~/utils/markdown'
@@ -121,7 +120,6 @@ const props = defineProps({
const emit = defineEmits(['deleted'])
const router = useRouter()
const showReplies = ref(props.level === 0 ? true : props.defaultShowReplies)
watch(
() => props.defaultShowReplies,
@@ -237,11 +235,11 @@ const submitReply = async (parentUserName, text, clear) => {
reply: [],
openReplies: false,
src: r.author.avatar,
iconClick: () => router.push(`/users/${r.author.id}`),
iconClick: () => navigateTo(`/users/${r.author.id}`),
})),
openReplies: false,
src: data.author.avatar,
iconClick: () => router.push(`/users/${data.author.id}`),
iconClick: () => navigateTo(`/users/${data.author.id}`),
})
clear()
showEditor.value = false

View File

@@ -15,9 +15,11 @@
import ActivityPopup from '~/components/ActivityPopup.vue'
import MedalPopup from '~/components/MedalPopup.vue'
import NotificationSettingPopup from '~/components/NotificationSettingPopup.vue'
import { API_BASE_URL } from '~/main'
import { authState } from '~/utils/auth'
const config = useRuntimeConfig()
const API_BASE_URL = config.public.apiBaseUrl
const showMilkTeaPopup = ref(false)
const milkTeaIcon = ref('')
const showNotificationPopup = ref(false)

View File

@@ -48,7 +48,7 @@
</header>
</template>
<script>
<script setup>
import { ClientOnly } from '#components'
import { computed, nextTick, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
@@ -57,141 +57,109 @@ import SearchDropdown from '~/components/SearchDropdown.vue'
import { authState, clearToken, loadCurrentUser } from '~/utils/auth'
import { fetchUnreadCount, notificationState } from '~/utils/notification'
import { useIsMobile } from '~/utils/screen'
export default {
name: 'HeaderComponent',
components: { DropdownMenu, SearchDropdown },
props: {
showMenuBtn: {
type: Boolean,
default: true,
},
const props = defineProps({
showMenuBtn: {
type: Boolean,
default: true,
},
setup(props, { expose }) {
const isLogin = computed(() => authState.loggedIn)
const isMobile = useIsMobile()
const unreadCount = computed(() => notificationState.unreadCount)
const router = useRouter()
const avatar = ref('')
const showSearch = ref(false)
const searchDropdown = ref(null)
const userMenu = ref(null)
const menuBtn = ref(null)
})
expose({
menuBtn,
})
const isLogin = computed(() => authState.loggedIn)
const isMobile = useIsMobile()
const unreadCount = computed(() => notificationState.unreadCount)
const router = useRouter()
const avatar = ref('')
const showSearch = ref(false)
const searchDropdown = ref(null)
const userMenu = ref(null)
const menuBtn = ref(null)
const goToHome = () => {
router.push('/').then(() => {
window.location.reload()
})
const goToHome = async () => {
await navigateTo('/', { replace: true })
}
const search = () => {
showSearch.value = true
nextTick(() => {
searchDropdown.value.toggle()
})
}
const closeSearch = () => {
nextTick(() => {
showSearch.value = false
})
}
const goToLogin = () => {
navigateTo('/login', { replace: true })
}
const goToSettings = () => {
navigateTo('/settings', { replace: true })
}
const goToProfile = async () => {
if (!authState.loggedIn) {
navigateTo('/login', { replace: true })
return
}
let id = authState.username || authState.userId
if (!id) {
const user = await loadCurrentUser()
if (user) {
id = user.username || user.id
}
const search = () => {
showSearch.value = true
nextTick(() => {
searchDropdown.value.toggle()
})
}
const closeSearch = () => {
nextTick(() => {
showSearch.value = false
})
}
const goToLogin = () => {
router.push('/login')
}
const goToSettings = () => {
router.push('/settings')
}
const goToProfile = async () => {
if (!authState.loggedIn) {
router.push('/login')
return
}
let id = authState.username || authState.userId
if (!id) {
const user = await loadCurrentUser()
if (user) {
id = user.username || user.id
}
}
if (id) {
router.push(`/users/${id}`)
}
if (id) {
navigateTo(`/users/${id}`, { replace: true })
}
}
const goToSignup = () => {
navigateTo('/signup', { replace: true })
}
const goToLogout = () => {
clearToken()
navigateTo('/login', { replace: true })
}
const headerMenuItems = computed(() => [
{ text: '设置', onClick: goToSettings },
{ text: '个人主页', onClick: goToProfile },
{ text: '退出', onClick: goToLogout },
])
onMounted(async () => {
const updateAvatar = async () => {
if (authState.loggedIn) {
const user = await loadCurrentUser()
if (user && user.avatar) {
avatar.value = user.avatar
}
}
const goToSignup = () => {
router.push('/signup')
}
const goToLogout = () => {
clearToken()
this.$router.push('/login')
}
const updateUnread = async () => {
if (authState.loggedIn) {
await fetchUnreadCount()
} else {
notificationState.unreadCount = 0
}
}
const headerMenuItems = computed(() => [
{ text: '设置', onClick: goToSettings },
{ text: '个人主页', onClick: goToProfile },
{ text: '退出', onClick: goToLogout },
])
onMounted(async () => {
const updateAvatar = async () => {
if (authState.loggedIn) {
const user = await loadCurrentUser()
if (user && user.avatar) {
avatar.value = user.avatar
}
}
}
const updateUnread = async () => {
if (authState.loggedIn) {
await fetchUnreadCount()
} else {
notificationState.unreadCount = 0
}
}
await updateAvatar()
await updateUnread()
watch(
() => authState.loggedIn,
async () => {
await updateAvatar()
await updateUnread()
},
)
watch(
() => authState.loggedIn,
async () => {
await updateAvatar()
await updateUnread()
},
)
watch(
() => router.currentRoute.value.fullPath,
() => {
if (userMenu.value) userMenu.value.close()
showSearch.value = false
},
)
})
return {
isLogin,
isMobile,
headerMenuItems,
unreadCount,
goToHome,
search,
closeSearch,
goToLogin,
goToSettings,
goToProfile,
goToSignup,
goToLogout,
showSearch,
searchDropdown,
userMenu,
avatar,
menuBtn,
}
},
}
watch(
() => router.currentRoute.value.fullPath,
() => {
if (userMenu.value) userMenu.value.close()
showSearch.value = false
},
)
})
</script>
<style scoped>

View File

@@ -9,18 +9,9 @@
</div>
</template>
<script>
import { useRouter } from 'vue-router'
export default {
name: 'LoginOverlay',
setup() {
const router = useRouter()
const goLogin = () => {
router.push('/login')
}
return { goLogin }
},
<script setup>
const goLogin = () => {
navigateTo('/login', { replace: true })
}
</script>

View File

@@ -16,33 +16,25 @@
</BasePopup>
</template>
<script>
<script setup>
import BasePopup from '~/components/BasePopup.vue'
import { useRouter } from 'vue-router'
import { authState } from '~/utils/auth'
export default {
name: 'MedalPopup',
components: { BasePopup },
props: {
visible: { type: Boolean, default: false },
medals: { type: Array, default: () => [] },
},
emits: ['close'],
setup(props, { emit }) {
const router = useRouter()
const gotoMedals = () => {
emit('close')
if (authState.username) {
router.push(`/users/${authState.username}?tab=achievements`)
} else {
router.push('/')
}
}
const close = () => emit('close')
return { gotoMedals, close }
},
defineProps({
visible: { type: Boolean, default: false },
medals: { type: Array, default: () => [] },
})
const emit = defineEmits(['close'])
const gotoMedals = () => {
emit('close')
if (authState.username) {
navigateTo(`/users/${authState.username}?tab=achievements`, { replace: true })
} else {
navigateTo('/', { replace: true })
}
}
const close = () => emit('close')
</script>
<style scoped>

View File

@@ -124,10 +124,10 @@
</template>
<script setup>
import { ref, computed, watch, onMounted } from 'vue'
import { themeState, cycleTheme, ThemeMode } from '~/utils/theme'
import { authState } from '~/utils/auth'
import { fetchUnreadCount, notificationState } from '~/utils/notification'
import { ref, computed, watch, onMounted } from 'vue'
const config = useRuntimeConfig()
const API_BASE_URL = config.public.apiBaseUrl
@@ -140,7 +140,6 @@ const props = defineProps({
const emit = defineEmits(['item-click'])
const router = useRouter()
const categoryOpen = ref(true)
const tagOpen = ref(true)
const isLoadingCategory = ref(false)
@@ -193,9 +192,7 @@ onMounted(async () => {
})
const handleHomeClick = () => {
router.push('/').then(() => {
window.location.reload()
})
navigateTo('/', { replace: true })
}
const handleItemClick = () => {
@@ -209,13 +206,13 @@ const isImageIcon = (icon) => {
const gotoCategory = (c) => {
const value = encodeURIComponent(c.id ?? c.name)
router.push({ path: '/', query: { category: value } })
navigateTo({ path: '/', query: { category: value } }, { replace: true })
handleItemClick()
}
const gotoTag = (t) => {
const value = encodeURIComponent(t.id ?? t.name)
router.push({ path: '/', query: { tags: value } })
navigateTo({ path: '/', query: { tags: value } }, { replace: true })
handleItemClick()
}

View File

@@ -11,27 +11,19 @@
</BasePopup>
</template>
<script>
<script setup>
import BasePopup from '~/components/BasePopup.vue'
import { useRouter } from 'vue-router'
export default {
name: 'NotificationSettingPopup',
components: { BasePopup },
props: {
visible: { type: Boolean, default: false },
},
emits: ['close'],
setup(props, { emit }) {
const router = useRouter()
const gotoSetting = () => {
emit('close')
router.push('/message?tab=control')
}
const close = () => emit('close')
return { gotoSetting, close }
},
defineProps({
visible: { type: Boolean, default: false },
})
const emit = defineEmits(['close'])
const gotoSetting = () => {
emit('close')
navigateTo('/message?tab=control', { replace: true })
}
const close = () => emit('close')
</script>
<style scoped>

View File

@@ -54,6 +54,17 @@ import { reactionEmojiMap } from '~/utils/reactions'
const config = useRuntimeConfig()
const API_BASE_URL = config.public.apiBaseUrl
const emit = defineEmits(['update:modelValue'])
const props = defineProps({
modelValue: { type: Array, default: () => [] },
contentType: { type: String, required: true },
contentId: { type: [Number, String], required: true },
})
watch(
() => props.modelValue,
(v) => (reactions.value = v),
)
const reactions = ref(props.modelValue)
const reactionTypes = ref([])
@@ -76,17 +87,6 @@ const fetchTypes = async () => {
return cachedTypes
}
const props = defineProps({
modelValue: { type: Array, default: () => [] },
contentType: { type: String, required: true },
contentId: { type: [Number, String], required: true },
})
watch(
() => props.modelValue,
(v) => (reactions.value = v),
)
onMounted(async () => {
reactionTypes.value = await fetchTypes()
})

View File

@@ -37,17 +37,15 @@
</template>
<script setup>
import { ref, watch } from 'vue'
import { useIsMobile } from '~/utils/screen'
import { useRouter } from 'vue-router'
import Dropdown from '~/components/Dropdown.vue'
import { stripMarkdown } from '~/utils/markdown'
import { ref, watch } from 'vue'
const config = useRuntimeConfig()
const API_BASE_URL = config.public.apiBaseUrl
const emit = defineEmits(['close'])
const router = useRouter()
const keyword = ref('')
const selected = ref(null)
const results = ref([])
@@ -97,17 +95,17 @@ watch(selected, (val) => {
const opt = results.value.find((r) => r.id === val)
if (!opt) return
if (opt.type === 'post' || opt.type === 'post_title') {
router.push(`/posts/${opt.id}`)
navigateTo(`/posts/${opt.id}`, { replace: true })
} else if (opt.type === 'user') {
router.push(`/users/${opt.id}`)
navigateTo(`/users/${opt.id}`, { replace: true })
} else if (opt.type === 'comment') {
if (opt.postId) {
router.push(`/posts/${opt.postId}#comment-${opt.id}`)
navigateTo(`/posts/${opt.postId}#comment-${opt.id}`, { replace: true })
}
} else if (opt.type === 'category') {
router.push({ path: '/', query: { category: opt.id } })
navigateTo({ path: '/', query: { category: opt.id } }, { replace: true })
} else if (opt.type === 'tag') {
router.push({ path: '/', query: { tags: opt.id } })
navigateTo({ path: '/', query: { tags: opt.id } }, { replace: true })
}
selected.value = null
keyword.value = ''

View File

@@ -11,20 +11,15 @@
</div>
</template>
<script>
<script setup>
import BasePlaceholder from '~/components/BasePlaceholder.vue'
export default {
name: 'UserList',
components: { BasePlaceholder },
props: {
users: { type: Array, default: () => [] },
},
methods: {
handleUserClick(user) {
this.$router.push(`/users/${user.id}`)
},
},
defineProps({
users: { type: Array, default: () => [] },
})
const handleUserClick = (user) => {
navigateTo(`/users/${user.id}`, { replace: true })
}
</script>

View File

@@ -1 +0,0 @@
export const WEBSITE_BASE_URL = 'https://www.open-isle.com'

View File

@@ -5,6 +5,7 @@ export default defineNuxtConfig({
runtimeConfig: {
public: {
apiBaseUrl: process.env.NUXT_PUBLIC_API_BASE_URL || '',
websiteBaseUrl: process.env.NUXT_PUBLIC_WEBSITE_BASE_URL || '',
googleClientId: process.env.NUXT_PUBLIC_GOOGLE_CLIENT_ID || '',
githubClientId: process.env.NUXT_PUBLIC_GITHUB_CLIENT_ID || '',
discordClientId: process.env.NUXT_PUBLIC_DISCORD_CLIENT_ID || '',

View File

@@ -2,24 +2,20 @@
<CallbackPage />
</template>
<script>
<script setup>
import CallbackPage from '~/components/CallbackPage.vue'
import { discordExchange } from '~/utils/discord'
export default {
name: 'DiscordCallbackPageView',
components: { CallbackPage },
async mounted() {
const url = new URL(window.location.href)
const code = url.searchParams.get('code')
const state = url.searchParams.get('state')
const result = await discordExchange(code, state, '')
onMounted(async () => {
const url = new URL(window.location.href)
const code = url.searchParams.get('code')
const state = url.searchParams.get('state')
const result = await discordExchange(code, state, '')
if (result.needReason) {
this.$router.push('/signup-reason?token=' + result.token)
} else {
this.$router.push('/')
}
},
}
if (result.needReason) {
navigateTo(`/signup-reason?token=${result.token}`, { replace: true })
} else {
navigateTo('/', { replace: true })
}
})
</script>

View File

@@ -106,7 +106,7 @@ const resetPassword = async () => {
const data = await res.json()
if (res.ok) {
toast.success('密码已重置')
router.push('/login')
navigateTo('/login', { replace: true })
} else if (data.field === 'password') {
passwordError.value = data.error
} else {

View File

@@ -2,24 +2,20 @@
<CallbackPage />
</template>
<script>
<script setup>
import CallbackPage from '~/components/CallbackPage.vue'
import { githubExchange } from '~/utils/github'
export default {
name: 'GithubCallbackPageView',
components: { CallbackPage },
async mounted() {
const url = new URL(window.location.href)
const code = url.searchParams.get('code')
const state = url.searchParams.get('state')
const result = await githubExchange(code, state, '')
onMounted(async () => {
const url = new URL(window.location.href)
const code = url.searchParams.get('code')
const state = url.searchParams.get('state')
const result = await githubExchange(code, state, '')
if (result.needReason) {
this.$router.push('/signup-reason?token=' + result.token)
} else {
this.$router.push('/')
}
},
}
if (result.needReason) {
navigateTo(`/signup-reason?token=${result.token}`, { replace: true })
} else {
navigateTo('/', { replace: true })
}
})
</script>

View File

@@ -2,29 +2,25 @@
<CallbackPage />
</template>
<script>
<script setup>
import CallbackPage from '~/components/CallbackPage.vue'
import { googleAuthWithToken } from '~/utils/google'
export default {
name: 'GoogleCallbackPageView',
components: { CallbackPage },
async mounted() {
const hash = new URLSearchParams(window.location.hash.substring(1))
const idToken = hash.get('id_token')
if (idToken) {
await googleAuthWithToken(
idToken,
() => {
this.$router.push('/')
},
(token) => {
this.$router.push('/signup-reason?token=' + token)
},
)
} else {
this.$router.push('/login')
}
},
}
onMounted(async () => {
const hash = new URLSearchParams(window.location.hash.substring(1))
const idToken = hash.get('id_token')
if (idToken) {
await googleAuthWithToken(
idToken,
() => {
navigateTo('/', { replace: true })
},
(token) => {
navigateTo(`/signup-reason?token=${token}`, { replace: true })
},
)
} else {
navigateTo('/login', { replace: true })
}
})
</script>

View File

@@ -62,7 +62,6 @@ import BaseInput from '~/components/BaseInput.vue'
import { registerPush } from '~/utils/push'
const config = useRuntimeConfig()
const API_BASE_URL = config.public.apiBaseUrl
const username = ref('')
const password = ref('')
const isWaitingForLogin = ref(false)
@@ -81,15 +80,18 @@ const submitLogin = async () => {
await loadCurrentUser()
toast.success('登录成功')
registerPush()
router.push('/')
await navigateTo('/', { replace: true })
} else if (data.reason_code === 'NOT_VERIFIED') {
toast.info('当前邮箱未验证,已经为您重新发送验证码')
router.push({ path: '/signup', query: { verify: 1, u: username.value } })
await navigateTo(
{ path: '/signup', query: { verify: '1', u: username.value } },
{ replace: true },
)
} else if (data.reason_code === 'IS_APPROVING') {
toast.info('您的注册正在审批中, 请留意邮件')
router.push('/')
await navigateTo('/', { replace: true })
} else if (data.reason_code === 'NOT_APPROVED') {
router.push('/signup-reason?token=' + data.token)
await navigateTo({ path: '/signup-reason', query: { token: data.token } }, { replace: true })
} else {
toast.error(data.error || '登录失败')
}

View File

@@ -480,7 +480,6 @@
<script setup>
import { ref, onMounted, computed } from 'vue'
import { useRouter } from 'vue-router'
import BaseTimeline from '~/components/BaseTimeline.vue'
import BasePlaceholder from '~/components/BasePlaceholder.vue'
import NotificationContainer from '~/components/NotificationContainer.vue'
@@ -492,7 +491,6 @@ import TimeManager from '~/utils/time'
import { reactionEmojiMap } from '~/utils/reactions'
const config = useRuntimeConfig()
const API_BASE_URL = config.public.apiBaseUrl
const router = useRouter()
const route = useRoute()
const notifications = ref([])
const isLoadingMessage = ref(false)
@@ -590,7 +588,7 @@ const fetchNotifications = async () => {
src: n.comment.author.avatar,
iconClick: () => {
markRead(n.id)
router.push(`/users/${n.comment.author.id}`)
navigateTo(`/users/${n.comment.author.id}`, { replace: true })
},
})
} else if (n.type === 'REACTION') {
@@ -600,7 +598,7 @@ const fetchNotifications = async () => {
iconClick: () => {
if (n.fromUser) {
markRead(n.id)
router.push(`/users/${n.fromUser.id}`)
navigateTo(`/users/${n.fromUser.id}`, { replace: true })
}
},
})
@@ -612,7 +610,7 @@ const fetchNotifications = async () => {
iconClick: () => {
if (n.fromUser) {
markRead(n.id)
router.push(`/users/${n.fromUser.id}`)
navigateTo(`/users/${n.fromUser.id}`, { replace: true })
}
},
})
@@ -622,7 +620,7 @@ const fetchNotifications = async () => {
src: n.comment.author.avatar,
iconClick: () => {
markRead(n.id)
router.push(`/users/${n.comment.author.id}`)
navigateTo(`/users/${n.comment.author.id}`, { replace: true })
},
})
} else if (n.type === 'USER_ACTIVITY') {
@@ -631,7 +629,7 @@ const fetchNotifications = async () => {
src: n.comment.author.avatar,
iconClick: () => {
markRead(n.id)
router.push(`/users/${n.comment.author.id}`)
navigateTo(`/users/${n.comment.author.id}`, { replace: true })
},
})
} else if (n.type === 'MENTION') {
@@ -641,7 +639,7 @@ const fetchNotifications = async () => {
iconClick: () => {
if (n.fromUser) {
markRead(n.id)
router.push(`/users/${n.fromUser.id}`)
navigateTo(`/users/${n.fromUser.id}`, { replace: true })
}
},
})
@@ -652,7 +650,7 @@ const fetchNotifications = async () => {
iconClick: () => {
if (n.fromUser) {
markRead(n.id)
router.push(`/users/${n.fromUser.id}`)
navigateTo(`/users/${n.fromUser.id}`, { replace: true })
}
},
})
@@ -663,7 +661,7 @@ const fetchNotifications = async () => {
iconClick: () => {
if (n.post) {
markRead(n.id)
router.push(`/posts/${n.post.id}`)
navigateTo(`/posts/${n.post.id}`, { replace: true })
}
},
})
@@ -674,7 +672,7 @@ const fetchNotifications = async () => {
iconClick: () => {
if (n.post) {
markRead(n.id)
router.push(`/posts/${n.post.id}`)
navigateTo(`/posts/${n.post.id}`, { replace: true })
}
},
})
@@ -686,7 +684,7 @@ const fetchNotifications = async () => {
iconClick: () => {
if (n.post) {
markRead(n.id)
router.push(`/posts/${n.post.id}`)
navigateTo(`/posts/${n.post.id}`, { replace: true })
}
},
})

View File

@@ -37,7 +37,7 @@
<script setup>
import { ref, onMounted, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useRoute } from 'vue-router'
import PostEditor from '~/components/PostEditor.vue'
import CategorySelect from '~/components/CategorySelect.vue'
import TagSelect from '~/components/TagSelect.vue'
@@ -56,7 +56,6 @@ const isAiLoading = ref(false)
const isLogin = computed(() => authState.loggedIn)
const route = useRoute()
const router = useRouter()
const postId = route.params.id
const loadPost = async () => {
@@ -197,7 +196,7 @@ const submitPost = async () => {
}
}
const cancelEdit = () => {
router.push(`/posts/${postId}`)
navigateTo(`/posts/${postId}`, { replace: true })
}
</script>

View File

@@ -343,7 +343,7 @@ const startCountdown = () => {
updateCountdown()
countdownTimer = setInterval(updateCountdown, 1000)
}
const gotoUser = (id) => router.push(`/users/${id}`)
const gotoUser = (id) => navigateTo(`/users/${id}`, { replace: true })
const articleMenuItems = computed(() => {
const items = []
if (isAuthor.value || isAdmin.value) {
@@ -394,7 +394,7 @@ const mapComment = (c, parentUserName = '', level = 0) => ({
reply: (c.replies || []).map((r) => mapComment(r, c.author.username, level + 1)),
openReplies: level === 0,
src: c.author.avatar,
iconClick: () => router.push(`/users/${c.author.id}`),
iconClick: () => navigateTo(`/users/${c.author.id}`, { replace: true }),
parentUserName: parentUserName,
})
@@ -641,7 +641,7 @@ const unpinPost = async () => {
}
const editPost = () => {
router.push(`/posts/${postId}/edit`)
navigateTo(`/posts/${postId}/edit`, { replace: true })
}
const deletePost = async () => {
@@ -656,7 +656,7 @@ const deletePost = async () => {
})
if (res.ok) {
toast.success('已删除')
router.push('/')
navigateTo('/', { replace: true })
} else {
toast.error('操作失败')
}
@@ -766,7 +766,7 @@ const jumpToHashComment = async () => {
}
const gotoProfile = () => {
router.push(`/users/${author.value.id}`)
navigateTo(`/users/${author.value.id}`, { replace: true })
}
onMounted(async () => {

View File

@@ -65,6 +65,7 @@
</template>
<script setup>
import { ref, onMounted } from 'vue'
import AvatarCropper from '~/components/AvatarCropper.vue'
import BaseInput from '~/components/BaseInput.vue'
import Dropdown from '~/components/Dropdown.vue'
@@ -72,7 +73,6 @@ import { toast } from '~/main'
import { fetchCurrentUser, getToken, setToken } from '~/utils/auth'
const config = useRuntimeConfig()
const API_BASE_URL = config.public.apiBaseUrl
const router = useRouter()
const username = ref('')
const introduction = ref('')
const usernameError = ref('')
@@ -102,7 +102,7 @@ onMounted(async () => {
}
} else {
toast.error('请先登录')
router.push('/login')
navigateTo('/login', { replace: true })
}
isLoadingPage.value = false
})

View File

@@ -29,10 +29,10 @@ const error = ref('')
const isWaitingForRegister = ref(false)
const token = ref('')
onMounted(() => {
onMounted(async () => {
token.value = route.query.token || ''
if (!token.value) {
router.push('/signup')
await navigateTo({ path: '/signup' }, { replace: true })
}
})
@@ -58,10 +58,10 @@ const submit = async () => {
const data = await res.json()
if (res.ok) {
toast.success('注册理由已提交,请等待审核')
router.push('/')
await navigateTo('/', { replace: true })
} else if (data.reason_code === 'INVALID_CREDENTIALS') {
toast.error('登录已过期,请重新登录')
router.push('/login')
await navigateTo('/login', { replace: true })
} else {
toast.error(data.error || '提交失败')
}

View File

@@ -189,10 +189,10 @@ const verifyCode = async () => {
const data = await res.json()
if (res.ok) {
if (registerMode.value === 'WHITELIST') {
router.push('/signup-reason?token=' + data.token)
navigateTo(`/signup-reason?token=${data.token}`, { replace: true })
} else {
toast.success('注册成功,请登录')
router.push('/login')
navigateTo('/login', { replace: true })
}
} else {
toast.error(data.error || '注册失败')

View File

@@ -2,24 +2,20 @@
<CallbackPage />
</template>
<script>
<script setup>
import CallbackPage from '~/components/CallbackPage.vue'
import { twitterExchange } from '~/utils/twitter'
export default {
name: 'TwitterCallbackPageView',
components: { CallbackPage },
async mounted() {
const url = new URL(window.location.href)
const code = url.searchParams.get('code')
const state = url.searchParams.get('state')
const result = await twitterExchange(code, state, '')
onMounted(async () => {
const url = new URL(window.location.href)
const code = url.searchParams.get('code')
const state = url.searchParams.get('state')
const result = await twitterExchange(code, state, '')
if (result.needReason) {
this.$router.push('/signup-reason?token=' + result.token)
} else {
this.$router.push('/')
}
},
}
if (result.needReason) {
navigateTo(`/signup-reason?token=${result.token}`, { replace: true })
} else {
navigateTo('/', { replace: true })
}
})
</script>

View File

@@ -506,7 +506,7 @@ const unsubscribeUser = async () => {
const gotoTag = (tag) => {
const value = encodeURIComponent(tag.id ?? tag.name)
router.push({ path: '/', query: { tags: value } })
navigateTo({ path: '/', query: { tags: value } }, { replace: true })
}
const init = async () => {

View File

@@ -1,9 +1,11 @@
import { DISCORD_CLIENT_ID, toast } from '../main'
import { WEBSITE_BASE_URL } from '../constants'
import { toast } from '../main'
import { setToken, loadCurrentUser } from './auth'
import { registerPush } from './push'
export function discordAuthorize(state = '') {
const config = useRuntimeConfig()
const WEBSITE_BASE_URL = config.public.websiteBaseUrl
const DISCORD_CLIENT_ID = config.public.discordClientId
if (!DISCORD_CLIENT_ID) {
toast.error('Discord 登录不可用')
return

View File

@@ -1,9 +1,11 @@
import { GITHUB_CLIENT_ID, toast } from '../main'
import { toast } from '../main'
import { setToken, loadCurrentUser } from './auth'
import { WEBSITE_BASE_URL } from '../constants'
import { registerPush } from './push'
export function githubAuthorize(state = '') {
const config = useRuntimeConfig()
const WEBSITE_BASE_URL = config.public.websiteBaseUrl
const GITHUB_CLIENT_ID = config.public.githubClientId
if (!GITHUB_CLIENT_ID) {
toast.error('GitHub 登录不可用')
return

View File

@@ -1,9 +1,11 @@
import { GOOGLE_CLIENT_ID, toast } from '../main'
import { toast } from '../main'
import { setToken, loadCurrentUser } from './auth'
import { registerPush } from './push'
import { WEBSITE_BASE_URL } from '../constants'
export async function googleGetIdToken() {
const config = useRuntimeConfig()
const GOOGLE_CLIENT_ID = config.public.googleClientId
return new Promise((resolve, reject) => {
if (!window.google || !GOOGLE_CLIENT_ID) {
toast.error('Google 登录不可用, 请检查网络设置与VPN')
@@ -20,6 +22,8 @@ export async function googleGetIdToken() {
}
export function googleAuthorize() {
const config = useRuntimeConfig()
const GOOGLE_CLIENT_ID = config.public.googleClientId
if (!GOOGLE_CLIENT_ID) {
toast.error('Google 登录不可用, 请检查网络设置与VPN')
return
@@ -67,15 +71,13 @@ export async function googleSignIn(redirect_success, redirect_not_approved) {
}
}
import router from '../router'
export function loginWithGoogle() {
googleSignIn(
() => {
router.push('/')
navigateTo('/', { replace: true })
},
(token) => {
router.push('/signup-reason?token=' + token)
navigateTo(`/signup-reason?token=${token}`, { replace: true })
},
)
}

View File

@@ -1,5 +1,4 @@
import { TWITTER_CLIENT_ID, toast } from '../main'
import { WEBSITE_BASE_URL } from '../constants'
import { toast } from '../main'
import { setToken, loadCurrentUser } from './auth'
import { registerPush } from './push'
@@ -22,6 +21,9 @@ async function generateCodeChallenge(codeVerifier) {
}
export async function twitterAuthorize(state = '') {
const config = useRuntimeConfig()
const WEBSITE_BASE_URL = config.public.websiteBaseUrl
const TWITTER_CLIENT_ID = config.public.twitterClientId
if (!TWITTER_CLIENT_ID) {
toast.error('Twitter 登录不可用')
return