mirror of https://github.com/halo-dev/halo-admin
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
parent
9c29538c7a
commit
f7ece9135a
|
@ -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>
|
||||
|
|
|
@ -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)">
|
||||
|
|
Loading…
Reference in New Issue