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
Ryan Wang 2022-11-01 11:06:18 +08:00 committed by GitHub
parent f77be1ecb7
commit 9a24604a6b
11 changed files with 82 additions and 5 deletions

View File

@ -12,6 +12,7 @@
- 参数
1. language: 目前支持 `yaml`, `html`, `css`, `javascript`, `json`
2. height: 编辑器高度,如:`100px`
- `attachment`: 附件选择
- `menuCheckbox`:选择一组菜单
- `menuRadio`:选择一个菜单
- `menuItemSelect`:选择菜单项

View File

@ -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,

View File

@ -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>

View File

@ -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],
});

View File

@ -98,6 +98,7 @@ onMounted(async () => {
</script>
<template>
<AttachmentGroupEditingModal
v-if="!readonly"
v-model:visible="editingModal"
:group="groupToUpdate"
@close="onEditingModalClose"

View File

@ -61,6 +61,7 @@ const handleConfirm = () => {
<VModal
:visible="visible"
:width="1240"
:mount-to-body="true"
title="选择附件"
height="calc(100vh - 20px)"
@update:visible="onVisibleChange"

View File

@ -272,7 +272,7 @@ watchEffect(() => {
<FormKit
v-model="formState.page.spec.cover"
label="封面图"
type="text"
type="attachment"
name="cover"
></FormKit>
</FormKit>

View File

@ -152,7 +152,8 @@ watch(
help="需要主题适配以支持"
name="cover"
label="封面图"
type="text"
type="attachment"
validation="required"
></FormKit>
<FormKit
v-model="formState.spec.description"

View File

@ -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-->

View File

@ -171,7 +171,7 @@ watch(
name="cover"
help="需要主题适配以支持"
label="封面图"
type="text"
type="attachment"
></FormKit>
</FormKit>
<template #footer>

View File

@ -197,7 +197,7 @@ const handleRawModeChange = () => {
<FormKit
v-model="formState.spec.avatar"
label="头像"
type="text"
type="attachment"
name="avatar"
validation="url"
></FormKit>