Merge pull request #201 from nagisa77/codex/add-404-page-and-disable-editors

Add 404 and login overlays
This commit is contained in:
Tim
2025-07-14 18:19:55 +08:00
committed by GitHub
8 changed files with 186 additions and 15 deletions

View File

@@ -19,7 +19,7 @@
</template>
<script>
import { ref, onMounted, computed } from 'vue'
import { ref, onMounted, computed, watch } from 'vue'
import Vditor from 'vditor'
import 'vditor/dist/index.css'
@@ -34,13 +34,17 @@ export default {
loading: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
}
},
setup(props, { emit }) {
const vditorInstance = ref(null)
const text = ref('')
const isDisabled = computed(() => props.loading || !text.value.trim())
const isDisabled = computed(() => props.loading || props.disabled || !text.value.trim())
const submit = () => {
if (!vditorInstance.value || isDisabled.value) return
@@ -79,12 +83,39 @@ export default {
'image'
],
toolbarConfig: { pin: true },
input(value) {
text.value = value
}
})
input(value) {
text.value = value
}
})
if (props.disabled || props.loading) {
vditorInstance.value.disabled()
}
})
watch(
() => props.loading,
val => {
if (!vditorInstance.value) return
if (val) {
vditorInstance.value.disabled()
} else if (!props.disabled) {
vditorInstance.value.enable()
}
}
)
watch(
() => props.disabled,
val => {
if (!vditorInstance.value) return
if (val) {
vditorInstance.value.disabled()
} else if (!props.loading) {
vditorInstance.value.enable()
}
}
)
return { submit, isDisabled }
}
}

View File

@@ -0,0 +1,39 @@
<template>
<div class="login-overlay" @click="goLogin">
<div class="login-overlay-text">
请先登录点击跳转到登录页面
</div>
</div>
</template>
<script>
import { useRouter } from 'vue-router'
export default {
name: 'LoginOverlay',
setup() {
const router = useRouter()
const goLogin = () => {
router.push('/login')
}
return { goLogin }
}
}
</script>
<style scoped>
.login-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.7);
display: flex;
align-items: center;
justify-content: center;
text-align: center;
cursor: pointer;
z-index: 15;
}
</style>

View File

@@ -32,6 +32,10 @@ export default {
loading: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
}
},
setup(props, { emit }) {
@@ -48,6 +52,18 @@ export default {
}
)
watch(
() => props.disabled,
val => {
if (!vditorInstance.value) return
if (val) {
vditorInstance.value.disabled()
} else if (!props.loading) {
vditorInstance.value.enable()
}
}
)
watch(
() => props.modelValue,
val => {
@@ -118,6 +134,9 @@ export default {
},
after() {
vditorInstance.value.setValue(props.modelValue)
if (props.loading || props.disabled) {
vditorInstance.value.disabled()
}
}
})
})

View File

