feat: 添加 PDatePicker 组件
This commit is contained in:
@ -14,6 +14,7 @@ import { /* defaultConfig, */ bindings, plugin /* defaultConfig */ } from '@form
|
|||||||
import type { App } from 'vue';
|
import type { App } from 'vue';
|
||||||
import { addAsteriskPlugin } from './formkit.config.plugin.addAsteriskPlugin';
|
import { addAsteriskPlugin } from './formkit.config.plugin.addAsteriskPlugin';
|
||||||
import { debugPlugin } from './formkit.config.plugin.debug';
|
import { debugPlugin } from './formkit.config.plugin.debug';
|
||||||
|
import { PDatePicker } from '@/__fk-inputs__/inputs/p-date-picker';
|
||||||
|
|
||||||
const plugins: FormKitPlugin[] = [
|
const plugins: FormKitPlugin[] = [
|
||||||
// createLibraryPlugin(fkLibrary),
|
// createLibraryPlugin(fkLibrary),
|
||||||
@ -26,6 +27,7 @@ const plugins: FormKitPlugin[] = [
|
|||||||
PInputText,
|
PInputText,
|
||||||
PInputPassword,
|
PInputPassword,
|
||||||
PSelect,
|
PSelect,
|
||||||
|
PDatePicker,
|
||||||
}),
|
}),
|
||||||
// createLibraryPlugin(
|
// createLibraryPlugin(
|
||||||
// {
|
// {
|
||||||
|
26
package.json
26
package.json
@ -14,31 +14,33 @@
|
|||||||
"@formkit/addons": "^1.6.9",
|
"@formkit/addons": "^1.6.9",
|
||||||
"@formkit/core": "^1.6.9",
|
"@formkit/core": "^1.6.9",
|
||||||
"@formkit/icons": "^1.6.9",
|
"@formkit/icons": "^1.6.9",
|
||||||
"@formkit/pro": "^0.127.8",
|
"@formkit/pro": "^0.127.15",
|
||||||
"@formkit/themes": "^1.6.9",
|
"@formkit/themes": "^1.6.9",
|
||||||
"@formkit/vue": "^1.6.9",
|
"@formkit/vue": "^1.6.9",
|
||||||
"@formkit/zod": "^1.6.9",
|
"@formkit/zod": "^1.6.9",
|
||||||
"@headlessui/vue": "^1.7.23",
|
"@headlessui/vue": "^1.7.23",
|
||||||
"@primevue/forms": "^4.2.4",
|
"@primevue/forms": "^4.2.5",
|
||||||
"@primevue/themes": "^4.2.4",
|
"@primevue/themes": "^4.2.5",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"i18next": "^24.0.5",
|
"dayjs": "^1.11.13",
|
||||||
|
"i18next": "^24.2.0",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.4.49",
|
||||||
"primeicons": "^7.0.0",
|
"primeicons": "^7.0.0",
|
||||||
"primevue": "^4.2.4",
|
"primelocale": "^1.2.2",
|
||||||
"sweetalert2": "^11.14.5",
|
"primevue": "^4.2.5",
|
||||||
"tailwindcss": "^3.4.15",
|
"sweetalert2": "^11.15.3",
|
||||||
|
"tailwindcss": "^3.4.17",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"zod-i18n-map": "^2.27.0"
|
"zod-i18n-map": "^2.27.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@primevue/auto-import-resolver": "^4.2.4",
|
"@primevue/auto-import-resolver": "^4.2.5",
|
||||||
"@vitejs/plugin-vue": "^5.2.1",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
||||||
"typescript": "~5.6.0",
|
"typescript": "~5.7.2",
|
||||||
"unocss": "^0.65.0",
|
"unocss": "^0.65.2",
|
||||||
"unplugin-vue-components": "^0.27.5",
|
"unplugin-vue-components": "^0.28.0",
|
||||||
"vite": "^6.0.2",
|
"vite": "^6.0.5",
|
||||||
"vue-tsc": "^2.1.10"
|
"vue-tsc": "^2.1.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
1277
pnpm-lock.yaml
generated
1277
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
108
src/App.vue
108
src/App.vue
@ -4,27 +4,12 @@ import AllCustom from './all-custom/all-custom.vue';
|
|||||||
import TutorialForm from './tutorial-form/index.vue';
|
import TutorialForm from './tutorial-form/index.vue';
|
||||||
import ZodForm from './zod-form/index.vue';
|
import ZodForm from './zod-form/index.vue';
|
||||||
|
|
||||||
const selectedCity = ref('NY');
|
const datetime12h = ref();
|
||||||
const loading = ref(false);
|
const datetime24h = ref();
|
||||||
const cities = ref([
|
const time = ref();
|
||||||
{ name: 'New York', code: 'NY' },
|
const date = ref();
|
||||||
{ name: 'Rome', code: 'RM' },
|
const dates = ref();
|
||||||
{ name: 'London', code: 'LDN' },
|
|
||||||
{ name: 'Istanbul', code: 'IST' },
|
|
||||||
{ name: 'Paris', code: 'PRS' }
|
|
||||||
]);
|
|
||||||
const loadSelectedCity = () => {
|
|
||||||
loading.value = true;
|
|
||||||
setTimeout(() => {
|
|
||||||
loading.value = false;
|
|
||||||
}, 2000);
|
|
||||||
};
|
|
||||||
const handleE = (eventName: string) => {
|
|
||||||
return (...args: any[]) => {
|
|
||||||
console.log('eventName :>> ', eventName);
|
|
||||||
console.log('args :>> ', args);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -40,31 +25,62 @@ const handleE = (eventName: string) => {
|
|||||||
<!-- </div> -->
|
<!-- </div> -->
|
||||||
|
|
||||||
<div class="p-4 w-full bg-white rounded-lg shadow-md dark:bg-gray-800 dark:text-white mt-4">
|
<div class="p-4 w-full bg-white rounded-lg shadow-md dark:bg-gray-800 dark:text-white mt-4">
|
||||||
<Select
|
|
||||||
v-model="selectedCity"
|
|
||||||
:placeholder="loading ? 'Loading' : 'Select a City'"
|
<div class="card flex flex-wrap gap-4 items-start">
|
||||||
:loading="loading"
|
<DatePicker
|
||||||
class="w-full md:w-56"
|
v-model="date"
|
||||||
:options="cities"
|
view="month"
|
||||||
optionLabel="name"
|
dateFormat="mm/yy"
|
||||||
optionValue="code"
|
/>
|
||||||
@input="(e: any) => handleE('input')(e)"
|
|
||||||
@value-change="(e) => handleE('value-change')(e)"
|
<DatePicker
|
||||||
@change="(e) => handleE('change')(e)"
|
v-model="dates"
|
||||||
@blur="(e) => handleE('blur')(e)"
|
selectionMode="range"
|
||||||
></Select>
|
:manualInput="false"
|
||||||
<Select
|
/>
|
||||||
:options="cities"
|
|
||||||
class="w-full md:w-56"
|
|
||||||
>
|
<div class="flex-auto">
|
||||||
<template #value="slotProps">{{ slotProps.value }}</template>
|
<label
|
||||||
</Select>
|
for="datepicker-12h"
|
||||||
<Button
|
class="font-bold block mb-2"
|
||||||
id="load-selected-city"
|
> 12h Format </label>
|
||||||
label="Load Selected City"
|
<DatePicker
|
||||||
@click="loadSelectedCity"
|
id="datepicker-12h"
|
||||||
/>
|
v-model="datetime12h"
|
||||||
{{ { selectedCity } }}
|
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>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
105
src/__fk-inputs__/inputs/p-date-picker.tsx
Normal file
105
src/__fk-inputs__/inputs/p-date-picker.tsx
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import type { FormKitFrameworkContext, FormKitTypeDefinition } from '@formkit/core';
|
||||||
|
import type { FormKitInputs } from '@formkit/inputs';
|
||||||
|
import { createSection, label, outer } from '@formkit/inputs';
|
||||||
|
import type { DatePickerEmitsOptions, DatePickerProps } from 'primevue/datepicker';
|
||||||
|
import PrimevueDatepicker from 'primevue/datepicker';
|
||||||
|
import { defineComponent, markRaw } from 'vue';
|
||||||
|
import { floatLabel } from '../sections/floatLabel';
|
||||||
|
import { messages } from '../sections/messages';
|
||||||
|
|
||||||
|
// :plugins="[castNumber]"
|
||||||
|
type PickerValue = Date | Array<Date> | Array<Date | null> | undefined | null;
|
||||||
|
type PropValueToDate = (value: unknown) => PickerValue;
|
||||||
|
type PropDateToValue = (date?: PickerValue) => unknown;
|
||||||
|
|
||||||
|
type PrimevueDatepickerListeners = {
|
||||||
|
'onUpdate:modelValue': DatePickerEmitsOptions['update:modelValue'];
|
||||||
|
'onBlur': DatePickerEmitsOptions['blur'];
|
||||||
|
};
|
||||||
|
|
||||||
|
const SchemaComponent = defineComponent(
|
||||||
|
(vueProps: { context: FormKitFrameworkContext & { valueToDate: PropValueToDate; dateToValue: PropDateToValue } }) => {
|
||||||
|
const formkitContext = vueProps.context;
|
||||||
|
const { valueToDate, dateToValue } = formkitContext;
|
||||||
|
const listeners: PrimevueDatepickerListeners = {
|
||||||
|
'onUpdate:modelValue': value => {
|
||||||
|
let newValue = value as unknown;
|
||||||
|
try {
|
||||||
|
newValue = dateToValue(value);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
formkitContext.node.input(newValue);
|
||||||
|
},
|
||||||
|
'onBlur': async e => {
|
||||||
|
setTimeout(
|
||||||
|
() => formkitContext.handlers.blur.call(undefined, e as never),
|
||||||
|
166, // 因为会触发两次。所以让blur事件延迟一点,可以考虑优化。
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
let value = formkitContext._value;
|
||||||
|
try {
|
||||||
|
value = valueToDate(formkitContext._value);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<PrimevueDatepicker
|
||||||
|
inputId={formkitContext.id}
|
||||||
|
fluid
|
||||||
|
invalid={formkitContext.state.invalid}
|
||||||
|
disabled={!!formkitContext.disabled}
|
||||||
|
modelValue={value}
|
||||||
|
dateFormat={formkitContext.dateFormat as never}
|
||||||
|
manualInput={formkitContext.manualInput as never}
|
||||||
|
{...listeners}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
props: ['context'],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = createSection('input', () => ({
|
||||||
|
$cmp: markRaw(SchemaComponent) as never,
|
||||||
|
bind: '$attrs',
|
||||||
|
props: {
|
||||||
|
context: '$node.context',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const PDatePicker: FormKitTypeDefinition = {
|
||||||
|
type: 'input',
|
||||||
|
schema: outer(
|
||||||
|
floatLabel(
|
||||||
|
input(), //
|
||||||
|
label('$label'),
|
||||||
|
),
|
||||||
|
messages(),
|
||||||
|
),
|
||||||
|
props: ['valueToDate', 'dateToValue', 'dateFormat'],
|
||||||
|
schemaMemoKey: 'q2dkascustq', // Math.random().toString(36).substring(2, 15)
|
||||||
|
};
|
||||||
|
|
||||||
|
declare module '@formkit/inputs' {
|
||||||
|
// https://formkit.com/essentials/custom-inputs#typescript-support
|
||||||
|
interface FormKitInputProps<Props extends FormKitInputs<Props>> {
|
||||||
|
PDatePicker: {
|
||||||
|
type: 'PDatePicker';
|
||||||
|
valueToDate?: PropValueToDate;
|
||||||
|
dateToValue?: PropDateToValue;
|
||||||
|
value?: unknown;
|
||||||
|
view?: DatePickerProps['view'];
|
||||||
|
/**
|
||||||
|
* https://primevue.org/datepicker/#format
|
||||||
|
*/
|
||||||
|
dateFormat?: DatePickerProps['dateFormat'];
|
||||||
|
manualInput?: DatePickerProps['manualInput'];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -15,10 +15,10 @@ type PrimevueSelectListeners = {
|
|||||||
'onBlur': SelectEmitsOptions['blur'];
|
'onBlur': SelectEmitsOptions['blur'];
|
||||||
};
|
};
|
||||||
|
|
||||||
const PSelectComp = defineComponent(
|
const SchemaComponent = defineComponent(
|
||||||
(vueProps: { context: FormKitFrameworkContext }) => {
|
(vueProps: { context: FormKitFrameworkContext }) => {
|
||||||
const formkitContext = vueProps.context;
|
const formkitContext = vueProps.context;
|
||||||
const primevueSelectInstance = ref<Record<string, unknown> | undefined>();
|
const primevueSelectInstance = ref<{ label: string } | undefined>();
|
||||||
const listeners: PrimevueSelectListeners = {
|
const listeners: PrimevueSelectListeners = {
|
||||||
'onUpdate:modelValue': (value: unknown) => {
|
'onUpdate:modelValue': (value: unknown) => {
|
||||||
formkitContext.node.input(value);
|
formkitContext.node.input(value);
|
||||||
@ -98,7 +98,7 @@ const PSelectComp = defineComponent(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const input = createSection('input', () => ({
|
const input = createSection('input', () => ({
|
||||||
$cmp: markRaw(PSelectComp) as never,
|
$cmp: markRaw(SchemaComponent) as never,
|
||||||
bind: '$attrs',
|
bind: '$attrs',
|
||||||
props: {
|
props: {
|
||||||
context: '$node.context',
|
context: '$node.context',
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import { FormKitNode } from '@formkit/core';
|
import { FormKitNode } from '@formkit/core';
|
||||||
import { text } from '@formkit/inputs';
|
import { text } from '@formkit/inputs';
|
||||||
import Swal from 'sweetalert2';
|
import Swal from 'sweetalert2';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
async function submit(formData: Record<string, any>, formNode: FormKitNode) {
|
async function submit(formData: Record<string, any>, formNode: FormKitNode) {
|
||||||
console.group('submit');
|
console.group('submit');
|
||||||
@ -98,6 +99,18 @@ const promiseOptions = new Promise<typeof K_OPTIONS>(resolve => {
|
|||||||
optionLabel="label"
|
optionLabel="label"
|
||||||
optionValue="value"
|
optionValue="value"
|
||||||
/>
|
/>
|
||||||
|
<FormKit
|
||||||
|
type="PDatePicker"
|
||||||
|
name="PDatePicker"
|
||||||
|
:manualInput="false"
|
||||||
|
dateFormat="yy年mm月"
|
||||||
|
:dateToValue="(date) => dayjs(date as Date).format('YYYY-MM')"
|
||||||
|
:valueToDate="(value) => dayjs(value as string).toDate()"
|
||||||
|
view="month"
|
||||||
|
value="2022-01"
|
||||||
|
label="选日期"
|
||||||
|
validation="required"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-4">
|
<div class="flex flex-wrap gap-4">
|
||||||
<FormKit
|
<FormKit
|
||||||
|
34
src/main.ts
34
src/main.ts
@ -1,9 +1,10 @@
|
|||||||
import Aura from "@primevue/themes/aura";
|
import Aura from '@primevue/themes/aura';
|
||||||
import PrimeVue from "primevue/config";
|
import zhCN from 'primelocale/zh-CN.json';
|
||||||
import ToastService from "primevue/toastservice";
|
import PrimeVue from 'primevue/config';
|
||||||
import { createApp } from "vue";
|
import ToastService from 'primevue/toastservice';
|
||||||
import { setupFormKit } from "../formkit.config";
|
import { createApp } from 'vue';
|
||||||
import App from "./App.vue";
|
import { setupFormKit } from '../formkit.config';
|
||||||
|
import App from './App.vue';
|
||||||
|
|
||||||
// import '@unocss/reset/normalize.css'
|
// import '@unocss/reset/normalize.css'
|
||||||
// import '@unocss/reset/sanitize/sanitize.css'
|
// import '@unocss/reset/sanitize/sanitize.css'
|
||||||
@ -12,15 +13,28 @@ import App from "./App.vue";
|
|||||||
// import '@unocss/reset/tailwind-compat.css'
|
// import '@unocss/reset/tailwind-compat.css'
|
||||||
// import '@unocss/reset/tailwind.css'
|
// import '@unocss/reset/tailwind.css'
|
||||||
|
|
||||||
import "primeicons/primeicons.css";
|
import 'primeicons/primeicons.css';
|
||||||
import "virtual:uno.css";
|
import 'virtual:uno.css';
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
app.use(PrimeVue, { theme: { preset: Aura } });
|
app.use(PrimeVue, {
|
||||||
|
locale: {
|
||||||
|
...zhCN['zh-CN'],
|
||||||
|
noFileChosenMessage: '未选择文件',
|
||||||
|
pending: '待上传',
|
||||||
|
completed: '已上传',
|
||||||
|
}, // usePrimeVue().config.locale
|
||||||
|
theme: { preset: Aura },
|
||||||
|
options: {
|
||||||
|
prefix: 'p',
|
||||||
|
darkModeSelector: 'system',
|
||||||
|
cssLayer: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
app.use(ToastService);
|
app.use(ToastService);
|
||||||
|
|
||||||
// https://github.dev/formkit/auto-animate/blob/master/docs/src/examples/formkit/ActualFormKit.vue
|
// https://github.dev/formkit/auto-animate/blob/master/docs/src/examples/formkit/ActualFormKit.vue
|
||||||
|
|
||||||
setupFormKit(app);
|
setupFormKit(app);
|
||||||
|
|
||||||
app.mount("#app");
|
app.mount('#app');
|
||||||
|
Reference in New Issue
Block a user