mirror of https://github.com/halo-dev/halo
feat: add formkit custom input of attachment (halo-dev/console#674)
#### What type of PR is this? /kind feature /milestone 2.0 #### What this PR does / why we need it: 添加选择附件类型的 FormKit 输入框。 在 Vue 单组件中使用: ```vue <script lang="ts" setup> const logo = ref("") </script> <template> <FormKit v-model="logo" label="Logo" type="attachment" validation="required" /> </template> ``` 在 FormKit Schema 中使用(插件 / 主题设置表单定义): ```yaml - $formkit: attachment name: logo label: Logo ``` #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/2558 #### Screenshots: <img width="671" alt="image" src="https://user-images.githubusercontent.com/21301288/198980581-ba90ec32-f205-4d03-8546-3c93238298e7.png"> #### Special notes for your reviewer: /cc @halo-dev/sig-halo-console #### Does this PR introduce a user-facing change? ```release-note 添加选择附件类型的 FormKit 输入框。 ```pull/3445/head
parent
f77be1ecb7
commit
9a24604a6b
|
@ -12,6 +12,7 @@
|
|||
- 参数
|
||||
1. language: 目前支持 `yaml`, `html`, `css`, `javascript`, `json`
|
||||
2. height: 编辑器高度,如:`100px`
|
||||
- `attachment`: 附件选择
|
||||
- `menuCheckbox`:选择一组菜单
|
||||
- `menuRadio`:选择一个菜单
|
||||
- `menuItemSelect`:选择菜单项
|
||||
|
|
|
@ -4,6 +4,7 @@ import { createAutoAnimatePlugin } from "@formkit/addons";
|
|||
import { zh } from "@formkit/i18n";
|
||||
import type { DefaultConfigOptions } from "@formkit/vue";
|
||||
import { form } from "./inputs/form";
|
||||
import { attachment } from "./inputs/attachment";
|
||||
import { code } from "./inputs/code";
|
||||
import { menuCheckbox } from "./inputs/menu-checkbox";
|
||||
import { menuRadio } from "./inputs/menu-radio";
|
||||
|
@ -22,6 +23,7 @@ const config: DefaultConfigOptions = {
|
|||
plugins: [createAutoAnimatePlugin()],
|
||||
inputs: {
|
||||
form,
|
||||
attachment,
|
||||
code,
|
||||
menuCheckbox,
|
||||
menuRadio,
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
<script lang="ts" setup>
|
||||
import type { FormKitFrameworkContext } from "@formkit/core";
|
||||
import { IconFolder } from "@halo-dev/components";
|
||||
import AttachmentSelectorModal from "@/modules/contents/attachments/components/AttachmentSelectorModal.vue";
|
||||
import { ref, type PropType } from "vue";
|
||||
import type { AttachmentLike } from "@halo-dev/console-shared";
|
||||
|
||||
const props = defineProps({
|
||||
context: {
|
||||
type: Object as PropType<FormKitFrameworkContext>,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const attachmentSelectorModal = ref(false);
|
||||
|
||||
const onInput = (e: Event) => {
|
||||
props.context.handlers.DOMInput(e);
|
||||
};
|
||||
|
||||
const onAttachmentSelect = (attachments: AttachmentLike[]) => {
|
||||
const urls: (string | undefined)[] = attachments.map((attachment) => {
|
||||
if (typeof attachment === "string") {
|
||||
return attachment;
|
||||
}
|
||||
if ("url" in attachment) {
|
||||
return attachment.url;
|
||||
}
|
||||
if ("spec" in attachment) {
|
||||
return attachment.status?.permalink;
|
||||
}
|
||||
});
|
||||
|
||||
if (urls.length) {
|
||||
props.context.node.input(urls[0]);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input
|
||||
:id="context.id"
|
||||
:value="context._value"
|
||||
:class="context.classes.input"
|
||||
:name="context.node.name"
|
||||
v-bind="context.attrs"
|
||||
type="text"
|
||||
@blur="context.handlers.blur()"
|
||||
@input="onInput"
|
||||
/>
|
||||
<div
|
||||
class="group flex h-full cursor-pointer items-center border-l px-3 transition-all hover:bg-gray-100"
|
||||
@click="attachmentSelectorModal = true"
|
||||
>
|
||||
<IconFolder class="h-4 w-4 text-gray-500 group-hover:text-gray-700" />
|
||||
</div>
|
||||
<AttachmentSelectorModal
|
||||
v-model:visible="attachmentSelectorModal"
|
||||
@select="onAttachmentSelect"
|
||||
/>
|
||||
</template>
|
|
@ -0,0 +1,10 @@
|
|||
import { initialValue } from "@formkit/inputs";
|
||||
import { createInput } from "@formkit/vue";
|
||||
import AttachmentInput from "./AttachmentInput.vue";
|
||||
|
||||
export const attachment = createInput(AttachmentInput, {
|
||||
type: "input",
|
||||
props: [],
|
||||
forceTypeProp: "text",
|
||||
features: [initialValue],
|
||||
});
|
|
@ -98,6 +98,7 @@ onMounted(async () => {
|
|||
</script>
|
||||
<template>
|
||||
<AttachmentGroupEditingModal
|
||||
v-if="!readonly"
|
||||
v-model:visible="editingModal"
|
||||
:group="groupToUpdate"
|
||||
@close="onEditingModalClose"
|
||||
|
|
|
@ -61,6 +61,7 @@ const handleConfirm = () => {
|
|||
<VModal
|
||||
:visible="visible"
|
||||
:width="1240"
|
||||
:mount-to-body="true"
|
||||
title="选择附件"
|
||||
height="calc(100vh - 20px)"
|
||||
@update:visible="onVisibleChange"
|
||||
|
|
|
@ -272,7 +272,7 @@ watchEffect(() => {
|
|||
<FormKit
|
||||
v-model="formState.page.spec.cover"
|
||||
label="封面图"
|
||||
type="text"
|
||||
type="attachment"
|
||||
name="cover"
|
||||
></FormKit>
|
||||
</FormKit>
|
||||
|
|
|
@ -152,7 +152,8 @@ watch(
|
|||
help="需要主题适配以支持"
|
||||
name="cover"
|
||||
label="封面图"
|
||||
type="text"
|
||||
type="attachment"
|
||||
validation="required"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="formState.spec.description"
|
||||
|
|
|
@ -285,7 +285,7 @@ watchEffect(() => {
|
|||
v-model="formState.post.spec.cover"
|
||||
name="cover"
|
||||
label="封面图"
|
||||
type="text"
|
||||
type="attachment"
|
||||
></FormKit>
|
||||
</FormKit>
|
||||
<!--TODO: add SEO/Metas/Inject Code form-->
|
||||
|
|
|
@ -171,7 +171,7 @@ watch(
|
|||
name="cover"
|
||||
help="需要主题适配以支持"
|
||||
label="封面图"
|
||||
type="text"
|
||||
type="attachment"
|
||||
></FormKit>
|
||||
</FormKit>
|
||||
<template #footer>
|
||||
|
|
|
@ -197,7 +197,7 @@ const handleRawModeChange = () => {
|
|||
<FormKit
|
||||
v-model="formState.spec.avatar"
|
||||
label="头像"
|
||||
type="text"
|
||||
type="attachment"
|
||||
name="avatar"
|
||||
validation="url"
|
||||
></FormKit>
|
||||
|
|
Loading…
Reference in New Issue