This commit is contained in:
@ -15,10 +15,13 @@
|
|||||||
"@formkit/icons": "latest",
|
"@formkit/icons": "latest",
|
||||||
"@formkit/themes": "latest",
|
"@formkit/themes": "latest",
|
||||||
"@formkit/vue": "latest",
|
"@formkit/vue": "latest",
|
||||||
|
"@formkit/zod": "^1.6.9",
|
||||||
"autoprefixer": "latest",
|
"autoprefixer": "latest",
|
||||||
|
"i18next": "^23.16.6",
|
||||||
"postcss": "latest",
|
"postcss": "latest",
|
||||||
"tailwindcss": "latest",
|
"tailwindcss": "latest",
|
||||||
"vue": "^3.5.13"
|
"vue": "^3.5.13",
|
||||||
|
"zod-i18n-map": "^2.27.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.2.0",
|
"@vitejs/plugin-vue": "^5.2.0",
|
||||||
|
55
pnpm-lock.yaml
generated
55
pnpm-lock.yaml
generated
@ -23,9 +23,15 @@ importers:
|
|||||||
'@formkit/vue':
|
'@formkit/vue':
|
||||||
specifier: latest
|
specifier: latest
|
||||||
version: 1.6.9(tailwindcss@3.4.15)(vue@3.5.13(typescript@5.6.3))
|
version: 1.6.9(tailwindcss@3.4.15)(vue@3.5.13(typescript@5.6.3))
|
||||||
|
'@formkit/zod':
|
||||||
|
specifier: ^1.6.9
|
||||||
|
version: 1.6.9(zod@3.23.8)
|
||||||
autoprefixer:
|
autoprefixer:
|
||||||
specifier: latest
|
specifier: latest
|
||||||
version: 10.4.20(postcss@8.4.49)
|
version: 10.4.20(postcss@8.4.49)
|
||||||
|
i18next:
|
||||||
|
specifier: ^23.16.6
|
||||||
|
version: 23.16.6
|
||||||
postcss:
|
postcss:
|
||||||
specifier: latest
|
specifier: latest
|
||||||
version: 8.4.49
|
version: 8.4.49
|
||||||
@ -35,6 +41,9 @@ importers:
|
|||||||
vue:
|
vue:
|
||||||
specifier: ^3.5.13
|
specifier: ^3.5.13
|
||||||
version: 3.5.13(typescript@5.6.3)
|
version: 3.5.13(typescript@5.6.3)
|
||||||
|
zod-i18n-map:
|
||||||
|
specifier: ^2.27.0
|
||||||
|
version: 2.27.0(i18next@23.16.6)(zod@3.23.8)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@vitejs/plugin-vue':
|
'@vitejs/plugin-vue':
|
||||||
specifier: ^5.2.0
|
specifier: ^5.2.0
|
||||||
@ -68,6 +77,10 @@ packages:
|
|||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
'@babel/runtime@7.26.0':
|
||||||
|
resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/types@7.26.0':
|
'@babel/types@7.26.0':
|
||||||
resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==}
|
resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@ -262,6 +275,11 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
vue: ^3.4.0
|
vue: ^3.4.0
|
||||||
|
|
||||||
|
'@formkit/zod@1.6.9':
|
||||||
|
resolution: {integrity: sha512-oQp2n2UoJTZTVsbbiDLRzpdOcGPrFTpmoaqp3tzbL4m7XJ9CE+Z9Q1sFA3MXTb1xrdCh3xWY2KAyXB0x4j5svw==}
|
||||||
|
peerDependencies:
|
||||||
|
zod: ^3.0.0
|
||||||
|
|
||||||
'@isaacs/cliui@8.0.2':
|
'@isaacs/cliui@8.0.2':
|
||||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@ -621,6 +639,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
|
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
i18next@23.16.6:
|
||||||
|
resolution: {integrity: sha512-wGdE5rUfkZtrL5k6MCptxbpjmgwku4rBRVU/YOJ7Xfd841fgaZjlxHpVJ5NIz8sfSvAJhEhJrvJ8qE7AWXE4Xg==}
|
||||||
|
|
||||||
is-binary-path@2.1.0:
|
is-binary-path@2.1.0:
|
||||||
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
|
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -801,6 +822,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||||
engines: {node: '>=8.10.0'}
|
engines: {node: '>=8.10.0'}
|
||||||
|
|
||||||
|
regenerator-runtime@0.14.1:
|
||||||
|
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
|
||||||
|
|
||||||
resolve@1.22.8:
|
resolve@1.22.8:
|
||||||
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
|
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -962,6 +986,15 @@ packages:
|
|||||||
engines: {node: '>= 14'}
|
engines: {node: '>= 14'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
zod-i18n-map@2.27.0:
|
||||||
|
resolution: {integrity: sha512-ORu9XpiVh3WDiEUs5Cr9siGgnpeODoBsTIgSD8sQCH9B//f9KowlzqHUEdPYb3vFonaSH8yPvPCOFM4niwp3Sg==}
|
||||||
|
peerDependencies:
|
||||||
|
i18next: '>=21.3.0'
|
||||||
|
zod: '>=3.17.0'
|
||||||
|
|
||||||
|
zod@3.23.8:
|
||||||
|
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
'@alloc/quick-lru@5.2.0': {}
|
'@alloc/quick-lru@5.2.0': {}
|
||||||
@ -974,6 +1007,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@babel/types': 7.26.0
|
'@babel/types': 7.26.0
|
||||||
|
|
||||||
|
'@babel/runtime@7.26.0':
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime: 0.14.1
|
||||||
|
|
||||||
'@babel/types@7.26.0':
|
'@babel/types@7.26.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/helper-string-parser': 7.25.9
|
'@babel/helper-string-parser': 7.25.9
|
||||||
@ -1123,6 +1160,11 @@ snapshots:
|
|||||||
- unocss
|
- unocss
|
||||||
- windicss
|
- windicss
|
||||||
|
|
||||||
|
'@formkit/zod@1.6.9(zod@3.23.8)':
|
||||||
|
dependencies:
|
||||||
|
'@formkit/core': 1.6.9
|
||||||
|
zod: 3.23.8
|
||||||
|
|
||||||
'@isaacs/cliui@8.0.2':
|
'@isaacs/cliui@8.0.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
string-width: 5.1.2
|
string-width: 5.1.2
|
||||||
@ -1490,6 +1532,10 @@ snapshots:
|
|||||||
|
|
||||||
he@1.2.0: {}
|
he@1.2.0: {}
|
||||||
|
|
||||||
|
i18next@23.16.6:
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.26.0
|
||||||
|
|
||||||
is-binary-path@2.1.0:
|
is-binary-path@2.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
binary-extensions: 2.3.0
|
binary-extensions: 2.3.0
|
||||||
@ -1631,6 +1677,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
picomatch: 2.3.1
|
picomatch: 2.3.1
|
||||||
|
|
||||||
|
regenerator-runtime@0.14.1: {}
|
||||||
|
|
||||||
resolve@1.22.8:
|
resolve@1.22.8:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-core-module: 2.15.1
|
is-core-module: 2.15.1
|
||||||
@ -1806,3 +1854,10 @@ snapshots:
|
|||||||
strip-ansi: 7.1.0
|
strip-ansi: 7.1.0
|
||||||
|
|
||||||
yaml@2.6.0: {}
|
yaml@2.6.0: {}
|
||||||
|
|
||||||
|
zod-i18n-map@2.27.0(i18next@23.16.6)(zod@3.23.8):
|
||||||
|
dependencies:
|
||||||
|
i18next: 23.16.6
|
||||||
|
zod: 3.23.8
|
||||||
|
|
||||||
|
zod@3.23.8: {}
|
||||||
|
228
src/App.vue
228
src/App.vue
@ -1,233 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { FormKitSchemaDefinition, getNode, type FormKitNode } from '@formkit/core';
|
import TutorialForm from './tutorial-form/index.vue'
|
||||||
import { onMounted } from 'vue';
|
import ZodForm from './zod-form/index.vue'
|
||||||
import Guests from './guests.vue';
|
|
||||||
|
|
||||||
async function submit(...args: any[]) {
|
|
||||||
console.debug('submit', `args :>> `, args);
|
|
||||||
await new Promise(r => setTimeout(r, 1000))
|
|
||||||
alert('Submitted! 🎉')
|
|
||||||
}
|
|
||||||
|
|
||||||
const castNumber = (node: FormKitNode) => {
|
|
||||||
node.hook.input((value, next) => next(Number(value)))
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
console.group('onMounted')
|
|
||||||
const nameNode = getNode('nameNodeId') // 用 id 获取节点
|
|
||||||
Object.assign(window, { nameNode })
|
|
||||||
console.debug(`nameNode :>> `, nameNode);
|
|
||||||
|
|
||||||
const schemaGroupInput = nameNode?.root.at('schemaGroup.input') // 用路径获取节点
|
|
||||||
console.debug(`schemaGroupInput :>> `, schemaGroupInput);
|
|
||||||
nameNode?.on('commit', ({ payload }) => {
|
|
||||||
console.debug('[nameNode] [on commit]', `payload :>> `, payload);
|
|
||||||
schemaGroupInput?.input(`姓名:${payload}`)
|
|
||||||
})
|
|
||||||
console.groupEnd()
|
|
||||||
})
|
|
||||||
|
|
||||||
const SCHEMA: FormKitSchemaDefinition = [
|
|
||||||
{
|
|
||||||
$el: 'h1',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
$el: 'span',
|
|
||||||
children: 'JSON Schema Form',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
attrs: {
|
|
||||||
class: 'font-bold text-2xl mb-4',
|
|
||||||
'data-foo': 'bar',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$formkit: 'group',
|
|
||||||
name: 'schemaGroup',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
$formkit: 'text',
|
|
||||||
label: 'JSON Schema 渲染的输入框',
|
|
||||||
name: 'input',
|
|
||||||
validation: 'required',
|
|
||||||
validationLabel: 'validationLabel', // validation message 使用顺序:validationLabel > label > name
|
|
||||||
errors: ['显式设置的错误是非阻塞的,这意味着它们不会像验证错误那样阻止表单提交。您可以在表单文档中了解更多有关错误处理的信息。']
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
function onChangeName() {
|
|
||||||
const nameNode = getNode('nameNodeId')
|
|
||||||
nameNode?.input('测试')
|
|
||||||
}
|
|
||||||
|
|
||||||
import autoAnimate from "@formkit/auto-animate"
|
|
||||||
onMounted(() => {
|
|
||||||
// setTimeout(() => {
|
|
||||||
autoAnimate(document.getElementById('attributes-group-wrapper')!);
|
|
||||||
// });
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-white rounded-xl shadow-xl p-8 mx-auto my-16 max-w-[450px]">
|
<ZodForm />
|
||||||
|
<TutorialForm />
|
||||||
<FormKit
|
|
||||||
type="form"
|
|
||||||
#default="{ value }"
|
|
||||||
@submit="submit"
|
|
||||||
submit-label="提交 ✨"
|
|
||||||
>
|
|
||||||
<!-- v-auto-animate -->
|
|
||||||
<div
|
|
||||||
class="character-attributes"
|
|
||||||
id="attributes-group-wrapper"
|
|
||||||
>
|
|
||||||
<FormKit
|
|
||||||
type="group"
|
|
||||||
name="attributes"
|
|
||||||
:validation-rules="{
|
|
||||||
validateGroup: (node) => {
|
|
||||||
const name = (node.value as Record<string, any>).name
|
|
||||||
return name?.includes('测试') ? true : false
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
validation-visibility="dirty"
|
|
||||||
validation="validateGroup"
|
|
||||||
:validation-messages="{
|
|
||||||
validateGroup: ({ name/* , args */ }) => {
|
|
||||||
return `分组 ${name} 下的姓名必须包含 '测试' 字符串。`
|
|
||||||
},
|
|
||||||
}"
|
|
||||||
#default="{ id, messages, fns, classes, value: attributes, }"
|
|
||||||
>
|
|
||||||
|
|
||||||
<FormKit
|
|
||||||
type="text"
|
|
||||||
name="name"
|
|
||||||
id="nameNodeId"
|
|
||||||
label="姓名"
|
|
||||||
:help="`现在 confirmName 的值是:${attributes?.confirmName}`"
|
|
||||||
some-attr="some-attr-value"
|
|
||||||
>
|
|
||||||
<template #label="{ classes, attrs, label }">
|
|
||||||
<!-- <pre
|
|
||||||
class="font-mono text-sm p-4 bg-slate-100 mb-4">{{ JSON.stringify({ classes, attrs, label }, null, 2) }}</pre> -->
|
|
||||||
<label
|
|
||||||
for="nameNodeId"
|
|
||||||
v-bind="attrs"
|
|
||||||
:class="classes.label"
|
|
||||||
>{{ label }}<button
|
|
||||||
class="ml-2 px-2 py-1 bg-blue-500 text-white rounded"
|
|
||||||
type="button"
|
|
||||||
@click="onChangeName"
|
|
||||||
>点击赋值</button>
|
|
||||||
</label>
|
|
||||||
</template>
|
|
||||||
</FormKit>
|
|
||||||
|
|
||||||
<FormKit
|
|
||||||
v-if="attributes?.name"
|
|
||||||
:preserve="false"
|
|
||||||
name="confirmName"
|
|
||||||
type="checkbox"
|
|
||||||
:value="false"
|
|
||||||
:label="`如果清空 name 输入框,这个确认框及其值将被移除。`"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormKit
|
|
||||||
type="checkbox"
|
|
||||||
name="flavors"
|
|
||||||
label="最喜欢的冰淇淋口味"
|
|
||||||
:options="{
|
|
||||||
'vanilla': '香草',
|
|
||||||
'chocolate': '巧克力',
|
|
||||||
'strawberry': '草莓',
|
|
||||||
}"
|
|
||||||
validation="required|min:2"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormKit
|
|
||||||
type="range"
|
|
||||||
name="strength"
|
|
||||||
id="strength"
|
|
||||||
label="力量"
|
|
||||||
value="5"
|
|
||||||
validation="min:2|max:9"
|
|
||||||
validation-visibility="live"
|
|
||||||
min="1"
|
|
||||||
max="10"
|
|
||||||
step="1"
|
|
||||||
help="这个角色应该有多少力量点?"
|
|
||||||
:plugins="[castNumber]"
|
|
||||||
class="mb-4"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormKit
|
|
||||||
type="textarea"
|
|
||||||
auto-height
|
|
||||||
label="我有自动高度插件"
|
|
||||||
help="这个文本框会随着输入内容增加而增高"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- By default groups do not show validation messages, so we need to add it manually -->
|
|
||||||
<ul
|
|
||||||
class="error-box"
|
|
||||||
:class="classes.messages"
|
|
||||||
v-if="fns.length(messages)"
|
|
||||||
>
|
|
||||||
<li
|
|
||||||
v-for="message in messages"
|
|
||||||
:key="message.key"
|
|
||||||
:id="`${id}-${message.key}`"
|
|
||||||
:data-message-type="message.type"
|
|
||||||
>
|
|
||||||
{{ message.value }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</FormKit>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="character-attributes">
|
|
||||||
<FormKitSchema :schema="SCHEMA" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Guests />
|
|
||||||
|
|
||||||
<FormKit
|
|
||||||
type="checkbox"
|
|
||||||
name="agree"
|
|
||||||
label="我同意FormKit是最好的表单开发框架。"
|
|
||||||
class="mb-4"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<pre class="font-mono text-sm p-4 bg-slate-100 mb-4">{{ value }}</pre>
|
|
||||||
|
|
||||||
</FormKit>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
ul.error-box {
|
|
||||||
padding: 0.75rem;
|
|
||||||
border: 1px solid #ef4444;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
background-color: #fee2e2;
|
|
||||||
color: #b91c1c;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
line-height: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.error-box p {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.character-attributes {
|
.attributes-group {
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
border: 1px solid #e5e7eb;
|
border: 1px solid #e5e7eb;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
defineProps<{ msg: string }>()
|
|
||||||
|
|
||||||
const count = ref(0)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<h1>{{ msg }}</h1>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<button type="button" @click="count++">count is {{ count }}</button>
|
|
||||||
<p>
|
|
||||||
Edit
|
|
||||||
<code>components/HelloWorld.vue</code> to test HMR
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Check out
|
|
||||||
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
|
||||||
>create-vue</a
|
|
||||||
>, the official Vue + Vite starter
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Learn more about IDE Support for Vue in the
|
|
||||||
<a
|
|
||||||
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
|
|
||||||
target="_blank"
|
|
||||||
>Vue Docs Scaling up Guide</a
|
|
||||||
>.
|
|
||||||
</p>
|
|
||||||
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.read-the-docs {
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -13,7 +13,7 @@ import { FormKitMessages } from '@formkit/vue'
|
|||||||
#default="{ items, node, value }"
|
#default="{ items, node, value }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="character-attributes"
|
class="attributes-group"
|
||||||
v-auto-animate
|
v-auto-animate
|
||||||
>
|
>
|
||||||
<FormKitMessages />
|
<FormKitMessages />
|
240
src/tutorial-form/index.vue
Normal file
240
src/tutorial-form/index.vue
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { FormKitSchemaDefinition, getNode, type FormKitNode } from '@formkit/core';
|
||||||
|
import { onMounted } from 'vue';
|
||||||
|
import Guests from './guests.vue';
|
||||||
|
|
||||||
|
async function submit(...args: any[]) {
|
||||||
|
console.debug('submit', `args :>> `, args);
|
||||||
|
await new Promise(r => setTimeout(r, 1000))
|
||||||
|
alert('Submitted! 🎉')
|
||||||
|
}
|
||||||
|
|
||||||
|
const castNumber = (node: FormKitNode) => {
|
||||||
|
node.hook.input((value, next) => next(Number(value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
console.group('onMounted')
|
||||||
|
const nameNode = getNode('nameNodeId') // 用 id 获取节点
|
||||||
|
Object.assign(window, { nameNode })
|
||||||
|
console.debug(`nameNode :>> `, nameNode);
|
||||||
|
|
||||||
|
const schemaGroupInput = nameNode?.root.at('schemaGroup.input') // 用路径获取节点
|
||||||
|
console.debug(`schemaGroupInput :>> `, schemaGroupInput);
|
||||||
|
nameNode?.on('commit', ({ payload }) => {
|
||||||
|
console.debug('[nameNode] [on commit]', `payload :>> `, payload);
|
||||||
|
schemaGroupInput?.input(`姓名:${payload}`)
|
||||||
|
})
|
||||||
|
console.groupEnd()
|
||||||
|
})
|
||||||
|
|
||||||
|
const SCHEMA: FormKitSchemaDefinition = [
|
||||||
|
{
|
||||||
|
$el: 'h1',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
$el: 'span',
|
||||||
|
children: 'JSON Schema Form',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
attrs: {
|
||||||
|
class: 'font-bold text-2xl mb-4',
|
||||||
|
'data-foo': 'bar',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$formkit: 'group',
|
||||||
|
name: 'schemaGroup',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
$formkit: 'text',
|
||||||
|
label: 'JSON Schema 渲染的输入框',
|
||||||
|
name: 'input',
|
||||||
|
validation: 'required',
|
||||||
|
validationLabel: 'validationLabel', // validation message 使用顺序:validationLabel > label > name
|
||||||
|
errors: ['显式设置的错误是非阻塞的,这意味着它们不会像验证错误那样阻止表单提交。您可以在表单文档中了解更多有关错误处理的信息。']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
function onChangeName() {
|
||||||
|
const nameNode = getNode('nameNodeId')
|
||||||
|
nameNode?.input('测试')
|
||||||
|
}
|
||||||
|
|
||||||
|
import autoAnimate from "@formkit/auto-animate"
|
||||||
|
onMounted(() => {
|
||||||
|
// setTimeout(() => {
|
||||||
|
autoAnimate(document.getElementById('attributes-group-wrapper')!);
|
||||||
|
// });
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="bg-white rounded-xl shadow-xl p-8 mx-auto my-16 max-w-[450px]">
|
||||||
|
<h1
|
||||||
|
class="font-bold text-2xl mb-4"
|
||||||
|
data-foo="bar"
|
||||||
|
>Tutorial Form</h1>
|
||||||
|
<FormKit
|
||||||
|
type="form"
|
||||||
|
#default="{ value }"
|
||||||
|
@submit="submit"
|
||||||
|
submit-label="提交 ✨"
|
||||||
|
>
|
||||||
|
<!-- v-auto-animate -->
|
||||||
|
<div
|
||||||
|
class="attributes-group"
|
||||||
|
id="attributes-group-wrapper"
|
||||||
|
>
|
||||||
|
<FormKit
|
||||||
|
type="group"
|
||||||
|
name="attributes"
|
||||||
|
:validation-rules="{
|
||||||
|
validateGroup: (node) => {
|
||||||
|
const name = (node.value as Record<string, any>).name
|
||||||
|
return name?.includes('测试') ? true : false
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
validation-visibility="dirty"
|
||||||
|
validation="validateGroup"
|
||||||
|
:validation-messages="{
|
||||||
|
validateGroup: ({ name/* , args */ }) => {
|
||||||
|
return `分组 ${name} 下的姓名必须包含 '测试' 字符串。`
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
#default="{ id, messages, fns, classes, value: attributes, }"
|
||||||
|
>
|
||||||
|
|
||||||
|
<FormKit
|
||||||
|
type="text"
|
||||||
|
name="name"
|
||||||
|
id="nameNodeId"
|
||||||
|
label="姓名"
|
||||||
|
:help="`现在 confirmName 的值是:${attributes?.confirmName}`"
|
||||||
|
some-attr="some-attr-value"
|
||||||
|
>
|
||||||
|
<template #label="{ classes, attrs, label }">
|
||||||
|
<!-- <pre
|
||||||
|
class="font-mono text-sm p-4 bg-slate-100 mb-4">{{ JSON.stringify({ classes, attrs, label }, null, 2) }}</pre> -->
|
||||||
|
<label
|
||||||
|
for="nameNodeId"
|
||||||
|
v-bind="attrs"
|
||||||
|
:class="classes.label"
|
||||||
|
>{{ label }}<button
|
||||||
|
class="ml-2 px-2 py-1 bg-blue-500 text-white rounded"
|
||||||
|
type="button"
|
||||||
|
@click="onChangeName"
|
||||||
|
>点击赋值</button>
|
||||||
|
</label>
|
||||||
|
</template>
|
||||||
|
</FormKit>
|
||||||
|
|
||||||
|
<FormKit
|
||||||
|
v-if="attributes?.name"
|
||||||
|
:preserve="false"
|
||||||
|
name="confirmName"
|
||||||
|
type="checkbox"
|
||||||
|
:value="false"
|
||||||
|
:label="`如果清空 name 输入框,这个确认框及其值将被移除。`"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormKit
|
||||||
|
type="checkbox"
|
||||||
|
name="flavors"
|
||||||
|
label="最喜欢的冰淇淋口味"
|
||||||
|
:options="{
|
||||||
|
'vanilla': '香草',
|
||||||
|
'chocolate': '巧克力',
|
||||||
|
'strawberry': '草莓',
|
||||||
|
}"
|
||||||
|
validation="required|min:2"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormKit
|
||||||
|
type="range"
|
||||||
|
name="strength"
|
||||||
|
id="strength"
|
||||||
|
label="力量"
|
||||||
|
value="5"
|
||||||
|
validation="min:2|max:9"
|
||||||
|
validation-visibility="live"
|
||||||
|
min="1"
|
||||||
|
max="10"
|
||||||
|
step="1"
|
||||||
|
help="这个角色应该有多少力量点?"
|
||||||
|
:plugins="[castNumber]"
|
||||||
|
class="mb-4"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormKit
|
||||||
|
type="textarea"
|
||||||
|
auto-height
|
||||||
|
label="我有自动高度插件"
|
||||||
|
help="这个文本框会随着输入内容增加而增高"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- By default groups do not show validation messages, so we need to add it manually -->
|
||||||
|
<ul
|
||||||
|
class="error-box"
|
||||||
|
:class="classes.messages"
|
||||||
|
v-if="fns.length(messages)"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
v-for="message in messages"
|
||||||
|
:key="message.key"
|
||||||
|
:id="`${id}-${message.key}`"
|
||||||
|
:data-message-type="message.type"
|
||||||
|
>
|
||||||
|
{{ message.value }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</FormKit>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="attributes-group">
|
||||||
|
<FormKitSchema :schema="SCHEMA" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Guests />
|
||||||
|
|
||||||
|
<FormKit
|
||||||
|
type="checkbox"
|
||||||
|
name="agree"
|
||||||
|
label="我同意FormKit是最好的表单开发框架。"
|
||||||
|
class="mb-4"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<pre class="font-mono text-sm p-4 bg-slate-100 mb-4">{{ value }}</pre>
|
||||||
|
|
||||||
|
</FormKit>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
ul.error-box {
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid #ef4444;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
background-color: #fee2e2;
|
||||||
|
color: #b91c1c;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.error-box p {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.attributes-group {
|
||||||
|
padding: 1.5rem;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
}
|
||||||
|
</style>
|
77
src/zod-form/index.vue
Normal file
77
src/zod-form/index.vue
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { createZodPlugin } from '@formkit/zod'
|
||||||
|
import { z } from 'zod'
|
||||||
|
// https://github.com/aiji42/zod-i18n?tab=readme-ov-file#how-to-use
|
||||||
|
import i18next from "i18next";
|
||||||
|
import { zodI18nMap } from "zod-i18n-map";
|
||||||
|
// Import your language translation files
|
||||||
|
import translation from "zod-i18n-map/locales/zh-CN/zod.json";
|
||||||
|
|
||||||
|
// lng and resources key depend on your locale.
|
||||||
|
i18next.init({
|
||||||
|
lng: "es",
|
||||||
|
resources: {
|
||||||
|
es: { zod: translation },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
z.setErrorMap(zodI18nMap);
|
||||||
|
|
||||||
|
const zodSchema = z.object({
|
||||||
|
personalInfo: z.object({
|
||||||
|
firstName: z.string().min(3).max(25),
|
||||||
|
lastName: z.string().min(3).max(25),
|
||||||
|
}),
|
||||||
|
email: z.string().email(),
|
||||||
|
arrayMin: z.string().array().min(2),
|
||||||
|
})
|
||||||
|
|
||||||
|
const [zodPlugin, submitHandler] = createZodPlugin(
|
||||||
|
zodSchema,
|
||||||
|
async (formData) => {
|
||||||
|
// fake submit handler, but this is where you
|
||||||
|
// do something with your valid data.
|
||||||
|
await new Promise((r) => setTimeout(r, 1000))
|
||||||
|
alert('Form was submitted!')
|
||||||
|
console.log(formData)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="bg-white rounded-xl shadow-xl p-8 mx-auto my-16 max-w-[450px]">
|
||||||
|
<h1>Validation from Zod schema</h1>
|
||||||
|
<FormKit
|
||||||
|
type="form"
|
||||||
|
:plugins="[zodPlugin]"
|
||||||
|
@submit="submitHandler"
|
||||||
|
>
|
||||||
|
<FormKit
|
||||||
|
type="group"
|
||||||
|
name="personalInfo"
|
||||||
|
>
|
||||||
|
<FormKit
|
||||||
|
validation-visibility="live"
|
||||||
|
type="text"
|
||||||
|
name="firstName"
|
||||||
|
label="First Name"
|
||||||
|
/>
|
||||||
|
<FormKit
|
||||||
|
type="text"
|
||||||
|
name="lastName"
|
||||||
|
label="Last Name"
|
||||||
|
/>
|
||||||
|
</FormKit>
|
||||||
|
<FormKit
|
||||||
|
type="text"
|
||||||
|
name="email"
|
||||||
|
label="Your email"
|
||||||
|
/>
|
||||||
|
<FormKit
|
||||||
|
type="checkbox"
|
||||||
|
name="arrayMin"
|
||||||
|
label="Zod features"
|
||||||
|
:options="['Validation', 'Type-Safety', 'Reusability']"
|
||||||
|
/>
|
||||||
|
</FormKit>
|
||||||
|
</div>
|
||||||
|
</template>
|
Reference in New Issue
Block a user