Merge pull request #168 from nagisa77/2gqwih-codex

Fix filter dropdown preloading
This commit is contained in:
Tim
2025-07-10 13:53:47 +08:00
committed by GitHub
4 changed files with 91 additions and 23 deletions

View File

@@ -1,5 +1,5 @@
<template>
<Dropdown v-model="selected" :fetch-options="fetchCategories" placeholder="选择分类">
<Dropdown v-model="selected" :fetch-options="fetchCategories" placeholder="选择分类" :initial-options="providedOptions">
<template #option="{ option }">
<div class="option-container">
<div class="option-main">
@@ -17,7 +17,7 @@
</template>
<script>
import { computed } from 'vue'
import { computed, ref, watch } from 'vue'
import { API_BASE_URL } from '../main'
import Dropdown from './Dropdown.vue'
@@ -25,11 +25,24 @@ export default {
name: 'CategorySelect',
components: { Dropdown },
props: {
modelValue: { type: [String, Number], default: '' }
modelValue: { type: [String, Number], default: '' },
options: { type: Array, default: () => [] }
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const providedOptions = ref(Array.isArray(props.options) ? [...props.options] : [])
watch(
() => props.options,
val => {
providedOptions.value = Array.isArray(val) ? [...val] : []
}
)
const fetchCategories = async () => {
if (providedOptions.value.length) {
return [{ id: '', name: '无分类' }, ...providedOptions.value]
}
const res = await fetch(`${API_BASE_URL}/api/categories`)
if (!res.ok) return []
const data = await res.json()
@@ -46,7 +59,7 @@ export default {
set: v => emit('update:modelValue', v)
})
return { fetchCategories, selected, isImageIcon }
return { fetchCategories, selected, isImageIcon, providedOptions }
}
}
</script>

View File

@@ -71,7 +71,8 @@ export default {
remote: { type: Boolean, default: false },
menuClass: { type: String, default: '' },
optionClass: { type: String, default: '' },
showSearch: { type: Boolean, default: true }
showSearch: { type: Boolean, default: true },
initialOptions: { type: Array, default: () => [] }
},
emits: ['update:modelValue'],
setup(props, { emit }) {
@@ -80,7 +81,7 @@ export default {
const setSearch = (val) => {
search.value = val
}
const options = ref([])
const options = ref(Array.isArray(props.initialOptions) ? [...props.initialOptions] : [])
const loaded = ref(false)
const loading = ref(false)
const wrapper = ref(null)
@@ -136,6 +137,15 @@ export default {
}
}
watch(
() => props.initialOptions,
val => {
if (Array.isArray(val)) {
options.value = [...val]
}
}
)
watch(open, async val => {
if (val) {
if (props.remote) {

View File

@@ -1,5 +1,5 @@
<template>
<Dropdown v-model="selected" :fetch-options="fetchTags" multiple placeholder="选择标签" remote>
<Dropdown v-model="selected" :fetch-options="fetchTags" multiple placeholder="选择标签" remote :initial-options="mergedOptions">
<template #option="{ option }">
<div class="option-container">
<div class="option-main">
@@ -17,7 +17,7 @@
</template>
<script>
import { computed, ref } from 'vue'
import { computed, ref, watch } from 'vue'
import { API_BASE_URL, toast } from '../main'
import Dropdown from './Dropdown.vue'
@@ -26,11 +26,25 @@ export default {
components: { Dropdown },
props: {
modelValue: { type: Array, default: () => [] },
creatable: { type: Boolean, default: false }
creatable: { type: Boolean, default: false },
options: { type: Array, default: () => [] }
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const localTags = ref([])
const providedTags = ref(Array.isArray(props.options) ? [...props.options] : [])
watch(
() => props.options,
val => {
providedTags.value = Array.isArray(val) ? [...val] : []
}
)
const mergedOptions = computed(() => {
const arr = [...providedTags.value, ...localTags.value]
return arr.filter((v, i, a) => a.findIndex(t => t.id === v.id) === i)
})
const isImageIcon = icon => {
if (!icon) return false
@@ -38,17 +52,21 @@ export default {
}
const fetchTags = async (kw = '') => {
const url = new URL(`${API_BASE_URL}/api/tags`)
if (kw) url.searchParams.set('keyword', kw)
url.searchParams.set('limit', '10')
let data = []
try {
const res = await fetch(url.toString())
if (res.ok) {
data = await res.json()
if (!kw && providedTags.value.length) {
data = [...providedTags.value]
} else {
const url = new URL(`${API_BASE_URL}/api/tags`)
if (kw) url.searchParams.set('keyword', kw)
url.searchParams.set('limit', '10')
try {
const res = await fetch(url.toString())
if (res.ok) {
data = await res.json()
}
} catch {
toast.error('获取标签失败')
}
} catch {
toast.error('获取标签失败')
}
let options = [...data, ...localTags.value]
@@ -91,7 +109,7 @@ export default {
}
})
return { fetchTags, selected, isImageIcon }
return { fetchTags, selected, isImageIcon, mergedOptions }
}
}
</script>

View File

@@ -18,8 +18,8 @@
>
{{ topic }}
</div>
<CategorySelect v-model="selectedCategory" />
<TagSelect v-model="selectedTags" />
<CategorySelect v-model="selectedCategory" :options="categoryOptions" />
<TagSelect v-model="selectedTags" :options="tagOptions" />
</div>
</div>
@@ -131,6 +131,8 @@ export default {
.map(v => decodeURIComponent(v))
.map(v => (isNaN(v) ? v : Number(v)))
}
const tagOptions = ref([])
const categoryOptions = ref([])
const isLoadingPosts = ref(false)
const topics = ref(['最新', '排行榜' /*, '热门', '类别'*/])
const selectedTopic = ref(route.query.view === 'ranking' ? '排行榜' : '最新')
@@ -140,6 +142,30 @@ export default {
const pageSize = 5
const allLoaded = ref(false)
const loadOptions = async () => {
if (selectedCategory.value && !isNaN(selectedCategory.value)) {
try {
const res = await fetch(`${API_BASE_URL}/api/categories/${selectedCategory.value}`)
if (res.ok) {
categoryOptions.value = [await res.json()]
}
} catch (e) { /* ignore */ }
}
if (selectedTags.value.length) {
const arr = []
for (const t of selectedTags.value) {
if (!isNaN(t)) {
try {
const r = await fetch(`${API_BASE_URL}/api/tags/${t}`)
if (r.ok) arr.push(await r.json())
} catch (e) { /* ignore */ }
}
}
tagOptions.value = arr
}
}
const buildUrl = () => {
let url = `${API_BASE_URL}/api/posts?page=${page.value}&pageSize=${pageSize}`
if (selectedCategory.value) {
@@ -249,7 +275,8 @@ export default {
}
}
onMounted(() => {
onMounted(async () => {
await loadOptions()
if (selectedTopic.value === '排行榜') {
fetchRanking()
} else {
@@ -275,7 +302,7 @@ export default {
const sanitizeDescription = (text) => stripMarkdown(text)
return { topics, selectedTopic, articles, sanitizeDescription, isLoadingPosts, handleScroll, selectedCategory, selectedTags }
return { topics, selectedTopic, articles, sanitizeDescription, isLoadingPosts, handleScroll, selectedCategory, selectedTags, tagOptions, categoryOptions }
}
}
</script>