feat: 添加 PCascadeSelect 组件,更新相关配置和依赖
Some checks failed
/ test (push) Successful in 3s
/ surge (push) Failing after 2m27s

This commit is contained in:
严浩
2024-12-23 18:25:10 +08:00
parent 70a9aa5b79
commit a3e6509783
7 changed files with 265 additions and 69 deletions

View File

@ -15,6 +15,7 @@ import type { App } from 'vue';
import { addAsteriskPlugin } from './formkit.config.plugin.addAsteriskPlugin';
import { debugPlugin } from './formkit.config.plugin.debug';
import { PDatePicker } from '@/__fk-inputs__/inputs/p-date-picker';
import { PCascadeSelect } from '@/__fk-inputs__/inputs/p-cascade-select';
const plugins: FormKitPlugin[] = [
// createLibraryPlugin(fkLibrary),
@ -28,6 +29,7 @@ const plugins: FormKitPlugin[] = [
PInputPassword,
PSelect,
PDatePicker,
PCascadeSelect,
}),
// createLibraryPlugin(
// {

View File

@ -30,6 +30,7 @@
"primevue": "^4.2.5",
"sweetalert2": "^11.15.3",
"tailwindcss": "^3.4.17",
"utils4u": "^2.19.2",
"vue": "^3.5.13",
"zod-i18n-map": "^2.27.0"
},

16
pnpm-lock.yaml generated
View File

