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>
|
<script lang="ts" setup>
|
||||||
import {
|
import {
|
||||||
IconArrowDown,
|
|
||||||
IconArrowLeft,
|
IconArrowLeft,
|
||||||
IconArrowRight,
|
IconArrowRight,
|
||||||
IconCheckboxFill,
|
IconCheckboxFill,
|
||||||
|
@ -30,7 +29,7 @@ import AttachmentUploadModal from "./components/AttachmentUploadModal.vue";
|
||||||
import AttachmentPoliciesModal from "./components/AttachmentPoliciesModal.vue";
|
import AttachmentPoliciesModal from "./components/AttachmentPoliciesModal.vue";
|
||||||
import AttachmentGroupList from "./components/AttachmentGroupList.vue";
|
import AttachmentGroupList from "./components/AttachmentGroupList.vue";
|
||||||
import { computed, onMounted, ref, watch } from "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 { formatDatetime } from "@/utils/date";
|
||||||
import prettyBytes from "pretty-bytes";
|
import prettyBytes from "pretty-bytes";
|
||||||
import { useFetchAttachmentPolicy } from "./composables/use-attachment-policy";
|
import { useFetchAttachmentPolicy } from "./composables/use-attachment-policy";
|
||||||
|
@ -42,8 +41,6 @@ import { isImage } from "@/utils/image";
|
||||||
import { useRouteQuery } from "@vueuse/router";
|
import { useRouteQuery } from "@vueuse/router";
|
||||||
import { useFetchAttachmentGroup } from "./composables/use-attachment-group";
|
import { useFetchAttachmentGroup } from "./composables/use-attachment-group";
|
||||||
import { usePermission } from "@/utils/permission";
|
import { usePermission } from "@/utils/permission";
|
||||||
import FilterTag from "@/components/filter/FilterTag.vue";
|
|
||||||
import { getNode } from "@formkit/core";
|
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import UserFilterDropdown from "@/components/filter/UserFilterDropdown.vue";
|
import UserFilterDropdown from "@/components/filter/UserFilterDropdown.vue";
|
||||||
|
|
||||||
|
@ -60,81 +57,35 @@ const { groups, handleFetchGroups } = useFetchAttachmentGroup();
|
||||||
const selectedGroup = ref<Group>();
|
const selectedGroup = ref<Group>();
|
||||||
|
|
||||||
// Filter
|
// Filter
|
||||||
interface SortItem {
|
const keyword = ref<string>("");
|
||||||
label: string;
|
const page = ref<number>(1);
|
||||||
value: string;
|
const size = ref<number>(60);
|
||||||
}
|
const selectedPolicy = ref();
|
||||||
|
|
||||||
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 selectedUser = ref();
|
const selectedUser = ref();
|
||||||
const selectedSortItem = ref<SortItem>();
|
const selectedSort = ref();
|
||||||
const selectedSortItemValue = computed(() => {
|
|
||||||
return selectedSortItem.value?.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleSelectPolicy(policy: Policy | undefined) {
|
watch(
|
||||||
selectedPolicy.value = policy;
|
() => [
|
||||||
page.value = 1;
|
selectedPolicy.value,
|
||||||
}
|
selectedUser.value,
|
||||||
|
selectedSort.value,
|
||||||
function handleSortItemChange(sortItem?: SortItem) {
|
keyword.value,
|
||||||
selectedSortItem.value = sortItem;
|
],
|
||||||
page.value = 1;
|
() => {
|
||||||
}
|
page.value = 1;
|
||||||
|
|
||||||
function handleKeywordChange() {
|
|
||||||
const keywordNode = getNode("keywordInput");
|
|
||||||
if (keywordNode) {
|
|
||||||
keyword.value = keywordNode._value as string;
|
|
||||||
}
|
}
|
||||||
page.value = 1;
|
);
|
||||||
}
|
|
||||||
|
|
||||||
function handleClearKeyword() {
|
|
||||||
keyword.value = "";
|
|
||||||
page.value = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasFilters = computed(() => {
|
const hasFilters = computed(() => {
|
||||||
return (
|
return selectedPolicy.value || selectedUser.value || selectedSort.value;
|
||||||
selectedPolicy.value ||
|
|
||||||
selectedUser.value ||
|
|
||||||
selectedSortItem.value ||
|
|
||||||
keyword.value
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleClearFilters() {
|
function handleClearFilters() {
|
||||||
selectedPolicy.value = undefined;
|
selectedPolicy.value = undefined;
|
||||||
selectedUser.value = undefined;
|
selectedUser.value = undefined;
|
||||||
selectedSortItem.value = undefined;
|
selectedSort.value = undefined;
|
||||||
keyword.value = "";
|
|
||||||
page.value = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyword = ref<string>("");
|
|
||||||
const page = ref<number>(1);
|
|
||||||
const size = ref<number>(60);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
attachments,
|
attachments,
|
||||||
selectedAttachment,
|
selectedAttachment,
|
||||||
|
@ -154,10 +105,14 @@ const {
|
||||||
handleReset,
|
handleReset,
|
||||||
} = useAttachmentControl({
|
} = useAttachmentControl({
|
||||||
group: selectedGroup,
|
group: selectedGroup,
|
||||||
policy: selectedPolicy,
|
policy: computed(() => {
|
||||||
|
return policies.value?.find(
|
||||||
|
(policy) => policy.metadata.name === selectedPolicy.value
|
||||||
|
);
|
||||||
|
}),
|
||||||
user: selectedUser,
|
user: selectedUser,
|
||||||
keyword: keyword,
|
keyword: keyword,
|
||||||
sort: selectedSortItemValue,
|
sort: selectedSort,
|
||||||
page: page,
|
page: page,
|
||||||
size: size,
|
size: size,
|
||||||
});
|
});
|
||||||
|
@ -348,55 +303,10 @@ onMounted(() => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex w-full flex-1 items-center sm:w-auto">
|
<div class="flex w-full flex-1 items-center sm:w-auto">
|
||||||
<div
|
<SearchInput
|
||||||
v-if="!selectedAttachments.size"
|
v-if="!selectedAttachments.size"
|
||||||
class="flex items-center gap-2"
|
v-model="keyword"
|
||||||
>
|
/>
|
||||||
<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>
|
|
||||||
<VSpace v-else>
|
<VSpace v-else>
|
||||||
<VButton type="danger" @click="handleDeleteInBatch">
|
<VButton type="danger" @click="handleDeleteInBatch">
|
||||||
{{ $t("core.common.buttons.delete") }}
|
{{ $t("core.common.buttons.delete") }}
|
||||||
|
@ -424,72 +334,64 @@ onMounted(() => {
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 flex sm:mt-0">
|
<div class="mt-4 flex sm:mt-0">
|
||||||
<VSpace spacing="lg">
|
<VSpace spacing="lg">
|
||||||
<VDropdown>
|
<FilterCleanButton
|
||||||
<div
|
v-if="hasFilters"
|
||||||
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
@click="handleClearFilters"
|
||||||
>
|
/>
|
||||||
<span class="mr-0.5">
|
<FilterDropdown
|
||||||
{{
|
v-model="selectedPolicy"
|
||||||
$t("core.attachment.filters.storage_policy.label")
|
:label="
|
||||||
}}
|
$t('core.attachment.filters.storage_policy.label')
|
||||||
</span>
|
"
|
||||||
<span>
|
:items="[
|
||||||
<IconArrowDown />
|
{
|
||||||
</span>
|
label: t('core.common.filters.item_labels.all'),
|
||||||
</div>
|
},
|
||||||
<template #popper>
|
...(policies?.map((policy) => {
|
||||||
<VDropdownItem
|
return {
|
||||||
v-for="(policy, index) in policies"
|
label: policy.spec.displayName,
|
||||||
:key="index"
|
value: policy.metadata.name,
|
||||||
:selected="policy === selectedPolicy"
|
};
|
||||||
@click="handleSelectPolicy(policy)"
|
}) || []),
|
||||||
>
|
]"
|
||||||
{{ policy.spec.displayName }}
|
/>
|
||||||
</VDropdownItem>
|
|
||||||
</template>
|
|
||||||
</VDropdown>
|
|
||||||
|
|
||||||
<UserFilterDropdown
|
<UserFilterDropdown
|
||||||
v-model="selectedUser"
|
v-model="selectedUser"
|
||||||
:label="$t('core.attachment.filters.owner.label')"
|
:label="$t('core.attachment.filters.owner.label')"
|
||||||
/>
|
/>
|
||||||
<!-- TODO: add filter by ref support -->
|
<FilterDropdown
|
||||||
<VDropdown v-if="false">
|
v-model="selectedSort"
|
||||||
<div
|
:label="$t('core.common.filters.labels.sort')"
|
||||||
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
:items="[
|
||||||
>
|
{
|
||||||
<span class="mr-0.5">引用位置</span>
|
label: t('core.common.filters.item_labels.default'),
|
||||||
<span>
|
},
|
||||||
<IconArrowDown />
|
{
|
||||||
</span>
|
label: t(
|
||||||
</div>
|
'core.attachment.filters.sort.items.create_time_desc'
|
||||||
<template #popper>
|
),
|
||||||
<VDropdownItem> 未被引用 </VDropdownItem>
|
value: 'creationTimestamp,desc',
|
||||||
<VDropdownItem> 文章 </VDropdownItem>
|
},
|
||||||
</template>
|
{
|
||||||
</VDropdown>
|
label: t(
|
||||||
<VDropdown>
|
'core.attachment.filters.sort.items.create_time_asc'
|
||||||
<div
|
),
|
||||||
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
value: 'creationTimestamp,asc',
|
||||||
>
|
},
|
||||||
<span class="mr-0.5">
|
{
|
||||||
{{ $t("core.common.filters.labels.sort") }}
|
label: t(
|
||||||
</span>
|
'core.attachment.filters.sort.items.size_desc'
|
||||||
<span>
|
),
|
||||||
<IconArrowDown />
|
value: 'size,desc',
|
||||||
</span>
|
},
|
||||||
</div>
|
{
|
||||||
<template #popper>
|
label: t(
|
||||||
<VDropdownItem
|
'core.attachment.filters.sort.items.size_asc'
|
||||||
v-for="(sortItem, index) in SortItems"
|
),
|
||||||
:key="index"
|
value: 'size,asc',
|
||||||
:selected="sortItem.value === selectedSortItem?.value"
|
},
|
||||||
@click="handleSortItemChange(sortItem)"
|
]"
|
||||||
>
|
/>
|
||||||
{{ sortItem.label }}
|
|
||||||
</VDropdownItem>
|
|
||||||
</template>
|
|
||||||
</VDropdown>
|
|
||||||
<div class="flex flex-row gap-2">
|
<div class="flex flex-row gap-2">
|
||||||
<div
|
<div
|
||||||
v-for="(item, index) in viewTypes"
|
v-for="(item, index) in viewTypes"
|
||||||
|
|
Loading…
Reference in New Issue