@@ -8,6 +8,7 @@ import SignupPageView from '../views/SignupPageView.vue'
import NewPostPageView from '../views/NewPostPageView.vue'
import SettingsPageView from '../views/SettingsPageView.vue'
import ProfileView from '../views/ProfileView.vue'
import NotFoundPageView from '../views/NotFoundPageView.vue'
const routes = [
{
@@ -55,6 +56,15 @@ const routes = [
name: 'users',
component: ProfileView
},
{
path: '/404',
name: 'not-found',
component: NotFoundPageView
},
{
path: '/:pathMatch(.*)*',
redirect: '/404'
}
]
const router = createRouter({

View File

@@ -3,7 +3,8 @@
<div class="new-post-form">
<input class="post-title-input" v-model="title" placeholder="标题" />
<div class="post-editor-container">
<PostEditor v-model="content" :loading="isAiLoading" />
<PostEditor v-model="content" :loading="isAiLoading" :disabled="!isLogin" />
<LoginOverlay v-if="!isLogin" />
</div>
<div class="post-options">
<div class="post-options-left">
@@ -22,7 +23,12 @@
<i class="fa-solid fa-floppy-disk"></i>
存草稿
</div>
<div v-if="!isWaitingPosting" class="post-submit" @click="submitPost">发布</div>
<div
v-if="!isWaitingPosting"
class="post-submit"
:class="{ disabled: !isLogin }"
@click="isLogin && submitPost"
>发布</div>
<div v-else class="post-submit-loading"> <i class="fa-solid fa-spinner fa-spin"></i> 发布中...</div>
</div>
</div>
@@ -31,16 +37,17 @@
</template>
<script>
import { ref, onMounted } from 'vue'
import { ref, onMounted, computed } from 'vue'
import PostEditor from '../components/PostEditor.vue'
import CategorySelect from '../components/CategorySelect.vue'
import TagSelect from '../components/TagSelect.vue'
import { API_BASE_URL, toast } from '../main'
import { getToken } from '../utils/auth'
import { getToken, authState } from '../utils/auth'
import LoginOverlay from '../components/LoginOverlay.vue'
export default {
name: 'NewPostPageView',
components: { PostEditor, CategorySelect, TagSelect },
components: { PostEditor, CategorySelect, TagSelect, LoginOverlay },
setup() {
const title = ref('')
const content = ref('')
@@ -48,6 +55,7 @@ export default {
const selectedTags = ref([])
const isWaitingPosting = ref(false)
const isAiLoading = ref(false)
const isLogin = computed(() => authState.loggedIn)
const loadDraft = async () => {
const token = getToken()
@@ -237,7 +245,7 @@ export default {
isWaitingPosting.value = false
}
}
return { title, content, selectedCategory, selectedTags, submitPost, saveDraft, clearPost, isWaitingPosting, aiGenerate, isAiLoading }
return { title, content, selectedCategory, selectedTags, submitPost, saveDraft, clearPost, isWaitingPosting, aiGenerate, isAiLoading, isLogin }
}
}
</script>
@@ -296,6 +304,10 @@ export default {
opacity: 0.7;
}
.post-editor-container {
position: relative;
}
.post-submit {
background-color: var(--primary-color);
color: #fff;
@@ -305,9 +317,17 @@ export default {
cursor: pointer;
}
.post-submit.disabled {
background-color: var(--primary-color-disabled);
cursor: not-allowed;
}
.post-submit:hover {
background-color: var(--primary-color-hover);
}
.post-submit.disabled:hover {
background-color: var(--primary-color-disabled);
}
.post-submit-loading {
color: white;

View File

@@ -0,0 +1,33 @@
<template>
<div class="not-found-page">
<h1>404 - 页面不存在</h1>
<p>你访问的页面不存在或已被删除</p>
<router-link to="/">返回首页</router-link>
</div>
</template>
<script>
export default {
name: 'NotFoundPageView'
}
</script>
<style scoped>
.not-found-page {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: calc(100vh - var(--header-height));
text-align: center;
}
.not-found-page h1 {
margin-bottom: 20px;
}
.not-found-page a {
color: var(--primary-color);
text-decoration: underline;
}
</style>

View File

@@ -59,7 +59,14 @@
</div>
</div>
<CommentEditor @submit="postComment" :loading="isWaitingPostingComment" />
<div class="comment-editor-wrapper">
<CommentEditor
@submit="postComment"
:loading="isWaitingPostingComment"
:disabled="!loggedIn"
/>
<LoginOverlay v-if="!loggedIn" />
</div>
<div class="comments-container">
<BaseTimeline :items="comments">
@@ -104,6 +111,7 @@ import ArticleTags from '../components/ArticleTags.vue'
import ArticleCategory from '../components/ArticleCategory.vue'
import ReactionsGroup from '../components/ReactionsGroup.vue'
import DropdownMenu from '../components/DropdownMenu.vue'
import LoginOverlay from '../components/LoginOverlay.vue'
import { renderMarkdown } from '../utils/markdown'
import { API_BASE_URL, toast } from '../main'
import { getToken, authState } from '../utils/auth'
@@ -114,7 +122,7 @@ hatch.register()
export default {
name: 'PostPageView',
components: { CommentItem, CommentEditor, BaseTimeline, ArticleTags, ArticleCategory, ReactionsGroup, DropdownMenu },
components: { CommentItem, CommentEditor, BaseTimeline, ArticleTags, ArticleCategory, ReactionsGroup, DropdownMenu, LoginOverlay },
setup() {
const route = useRoute()
const postId = route.params.id
@@ -235,7 +243,12 @@ export default {
headers: { Authorization: token ? `Bearer ${token}` : '' }
})
isWaitingFetchingPost.value = false;
if (!res.ok) return
if (!res.ok) {
if (res.status === 404) {
router.replace('/404')
}
return
}
const data = await res.json()
postContent.value = data.content
author.value = data.author
@@ -779,4 +792,8 @@ export default {
.copy-link:hover {
background-color: #e2e2e2;
}
.comment-editor-wrapper {
position: relative;
}
</style>

View File

@@ -281,6 +281,8 @@ export default {
const data = await res.json()
user.value = data
subscribed.value = !!data.subscribed
} else if (res.status === 404) {
router.replace('/404')
}
}