refactor: add validation for post and singlePage settings form (#791)

#### What type of PR is this?

/kind improvement

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

重构文章和自定义页面的设置表单,支持提交时验证表单。

> 因为之前的多选项卡设计导致无法同时验证所有表单,所以这个 PR 重构了表单的布局。

#### Which issue(s) this PR fixes:

Fixes https://github.com/halo-dev/halo/issues/2859

#### Screenshots:

![2022-12-21 17 23 22](https://user-images.githubusercontent.com/21301288/208870059-5039a565-def2-4622-9a78-de30dceb4d65.gif)

#### Special notes for your reviewer:

测试方式:

1. 测试在内容编辑页面和列表打开文章和自定义页面的设置表单。
2. 检查表单验证是否有效。

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


```release-note
重构 Console 端文章和自定义页面的设置表单布局,支持提交时验证表单。
```
pull/797/head^2
Ryan Wang 2022-12-24 12:14:30 +08:00 committed by GitHub
parent 9c29538c7a
commit f7ece9135a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 363 additions and 280 deletions

View File

@ -1,13 +1,6 @@
<script lang="ts" setup>
import {
Toast,
VButton,
VModal,
VSpace,
VTabItem,
VTabs,
} from "@halo-dev/components";
import { computed, ref, watchEffect } from "vue";
import { Toast, VButton, VModal, VSpace } from "@halo-dev/components";
import { computed, nextTick, ref, watchEffect } from "vue";
import type { SinglePage } from "@halo-dev/api-client";
import cloneDeep from "lodash.clonedeep";
import { apiClient } from "@/utils/api-client";
@ -15,6 +8,7 @@ import { useThemeCustomTemplates } from "@/modules/interface/themes/composables/
import { singlePageLabels } from "@/constants/labels";
import { randomUUID } from "@/utils/id";
import { toDatetimeLocal, toISOString } from "@/utils/date";
import { submitForm } from "@formkit/core";
const initialFormState: SinglePage = {
spec: {
@ -64,11 +58,11 @@ const emit = defineEmits<{
(event: "published", singlePage: SinglePage): void;
}>();
const activeTab = ref("general");
const formState = ref<SinglePage>(cloneDeep(initialFormState));
const saving = ref(false);
const publishing = ref(false);
const publishCanceling = ref(false);
const submitType = ref<"publish" | "save">();
const isUpdateMode = computed(() => {
return !!formState.value.metadata.creationTimestamp;
@ -77,13 +71,35 @@ const isUpdateMode = computed(() => {
const onVisibleChange = (visible: boolean) => {
emit("update:visible", visible);
if (!visible) {
setTimeout(() => {
activeTab.value = "general";
}, 200);
emit("close");
}
};
const handleSubmit = () => {
if (submitType.value === "publish") {
handlePublish();
}
if (submitType.value === "save") {
handleSave();
}
};
const handleSaveClick = () => {
submitType.value = "save";
nextTick(() => {
submitForm("singlePage-setting-form");
});
};
const handlePublishClick = () => {
submitType.value = "publish";
nextTick(() => {
submitForm("singlePage-setting-form");
});
};
const handleSave = async () => {
if (props.onlyEmit) {
emit("saved", formState.value);
@ -121,50 +137,71 @@ const handleSave = async () => {
}
};
const handleSwitchPublish = async (publish: boolean) => {
const handlePublish = async () => {
if (props.onlyEmit) {
emit("published", formState.value);
return;
}
try {
if (publish) {
publishing.value = true;
} else {
publishCanceling.value = true;
}
if (publish) {
formState.value.spec.releaseSnapshot = formState.value.spec.headSnapshot;
}
const singlePageToUpdate = cloneDeep(formState.value);
singlePageToUpdate.spec.releaseSnapshot =
singlePageToUpdate.spec.headSnapshot;
singlePageToUpdate.spec.publish = true;
const { data } =
await apiClient.extension.singlePage.updatecontentHaloRunV1alpha1SinglePage(
{
name: formState.value.metadata.name,
singlePage: {
...formState.value,
spec: {
...formState.value.spec,
publish: publish,
},
},
singlePage: singlePageToUpdate,
}
);
formState.value = data;
if (publish) {
emit("published", data);
}
onVisibleChange(false);
Toast.success(`${publish ? "发布" : "取消发布"}成功`);
Toast.success("发布成功");
} catch (error) {
console.error("Failed to publish single page", error);
} finally {
publishing.value = false;
}
};
const handleUnpublish = async () => {
try {
publishCanceling.value = true;
const { data: singlePage } =
await apiClient.extension.singlePage.getcontentHaloRunV1alpha1SinglePage({
name: formState.value.metadata.name,
});
const singlePageToUpdate = cloneDeep(singlePage);
singlePageToUpdate.spec.publish = false;
const { data } =
await apiClient.extension.singlePage.updatecontentHaloRunV1alpha1SinglePage(
{
name: formState.value.metadata.name,
singlePage: singlePageToUpdate,
}
);
formState.value = data;
onVisibleChange(false);
Toast.success("取消发布成功");
} catch (error) {
console.error("Failed to unpublish single page", error);
} finally {
publishCanceling.value = false;
}
};
@ -204,15 +241,23 @@ const onPublishTimeChange = (value: string) => {
<slot name="actions"></slot>
</template>
<VTabs v-model:active-id="activeTab" type="outline">
<VTabItem id="general" label="常规">
<FormKit
id="basic"
name="basic"
:actions="false"
:preserve="true"
id="singlePage-setting-form"
type="form"
name="singlePage-setting-form"
:config="{ validationVisibility: 'submit' }"
@submit="handleSubmit"
>
<div>
<div class="md:grid md:grid-cols-4 md:gap-6">
<div class="md:col-span-1">
<div class="sticky top-0">
<span class="text-base font-medium text-gray-900">
常规设置
</span>
</div>
</div>
<div class="mt-5 divide-y divide-gray-100 md:col-span-3 md:mt-0">
<FormKit
v-model="formState.spec.title"
label="标题"
@ -247,16 +292,22 @@ const onPublishTimeChange = (value: string) => {
validation="length:0,1024"
:rows="5"
></FormKit>
</FormKit>
</VTabItem>
<VTabItem id="advanced" label="高级">
<FormKit
id="advanced"
name="advanced"
:actions="false"
:preserve="true"
type="form"
>
</div>
</div>
<div class="py-5">
<div class="border-t border-gray-200"></div>
</div>
<div class="md:grid md:grid-cols-4 md:gap-6">
<div class="md:col-span-1">
<div class="sticky top-0">
<span class="text-base font-medium text-gray-900">
高级设置
</span>
</div>
</div>
<div class="mt-5 divide-y divide-gray-100 md:col-span-3 md:mt-0">
<FormKit
v-model="formState.spec.allowComment"
:options="[
@ -308,10 +359,10 @@ const onPublishTimeChange = (value: string) => {
name="cover"
validation="length:0,1024"
></FormKit>
</div>
</div>
</div>
</FormKit>
<!--TODO: add SEO/Metas/Inject Code form-->
</VTabItem>
</VTabs>
<template #footer>
<VSpace>
@ -322,7 +373,7 @@ const onPublishTimeChange = (value: string) => {
"
:loading="publishing"
type="secondary"
@click="handleSwitchPublish(true)"
@click="handlePublishClick()"
>
发布
</VButton>
@ -330,17 +381,15 @@ const onPublishTimeChange = (value: string) => {
v-else
:loading="publishCanceling"
type="danger"
@click="handleSwitchPublish(false)"
@click="handleUnpublish()"
>
取消发布
</VButton>
</template>
<VButton :loading="saving" type="secondary" @click="handleSave">
<VButton :loading="saving" type="secondary" @click="handleSaveClick">
保存
</VButton>
<VButton size="sm" type="default" @click="onVisibleChange(false)">
关闭
</VButton>
<VButton type="default" @click="onVisibleChange(false)"> </VButton>
</VSpace>
</template>
</VModal>

View File

@ -1,13 +1,6 @@
<script lang="ts" setup>
import {
Toast,
VButton,
VModal,
VSpace,
VTabItem,
VTabs,
} from "@halo-dev/components";
import { computed, ref, watchEffect } from "vue";
import { Toast, VButton, VModal, VSpace } from "@halo-dev/components";
import { computed, nextTick, ref, watchEffect } from "vue";
import type { Post } from "@halo-dev/api-client";
import cloneDeep from "lodash.clonedeep";
import { apiClient } from "@/utils/api-client";
@ -15,6 +8,7 @@ import { useThemeCustomTemplates } from "@/modules/interface/themes/composables/
import { postLabels } from "@/constants/labels";
import { randomUUID } from "@/utils/id";
import { toDatetimeLocal, toISOString } from "@/utils/date";
import { submitForm } from "@formkit/core";
const initialFormState: Post = {
spec: {
@ -66,11 +60,11 @@ const emit = defineEmits<{
(event: "published", post: Post): void;
}>();
const activeTab = ref("general");
const formState = ref<Post>(cloneDeep(initialFormState));
const saving = ref(false);
const publishing = ref(false);
const publishCanceling = ref(false);
const submitType = ref<"publish" | "save">();
const isUpdateMode = computed(() => {
return !!formState.value.metadata.creationTimestamp;
@ -79,13 +73,39 @@ const isUpdateMode = computed(() => {
const handleVisibleChange = (visible: boolean) => {
emit("update:visible", visible);
if (!visible) {
setTimeout(() => {
activeTab.value = "general";
}, 200);
emit("close");
}
};
const handleSubmit = () => {
if (submitType.value === "publish") {
handlePublish();
}
if (submitType.value === "save") {
handleSave();
}
};
const handleSaveClick = () => {
submitType.value = "save";
nextTick(() => {
submitForm("post-setting-form");
});
};
const handlePublishClick = () => {
if (!props.onlyEmit) {
handlePublish();
}
submitType.value = "publish";
nextTick(() => {
submitForm("post-setting-form");
});
};
const handleSave = async () => {
if (props.onlyEmit) {
emit("saved", formState.value);
@ -197,15 +217,23 @@ const onPublishTimeChange = (value: string) => {
<slot name="actions"></slot>
</template>
<VTabs v-model:active-id="activeTab" type="outline">
<VTabItem id="general" label="常规">
<FormKit
id="basic"
name="basic"
:actions="false"
:preserve="true"
id="post-setting-form"
type="form"
name="post-setting-form"
:config="{ validationVisibility: 'submit' }"
@submit="handleSubmit"
>
<div>
<div class="md:grid md:grid-cols-4 md:gap-6">
<div class="md:col-span-1">
<div class="sticky top-0">
<span class="text-base font-medium text-gray-900">
常规设置
</span>
</div>
</div>
<div class="mt-5 divide-y divide-gray-100 md:col-span-3 md:mt-0">
<FormKit
v-model="formState.spec.title"
label="标题"
@ -252,16 +280,22 @@ const onPublishTimeChange = (value: string) => {
:rows="5"
validation="length:0,1024"
></FormKit>
</FormKit>
</VTabItem>
<VTabItem id="advanced" label="高级">
<FormKit
id="advanced"
name="advanced"
:actions="false"
:preserve="true"
type="form"
>
</div>
</div>
<div class="py-5">
<div class="border-t border-gray-200"></div>
</div>
<div class="md:grid md:grid-cols-4 md:gap-6">
<div class="md:col-span-1">
<div class="sticky top-0">
<span class="text-base font-medium text-gray-900">
高级设置
</span>
</div>
</div>
<div class="mt-5 divide-y divide-gray-100 md:col-span-3 md:mt-0">
<FormKit
v-model="formState.spec.allowComment"
:options="[
@ -311,10 +345,10 @@ const onPublishTimeChange = (value: string) => {
type="attachment"
validation="length:0,1024"
></FormKit>
</div>
</div>
</div>
</FormKit>
<!--TODO: add SEO/Metas/Inject Code form-->
</VTabItem>
</VTabs>
<template #footer>
<VSpace>
@ -323,7 +357,7 @@ const onPublishTimeChange = (value: string) => {
v-if="formState.metadata.labels?.[postLabels.PUBLISHED] !== 'true'"
:loading="publishing"
type="secondary"
@click="handlePublish()"
@click="handlePublishClick()"
>
发布
</VButton>
@ -336,7 +370,7 @@ const onPublishTimeChange = (value: string) => {
取消发布
</VButton>
</template>
<VButton :loading="saving" type="secondary" @click="handleSave">
<VButton :loading="saving" type="secondary" @click="handleSaveClick()">
保存
</VButton>
<VButton type="default" @click="handleVisibleChange(false)">