mirror of https://github.com/halo-dev/halo
refactor: logic of attachment data filtering (#4194)
#### What type of PR is this? /area console /kind improvement /milestone 2.8.x #### What this PR does / why we need it: 重构附件数据管理的筛选条件逻辑以及 UI。 <img width="1645" alt="image" src="https://github.com/halo-dev/halo/assets/21301288/6b13045e-ab6b-4306-a1bb-f19d97115191"> Ref https://github.com/halo-dev/halo/pull/4182 Ref https://github.com/halo-dev/halo/issues/4181 #### Special notes for your reviewer: 需要测试: 1. 测试附件的筛选条件包括关键词筛选功能是否正常。 #### Does this PR introduce a user-facing change? ```release-note 重构 Console 端附件数据列表的筛选项 UI 和逻辑。 ```pull/4212/head
parent
6bba5073fa
commit
efcf526f1b
|
@ -1,6 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
import {
|
||||
IconArrowDown,
|
||||
IconArrowLeft,
|
||||
IconArrowRight,
|
||||
IconCheckboxFill,
|
||||
|
@ -30,7 +29,7 @@ import AttachmentUploadModal from "./components/AttachmentUploadModal.vue";
|
|||
import AttachmentPoliciesModal from "./components/AttachmentPoliciesModal.vue";
|
||||
import AttachmentGroupList from "./components/AttachmentGroupList.vue";
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import type { Attachment, Group, Policy } from "@halo-dev/api-client";
|
||||
import type { Attachment, Group } from "@halo-dev/api-client";
|
||||
import { formatDatetime } from "@/utils/date";
|
||||
import prettyBytes from "pretty-bytes";
|
||||
import { useFetchAttachmentPolicy } from "./composables/use-attachment-policy";
|
||||
|
@ -42,8 +41,6 @@ import { isImage } from "@/utils/image";
|
|||
import { useRouteQuery } from "@vueuse/router";
|
||||
import { useFetchAttachmentGroup } from "./composables/use-attachment-group";
|
||||
import { usePermission } from "@/utils/permission";
|
||||
import FilterTag from "@/components/filter/FilterTag.vue";
|
||||
import { getNode } from "@formkit/core";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import UserFilterDropdown from "@/components/filter/UserFilterDropdown.vue";
|
||||
|
||||
|
@ -60,81 +57,35 @@ const { groups, handleFetchGroups } = useFetchAttachmentGroup();
|
|||
const selectedGroup = ref<Group>();
|
||||
|
||||
// Filter
|
||||
interface SortItem {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
const SortItems: SortItem[] = [
|
||||
{
|
||||
label: t("core.attachment.filters.sort.items.create_time_desc"),
|
||||
value: "creationTimestamp,desc",
|
||||
},
|
||||
{
|
||||
label: t("core.attachment.filters.sort.items.create_time_asc"),
|
||||
value: "creationTimestamp,asc",
|
||||
},
|
||||
{
|
||||
label: t("core.attachment.filters.sort.items.size_desc"),
|
||||
value: "size,desc",
|
||||
},
|
||||
{
|
||||
label: t("core.attachment.filters.sort.items.size_asc"),
|
||||
value: "size,asc",
|
||||
},
|
||||
];
|
||||
|
||||
const selectedPolicy = ref<Policy>();
|
||||
const keyword = ref<string>("");
|
||||
const page = ref<number>(1);
|
||||
const size = ref<number>(60);
|
||||
const selectedPolicy = ref();
|
||||
const selectedUser = ref();
|
||||
const selectedSortItem = ref<SortItem>();
|
||||
const selectedSortItemValue = computed(() => {
|
||||
return selectedSortItem.value?.value;
|
||||
});
|
||||
const selectedSort = ref();
|
||||
|
||||
function handleSelectPolicy(policy: Policy | undefined) {
|
||||
selectedPolicy.value = policy;
|
||||
page.value = 1;
|
||||
}
|
||||
|
||||
function handleSortItemChange(sortItem?: SortItem) {
|
||||
selectedSortItem.value = sortItem;
|
||||
page.value = 1;
|
||||
}
|
||||
|
||||
function handleKeywordChange() {
|
||||
const keywordNode = getNode("keywordInput");
|
||||
if (keywordNode) {
|
||||
keyword.value = keywordNode._value as string;
|
||||
watch(
|
||||
() => [
|
||||
selectedPolicy.value,
|
||||
selectedUser.value,
|
||||
selectedSort.value,
|
||||
keyword.value,
|
||||
],
|
||||
() => {
|
||||
page.value = 1;
|
||||
}
|
||||
page.value = 1;
|
||||
}
|
||||
|
||||
function handleClearKeyword() {
|
||||
keyword.value = "";
|
||||
page.value = 1;
|
||||
}
|
||||
);
|
||||
|
||||
const hasFilters = computed(() => {
|
||||
return (
|
||||
selectedPolicy.value ||
|
||||
selectedUser.value ||
|
||||
selectedSortItem.value ||
|
||||
keyword.value
|
||||
);
|
||||
return selectedPolicy.value || selectedUser.value || selectedSort.value;
|
||||
});
|
||||
|
||||
function handleClearFilters() {
|
||||
selectedPolicy.value = undefined;
|
||||
selectedUser.value = undefined;
|
||||
selectedSortItem.value = undefined;
|
||||
keyword.value = "";
|
||||
page.value = 1;
|
||||
selectedSort.value = undefined;
|
||||
}
|
||||
|
||||
const keyword = ref<string>("");
|
||||
const page = ref<number>(1);
|
||||
const size = ref<number>(60);
|
||||
|
||||
const {
|
||||
attachments,
|
||||
selectedAttachment,
|
||||
|
@ -154,10 +105,14 @@ const {
|
|||
handleReset,
|
||||
} = useAttachmentControl({
|
||||
group: selectedGroup,
|
||||
policy: selectedPolicy,
|
||||
policy: computed(() => {
|
||||
return policies.value?.find(
|
||||
(policy) => policy.metadata.name === selectedPolicy.value
|
||||
);
|
||||
}),
|
||||
user: selectedUser,
|
||||
keyword: keyword,
|
||||
sort: selectedSortItemValue,
|
||||
sort: selectedSort,
|
||||
page: page,
|
||||
size: size,
|
||||
});
|
||||
|
@ -348,55 +303,10 @@ onMounted(() => {
|
|||
/>
|
||||
</div>
|
||||
<div class="flex w-full flex-1 items-center sm:w-auto">
|
||||
<div
|
||||
<SearchInput
|
||||
v-if="!selectedAttachments.size"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<FormKit
|
||||
id="keywordInput"
|
||||
outer-class="!p-0"
|
||||
:placeholder="$t('core.common.placeholder.search')"
|
||||
type="text"
|
||||
name="keyword"
|
||||
:model-value="keyword"
|
||||
@keyup.enter="handleKeywordChange"
|
||||
></FormKit>
|
||||
|
||||
<FilterTag v-if="keyword" @close="handleClearKeyword()">
|
||||
{{
|
||||
$t("core.common.filters.results.keyword", {
|
||||
keyword: keyword,
|
||||
})
|
||||
}}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedPolicy"
|
||||
@close="handleSelectPolicy(undefined)"
|
||||
>
|
||||
{{
|
||||
$t("core.attachment.filters.storage_policy.result", {
|
||||
storage_policy: selectedPolicy.spec.displayName,
|
||||
})
|
||||
}}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedSortItem"
|
||||
@click="handleSortItemChange()"
|
||||
>
|
||||
{{
|
||||
$t("core.common.filters.results.sort", {
|
||||
sort: selectedSortItem.label,
|
||||
})
|
||||
}}
|
||||
</FilterTag>
|
||||
|
||||
<FilterCleanButton
|
||||
v-if="hasFilters"
|
||||
@click="handleClearFilters"
|
||||
/>
|
||||
</div>
|
||||
v-model="keyword"
|
||||
/>
|
||||
<VSpace v-else>
|
||||
<VButton type="danger" @click="handleDeleteInBatch">
|
||||
{{ $t("core.common.buttons.delete") }}
|
||||
|
@ -424,72 +334,64 @@ onMounted(() => {
|
|||
</div>
|
||||
<div class="mt-4 flex sm:mt-0">
|
||||
<VSpace spacing="lg">
|
||||
<VDropdown>
|
||||
<div
|
||||
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
||||
>
|
||||
<span class="mr-0.5">
|
||||
{{
|
||||
$t("core.attachment.filters.storage_policy.label")
|
||||
}}
|
||||
</span>
|
||||
<span>
|
||||
<IconArrowDown />
|
||||
</span>
|
||||
</div>
|
||||
<template #popper>
|
||||
<VDropdownItem
|
||||
v-for="(policy, index) in policies"
|
||||
:key="index"
|
||||
:selected="policy === selectedPolicy"
|
||||
@click="handleSelectPolicy(policy)"
|
||||
>
|
||||
{{ policy.spec.displayName }}
|
||||
</VDropdownItem>
|
||||
</template>
|
||||
</VDropdown>
|
||||
|
||||
<FilterCleanButton
|
||||
v-if="hasFilters"
|
||||
@click="handleClearFilters"
|
||||
/>
|
||||
<FilterDropdown
|
||||
v-model="selectedPolicy"
|
||||
:label="
|
||||
$t('core.attachment.filters.storage_policy.label')
|
||||
"
|
||||
:items="[
|
||||
{
|
||||
label: t('core.common.filters.item_labels.all'),
|
||||
},
|
||||
...(policies?.map((policy) => {
|
||||
return {
|
||||
label: policy.spec.displayName,
|
||||
value: policy.metadata.name,
|
||||
};
|
||||
}) || []),
|
||||
]"
|
||||
/>
|
||||
<UserFilterDropdown
|
||||
v-model="selectedUser"
|
||||
:label="$t('core.attachment.filters.owner.label')"
|
||||
/>
|
||||
<!-- TODO: add filter by ref support -->
|
||||
<VDropdown v-if="false">
|
||||
<div
|
||||
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
||||
>
|
||||
<span class="mr-0.5">引用位置</span>
|
||||
<span>
|
||||
<IconArrowDown />
|
||||
</span>
|
||||
</div>
|
||||
<template #popper>
|
||||
<VDropdownItem> 未被引用 </VDropdownItem>
|
||||
<VDropdownItem> 文章 </VDropdownItem>
|
||||
</template>
|
||||
</VDropdown>
|
||||
<VDropdown>
|
||||
<div
|
||||
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
||||
>
|
||||
<span class="mr-0.5">
|
||||
{{ $t("core.common.filters.labels.sort") }}
|
||||
</span>
|
||||
<span>
|
||||
<IconArrowDown />
|
||||
</span>
|
||||
</div>
|
||||
<template #popper>
|
||||
<VDropdownItem
|
||||
v-for="(sortItem, index) in SortItems"
|
||||
:key="index"
|
||||
:selected="sortItem.value === selectedSortItem?.value"
|
||||
@click="handleSortItemChange(sortItem)"
|
||||
>
|
||||
{{ sortItem.label }}
|
||||
</VDropdownItem>
|
||||
</template>
|
||||
</VDropdown>
|
||||
<FilterDropdown
|
||||
v-model="selectedSort"
|
||||
:label="$t('core.common.filters.labels.sort')"
|
||||
:items="[
|
||||
{
|
||||
label: t('core.common.filters.item_labels.default'),
|
||||
},
|
||||
{
|
||||
label: t(
|
||||
'core.attachment.filters.sort.items.create_time_desc'
|
||||
),
|
||||
value: 'creationTimestamp,desc',
|
||||
},
|
||||
{
|
||||
label: t(
|
||||
'core.attachment.filters.sort.items.create_time_asc'
|
||||
),
|
||||
value: 'creationTimestamp,asc',
|
||||
},
|
||||
{
|
||||
label: t(
|
||||
'core.attachment.filters.sort.items.size_desc'
|
||||
),
|
||||
value: 'size,desc',
|
||||
},
|
||||
{
|
||||
label: t(
|
||||
'core.attachment.filters.sort.items.size_asc'
|
||||
),
|
||||
value: 'size,asc',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
<div class="flex flex-row gap-2">
|
||||
<div
|
||||
v-for="(item, index) in viewTypes"
|
||||
|
|
Loading…
Reference in New Issue