mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-08 16:11:05 +08:00
Compare commits
6 Commits
feature/ag
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76dd57b858 | ||
|
|
18edde64c3 | ||
|
|
ebc79f36e7 | ||
|
|
a7fbd1eb75 | ||
|
|
f773d17748 | ||
|
|
15d36709c3 |
@@ -21,7 +21,7 @@ const props = defineProps({
|
||||
const gotoCategory = async () => {
|
||||
if (!props.category) return
|
||||
const value = encodeURIComponent(props.category.id ?? props.category.name)
|
||||
await navigateTo({ path: '/', query: { category: value } }, { replace: true })
|
||||
await navigateTo({ path: '/', query: { category: value } })
|
||||
}
|
||||
|
||||
const isImageIcon = (icon) => {
|
||||
|
||||
@@ -30,7 +30,7 @@ defineProps({
|
||||
|
||||
const gotoTag = async (tag) => {
|
||||
const value = encodeURIComponent(tag.id ?? tag.name)
|
||||
await navigateTo({ path: '/', query: { tags: value } }, { replace: true })
|
||||
await navigateTo({ path: '/', query: { tags: value } })
|
||||
}
|
||||
|
||||
const isImageIcon = (icon) => {
|
||||
|
||||
@@ -357,13 +357,13 @@ const isImageIcon = (icon) => {
|
||||
|
||||
const gotoCategory = (c) => {
|
||||
const value = encodeURIComponent(c.id ?? c.name)
|
||||
navigateTo({ path: '/', query: { category: value } }, { replace: true })
|
||||
navigateTo({ path: '/', query: { category: value } })
|
||||
handleItemClick()
|
||||
}
|
||||
|
||||
const gotoTag = (t) => {
|
||||
const value = encodeURIComponent(t.id ?? t.name)
|
||||
navigateTo({ path: '/', query: { tags: value } }, { replace: true })
|
||||
navigateTo({ path: '/', query: { tags: value } })
|
||||
handleItemClick()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -170,9 +170,9 @@ watch(selected, (val) => {
|
||||
navigateTo(`/posts/${opt.postId}#comment-${opt.id}`, { replace: true })
|
||||
}
|
||||
} else if (opt.type === 'category') {
|
||||
navigateTo({ path: '/', query: { category: opt.id } }, { replace: true })
|
||||
navigateTo({ path: '/', query: { category: opt.id } })
|
||||
} else if (opt.type === 'tag') {
|
||||
navigateTo({ path: '/', query: { tags: opt.id } }, { replace: true })
|
||||
navigateTo({ path: '/', query: { tags: opt.id } })
|
||||
}
|
||||
selected.value = null
|
||||
keyword.value = ''
|
||||
|
||||
@@ -202,6 +202,28 @@ const selectedTagsSet = (tags) => {
|
||||
.map((v) => (isNaN(v) ? v : Number(v)))
|
||||
}
|
||||
|
||||
const normalizeCategoryFromQuery = (category) => {
|
||||
if (category == null || category === '') return ''
|
||||
const raw = Array.isArray(category) ? category[0] : category
|
||||
const decoded = decodeURIComponent(raw)
|
||||
return isNaN(decoded) ? decoded : Number(decoded)
|
||||
}
|
||||
|
||||
const normalizeTagsFromQuery = (tags) => {
|
||||
if (tags == null || tags === '') return []
|
||||
const raw = Array.isArray(tags) ? tags.join(',') : tags
|
||||
return raw
|
||||
.split(',')
|
||||
.filter((v) => v)
|
||||
.map((v) => decodeURIComponent(v))
|
||||
.map((v) => (isNaN(v) ? v : Number(v)))
|
||||
}
|
||||
|
||||
const arraysShallowEqual = (a = [], b = []) => {
|
||||
if (a.length !== b.length) return false
|
||||
return a.every((v, idx) => String(v) === String(b[idx]))
|
||||
}
|
||||
|
||||
/** 初始化:仅在客户端首渲染时根据路由同步一次 **/
|
||||
onMounted(() => {
|
||||
const { category, tags } = route.query
|
||||
@@ -239,6 +261,32 @@ watch(
|
||||
},
|
||||
)
|
||||
|
||||
// 从筛选器变更回写到 URL,确保浏览器历史可回退到上一个筛选状态。
|
||||
watch([selectedCategory, selectedTags], async ([category, tags]) => {
|
||||
const routeCategory = normalizeCategoryFromQuery(route.query.category)
|
||||
const routeTags = normalizeTagsFromQuery(route.query.tags)
|
||||
|
||||
const categoryChanged = String(category ?? '') !== String(routeCategory ?? '')
|
||||
const tagsChanged = !arraysShallowEqual(tags || [], routeTags)
|
||||
if (!categoryChanged && !tagsChanged) return
|
||||
|
||||
const nextQuery = { ...route.query }
|
||||
|
||||
if (category == null || category === '') {
|
||||
delete nextQuery.category
|
||||
} else {
|
||||
nextQuery.category = encodeURIComponent(String(category))
|
||||
}
|
||||
|
||||
if (!Array.isArray(tags) || tags.length === 0) {
|
||||
delete nextQuery.tags
|
||||
} else {
|
||||
nextQuery.tags = tags.map((v) => encodeURIComponent(String(v))).join(',')
|
||||
}
|
||||
|
||||
await navigateTo({ path: '/', query: nextQuery })
|
||||
})
|
||||
|
||||
/** 选项加载(分类/标签名称回填) **/
|
||||
const loadOptions = async () => {
|
||||
if (selectedCategory.value && !isNaN(selectedCategory.value)) {
|
||||
|
||||
@@ -643,7 +643,7 @@ const sendMessage = async () => {
|
||||
|
||||
const gotoTag = (tag) => {
|
||||
const value = encodeURIComponent(tag.id ?? tag.name)
|
||||
navigateTo({ path: '/', query: { tags: value } }, { replace: true })
|
||||
navigateTo({ path: '/', query: { tags: value } })
|
||||
}
|
||||
|
||||
const init = async () => {
|
||||
|
||||
@@ -82,6 +82,56 @@ export async function markNotificationsRead(ids) {
|
||||
}
|
||||
}
|
||||
|
||||
const MARK_ALL_FETCH_SIZE = 100
|
||||
const MARK_ALL_CHUNK_SIZE = 200
|
||||
const MARK_ALL_MAX_PAGES = 200
|
||||
|
||||
async function fetchUnreadNotificationsPage(page, size) {
|
||||
const config = useRuntimeConfig()
|
||||
const API_BASE_URL = config.public.apiBaseUrl
|
||||
const token = getToken()
|
||||
if (!token) throw new Error('NO_TOKEN')
|
||||
|
||||
const res = await fetch(`${API_BASE_URL}/api/notifications/unread?page=${page}&size=${size}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
})
|
||||
|
||||
if (!res.ok) throw new Error('FETCH_UNREAD_FAILED')
|
||||
const data = await res.json()
|
||||
return Array.isArray(data) ? data : []
|
||||
}
|
||||
|
||||
async function collectUnreadNotificationIds(excludedTypes = []) {
|
||||
const excludedTypeSet = new Set(excludedTypes)
|
||||
const ids = []
|
||||
|
||||
for (let page = 0; page < MARK_ALL_MAX_PAGES; page++) {
|
||||
const pageData = await fetchUnreadNotificationsPage(page, MARK_ALL_FETCH_SIZE)
|
||||
if (pageData.length === 0) break
|
||||
|
||||
for (const notification of pageData) {
|
||||
if (!notification || excludedTypeSet.has(notification.type)) continue
|
||||
if (typeof notification.id !== 'number') continue
|
||||
ids.push(notification.id)
|
||||
}
|
||||
|
||||
if (pageData.length < MARK_ALL_FETCH_SIZE) break
|
||||
}
|
||||
|
||||
return [...new Set(ids)]
|
||||
}
|
||||
|
||||
async function markNotificationsReadInChunks(ids) {
|
||||
for (let i = 0; i < ids.length; i += MARK_ALL_CHUNK_SIZE) {
|
||||
const chunk = ids.slice(i, i + MARK_ALL_CHUNK_SIZE)
|
||||
const ok = await markNotificationsRead(chunk)
|
||||
if (!ok) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export async function fetchNotificationPreferences() {
|
||||
try {
|
||||
const config = useRuntimeConfig()
|
||||
@@ -390,29 +440,37 @@ function createFetchNotifications() {
|
||||
}
|
||||
|
||||
const markAllRead = async () => {
|
||||
// 除了 REGISTER_REQUEST 类型消息
|
||||
const idsToMark = notifications.value
|
||||
// 为了覆盖分页中的全部未读,先从后端分页拉取全部未读 ID(排除 REGISTER_REQUEST)。
|
||||
const localIdsToMark = notifications.value
|
||||
.filter((n) => n.type !== 'REGISTER_REQUEST' && !n.read)
|
||||
.map((n) => n.id)
|
||||
if (idsToMark.length === 0) return
|
||||
|
||||
notifications.value.forEach((n) => {
|
||||
if (n.type !== 'REGISTER_REQUEST') n.read = true
|
||||
})
|
||||
notificationState.unreadCount = notifications.value.filter((n) => !n.read).length
|
||||
const ok = await markNotificationsRead(idsToMark)
|
||||
if (!ok) {
|
||||
|
||||
try {
|
||||
const idsToMark = await collectUnreadNotificationIds(['REGISTER_REQUEST'])
|
||||
if (idsToMark.length > 0) {
|
||||
const ok = await markNotificationsReadInChunks(idsToMark)
|
||||
if (!ok) throw new Error('MARK_READ_FAILED')
|
||||
}
|
||||
|
||||
await fetchUnreadCount()
|
||||
if (authState.role === 'ADMIN') {
|
||||
toast.success('已读所有消息(注册请求除外)')
|
||||
} else {
|
||||
toast.success('已读所有消息')
|
||||
}
|
||||
} catch (e) {
|
||||
notifications.value.forEach((n) => {
|
||||
if (idsToMark.includes(n.id)) n.read = false
|
||||
if (localIdsToMark.includes(n.id)) n.read = false
|
||||
})
|
||||
await fetchUnreadCount()
|
||||
toast.error('已读操作失败,请稍后重试')
|
||||
return
|
||||
}
|
||||
fetchUnreadCount()
|
||||
if (authState.role === 'ADMIN') {
|
||||
toast.success('已读所有消息(注册请求除外)')
|
||||
} else {
|
||||
toast.success('已读所有消息')
|
||||
}
|
||||
}
|
||||
return {
|
||||
fetchNotifications,
|
||||
|
||||
Reference in New Issue
Block a user