mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-03-07 04:20:47 +08:00
Merge pull request #520 from AnNingUI/main
fix: 清理掉了大部分warn,优化了在移动端侧边栏的逻辑问题
This commit is contained in:
@@ -1,7 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<div class="header-container">
|
<div class="header-container">
|
||||||
<HeaderComponent @toggle-menu="menuVisible = !menuVisible" :show-menu-btn="!hideMenu" />
|
<HeaderComponent
|
||||||
|
ref="header"
|
||||||
|
@toggle-menu="menuVisible = !menuVisible"
|
||||||
|
:show-menu-btn="!hideMenu"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="main-container">
|
<div class="main-container">
|
||||||
@@ -42,17 +46,26 @@ export default {
|
|||||||
].includes(useRoute().path)
|
].includes(useRoute().path)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const header = useTemplateRef('header')
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
menuVisible.value = window.innerWidth > 768
|
menuVisible.value = window.innerWidth > 768
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleMenuOutside = () => {
|
const handleMenuOutside = (event) => {
|
||||||
if (isMobile.value) menuVisible.value = false
|
const btn = header.value.$refs.menuBtn
|
||||||
|
if (btn && (btn === event.target || btn.contains(event.target))) {
|
||||||
|
return // 如果是菜单按钮的点击,不处理关闭
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMobile.value) {
|
||||||
|
menuVisible.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { menuVisible, hideMenu, handleMenuOutside }
|
return { menuVisible, hideMenu, handleMenuOutside, header }
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="header-content">
|
<div class="header-content">
|
||||||
<div class="header-content-left">
|
<div class="header-content-left">
|
||||||
<div v-if="showMenuBtn" class="menu-btn-wrapper">
|
<div v-if="showMenuBtn" class="menu-btn-wrapper">
|
||||||
<button class="menu-btn" @click="$emit('toggle-menu')">
|
<button class="menu-btn" ref="menuBtn" @click="$emit('toggle-menu')">
|
||||||
<i class="fas fa-bars"></i>
|
<i class="fas fa-bars"></i>
|
||||||
</button>
|
</button>
|
||||||
<span v-if="isMobile && unreadCount > 0" class="menu-unread-dot"></span>
|
<span v-if="isMobile && unreadCount > 0" class="menu-unread-dot"></span>
|
||||||
@@ -49,14 +49,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { authState, clearToken, loadCurrentUser } from '~/utils/auth'
|
import { ClientOnly } from '#components'
|
||||||
import { watch, nextTick, ref, computed } from 'vue'
|
import { computed, nextTick, ref, watch } from 'vue'
|
||||||
import { fetchUnreadCount, notificationState } from '~/utils/notification'
|
import { useRouter } from 'vue-router'
|
||||||
import DropdownMenu from '~/components/DropdownMenu.vue'
|
import DropdownMenu from '~/components/DropdownMenu.vue'
|
||||||
import SearchDropdown from '~/components/SearchDropdown.vue'
|
import SearchDropdown from '~/components/SearchDropdown.vue'
|
||||||
|
import { authState, clearToken, loadCurrentUser } from '~/utils/auth'
|
||||||
|
import { fetchUnreadCount, notificationState } from '~/utils/notification'
|
||||||
import { useIsMobile } from '~/utils/screen'
|
import { useIsMobile } from '~/utils/screen'
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { ClientOnly } from '#components'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HeaderComponent',
|
name: 'HeaderComponent',
|
||||||
@@ -67,7 +67,7 @@ export default {
|
|||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup(props, { expose }) {
|
||||||
const isLogin = computed(() => authState.loggedIn)
|
const isLogin = computed(() => authState.loggedIn)
|
||||||
const isMobile = useIsMobile()
|
const isMobile = useIsMobile()
|
||||||
const unreadCount = computed(() => notificationState.unreadCount)
|
const unreadCount = computed(() => notificationState.unreadCount)
|
||||||
@@ -76,6 +76,11 @@ export default {
|
|||||||
const showSearch = ref(false)
|
const showSearch = ref(false)
|
||||||
const searchDropdown = ref(null)
|
const searchDropdown = ref(null)
|
||||||
const userMenu = ref(null)
|
const userMenu = ref(null)
|
||||||
|
const menuBtn = ref(null)
|
||||||
|
|
||||||
|
expose({
|
||||||
|
menuBtn,
|
||||||
|
})
|
||||||
|
|
||||||
const goToHome = () => {
|
const goToHome = () => {
|
||||||
router.push('/').then(() => {
|
router.push('/').then(() => {
|
||||||
@@ -183,6 +188,7 @@ export default {
|
|||||||
searchDropdown,
|
searchDropdown,
|
||||||
userMenu,
|
userMenu,
|
||||||
avatar,
|
avatar,
|
||||||
|
menuBtn,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -210,17 +210,13 @@ export default {
|
|||||||
|
|
||||||
const gotoCategory = (c) => {
|
const gotoCategory = (c) => {
|
||||||
const value = encodeURIComponent(c.id ?? c.name)
|
const value = encodeURIComponent(c.id ?? c.name)
|
||||||
router.push({ path: '/', query: { category: value } }).then(() => {
|
router.push({ path: '/', query: { category: value } })
|
||||||
window.location.reload()
|
|
||||||
})
|
|
||||||
handleItemClick()
|
handleItemClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
const gotoTag = (t) => {
|
const gotoTag = (t) => {
|
||||||
const value = encodeURIComponent(t.id ?? t.name)
|
const value = encodeURIComponent(t.id ?? t.name)
|
||||||
router.push({ path: '/', query: { tags: value } }).then(() => {
|
router.push({ path: '/', query: { tags: value } })
|
||||||
window.location.reload()
|
|
||||||
})
|
|
||||||
handleItemClick()
|
handleItemClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,4 +43,9 @@ export default defineNuxtConfig({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
vue: {
|
||||||
|
compilerOptions: {
|
||||||
|
isCustomElement: (tag) => ['l-hatch', 'l-hatch-spinner'].includes(tag),
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -113,18 +113,17 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import ArticleCategory from '~/components/ArticleCategory.vue'
|
||||||
import { useScrollLoadMore } from '~/utils/loadMore'
|
import ArticleTags from '~/components/ArticleTags.vue'
|
||||||
import { stripMarkdown } from '~/utils/markdown'
|
import CategorySelect from '~/components/CategorySelect.vue'
|
||||||
|
import SearchDropdown from '~/components/SearchDropdown.vue'
|
||||||
|
import TagSelect from '~/components/TagSelect.vue'
|
||||||
import { API_BASE_URL } from '~/main'
|
import { API_BASE_URL } from '~/main'
|
||||||
import { getToken } from '~/utils/auth'
|
import { getToken } from '~/utils/auth'
|
||||||
import TimeManager from '~/utils/time'
|
import { useScrollLoadMore } from '~/utils/loadMore'
|
||||||
import CategorySelect from '~/components/CategorySelect.vue'
|
import { stripMarkdown } from '~/utils/markdown'
|
||||||
import TagSelect from '~/components/TagSelect.vue'
|
|
||||||
import ArticleTags from '~/components/ArticleTags.vue'
|
|
||||||
import ArticleCategory from '~/components/ArticleCategory.vue'
|
|
||||||
import SearchDropdown from '~/components/SearchDropdown.vue'
|
|
||||||
import { useIsMobile } from '~/utils/screen'
|
import { useIsMobile } from '~/utils/screen'
|
||||||
|
import TimeManager from '~/utils/time'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HomePageView',
|
name: 'HomePageView',
|
||||||
@@ -150,22 +149,9 @@ export default {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
const route = useRoute()
|
|
||||||
const selectedCategory = ref('')
|
const selectedCategory = ref('')
|
||||||
if (route.query.category) {
|
|
||||||
const c = decodeURIComponent(route.query.category)
|
|
||||||
selectedCategory.value = isNaN(c) ? c : Number(c)
|
|
||||||
}
|
|
||||||
const selectedTags = ref([])
|
const selectedTags = ref([])
|
||||||
if (route.query.tags) {
|
const route = useRoute()
|
||||||
const t = Array.isArray(route.query.tags) ? route.query.tags.join(',') : route.query.tags
|
|
||||||
selectedTags.value = t
|
|
||||||
.split(',')
|
|
||||||
.filter((v) => v)
|
|
||||||
.map((v) => decodeURIComponent(v))
|
|
||||||
.map((v) => (isNaN(v) ? v : Number(v)))
|
|
||||||
}
|
|
||||||
|
|
||||||
const tagOptions = ref([])
|
const tagOptions = ref([])
|
||||||
const categoryOptions = ref([])
|
const categoryOptions = ref([])
|
||||||
const isLoadingPosts = ref(false)
|
const isLoadingPosts = ref(false)
|
||||||
@@ -177,13 +163,50 @@ export default {
|
|||||||
? '最新'
|
? '最新'
|
||||||
: '最新回复',
|
: '最新回复',
|
||||||
)
|
)
|
||||||
|
|
||||||
const articles = ref([])
|
const articles = ref([])
|
||||||
const page = ref(0)
|
const page = ref(0)
|
||||||
const pageSize = 10
|
const pageSize = 10
|
||||||
const isMobile = useIsMobile()
|
const isMobile = useIsMobile()
|
||||||
const allLoaded = ref(false)
|
const allLoaded = ref(false)
|
||||||
|
|
||||||
|
const selectedCategorySet = (category) => {
|
||||||
|
const c = decodeURIComponent(category)
|
||||||
|
selectedCategory.value = isNaN(c) ? c : Number(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedTagsSet = (tags) => {
|
||||||
|
const t = Array.isArray(tags) ? tags.join(',') : tags
|
||||||
|
selectedTags.value = t
|
||||||
|
.split(',')
|
||||||
|
.filter((v) => v)
|
||||||
|
.map((v) => decodeURIComponent(v))
|
||||||
|
.map((v) => (isNaN(v) ? v : Number(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const query = route.query
|
||||||
|
const category = query.category
|
||||||
|
const tags = query.tags
|
||||||
|
|
||||||
|
if (category) {
|
||||||
|
selectedCategorySet(category)
|
||||||
|
}
|
||||||
|
if (tags) {
|
||||||
|
selectedTagsSet(tags)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.query,
|
||||||
|
() => {
|
||||||
|
const query = route.query
|
||||||
|
const category = query.category
|
||||||
|
const tags = query.tags
|
||||||
|
category && selectedCategorySet(category)
|
||||||
|
tags && selectedTagsSet(tags)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
const loadOptions = async () => {
|
const loadOptions = async () => {
|
||||||
if (selectedCategory.value && !isNaN(selectedCategory.value)) {
|
if (selectedCategory.value && !isNaN(selectedCategory.value)) {
|
||||||
try {
|
try {
|
||||||
@@ -696,6 +719,7 @@ export default {
|
|||||||
.header-item.activity {
|
.header-item.activity {
|
||||||
width: 10%;
|
width: 10%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.article-member-avatar-item:nth-child(n + 4) {
|
.article-member-avatar-item:nth-child(n + 4) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -249,6 +249,7 @@ import TimeManager from '~/utils/time'
|
|||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useIsMobile } from '~/utils/screen'
|
import { useIsMobile } from '~/utils/screen'
|
||||||
import Dropdown from '~/components/Dropdown.vue'
|
import Dropdown from '~/components/Dropdown.vue'
|
||||||
|
import { ClientOnly } from '#components'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PostPageView',
|
name: 'PostPageView',
|
||||||
@@ -262,6 +263,7 @@ export default {
|
|||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
VueEasyLightbox,
|
VueEasyLightbox,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
|
ClientOnly,
|
||||||
},
|
},
|
||||||
async setup() {
|
async setup() {
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|||||||
@@ -297,27 +297,26 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { ref, computed, onMounted, watch } from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { API_BASE_URL, toast } from '~/main'
|
import AchievementList from '~/components/AchievementList.vue'
|
||||||
import { getToken, authState } from '~/utils/auth'
|
|
||||||
import BaseTimeline from '~/components/BaseTimeline.vue'
|
|
||||||
import UserList from '~/components/UserList.vue'
|
|
||||||
import BasePlaceholder from '~/components/BasePlaceholder.vue'
|
import BasePlaceholder from '~/components/BasePlaceholder.vue'
|
||||||
|
import BaseTimeline from '~/components/BaseTimeline.vue'
|
||||||
import LevelProgress from '~/components/LevelProgress.vue'
|
import LevelProgress from '~/components/LevelProgress.vue'
|
||||||
|
import UserList from '~/components/UserList.vue'
|
||||||
|
import { API_BASE_URL, toast } from '~/main'
|
||||||
|
import { authState, getToken } from '~/utils/auth'
|
||||||
|
import { prevLevelExp } from '~/utils/level'
|
||||||
import { stripMarkdown, stripMarkdownLength } from '~/utils/markdown'
|
import { stripMarkdown, stripMarkdownLength } from '~/utils/markdown'
|
||||||
import TimeManager from '~/utils/time'
|
import TimeManager from '~/utils/time'
|
||||||
import { prevLevelExp } from '~/utils/level'
|
|
||||||
import AchievementList from '~/components/AchievementList.vue'
|
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
alias: ['/users/:id/'],
|
|
||||||
})
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ProfileView',
|
name: 'ProfileView',
|
||||||
components: { BaseTimeline, UserList, BasePlaceholder, LevelProgress, AchievementList },
|
components: { BaseTimeline, UserList, BasePlaceholder, LevelProgress, AchievementList },
|
||||||
setup() {
|
setup() {
|
||||||
|
definePageMeta({
|
||||||
|
alias: ['/users/:id/'],
|
||||||
|
})
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const username = route.params.id
|
const username = route.params.id
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// plugins/ldrs.client.ts
|
// plugins/ldrs.client.ts
|
||||||
import { defineNuxtPlugin } from '#app'
|
import { defineNuxtPlugin } from 'nuxt/app'
|
||||||
|
|
||||||
export default defineNuxtPlugin(async () => {
|
export default defineNuxtPlugin(async () => {
|
||||||
// 动态引入,防止打包时把 ldrs 拉进 SSR bundle
|
// 动态引入,防止打包时把 ldrs 拉进 SSR bundle
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import NProgress from 'nprogress'
|
import NProgress from 'nprogress'
|
||||||
import 'nprogress/nprogress.css'
|
import 'nprogress/nprogress.css'
|
||||||
|
import { defineNuxtPlugin } from 'nuxt/app'
|
||||||
|
|
||||||
export default defineNuxtPlugin((nuxtApp) => {
|
export default defineNuxtPlugin((nuxtApp) => {
|
||||||
NProgress.configure({ showSpinner: false })
|
NProgress.configure({ showSpinner: false })
|
||||||
@@ -12,7 +13,7 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|||||||
NProgress.done()
|
NProgress.done()
|
||||||
})
|
})
|
||||||
|
|
||||||
nuxtApp.hook('page:error', () => {
|
nuxtApp.hook('app:error', () => {
|
||||||
NProgress.done()
|
NProgress.done()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineNuxtPlugin } from '#app'
|
import { defineNuxtPlugin } from 'nuxt/app'
|
||||||
import { initTheme } from '~/utils/theme'
|
import { initTheme } from '~/utils/theme'
|
||||||
|
|
||||||
export default defineNuxtPlugin(() => {
|
export default defineNuxtPlugin(() => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineNuxtPlugin } from '#app'
|
import { defineNuxtPlugin } from 'nuxt/app'
|
||||||
import 'vue-toastification/dist/index.css'
|
import 'vue-toastification/dist/index.css'
|
||||||
import '~/assets/toast.css'
|
import '~/assets/toast.css'
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import MarkdownIt from 'markdown-it'
|
|
||||||
import hljs from 'highlight.js'
|
import hljs from 'highlight.js'
|
||||||
import 'highlight.js/styles/github.css'
|
import 'highlight.js/styles/github.css'
|
||||||
|
import MarkdownIt from 'markdown-it'
|
||||||
import { toast } from '../main'
|
import { toast } from '../main'
|
||||||
import { tiebaEmoji } from './tiebaEmoji'
|
import { tiebaEmoji } from './tiebaEmoji'
|
||||||
|
|
||||||
@@ -86,18 +86,19 @@ export function handleMarkdownClick(e) {
|
|||||||
|
|
||||||
export function stripMarkdown(text) {
|
export function stripMarkdown(text) {
|
||||||
const html = md.render(text || '')
|
const html = md.render(text || '')
|
||||||
// SSR 环境下没有 document
|
|
||||||
if (typeof window === 'undefined') {
|
// 统一使用正则表达式方法,确保服务端和客户端行为一致
|
||||||
// 用正则去除 HTML 标签
|
let plainText = html.replace(/<[^>]+>/g, '')
|
||||||
return html
|
|
||||||
.replace(/<[^>]+>/g, '')
|
// 标准化空白字符处理
|
||||||
.replace(/\s+/g, ' ')
|
plainText = plainText
|
||||||
.trim()
|
.replace(/\r\n/g, '\n') // Windows换行符转为Unix格式
|
||||||
} else {
|
.replace(/\r/g, '\n') // 旧Mac换行符转为Unix格式
|
||||||
const el = document.createElement('div')
|
.replace(/[ \t]+/g, ' ') // 合并空格和制表符为单个空格
|
||||||
el.innerHTML = html
|
.replace(/\n{3,}/g, '\n\n') // 最多保留两个连续换行(一个空行)
|
||||||
return el.textContent || el.innerText || ''
|
.trim()
|
||||||
}
|
|
||||||
|
return plainText
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stripMarkdownLength(text, length) {
|
export function stripMarkdownLength(text, length) {
|
||||||
|
|||||||
Reference in New Issue
Block a user