refactor: 将订阅和插件过滤弹窗改为站点管理风格的下拉列表菜单

This commit is contained in:
jxxghp
2026-04-09 12:28:30 +08:00
parent 942ecc4c04
commit 6ef047050d
2 changed files with 192 additions and 122 deletions
+47 -33
View File
@@ -82,14 +82,25 @@ const filterOptions = computed(() => {
] ]
}) })
// 计算筛选按钮颜色 // 当前选中的筛选选项
const currentFilter = computed(() => {
return filterOptions.value.find(option => option.value === (subscribeStatusFilter.value || 'all'))
})
// 计算筛选按钮颜色 - 有名称筛选或状态筛选时高亮
const filterButtonColor = computed(() => { const filterButtonColor = computed(() => {
if (subscribeFilter.value || (subscribeStatusFilter.value && subscribeStatusFilter.value !== 'all')) { if (subscribeFilter.value || (subscribeStatusFilter.value && subscribeStatusFilter.value !== 'all')) {
return 'primary' return currentFilter.value?.color || 'primary'
} }
return 'gray' return 'gray'
}) })
// 选择筛选选项
function selectFilter(value: string) {
subscribeStatusFilter.value = value
filterSubscribeDialog.value = false
}
// VMenu activator选择器 // VMenu activator选择器
const filterActivator = computed(() => '[data-menu-activator="filter-btn"]') const filterActivator = computed(() => '[data-menu-activator="filter-btn"]')
const searchActivator = computed(() => '[data-menu-activator="search-btn"]') const searchActivator = computed(() => '[data-menu-activator="search-btn"]')
@@ -200,44 +211,47 @@ onMounted(() => {
</VWindowItem> </VWindowItem>
</VWindow> </VWindow>
<!-- 订阅过滤弹窗 --> <!-- 订阅过滤下拉菜单 -->
<Teleport to="body" v-if="filterSubscribeDialog"> <Teleport to="body" v-if="filterSubscribeDialog">
<VMenu <VMenu
v-model="filterSubscribeDialog" v-model="filterSubscribeDialog"
width="25rem"
:close-on-content-click="false" :close-on-content-click="false"
:activator="filterActivator" :activator="filterActivator"
location="bottom end" location="bottom end"
> >
<VCard> <VCard min-width="220">
<VCardItem> <!-- 名称搜索 -->
<VCardTitle> <div class="px-3 pt-3 pb-1">
<VIcon icon="mdi-filter-multiple-outline" class="mr-2" /> <VTextField
{{ t('subscribe.filterSubscriptions') }} v-model="subscribeFilter"
</VCardTitle> :placeholder="t('subscribe.name')"
<VDialogCloseBtn @click="filterSubscribeDialog = false" /> prepend-inner-icon="mdi-magnify"
</VCardItem> density="compact"
<VCardText> variant="outlined"
<VRow> hide-details
<!-- 名称筛选 --> clearable
<VCol cols="6"> />
<VTextField v-model="subscribeFilter" :label="t('subscribe.name')" clearable density="comfortable" /> </div>
</VCol> <VDivider class="mt-2" />
<!-- 状态筛选列表 -->
<!-- 状态筛选 --> <VList density="compact" class="px-2 py-1">
<VCol cols="6"> <VListSubheader>{{ t('common.status') }}</VListSubheader>
<VSelect <VListItem
v-model="subscribeStatusFilter" v-for="option in filterOptions"
:items="filterOptions" :key="option.value"
item-title="label" :active="(subscribeStatusFilter || 'all') === option.value"
item-value="value" @click="selectFilter(option.value)"
:label="t('common.status')" density="compact"
density="comfortable" >
clearable <template #prepend>
/> <VIcon :icon="option.icon" :color="option.color" size="small" />
</VCol> </template>
</VRow> <VListItemTitle>{{ option.label }}</VListItemTitle>
</VCardText> <template #append>
<VIcon v-if="(subscribeStatusFilter || 'all') === option.value" icon="mdi-check" color="primary" size="small" />
</template>
</VListItem>
</VList>
</VCard> </VCard>
</VMenu> </VMenu>
</Teleport> </Teleport>
+145 -89
View File
@@ -219,6 +219,16 @@ const isFilterFormEmpty = computed(() => {
) )
}) })
// 切换市场过滤器多选项
function toggleMarketFilter(field: 'author' | 'label' | 'repo', value: string) {
const index = filterForm[field].indexOf(value)
if (index > -1) {
filterForm[field].splice(index, 1)
} else {
filterForm[field].push(value)
}
}
// 插件过滤条件 // 插件过滤条件
const installedFilter = ref(null) const installedFilter = ref(null)
@@ -1289,113 +1299,159 @@ function onDragStartPlugin(evt: any) {
<template> <template>
<div> <div>
<!-- 过滤弹窗 --> <!-- 已安装插件过滤下拉菜单 -->
<Teleport to="body" v-if="filterInstalledPluginDialog"> <Teleport to="body" v-if="filterInstalledPluginDialog">
<VMenu <VMenu
v-model="filterInstalledPluginDialog" v-model="filterInstalledPluginDialog"
width="20rem"
:close-on-content-click="false" :close-on-content-click="false"
:activator="'[data-menu-activator=installed-filter-btn]'" :activator="'[data-menu-activator=installed-filter-btn]'"
location="bottom end" location="bottom end"
> >
<VCard> <VCard min-width="220">
<VCardItem> <!-- 名称搜索 -->
<VCardTitle> <div class="px-3 pt-3 pb-1">
<VIcon icon="mdi-filter-multiple-outline" class="mr-2" /> <VCombobox
{{ t('plugin.filterPlugins') }} v-model="installedFilter"
</VCardTitle> :items="installedPluginNames"
<VDialogCloseBtn @click="filterInstalledPluginDialog = false" /> :placeholder="t('plugin.name')"
</VCardItem> prepend-inner-icon="mdi-magnify"
<VCardText> density="compact"
<VRow> variant="outlined"
<VCol cols="12"> hide-details
<VCombobox clearable
v-model="installedFilter" />
:items="installedPluginNames" </div>
:label="t('plugin.name')" <VDivider class="mt-2" />
density="comfortable" <!-- 快捷筛选 -->
clearable <VList density="compact" class="px-2 py-1">
/> <VListSubheader>{{ t('common.filter') }}</VListSubheader>
</VCol> <VListItem
<VCol cols="6"> :active="enabledFilter"
<VSwitch v-model="enabledFilter" :label="t('plugin.running')" /> @click="enabledFilter = !enabledFilter"
</VCol> density="compact"
<VCol cols="6"> >
<VSwitch v-model="hasUpdateFilter" :label="t('plugin.hasNewVersion')" /> <template #prepend>
</VCol> <VIcon icon="mdi-play-circle" color="success" size="small" />
</VRow> </template>
</VCardText> <VListItemTitle>{{ t('plugin.running') }}</VListItemTitle>
<template #append>
<VIcon v-if="enabledFilter" icon="mdi-check" color="primary" size="small" />
</template>
</VListItem>
<VListItem
:active="hasUpdateFilter"
@click="hasUpdateFilter = !hasUpdateFilter"
density="compact"
>
<template #prepend>
<VIcon icon="mdi-arrow-up-circle" color="info" size="small" />
</template>
<VListItemTitle>{{ t('plugin.hasNewVersion') }}</VListItemTitle>
<template #append>
<VIcon v-if="hasUpdateFilter" icon="mdi-check" color="primary" size="small" />
</template>
</VListItem>
</VList>
</VCard> </VCard>
</VMenu> </VMenu>
</Teleport> </Teleport>
<!-- 插件市场过滤下拉菜单 -->
<Teleport to="body" v-if="filterMarketPluginDialog"> <Teleport to="body" v-if="filterMarketPluginDialog">
<VMenu <VMenu
v-model="filterMarketPluginDialog" v-model="filterMarketPluginDialog"
width="25rem"
:close-on-content-click="false" :close-on-content-click="false"
:activator="'[data-menu-activator=market-filter-btn]'" :activator="'[data-menu-activator=market-filter-btn]'"
location="bottom end" location="bottom end"
max-height="80vh"
> >
<VCard> <VCard min-width="240" max-width="300">
<VCardItem> <!-- 名称搜索 -->
<VCardTitle> <div class="px-3 pt-3 pb-1">
<VIcon icon="mdi-filter-multiple-outline" class="mr-2" /> <VTextField
{{ t('plugin.filterPlugins') }} v-model="filterForm.name"
</VCardTitle> :placeholder="t('plugin.name')"
<VDialogCloseBtn @click="filterMarketPluginDialog = false" /> prepend-inner-icon="mdi-magnify"
</VCardItem> density="compact"
<VCardText> variant="outlined"
<!-- 过滤表单 --> hide-details
<div v-if="isAppMarketLoaded"> clearable
<VRow> />
<VCol cols="6"> </div>
<VTextField v-model="filterForm.name" density="comfortable" :label="t('plugin.name')" clearable /> <VDivider class="mt-2" />
</VCol> <!-- 排序 -->
<VCol v-if="authorFilterOptions.length > 0" cols="6"> <VList v-if="sortOptions.length > 0" density="compact" class="px-2 py-1">
<VSelect <VListSubheader>{{ t('plugin.sortTitle') }}</VListSubheader>
v-model="filterForm.author" <VListItem
:items="authorFilterOptions" v-for="option in sortOptions"
density="comfortable" :key="option.value"
chips :active="(activeSort || 'count') === option.value"
:label="t('plugin.author')" @click="activeSort = option.value"
multiple density="compact"
clearable >
/> <VListItemTitle>{{ option.title }}</VListItemTitle>
</VCol> <template #append>
<VCol v-if="labelFilterOptions.length > 0" cols="6"> <VIcon v-if="(activeSort || 'count') === option.value" icon="mdi-check" color="primary" size="small" />
<VSelect </template>
v-model="filterForm.label" </VListItem>
:items="labelFilterOptions" </VList>
density="comfortable" <!-- 作者筛选 -->
chips <template v-if="authorFilterOptions.length > 0">
:label="t('plugin.label')" <VDivider />
multiple <VList density="compact" class="px-2 py-1">
clearable <VListSubheader>{{ t('plugin.author') }}</VListSubheader>
/> <VListItem
</VCol> v-for="author in authorFilterOptions"
<VCol v-if="repoFilterOptions.length > 0" cols="6"> :key="author"
<VSelect :active="filterForm.author.includes(author)"
v-model="filterForm.repo" @click="toggleMarketFilter('author', author)"
:items="repoFilterOptions" density="compact"
density="comfortable" >
chips <VListItemTitle>{{ author }}</VListItemTitle>
:label="t('plugin.repository')" <template #append>
multiple <VIcon v-if="filterForm.author.includes(author)" icon="mdi-check" color="primary" size="small" />
clearable </template>
/> </VListItem>
</VCol> </VList>
<VCol v-if="sortOptions.length > 0" cols="6"> </template>
<VSelect <!-- 标签筛选 -->
v-model="activeSort" <template v-if="labelFilterOptions.length > 0">
:items="sortOptions" <VDivider />
density="comfortable" <VList density="compact" class="px-2 py-1">
:label="t('plugin.sortTitle')" <VListSubheader>{{ t('plugin.label') }}</VListSubheader>
/> <VListItem
</VCol> v-for="label in labelFilterOptions"
</VRow> :key="label"
</div> :active="filterForm.label.includes(label)"
</VCardText> @click="toggleMarketFilter('label', label)"
density="compact"
>
<VListItemTitle>{{ label }}</VListItemTitle>
<template #append>
<VIcon v-if="filterForm.label.includes(label)" icon="mdi-check" color="primary" size="small" />
</template>
</VListItem>
</VList>
</template>
<!-- 仓库筛选 -->
<template v-if="repoFilterOptions.length > 0">
<VDivider />
<VList density="compact" class="px-2 py-1">
<VListSubheader>{{ t('plugin.repository') }}</VListSubheader>
<VListItem
v-for="repo in repoFilterOptions"
:key="repo"
:active="filterForm.repo.includes(repo)"
@click="toggleMarketFilter('repo', repo)"
density="compact"
>
<VListItemTitle class="text-truncate" style="max-width: 220px">{{ repo }}</VListItemTitle>
<template #append>
<VIcon v-if="filterForm.repo.includes(repo)" icon="mdi-check" color="primary" size="small" />
</template>
</VListItem>
</VList>
</template>
</VCard> </VCard>
</VMenu> </VMenu>
</Teleport> </Teleport>