perf: optimize the creation of attachment storage policies (#681)

#### What type of PR is this?

/kind improvement
/milestone 2.0

#### What this PR does / why we need it:

优化首次上传附件时,创建存储策略的流程,现在可以直接打开新建策略的表单。

#### Screenshots:

before:

<img width="898" alt="image" src="https://user-images.githubusercontent.com/21301288/199915792-92547e21-ffbb-4c9f-9614-b1f89f7d6f75.png">

after:
<img width="1087" alt="image" src="https://user-images.githubusercontent.com/21301288/199915494-00447427-060a-4744-83b0-d1067e745517.png">


#### Special notes for your reviewer:

/cc @halo-dev/sig-halo-console 

测试方式:

1. 测试在上传弹窗中新建存储策略。

#### Does this PR introduce a user-facing change?


```release-note
优化首次上传附件时,创建存储策略的流程。
```
pull/684/head
Ryan Wang 2022-11-04 16:30:10 +08:00 committed by GitHub
parent b0359c4e17
commit d3d28dd3da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 154 additions and 69 deletions

View File

@ -95,7 +95,7 @@ const handleClose = () => {
mr-3 mr-3
flex-1 flex-1
font-medium font-medium
text-base; text-sm;
} }
.alert-close { .alert-close {
@ -113,7 +113,7 @@ const handleClose = () => {
} }
.alert-description { .alert-description {
@apply text-sm @apply text-xs
mt-2; mt-2;
} }

View File

@ -10,10 +10,12 @@ import {
import AttachmentPolicyEditingModal from "./AttachmentPolicyEditingModal.vue"; import AttachmentPolicyEditingModal from "./AttachmentPolicyEditingModal.vue";
import { ref, watch } from "vue"; import { ref, watch } from "vue";
import type { Policy, PolicyTemplate } from "@halo-dev/api-client"; import type { Policy, PolicyTemplate } from "@halo-dev/api-client";
import { apiClient } from "@/utils/api-client";
import { v4 as uuid } from "uuid"; import { v4 as uuid } from "uuid";
import { formatDatetime } from "@/utils/date"; import { formatDatetime } from "@/utils/date";
import { useFetchAttachmentPolicy } from "../composables/use-attachment-policy"; import {
useFetchAttachmentPolicy,
useFetchAttachmentPolicyTemplate,
} from "../composables/use-attachment-policy";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@ -30,22 +32,15 @@ const emit = defineEmits<{
}>(); }>();
const { policies, loading, handleFetchPolicies } = useFetchAttachmentPolicy(); const { policies, loading, handleFetchPolicies } = useFetchAttachmentPolicy();
const { policyTemplates, handleFetchPolicyTemplates } =
useFetchAttachmentPolicyTemplate({
fetchOnMounted: false,
});
const selectedPolicy = ref<Policy | null>(null); const selectedPolicy = ref<Policy>();
const policyTemplates = ref<PolicyTemplate[]>([] as PolicyTemplate[]);
const policyEditingModal = ref(false); const policyEditingModal = ref(false);
const handleFetchPolicyTemplates = async () => {
try {
const { data } =
await apiClient.extension.storage.policyTemplate.liststorageHaloRunV1alpha1PolicyTemplate();
policyTemplates.value = data.items;
} catch (e) {
console.error("Failed to fetch attachment policy templates", e);
}
};
function onVisibleChange(visible: boolean) { function onVisibleChange(visible: boolean) {
emit("update:visible", visible); emit("update:visible", visible);
if (!visible) { if (!visible) {
@ -79,14 +74,14 @@ const handleOpenCreateNewPolicyModal = (policyTemplate: PolicyTemplate) => {
}; };
const onEditingModalClose = () => { const onEditingModalClose = () => {
selectedPolicy.value = null; selectedPolicy.value = undefined;
handleFetchPolicies(); handleFetchPolicies();
}; };
watch( watch(
() => props.visible, () => props.visible,
(newValue) => { (visible) => {
if (newValue) { if (visible) {
handleFetchPolicyTemplates(); handleFetchPolicyTemplates();
handleFetchPolicies(); handleFetchPolicies();
} }

View File

@ -17,11 +17,11 @@ import { setFocus } from "@/formkit/utils/focus";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
visible: boolean; visible: boolean;
policy: Policy | null; policy?: Policy;
}>(), }>(),
{ {
visible: false, visible: false,
policy: null, policy: undefined,
} }
); );

View File

@ -52,6 +52,18 @@ const onVisibleChange = (visible: boolean) => {
} }
}; };
const onChangeProvider = (providerId: string) => {
const provider = attachmentSelectorPublicState.value.providers.find(
(provider) => provider.id === providerId
);
if (!provider) {
return;
}
activeId.value = providerId;
};
const handleConfirm = () => { const handleConfirm = () => {
emit("select", Array.from(selected.value)); emit("select", Array.from(selected.value));
onVisibleChange(false); onVisibleChange(false);
@ -69,7 +81,7 @@ const handleConfirm = () => {
<VTabbar <VTabbar
v-model:active-id="activeId" v-model:active-id="activeId"
:items="attachmentSelectorPublicState.providers" :items="attachmentSelectorPublicState.providers"
class="w-full !rounded-none" class="w-full"
type="outline" type="outline"
></VTabbar> ></VTabbar>
@ -83,6 +95,7 @@ const handleConfirm = () => {
:is="provider.component" :is="provider.component"
v-if="activeId === provider.id" v-if="activeId === provider.id"
v-model:selected="selected" v-model:selected="selected"
@change-provider="onChangeProvider"
></component> ></component>
<template #fallback> 加载中 </template> <template #fallback> 加载中 </template>
</Suspense> </Suspense>

View File

@ -1,16 +1,14 @@
<script lang="ts" setup> <script lang="ts" setup>
import { import { VModal, IconAddCircle, VAlert } from "@halo-dev/components";
VModal,
VEmpty,
IconAddCircle,
VButton,
VSpace,
} from "@halo-dev/components";
import UppyUpload from "@/components/upload/UppyUpload.vue"; import UppyUpload from "@/components/upload/UppyUpload.vue";
import { computed, ref, watch, watchEffect } from "vue"; import { computed, ref, watch, watchEffect } from "vue";
import type { Policy, Group } from "@halo-dev/api-client"; import type { Policy, Group, PolicyTemplate } from "@halo-dev/api-client";
import { useFetchAttachmentPolicy } from "../composables/use-attachment-policy"; import {
import AttachmentPoliciesModal from "./AttachmentPoliciesModal.vue"; useFetchAttachmentPolicy,
useFetchAttachmentPolicyTemplate,
} from "../composables/use-attachment-policy";
import AttachmentPolicyEditingModal from "./AttachmentPolicyEditingModal.vue";
import { v4 as uuid } from "uuid";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@ -28,15 +26,19 @@ const emit = defineEmits<{
(event: "close"): void; (event: "close"): void;
}>(); }>();
const { policies, loading, handleFetchPolicies } = useFetchAttachmentPolicy(); const { policies, handleFetchPolicies } = useFetchAttachmentPolicy({
fetchOnMounted: false,
});
const { policyTemplates, handleFetchPolicyTemplates } =
useFetchAttachmentPolicyTemplate();
const selectedPolicy = ref<Policy | null>(null); const selectedPolicy = ref<Policy>();
const policyVisible = ref(false);
const uploadVisible = ref(false); const uploadVisible = ref(false);
const policyEditingModal = ref(false);
const modalTitle = computed(() => { const modalTitle = computed(() => {
if (props.group && props.group.metadata.name) { if (props.group && props.group.metadata.name) {
return `上传附件${props.group.spec.displayName}`; return `上传附件到分组${props.group.spec.displayName}`;
} }
return "上传附件"; return "上传附件";
}); });
@ -47,11 +49,36 @@ watchEffect(() => {
} }
}); });
const handleOpenCreateNewPolicyModal = (policyTemplate: PolicyTemplate) => {
selectedPolicy.value = {
spec: {
displayName: "",
templateRef: {
name: policyTemplate.metadata.name,
},
configMapRef: {
name: uuid(),
},
},
apiVersion: "storage.halo.run/v1alpha1",
kind: "Policy",
metadata: {
name: uuid(),
},
};
policyEditingModal.value = true;
};
const onEditingModalClose = async () => {
await handleFetchPolicies();
selectedPolicy.value = policies.value[0];
};
const onVisibleChange = (visible: boolean) => { const onVisibleChange = (visible: boolean) => {
emit("update:visible", visible); emit("update:visible", visible);
if (!visible) { if (!visible) {
emit("close"); emit("close");
policyVisible.value = false; policyEditingModal.value = false;
} }
}; };
@ -60,6 +87,7 @@ watch(
(newValue) => { (newValue) => {
if (newValue) { if (newValue) {
handleFetchPolicies(); handleFetchPolicies();
handleFetchPolicyTemplates();
uploadVisible.value = true; uploadVisible.value = true;
} else { } else {
const uploadVisibleTimer = setTimeout(() => { const uploadVisibleTimer = setTimeout(() => {
@ -74,28 +102,11 @@ watch(
<VModal <VModal
:body-class="['!p-0']" :body-class="['!p-0']"
:visible="visible" :visible="visible"
:width="600" :width="650"
:title="modalTitle" :title="modalTitle"
@update:visible="onVisibleChange" @update:visible="onVisibleChange"
> >
<VEmpty <div class="w-full p-4">
v-if="!policies.length && !loading"
message="当前没有上传附件的存储策略,请先创建存储策略"
title="无存储策略"
>
<template #actions>
<VSpace>
<VButton @click="handleFetchPolicies"></VButton>
<VButton type="secondary" @click="policyVisible = true">
<template #icon>
<IconAddCircle class="h-full w-full" />
</template>
新建策略
</VButton>
</VSpace>
</template>
</VEmpty>
<div v-else class="w-full p-4">
<div class="mb-2"> <div class="mb-2">
<span class="text-sm text-gray-900">选择存储策略</span> <span class="text-sm text-gray-900">选择存储策略</span>
</div> </div>
@ -119,15 +130,41 @@ watch(
</span> </span>
</div> </div>
</div> </div>
<FloatingDropdown>
<div <div
class="flex cursor-pointer items-center rounded-base bg-gray-100 p-2 text-gray-500 transition-all hover:bg-gray-200 hover:text-gray-900 hover:shadow-sm" class="flex h-full cursor-pointer items-center rounded-base bg-gray-100 p-2 text-gray-500 transition-all hover:bg-gray-200 hover:text-gray-900 hover:shadow-sm"
@click="policyVisible = true"
> >
<div class="flex flex-1 items-center truncate"> <div class="flex flex-1 items-center truncate">
<span class="truncate text-sm">新建策略</span> <span class="truncate text-sm">新建策略</span>
</div> </div>
<IconAddCircle /> <IconAddCircle />
</div> </div>
<template #popper>
<div class="w-72 p-4">
<ul class="space-y-1">
<li
v-for="(policyTemplate, index) in policyTemplates"
:key="index"
v-close-popper
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
@click="handleOpenCreateNewPolicyModal(policyTemplate)"
>
<span class="truncate">
{{ policyTemplate.spec?.displayName }}
</span>
</li>
</ul>
</div>
</template>
</FloatingDropdown>
</div>
<div v-if="policies.length <= 0" class="mb-3">
<VAlert
title="没有存储策略"
description="在上传之前,需要新建一个存储策略"
:closable="false"
/>
</div> </div>
<UppyUpload <UppyUpload
v-if="uploadVisible" v-if="uploadVisible"
@ -143,8 +180,9 @@ watch(
</div> </div>
</VModal> </VModal>
<AttachmentPoliciesModal <AttachmentPolicyEditingModal
v-model:visible="policyVisible" v-model:visible="policyEditingModal"
@close="handleFetchPolicies" :policy="selectedPolicy"
@close="onEditingModalClose"
/> />
</template> </template>

View File

@ -34,6 +34,7 @@ withDefaults(
const emit = defineEmits<{ const emit = defineEmits<{
(event: "update:selected", attachments: AttachmentLike[]): void; (event: "update:selected", attachments: AttachmentLike[]): void;
(event: "change-provider", providerId: string): void;
}>(); }>();
const selectedGroup = ref<Group>(); const selectedGroup = ref<Group>();
@ -85,7 +86,7 @@ await handleFetchAttachments();
<template #actions> <template #actions>
<VSpace> <VSpace>
<VButton @click="handleFetchAttachments"></VButton> <VButton @click="handleFetchAttachments"></VButton>
<VButton type="secondary" @click="uploadVisible = true"> <VButton type="secondary" @click="emit('change-provider', 'upload')">
<template #icon> <template #icon>
<IconUpload class="h-full w-full" /> <IconUpload class="h-full w-full" />
</template> </template>

View File

@ -1,6 +1,6 @@
import { onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
import type { Ref } from "vue"; import type { Ref } from "vue";
import type { Policy } from "@halo-dev/api-client"; import type { Policy, PolicyTemplate } from "@halo-dev/api-client";
import { apiClient } from "@/utils/api-client"; import { apiClient } from "@/utils/api-client";
interface useFetchAttachmentPolicyReturn { interface useFetchAttachmentPolicyReturn {
@ -9,6 +9,12 @@ interface useFetchAttachmentPolicyReturn {
handleFetchPolicies: () => void; handleFetchPolicies: () => void;
} }
interface useFetchAttachmentPolicyTemplatesReturn {
policyTemplates: Ref<PolicyTemplate[]>;
loading: Ref<boolean>;
handleFetchPolicyTemplates: () => void;
}
export function useFetchAttachmentPolicy(options?: { export function useFetchAttachmentPolicy(options?: {
fetchOnMounted: boolean; fetchOnMounted: boolean;
}): useFetchAttachmentPolicyReturn { }): useFetchAttachmentPolicyReturn {
@ -40,3 +46,35 @@ export function useFetchAttachmentPolicy(options?: {
handleFetchPolicies, handleFetchPolicies,
}; };
} }
export function useFetchAttachmentPolicyTemplate(options?: {
fetchOnMounted: boolean;
}): useFetchAttachmentPolicyTemplatesReturn {
const { fetchOnMounted } = options || {};
const policyTemplates = ref<PolicyTemplate[]>([] as PolicyTemplate[]);
const loading = ref<boolean>(false);
const handleFetchPolicyTemplates = async () => {
try {
loading.value = true;
const { data } =
await apiClient.extension.storage.policyTemplate.liststorageHaloRunV1alpha1PolicyTemplate();
policyTemplates.value = data.items;
} catch (e) {
console.error("Failed to fetch attachment policy templates", e);
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchOnMounted && handleFetchPolicyTemplates();
});
return {
policyTemplates,
loading,
handleFetchPolicyTemplates,
};
}