feat: 添加 PCascadeSelect 组件,更新相关配置和依赖

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

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