@ -65,6 +65,9 @@ importers:
tailwindcss:
specifier: ^3.4.17
version: 3.4.17
utils4u:
specifier: ^2.19.2
version: 2.19.2
vue:
specifier: ^3.5.13
version: 3.5.13(typescript@5.7.2)
@ -1296,8 +1299,8 @@ packages:
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
engines: {node: '>=8.6.0'}
fastq@1.17.1:
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
fastq@1.18.0:
resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==}
fdir@6.4.2:
resolution: {integrity: sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==}
@ -1800,6 +1803,9 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
utils4u@2.19.2:
resolution: {integrity: sha512-QjnPId4oadpieslF4SxxXHOxGJKG5xFlY/GqJS4a/1tUUDlC3AoMWksmXaX4FWO9AVANn5qgy5tLuYgS+VndRw==}
vite@6.0.5:
resolution: {integrity: sha512-akD5IAH/ID5imgue2DYhzsEwCi0/4VKY31uhMLEYJwPP4TiUp8pL5PIK+Wo7H8qT8JY9i+pVfPydcFPYD1EL7g==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
@ -2448,7 +2454,7 @@ snapshots:
'@nodelib/fs.walk@1.2.8':
dependencies:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.17.1
fastq: 1.18.0
'@pkgjs/parseargs@0.11.0':
optional: true
@ -3078,7 +3084,7 @@ snapshots:
merge2: 1.4.1
micromatch: 4.0.8
fastq@1.17.1:
fastq@1.18.0:
dependencies:
reusify: 1.0.4
@ -3591,6 +3597,8 @@ snapshots:
util-deprecate@1.0.2: {}
utils4u@2.19.2: {}
vite@6.0.5(jiti@2.4.2)(tsx@4.19.2)(yaml@2.6.1):
dependencies:
esbuild: 0.24.0

View File

@ -4,13 +4,81 @@ import AllCustom from './all-custom/all-custom.vue';
import TutorialForm from './tutorial-form/index.vue';
import ZodForm from './zod-form/index.vue';
const datetime12h = ref();
const datetime24h = ref();
const time = ref();
const date = ref();
const dates = ref();
const selectedCity = ref();
const countries = ref([
{
name: 'Australia',
code: 'AU',
states: [
{
name: 'New South Wales',
cities: [
{ cname: 'Sydney', code: 'A-SY' },
{ cname: 'Newcastle', code: 'A-NE' },
{ cname: 'Wollongong', code: 'A-WO' }
]
},
{
name: 'Queensland',
cities: [
{ cname: 'Brisbane', code: 'A-BR' },
{ cname: 'Townsville', code: 'A-TO' }
]
}
]
},
{
name: 'Canada',
code: 'CA',
states: [
{
name: 'Quebec',
cities: [
{ cname: 'Montreal', code: 'C-MO' },
{ cname: 'Quebec City', code: 'C-QU' }
]
},
{
name: 'Ontario',
cities: [
{ cname: 'Ottawa', code: 'C-OT' },
{ cname: 'Toronto', code: 'C-TO' }
]
}
]
},
{
name: 'United States',
code: 'US',
states: [
{
name: 'California',
cities: [
{ cname: 'Los Angeles', code: 'US-LA' },
{ cname: 'San Diego', code: 'US-SD' },
{ cname: 'San Francisco', code: 'US-SF' }
]
},
{
name: 'Florida',
cities: [
{ cname: 'Jacksonville', code: 'US-JA' },
{ cname: 'Miami', code: 'US-MI' },
{ cname: 'Tampa', code: 'US-TA' },
{ cname: 'Orlando', code: 'US-OR' }
]
},
{
name: 'Texas',
cities: [
{ cname: 'Austin', code: 'US-AU' },
{ cname: 'Dallas', code: 'US-DA' },
{ cname: 'Houston', code: 'US-HO' }
]
}
]
}
]);
</script>
<template>
@ -26,60 +94,16 @@ const dates = ref();
<div class="p-4 w-full bg-white rounded-lg shadow-md dark:bg-gray-800 dark:text-white mt-4">
<div class="card flex flex-wrap gap-4 items-start">
<DatePicker
v-model="date"
view="month"
dateFormat="mm/yy"
/>
<DatePicker
v-model="dates"
selectionMode="range"
:manualInput="false"
/>
<div class="flex-auto">
<label
for="datepicker-12h"
class="font-bold block mb-2"
> 12h Format </label>
<DatePicker
id="datepicker-12h"
v-model="datetime12h"
showTime
hourFormat="12"
fluid
/>
</div>
<div class="flex-auto">
<label
for="datepicker-24h"
class="font-bold block mb-2"
> 24h Format </label>
<DatePicker
id="datepicker-24h"
v-model="datetime24h"
showTime
hourFormat="24"
fluid
/>
</div>
<div class="flex-auto">
<label
for="datepicker-timeonly"
class="font-bold block mb-2"
> Time Only </label>
<DatePicker
id="datepicker-timeonly"
v-model="time"
timeOnly
fluid
/>
</div>
</div>
<CascadeSelect
loading
v-model="selectedCity"
:options="countries"
optionLabel="cname"
optionGroupLabel="name"
:optionGroupChildren="['states', 'cities']"
class="w-56"
placeholder="Select a City"
/>
</div>
</div>

View File

@ -0,0 +1,113 @@
import type { FormKitFrameworkContext, FormKitTypeDefinition } from '@formkit/core';
import type { FormKitInputs } from '@formkit/inputs';
import { createSection, label, outer } from '@formkit/inputs';
import type { CascadeSelectEmitsOptions, CascadeSelectProps } from 'primevue/cascadeselect';
import PrimevueCascadeselect from 'primevue/cascadeselect';
import { defineComponent, markRaw, ref } from 'vue';
import { floatLabel } from '../sections/floatLabel';
import { messages } from '../sections/messages';
// https://formkit.com/inputs/dropdown
type PrimevueSelectListeners = {
'onUpdate:modelValue': CascadeSelectEmitsOptions['update:modelValue'];
'onBlur': CascadeSelectEmitsOptions['blur'];
};
const SchemaComponent = defineComponent(
(vueProps: { context: FormKitFrameworkContext }) => {
const formkitContext = vueProps.context;
const listeners: PrimevueSelectListeners = {
'onUpdate:modelValue': (value: unknown) => {
formkitContext.node.input(value);
},
'onBlur': async e => {
setTimeout(
() => formkitContext.handlers.blur.call(undefined, e as never),
166, // 因为会触发两次。所以让blur事件延迟一点可以考虑优化。Cascadeselect好像没有这个问题
);
},
};
const p_options = ref();
const loading = ref(true);
const loadOptions = async () => {
try {
let result;
if (formkitContext.options instanceof Promise) {
result = await formkitContext.options;
} else {
console.warn('未支持的 options 类型 :>> ', typeof formkitContext.options);
}
p_options.value = result;
} catch (error) {
console.error('Failed to load options:', error);
p_options.value = [];
} finally {
loading.value = false;
}
};
loadOptions(); // 立即加载options
return () => {
return (
<PrimevueCascadeselect
inputId={formkitContext.id}
optionLabel={formkitContext.optionLabel as never}
optionValue={formkitContext.optionValue as never}
optionGroupChildren={(formkitContext.optionGroupChildren as never) || 'children'}
optionGroupLabel={(formkitContext.optionGroupLabel as never) || (formkitContext.optionLabel as never)}
loading={loading.value}
options={p_options.value}
invalid={formkitContext.state.invalid}
fluid
disabled={!!formkitContext.disabled}
modelValue={formkitContext._value}
{...listeners}
/>
);
};
},
// https://cn.vuejs.org/api/general#definecomponent
// 目前仍然需要手动声明运行时的 props
// 在将来,我们计划提供一个 Babel 插件,自动推断并注入运行时 props (就像在单文件组件中的 defineProps 一样),以便省略运行时 props 的声明。
{
props: ['context'],
},
);
const input = createSection('input', () => ({
$cmp: markRaw(SchemaComponent) as never,
bind: '$attrs',
props: {
context: '$node.context',
},
}));
export const PCascadeSelect: FormKitTypeDefinition = {
type: 'input',
schema: outer(
floatLabel(
input(), //
label('$label'),
),
messages(),
),
props: ['options', 'optionLabel', 'optionValue'],
// schemaMemoKey: '72psvunq45', // Math.random().toString(36).substring(2, 15)
};
type OptionsType = CascadeSelectProps['options'];
declare module '@formkit/inputs' {
// https://formkit.com/essentials/custom-inputs#typescript-support
interface FormKitInputProps<Props extends FormKitInputs<Props>> {
PCascadeSelect: {
type: 'PCascadeSelect';
options: Promise<OptionsType>;
optionLabel: string;
optionValue: string;
};
}
}

View File

@ -32,7 +32,7 @@ const SchemaComponent = defineComponent(
};
// [optionsLoader](https://github.com/formkit/formkit/blob/2d5387ba98597775cb2a752af65aee84bc438863/packages/inputs/src/features/options.ts#L125)
const poptions = ref();
const p_options = ref();
const loading = ref(true);
const loadOptions = async () => {
@ -46,10 +46,10 @@ const SchemaComponent = defineComponent(
} else {
result = formkitContext.options;
}
poptions.value = result;
p_options.value = result;
} catch (error) {
console.error('Failed to load options:', error);
poptions.value = [];
p_options.value = [];
} finally {
loading.value = false;
}
@ -76,7 +76,7 @@ const SchemaComponent = defineComponent(
invalid={formkitContext.state.invalid || valueSlot.value !== undefined}
disabled={!!formkitContext.disabled}
loading={loading.value}
options={poptions.value}
options={p_options.value}
modelValue={formkitContext._value}
optionLabel={formkitContext.optionLabel as never}
optionValue={formkitContext.optionValue as never}

View File

@ -3,6 +3,7 @@ import { FormKitNode } from '@formkit/core';
import { text } from '@formkit/inputs';
import Swal from 'sweetalert2';
import dayjs from 'dayjs';
import { arrayToTree } from 'utils4u/array';
async function submit(formData: Record<string, any>, formNode: FormKitNode) {
console.group('submit');
@ -27,6 +28,44 @@ const K_OPTIONS = [
const promiseOptions = new Promise<typeof K_OPTIONS>(resolve => {
setTimeout(() => resolve(K_OPTIONS), 1000);
});
const K_FLAT_TREE = [
{
dictLabel: '北京市',
dictValue: '110000',
fullName: '北京市',
abbrName: '北京',
dictParent: '00',
},
{
dictLabel: '山西省',
dictValue: '140000',
fullName: '山西省',
abbrName: '山西',
dictParent: '00',
},
{
dictLabel: '太原市',
dictValue: '140100',
fullName: '山西省,太原市',
abbrName: '山西太原',
dictParent: '140000',
},
{
dictLabel: '大同市',
dictValue: '140200',
fullName: '山西省,大同市',
abbrName: '山西大同',
dictParent: '140000',
},
]
const promiseCascadeOptions = new Promise<typeof K_FLAT_TREE>((resolve) => {
setTimeout(() => {
resolve(arrayToTree(K_FLAT_TREE, { id: 'dictValue', parentId: 'dictParent', rootId: '00' }));
}, 1000);
});
/* const funcOptions = async () => {
await new Promise(r => setTimeout(r, 1000))
return K_OPTIONS;
@ -99,6 +138,15 @@ const promiseOptions = new Promise<typeof K_OPTIONS>(resolve => {
optionLabel="label"
optionValue="value"
/>
<FormKit
type="PCascadeSelect"
name="PCascadeSelect"
label="级联选择框"
validation="required"
:options="promiseCascadeOptions"
optionLabel="dictLabel"
optionValue="dictValue"
/>
<FormKit
type="PDatePicker"
name="PDatePicker"