diff --git a/docs/custom-formkit-input/README.md b/docs/custom-formkit-input/README.md
new file mode 100644
index 000000000..b5cc4958e
--- /dev/null
+++ b/docs/custom-formkit-input/README.md
@@ -0,0 +1,45 @@
+# 自定义 FormKit 输入组件
+
+## 原由
+
+目前不管是在 Console 中,还是在插件 / 主题设置表单中,都有可能选择系统当中的资源,所以可以通过自定义 FormKit 组件的方式提供常用的选择器。
+
+## 使用方式
+
+目前已提供以下类型:
+
+- `menuCheckbox`:选择一组菜单
+- `menuRadio`:选择一个菜单
+- `menuItemSelect`:选择菜单项
+- `postSelect`:选择文章
+- `singlePageSelect`:选择自定义页面
+- `categorySelect`:选择分类
+- `categoryCheckbox`:选择多个分类
+- `tagSelect`:选择标签
+- `tagCheckbox`:选择多个标签
+
+在 Vue 单组件中使用:
+
+```vue
+
+
+
+
+
+```
+
+在 FormKit Schema 中使用(插件 / 主题设置表单定义):
+
+```yaml
+- $formkit: menuRadio
+ name: menus
+ label: 底部菜单组
+```
diff --git a/src/formkit/formkit.config.ts b/src/formkit/formkit.config.ts
index ff0e9e681..e98f40bb4 100644
--- a/src/formkit/formkit.config.ts
+++ b/src/formkit/formkit.config.ts
@@ -4,6 +4,15 @@ import { createAutoAnimatePlugin } from "@formkit/addons";
import { zh } from "@formkit/i18n";
import type { DefaultConfigOptions } from "@formkit/vue";
import { form } from "./inputs/form";
+import { menuCheckbox } from "./inputs/menu-checkbox";
+import { menuRadio } from "./inputs/menu-radio";
+import { menuItemSelect } from "./inputs/menu-item-select";
+import { postSelect } from "./inputs/post-select";
+import { singlePageSelect } from "./inputs/singlePage-select";
+import { categorySelect } from "./inputs/category-select";
+import { tagSelect } from "./inputs/tag-select";
+import { categoryCheckbox } from "./inputs/category-checkbox";
+import { tagCheckbox } from "./inputs/tag-checkbox";
const config: DefaultConfigOptions = {
config: {
@@ -12,6 +21,15 @@ const config: DefaultConfigOptions = {
plugins: [createAutoAnimatePlugin()],
inputs: {
form,
+ menuCheckbox,
+ menuRadio,
+ menuItemSelect,
+ postSelect,
+ categorySelect,
+ tagSelect,
+ singlePageSelect,
+ categoryCheckbox,
+ tagCheckbox,
},
locales: { zh },
locale: "zh",
diff --git a/src/formkit/inputs/category-checkbox.ts b/src/formkit/inputs/category-checkbox.ts
new file mode 100644
index 000000000..6df15cd39
--- /dev/null
+++ b/src/formkit/inputs/category-checkbox.ts
@@ -0,0 +1,28 @@
+import { apiClient } from "@/utils/api-client";
+import type { FormKitNode, FormKitTypeDefinition } from "@formkit/core";
+import { checkbox, checkboxes, defaultIcon } from "@formkit/inputs";
+
+function optionsHandler(node: FormKitNode) {
+ node.on("created", async () => {
+ const { data } =
+ await apiClient.extension.category.listcontentHaloRunV1alpha1Category();
+
+ node.props.options = data.items.map((category) => {
+ return {
+ value: category.metadata.name,
+ label: category.spec.displayName,
+ };
+ });
+ });
+}
+
+export const categoryCheckbox: FormKitTypeDefinition = {
+ ...checkbox,
+ props: ["onValue", "offValue"],
+ forceTypeProp: "checkbox",
+ features: [
+ optionsHandler,
+ checkboxes,
+ defaultIcon("decorator", "checkboxDecorator"),
+ ],
+};
diff --git a/src/formkit/inputs/category-select.ts b/src/formkit/inputs/category-select.ts
new file mode 100644
index 000000000..35a428694
--- /dev/null
+++ b/src/formkit/inputs/category-select.ts
@@ -0,0 +1,24 @@
+import { apiClient } from "@/utils/api-client";
+import type { FormKitNode, FormKitTypeDefinition } from "@formkit/core";
+import { select, selects, defaultIcon } from "@formkit/inputs";
+
+function optionsHandler(node: FormKitNode) {
+ node.on("created", async () => {
+ const { data } =
+ await apiClient.extension.category.listcontentHaloRunV1alpha1Category();
+
+ node.props.options = data.items.map((category) => {
+ return {
+ value: category.metadata.name,
+ label: category.spec.displayName,
+ };
+ });
+ });
+}
+
+export const categorySelect: FormKitTypeDefinition = {
+ ...select,
+ props: ["placeholder"],
+ forceTypeProp: "select",
+ features: [optionsHandler, selects, defaultIcon("select", "select")],
+};
diff --git a/src/formkit/inputs/form.ts b/src/formkit/inputs/form.ts
index 99ad1f37a..5145e9dd1 100644
--- a/src/formkit/inputs/form.ts
+++ b/src/formkit/inputs/form.ts
@@ -31,6 +31,7 @@ export const form: FormKitTypeDefinition = {
"submitBehavior",
"incompleteMessage",
],
+ forceTypeProp: "form",
/**
* Additional features that should be added to your input
*/
diff --git a/src/formkit/inputs/menu-checkbox.ts b/src/formkit/inputs/menu-checkbox.ts
new file mode 100644
index 000000000..6ab0d3fd1
--- /dev/null
+++ b/src/formkit/inputs/menu-checkbox.ts
@@ -0,0 +1,27 @@
+import { apiClient } from "@/utils/api-client";
+import type { FormKitNode, FormKitTypeDefinition } from "@formkit/core";
+import { checkbox, checkboxes, defaultIcon } from "@formkit/inputs";
+
+function optionsHandler(node: FormKitNode) {
+ node.on("created", async () => {
+ const { data } = await apiClient.extension.menu.listv1alpha1Menu();
+
+ node.props.options = data.items.map((menu) => {
+ return {
+ value: menu.metadata.name,
+ label: menu.spec.displayName,
+ };
+ });
+ });
+}
+
+export const menuCheckbox: FormKitTypeDefinition = {
+ ...checkbox,
+ props: ["onValue", "offValue"],
+ forceTypeProp: "checkbox",
+ features: [
+ optionsHandler,
+ checkboxes,
+ defaultIcon("decorator", "checkboxDecorator"),
+ ],
+};
diff --git a/src/formkit/inputs/menu-item-select.ts b/src/formkit/inputs/menu-item-select.ts
new file mode 100644
index 000000000..e06f13b46
--- /dev/null
+++ b/src/formkit/inputs/menu-item-select.ts
@@ -0,0 +1,25 @@
+import { apiClient } from "@/utils/api-client";
+import type { FormKitNode, FormKitTypeDefinition } from "@formkit/core";
+import { select, selects, defaultIcon } from "@formkit/inputs";
+
+function optionsHandler(node: FormKitNode) {
+ node.on("created", async () => {
+ const { data } = await apiClient.extension.menuItem.listv1alpha1MenuItem({
+ fieldSelector: [`name=(${node.props.menuItems.join(",")})`],
+ });
+
+ node.props.options = data.items.map((menuItem) => {
+ return {
+ value: menuItem.metadata.name,
+ label: menuItem.status?.displayName,
+ };
+ });
+ });
+}
+
+export const menuItemSelect: FormKitTypeDefinition = {
+ ...select,
+ props: ["placeholder", "menuItems"],
+ forceTypeProp: "select",
+ features: [optionsHandler, selects, defaultIcon("select", "select")],
+};
diff --git a/src/formkit/inputs/menu-radio.ts b/src/formkit/inputs/menu-radio.ts
new file mode 100644
index 000000000..166a28a86
--- /dev/null
+++ b/src/formkit/inputs/menu-radio.ts
@@ -0,0 +1,27 @@
+import { apiClient } from "@/utils/api-client";
+import type { FormKitNode, FormKitTypeDefinition } from "@formkit/core";
+import { radio, radios, defaultIcon } from "@formkit/inputs";
+
+function optionsHandler(node: FormKitNode) {
+ node.on("created", async () => {
+ const { data } = await apiClient.extension.menu.listv1alpha1Menu();
+
+ node.props.options = data.items.map((menu) => {
+ return {
+ value: menu.metadata.name,
+ label: menu.spec.displayName,
+ };
+ });
+ });
+}
+
+export const menuRadio: FormKitTypeDefinition = {
+ ...radio,
+ props: ["onValue", "offValue"],
+ forceTypeProp: "radio",
+ features: [
+ optionsHandler,
+ radios,
+ defaultIcon("decorator", "radioDecorator"),
+ ],
+};
diff --git a/src/formkit/inputs/post-select.ts b/src/formkit/inputs/post-select.ts
new file mode 100644
index 000000000..9258a4a9e
--- /dev/null
+++ b/src/formkit/inputs/post-select.ts
@@ -0,0 +1,24 @@
+import { apiClient } from "@/utils/api-client";
+import type { FormKitNode, FormKitTypeDefinition } from "@formkit/core";
+import { select, selects, defaultIcon } from "@formkit/inputs";
+
+function optionsHandler(node: FormKitNode) {
+ node.on("created", async () => {
+ const { data } =
+ await apiClient.extension.post.listcontentHaloRunV1alpha1Post();
+
+ node.props.options = data.items.map((post) => {
+ return {
+ value: post.metadata.name,
+ label: post.spec.title,
+ };
+ });
+ });
+}
+
+export const postSelect: FormKitTypeDefinition = {
+ ...select,
+ props: ["placeholder"],
+ forceTypeProp: "select",
+ features: [optionsHandler, selects, defaultIcon("select", "select")],
+};
diff --git a/src/formkit/inputs/singlePage-select.ts b/src/formkit/inputs/singlePage-select.ts
new file mode 100644
index 000000000..738c3d8fa
--- /dev/null
+++ b/src/formkit/inputs/singlePage-select.ts
@@ -0,0 +1,24 @@
+import { apiClient } from "@/utils/api-client";
+import type { FormKitNode, FormKitTypeDefinition } from "@formkit/core";
+import { select, selects, defaultIcon } from "@formkit/inputs";
+
+function optionsHandler(node: FormKitNode) {
+ node.on("created", async () => {
+ const { data } =
+ await apiClient.extension.singlePage.listcontentHaloRunV1alpha1SinglePage();
+
+ node.props.options = data.items.map((singlePage) => {
+ return {
+ value: singlePage.metadata.name,
+ label: singlePage.spec.title,
+ };
+ });
+ });
+}
+
+export const singlePageSelect: FormKitTypeDefinition = {
+ ...select,
+ props: ["placeholder"],
+ forceTypeProp: "select",
+ features: [optionsHandler, selects, defaultIcon("select", "select")],
+};
diff --git a/src/formkit/inputs/tag-checkbox.ts b/src/formkit/inputs/tag-checkbox.ts
new file mode 100644
index 000000000..138b4b401
--- /dev/null
+++ b/src/formkit/inputs/tag-checkbox.ts
@@ -0,0 +1,28 @@
+import { apiClient } from "@/utils/api-client";
+import type { FormKitNode, FormKitTypeDefinition } from "@formkit/core";
+import { checkbox, checkboxes, defaultIcon } from "@formkit/inputs";
+
+function optionsHandler(node: FormKitNode) {
+ node.on("created", async () => {
+ const { data } =
+ await apiClient.extension.tag.listcontentHaloRunV1alpha1Tag();
+
+ node.props.options = data.items.map((tag) => {
+ return {
+ value: tag.metadata.name,
+ label: tag.spec.displayName,
+ };
+ });
+ });
+}
+
+export const tagCheckbox: FormKitTypeDefinition = {
+ ...checkbox,
+ props: ["onValue", "offValue"],
+ forceTypeProp: "checkbox",
+ features: [
+ optionsHandler,
+ checkboxes,
+ defaultIcon("decorator", "checkboxDecorator"),
+ ],
+};
diff --git a/src/formkit/inputs/tag-select.ts b/src/formkit/inputs/tag-select.ts
new file mode 100644
index 000000000..c09ea09c2
--- /dev/null
+++ b/src/formkit/inputs/tag-select.ts
@@ -0,0 +1,24 @@
+import { apiClient } from "@/utils/api-client";
+import type { FormKitNode, FormKitTypeDefinition } from "@formkit/core";
+import { select, selects, defaultIcon } from "@formkit/inputs";
+
+function optionsHandler(node: FormKitNode) {
+ node.on("created", async () => {
+ const { data } =
+ await apiClient.extension.tag.listcontentHaloRunV1alpha1Tag();
+
+ node.props.options = data.items.map((tag) => {
+ return {
+ value: tag.metadata.name,
+ label: tag.spec.displayName,
+ };
+ });
+ });
+}
+
+export const tagSelect: FormKitTypeDefinition = {
+ ...select,
+ props: ["placeholder"],
+ forceTypeProp: "select",
+ features: [optionsHandler, selects, defaultIcon("select", "select")],
+};
diff --git a/src/modules/contents/posts/components/PostSettingModal.vue b/src/modules/contents/posts/components/PostSettingModal.vue
index 1340a8a53..a53b73c23 100644
--- a/src/modules/contents/posts/components/PostSettingModal.vue
+++ b/src/modules/contents/posts/components/PostSettingModal.vue
@@ -1,10 +1,8 @@
@@ -407,26 +292,25 @@ watch(
v-if="selectedMenuItemSource === 'singlePage'"
v-model="selectedRef"
label="自定义页面"
- type="select"
- :options="singlePageMap"
+ type="singlePageSelect"
validation="required"
/>