Files
AstrBot/dashboard/src/components/extension/PluginSortControl.vue
T
ChuwuYo 795aec9578 feat(extension): add filtering and sorting for installed plugins in WebUI (#5923)
* feat(extension): add PluginSortControl reusable component for sorting

* i18n: add i18n keys for plugin sorting and filtering features

* feat(extension): add sorting and status filtering for installed plugins

Backend changes (plugin.py):
- Add _resolve_plugin_dir method to resolve plugin directory path
- Add _get_plugin_installed_at method to get installation time from file mtime
- Add installed_at field to plugin API response

Frontend changes (InstalledPluginsTab.vue):
- Import PluginSortControl component
- Add status filter toggle (all/enabled/disabled) using v-btn-toggle
- Integrate PluginSortControl for sorting options
- Add toolbar layout with actions and controls sections

Frontend changes (MarketPluginsTab.vue):
- Import PluginSortControl component
- Replace v-select + v-btn combination with unified PluginSortControl

Frontend changes (useExtensionPage.js):
- Add installedStatusFilter, installedSortBy, installedSortOrder refs
- Add installedSortItems and installedSortUsesOrder computed properties
- Add sortInstalledPlugins function with multi-criteria support
- Support sorting by install time, name, author, and update status
- Add status filtering in filteredPlugins computed property
- Disable default table sorting by setting sortable: false

* test: add tests for installed_at field in plugin API

- Assert all plugins have installed_at field in get_plugins response
- Assert installed_at is not null after plugin installation

* fix(extension): add explicit fallbacks for installed plugin sort comparisons

* i18n(extension): rename install time label to last modified

* fix(extension): cache installed_at parsing and validate timestamp format in tests

* test(dashboard): strengthen installed_at coverage for plugin API
2026-03-09 17:12:22 +09:00

98 lines
1.9 KiB
Vue

<script setup>
const props = defineProps({
modelValue: {
type: String,
required: true,
},
items: {
type: Array,
required: true,
},
label: {
type: String,
required: true,
},
order: {
type: String,
default: "desc",
},
ascendingLabel: {
type: String,
default: "Ascending",
},
descendingLabel: {
type: String,
default: "Descending",
},
showOrder: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:modelValue", "update:order"]);
const updateSortBy = (value) => {
emit("update:modelValue", value);
};
const toggleOrder = () => {
emit("update:order", props.order === "desc" ? "asc" : "desc");
};
</script>
<template>
<div class="plugin-sort-control">
<v-select
:model-value="modelValue"
:items="items"
density="compact"
variant="outlined"
hide-details
:label="label"
class="plugin-sort-control__select"
@update:model-value="updateSortBy"
>
<template #prepend-inner>
<v-icon size="small">mdi-sort</v-icon>
</template>
</v-select>
<v-btn
v-if="showOrder"
icon
variant="text"
density="compact"
@click="toggleOrder"
>
<v-icon>{{
order === "desc" ? "mdi-arrow-down-thin" : "mdi-arrow-up-thin"
}}</v-icon>
<v-tooltip activator="parent" location="top">
{{ order === "desc" ? descendingLabel : ascendingLabel }}
</v-tooltip>
</v-btn>
</div>
</template>
<style scoped>
.plugin-sort-control {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.plugin-sort-control__select {
min-width: 180px;
max-width: 220px;
}
.plugin-sort-control__select :deep(.v-field__input),
.plugin-sort-control__select :deep(.v-field-label),
.plugin-sort-control__select :deep(.v-select__selection-text),
.plugin-sort-control__select :deep(.v-field__prepend-inner) {
font-size: 0.875rem;
}
</style>