Compare commits
79 Commits
Author | SHA1 | Date | |
---|---|---|---|
2b74c29556 | |||
693df86068 | |||
6bb5de5330 | |||
33c3e5c31d | |||
17c4afefd9 | |||
7277e8e392 | |||
86f47b5113 | |||
f94bec1118 | |||
6a1a7d2d47 | |||
ab39617e7b | |||
7a1712807f | |||
e85666fec1 | |||
b8280cecdd | |||
28ff670dc5 | |||
a3e6509783 | |||
70a9aa5b79 | |||
c1c8e470fd | |||
c3bdd9ba9a | |||
9b78830313 | |||
c4afb0342e | |||
a6ccc16adb | |||
7b7828d384 | |||
603c1047fc | |||
a9fb4bc208 | |||
ba4d361700 | |||
c2772dcf3a | |||
f516aac5d2 | |||
b65b6bf48e | |||
0afb696583 | |||
dc6b45285d | |||
f9d1a5c2e2 | |||
8be5378514 | |||
45e5396153 | |||
1260e86c1c | |||
0d7f3b7dec | |||
884099b2ee | |||
5d111ead30 | |||
9255f7ac9d | |||
7d850957a6 | |||
20ce563e32 | |||
223b460af8 | |||
de0014590e | |||
8746997d36 | |||
8a1e055462 | |||
be4adcc2b4 | |||
e4907efab5 | |||
0f45fcbf25 | |||
d7058af64c | |||
5511685f18 | |||
a2e30f45e4 | |||
7b5a8e6e41 | |||
3e1995b0de | |||
299a47dc8b | |||
ec9bef3fa9 | |||
282559ea17 | |||
7bb954e348 | |||
07320f09b0 | |||
781eb610ce | |||
742d92d8fd | |||
fb329d5746 | |||
dc0c135c16 | |||
11f787d172 | |||
ab5cf852ff | |||
29db04c54a | |||
edd6decdbd | |||
f0098018cf | |||
6519838995 | |||
2aa80123b8 | |||
798954d6f3 | |||
c9601cc680 | |||
a47d6a6a19 | |||
b373f330de | |||
533a762151 | |||
fa094e68ff | |||
0b40d065ca | |||
7fdbea82d6 | |||
a24de16922 | |||
da6b27fb7a | |||
7a69e5755d |
12
.github/workflows/ci.yaml
vendored
12
.github/workflows/ci.yaml
vendored
@ -3,6 +3,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- unocss-fk
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
surge:
|
surge:
|
||||||
@ -16,14 +17,3 @@ jobs:
|
|||||||
cp dist/index.html dist/200.html
|
cp dist/index.html dist/200.html
|
||||||
npx surge --project ./dist --domain $DEPLOY_DOMAIN --token d843de16b331c626f10771245c56ed93 # npx surge token
|
npx surge --project ./dist --domain $DEPLOY_DOMAIN --token d843de16b331c626f10771245c56ed93 # npx surge token
|
||||||
echo the preview URL is $DEPLOY_DOMAIN
|
echo the preview URL is $DEPLOY_DOMAIN
|
||||||
netlify:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: yanhao98/composite-actions/setup-node-environment@main
|
|
||||||
- name: deploy
|
|
||||||
run: |
|
|
||||||
npx vite build
|
|
||||||
npx netlify deploy --prod --dir dist
|
|
||||||
env:
|
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
|
||||||
NETLIFY_SITE_ID: 9273cadf-2b0a-4616-bbd6-b06a33646100
|
|
||||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -22,3 +22,8 @@ dist-ssr
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
|
# Local Netlify folder
|
||||||
|
.netlify
|
||||||
|
|
||||||
|
components.d.ts
|
5
.npmrc
5
.npmrc
@ -1 +1,4 @@
|
|||||||
shamefully-hoist=true
|
registry=https://nexus.oo1.dev/repository/npm/
|
||||||
|
use-node-version=22.14.0
|
||||||
|
|
||||||
|
shamefully-hoist=true
|
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
@ -3,9 +3,12 @@
|
|||||||
"source.fixAll": "explicit"
|
"source.fixAll": "explicit"
|
||||||
},
|
},
|
||||||
"[vue]": {
|
"[vue]": {
|
||||||
"editor.defaultFormatter": "Vue.volar",
|
"editor.defaultFormatter": "Vue.volar"
|
||||||
|
},
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.formatOnSaveMode": "file",
|
"editor.formatOnSaveMode": "file",
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib"
|
||||||
}
|
}
|
||||||
|
11
README.md
11
README.md
@ -1,21 +1,30 @@
|
|||||||
# [Vue FormKit Example](https://formkit.com)
|
# [Vue FormKit Example](https://formkit.com)
|
||||||
|
|
||||||
## Knowledge
|
## Knowledge
|
||||||
|
|
||||||
- https://youtu.be/v8Sb1mXDWa8
|
- https://youtu.be/v8Sb1mXDWa8
|
||||||
- [sections](https://formkit.com/inputs/text#sections)
|
- [sections](https://formkit.com/inputs/text#sections)
|
||||||
- [sections](https://formkit.com/essentials/inputs#sections)
|
- [sections](https://formkit.com/essentials/inputs#sections)
|
||||||
- [useformkitcontext](https://formkit.com/inputs/form#useformkitcontext)
|
- [useformkitcontext](https://formkit.com/inputs/form#useformkitcontext)
|
||||||
|
- [debouncing](https://formkit.com/essentials/inputs#debouncing)
|
||||||
|
|
||||||
|
延迟 prop 的默认值是 20 毫秒。然而,group 和 list 输入默认使用 0 毫秒,以防止防抖延迟在每个深度级别“累积”。
|
||||||
|
|
||||||
## Custom Input
|
## Custom Input
|
||||||
|
|
||||||
- https://formkit.com/guides/export-and-restructure-inputs
|
- https://formkit.com/guides/export-and-restructure-inputs
|
||||||
|
- `npx formkit@latest export`
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
- https://formkit.com/essentials/examples
|
- https://formkit.com/essentials/examples
|
||||||
|
|
||||||
## Theme
|
## Theme
|
||||||
|
|
||||||
- https://themes.formkit.com/editor?theme=regenesis
|
- https://themes.formkit.com/editor?theme=regenesis
|
||||||
- [Creating a Tailwind CSS theme for FormKit](https://dev.to/andrewboyd/creating-a-tailwind-css-theme-for-formkit-45o4)
|
- [Creating a Tailwind CSS theme for FormKit](https://dev.to/andrewboyd/creating-a-tailwind-css-theme-for-formkit-45o4)
|
||||||
- [issues: Tailwind prefix support for regenesis theme](https://github.com/formkit/formkit/issues/1157)
|
- [issues: Tailwind prefix support for regenesis theme](https://github.com/formkit/formkit/issues/1157)
|
||||||
|
|
||||||
## 3rd Party
|
## 3rd Party
|
||||||
- https://github.com/mathsgod/formkit-element
|
|
||||||
|
- https://github.com/mathsgod/formkit-element
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
import type { FormKitNode } from "@formkit/core";
|
|
||||||
|
|
||||||
const legends = ['checkbox_multi', 'radio_multi', 'repeater', 'transferlist'];
|
|
||||||
export function addAsteriskPlugin(node: FormKitNode) {
|
|
||||||
if (['button', 'submit', 'hidden', 'group', 'list', 'meta'].includes(node.props.type)) return;
|
|
||||||
|
|
||||||
node.on('created', () => {
|
|
||||||
const legendOrLabel = legends.includes(`${node.props.type}${node.props.options ? '_multi' : ''}`) ? 'legend' : 'label';
|
|
||||||
|
|
||||||
console.group('[node created]', node.props.label || node.props.submitLabel)
|
|
||||||
console.debug(`node :>> `, node);
|
|
||||||
console.debug(`node.props.definition :>> `, node.props.definition);
|
|
||||||
// if (typeof node.props.definition!.schema === 'function') {
|
|
||||||
// console.debug(`node.props.definition.schema.call :>> `, node.props.definition!.schema.call(node.props.definition, {}));
|
|
||||||
// }
|
|
||||||
console.debug(`legendOrLabel :>> `, legendOrLabel);
|
|
||||||
console.groupEnd()
|
|
||||||
|
|
||||||
if (node.props.definition?.schemaMemoKey) {
|
|
||||||
node.props.definition.schemaMemoKey += `${node.props.options ? '_multi' : ''}_add_asterisk`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const schemaFn = node.props.definition!.schema!;
|
|
||||||
node.props.definition!.schema = (sectionsSchema = {}) => {
|
|
||||||
sectionsSchema[legendOrLabel] = {
|
|
||||||
children: ['$label', {
|
|
||||||
$el: 'span',
|
|
||||||
if: '$state.required',
|
|
||||||
attrs: {
|
|
||||||
class: '$classes.asterisk',
|
|
||||||
},
|
|
||||||
children: ['*']
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
return typeof schemaFn === 'function' ? schemaFn(sectionsSchema) : schemaFn
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
37
formkit.config.plugin.addAsteriskPlugin.ts
Normal file
37
formkit.config.plugin.addAsteriskPlugin.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import type { FormKitExtendableSchemaRoot, FormKitNode } from '@formkit/core';
|
||||||
|
|
||||||
|
const legends = ['checkbox_multi', 'radio_multi', 'repeater', 'transferlist'];
|
||||||
|
export function addAsteriskPlugin(node: FormKitNode) {
|
||||||
|
if (['button', 'submit', 'hidden', 'group', 'list', 'meta'].includes(node.props.type)) return;
|
||||||
|
|
||||||
|
node.on('created', () => {
|
||||||
|
const legendOrLabel = legends.includes(`${node.props.type}${node.props.options ? '_multi' : ''}`)
|
||||||
|
? 'legend'
|
||||||
|
: 'label';
|
||||||
|
|
||||||
|
if (!node.props.definition) return;
|
||||||
|
|
||||||
|
if (node.props.definition.schemaMemoKey) {
|
||||||
|
node.props.definition.schemaMemoKey += `${node.props.options ? '_multi' : ''}_add_asterisk`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const schemaFn = node.props.definition.schema! as FormKitExtendableSchemaRoot;
|
||||||
|
node.props.definition!.schema = (sectionsSchema = {}) => {
|
||||||
|
sectionsSchema[legendOrLabel] = {
|
||||||
|
children: [
|
||||||
|
'$label',
|
||||||
|
{
|
||||||
|
$el: 'span',
|
||||||
|
if: '$state.required',
|
||||||
|
attrs: {
|
||||||
|
class: '$classes.asterisk',
|
||||||
|
},
|
||||||
|
children: ['*'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
return schemaFn(sectionsSchema);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
10
formkit.config.plugin.debug.ts
Normal file
10
formkit.config.plugin.debug.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export function debugPlugin(node: import('@formkit/core').FormKitNode) {
|
||||||
|
if (['button', 'submit', 'hidden', 'group', 'list', 'meta'].includes(node.props.type)) return;
|
||||||
|
|
||||||
|
node.on('created', () => {
|
||||||
|
console.group('[node created]', node.props.label || node.props.submitLabel || node.name);
|
||||||
|
console.debug(`node :>> `, node);
|
||||||
|
console.debug(`node.props.definition :>> `, node.props.definition);
|
||||||
|
console.groupEnd();
|
||||||
|
});
|
||||||
|
}
|
@ -1,56 +1,79 @@
|
|||||||
import { createAutoAnimatePlugin, createAutoHeightTextareaPlugin } from '@formkit/addons'
|
import { form } from '@/__fk-inputs__/inputs/form';
|
||||||
import type { FormKitOptions } from '@formkit/core'
|
import { PCascadeSelect } from '@/__fk-inputs__/inputs/p-cascade-select';
|
||||||
import { createI18nPlugin, zh } from '@formkit/i18n'
|
import { PDatePicker } from '@/__fk-inputs__/inputs/p-date-picker';
|
||||||
import { genesisIcons } from '@formkit/icons'
|
import { PFileUpload } from '@/__fk-inputs__/inputs/p-file-upload';
|
||||||
import { checkbox, createLibraryPlugin, form, group, list, number, range, submit, text, textarea, } from '@formkit/inputs'
|
import { PInputPassword } from '@/__fk-inputs__/inputs/p-input-password';
|
||||||
import { createProPlugin, toggle } from '@formkit/pro'
|
import { PInputText } from '@/__fk-inputs__/inputs/p-input-text';
|
||||||
import * as defaultRules from '@formkit/rules'
|
import { PSelect } from '@/__fk-inputs__/inputs/p-select';
|
||||||
import { createThemePlugin } from '@formkit/themes'
|
import { createAutoAnimatePlugin, createAutoHeightTextareaPlugin } from '@formkit/addons';
|
||||||
import { createValidationPlugin } from '@formkit/validation'
|
import { autoAnimatePlugin } from '@formkit/auto-animate/vue';
|
||||||
import { /* defaultConfig, */ bindings, createInput } from '@formkit/vue'
|
import type { FormKitOptions, FormKitPlugin } from '@formkit/core';
|
||||||
import { addAsteriskPlugin } from './formkit.addAsteriskPlugin'
|
import { register as decodeErrors } from '@formkit/dev';
|
||||||
import { rootClasses } from "./formkit.config.theme"
|
import { createI18nPlugin, zh } from '@formkit/i18n';
|
||||||
import HeadlessuiToggle from "./src/headlessui-switch.vue";
|
import { createLibraryPlugin, group, list, submit } from '@formkit/inputs';
|
||||||
|
import * as defaultRules from '@formkit/rules';
|
||||||
|
import { createValidationPlugin } from '@formkit/validation';
|
||||||
const library = createLibraryPlugin({
|
import { /* defaultConfig, */ bindings, plugin /* defaultConfig */ } from '@formkit/vue';
|
||||||
text, form, submit, group, checkbox, range, list, number, textarea,
|
import type { App } from 'vue';
|
||||||
'headlessuiSwitch': createInput(HeadlessuiToggle)
|
import { addAsteriskPlugin } from './formkit.config.plugin.addAsteriskPlugin';
|
||||||
})
|
import { debugPlugin } from './formkit.config.plugin.debug';
|
||||||
const validation = createValidationPlugin(defaultRules)
|
|
||||||
const i18n = createI18nPlugin({ zh })
|
|
||||||
const theme = undefined;
|
|
||||||
const icons = genesisIcons;
|
|
||||||
const themePlugin = createThemePlugin(theme, icons/* , iconLoaderUrl, iconLoader */)
|
|
||||||
|
|
||||||
const apiKey = 'fk-6cdd5192223'
|
|
||||||
export default {
|
|
||||||
plugins: [
|
|
||||||
createProPlugin(apiKey, { toggle }),
|
|
||||||
|
|
||||||
library,
|
|
||||||
|
|
||||||
themePlugin, bindings, i18n, validation, addAsteriskPlugin,
|
|
||||||
|
|
||||||
// https://github.com/formkit/formkit/blob/ac1947a305eb5082ba95f53800305d080787cb32/packages/addons/src/plugins/autoHeightTextarea.ts
|
|
||||||
createAutoHeightTextareaPlugin(),
|
|
||||||
|
|
||||||
|
const plugins: FormKitPlugin[] = [
|
||||||
|
// createLibraryPlugin(fkLibrary),
|
||||||
|
createLibraryPlugin({
|
||||||
|
submit,
|
||||||
|
list,
|
||||||
|
group,
|
||||||
|
// ==============================
|
||||||
|
form,
|
||||||
|
PInputText,
|
||||||
|
PInputPassword,
|
||||||
|
PSelect,
|
||||||
|
PDatePicker,
|
||||||
|
PCascadeSelect,
|
||||||
|
PFileUpload,
|
||||||
|
}),
|
||||||
|
// createLibraryPlugin(
|
||||||
|
// {
|
||||||
|
// 'headlessuiSwitch': createInput(HeadlessuiToggle),
|
||||||
|
// }
|
||||||
|
// ),
|
||||||
|
// createThemePlugin(
|
||||||
|
// theme: undefined /* icons: genesisIcons, iconLoaderUrl, iconLoader */,
|
||||||
|
// ),
|
||||||
|
// #############################
|
||||||
|
bindings,
|
||||||
|
createI18nPlugin({ zh }),
|
||||||
|
createValidationPlugin(defaultRules),
|
||||||
|
addAsteriskPlugin,
|
||||||
|
createAutoHeightTextareaPlugin(/* https://github.com/formkit/formkit/blob/ac1947a305eb5082ba95f53800305d080787cb32/packages/addons/src/plugins/autoHeightTextarea.ts */),
|
||||||
|
createAutoAnimatePlugin(
|
||||||
// https://auto-animate.formkit.com/#usage
|
// https://auto-animate.formkit.com/#usage
|
||||||
// https://github.com/formkit/auto-animate/
|
// https://github.com/formkit/auto-animate/
|
||||||
// https://github.com/formkit/formkit/blob/46d64d05c1b37875fc6227853f2bcfa987550c91/packages/addons/src/plugins/autoAnimatePlugin.ts
|
// https://github.com/formkit/formkit/blob/46d64d05c1b37875fc6227853f2bcfa987550c91/packages/addons/src/plugins/autoAnimatePlugin.ts
|
||||||
createAutoAnimatePlugin(
|
{
|
||||||
{
|
duration: 250,
|
||||||
duration: 250,
|
easing: 'ease-in-out',
|
||||||
easing: 'ease-in-out',
|
},
|
||||||
},
|
{
|
||||||
// {
|
/* optional animation targets object */
|
||||||
// /* optional animation targets object */
|
// default:
|
||||||
// // default:
|
global: ['outer', 'inner'],
|
||||||
// global: ['outer', 'inner'],
|
form: ['form'],
|
||||||
// form: ['form'],
|
repeater: ['items'],
|
||||||
// repeater: ['items'],
|
},
|
||||||
// }
|
),
|
||||||
)
|
];
|
||||||
],
|
|
||||||
config: { rootClasses },
|
if (import.meta.env.VITE_DEBUG_FORMKIT === 'true') plugins.push(debugPlugin);
|
||||||
} satisfies FormKitOptions
|
|
||||||
|
const config: FormKitOptions = {
|
||||||
|
plugins,
|
||||||
|
config: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function setupFormKit(app: App) {
|
||||||
|
decodeErrors();
|
||||||
|
app.use(plugin, config);
|
||||||
|
app.use(autoAnimatePlugin); // v-auto-animate="{ duration: 100 }"
|
||||||
|
}
|
||||||
|
91
index.html
91
index.html
@ -1,30 +1,69 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/svg+xml"
|
||||||
|
href="/vite.svg"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||||
|
/>
|
||||||
|
<title>Vue FormKit Example</title>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="/src/assets/tailwind.css"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="/src/assets/main.css"
|
||||||
|
/>
|
||||||
|
|
||||||
<head>
|
<script>
|
||||||
<meta charset="UTF-8" />
|
function setAppHeight() {
|
||||||
<link
|
const app = document.getElementById('app');
|
||||||
rel="icon"
|
app.style.minHeight = `${window.innerHeight}px`;
|
||||||
type="image/svg+xml"
|
}
|
||||||
href="/vite.svg"
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
name="viewport"
|
|
||||||
content="width=device-width, initial-scale=1.0"
|
|
||||||
/>
|
|
||||||
<title>Vue FormKit Example</title>
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="/src/assets/main.css"
|
|
||||||
>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
window.addEventListener('resize', setAppHeight);
|
||||||
<div id="app"></div>
|
window.addEventListener('load', setAppHeight);
|
||||||
<script
|
</script>
|
||||||
type="module"
|
<style>
|
||||||
src="/src/main.ts"
|
#app {
|
||||||
></script>
|
min-height: 100vh;
|
||||||
</body>
|
max-width: 100vw;
|
||||||
|
overflow-x: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
</html>
|
@supports (min-height: 100dvh) {
|
||||||
|
#app {
|
||||||
|
min-height: 100dvh;
|
||||||
|
max-width: 100dvw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-wrapper {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<div
|
||||||
|
class="page-wrapper"
|
||||||
|
style="display: flex; justify-content: center; align-items: center"
|
||||||
|
>
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script
|
||||||
|
type="module"
|
||||||
|
src="/src/main.ts"
|
||||||
|
></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
46
package.json
46
package.json
@ -7,30 +7,42 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --port 1115",
|
"dev": "vite --port 1115",
|
||||||
"build": "vue-tsc -b && vite build",
|
"build": "vue-tsc -b && vite build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"preview:netlify": "netlify deploy --dir dist"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formkit/addons": "^1.6.9",
|
"@formkit/addons": "^1.6.9",
|
||||||
"@formkit/core": "latest",
|
"@formkit/core": "^1.6.9",
|
||||||
"@formkit/icons": "latest",
|
"@formkit/icons": "^1.6.9",
|
||||||
"@formkit/pro": "^0.127.7",
|
"@formkit/pro": "^0.127.21",
|
||||||
"@formkit/themes": "latest",
|
"@formkit/themes": "^1.6.9",
|
||||||
"@formkit/vue": "latest",
|
"@formkit/vue": "1.6.10-fix.202501212134",
|
||||||
"@formkit/zod": "^1.6.9",
|
"@formkit/zod": "^1.6.9",
|
||||||
"@headlessui/vue": "^1.7.23",
|
"@headlessui/vue": "^1.7.23",
|
||||||
"autoprefixer": "latest",
|
"@primevue/forms": "^4.2.5",
|
||||||
"i18next": "^23.16.6",
|
"@primevue/themes": "^4.2.5",
|
||||||
"postcss": "latest",
|
"autoprefixer": "^10.4.20",
|
||||||
"sweetalert2": "^11.14.5",
|
"axios": "^1.7.9",
|
||||||
"tailwindcss": "latest",
|
"dayjs": "^1.11.13",
|
||||||
|
"i18next": "^24.2.2",
|
||||||
|
"postcss": "^8.5.2",
|
||||||
|
"primeicons": "^7.0.0",
|
||||||
|
"primelocale": "^1.6.0",
|
||||||
|
"primevue": "4.2.5",
|
||||||
|
"sweetalert2": "^11.17.2",
|
||||||
|
"tailwindcss": "^3.4.17",
|
||||||
|
"utils4u": "^4.0.0",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"zod-i18n-map": "^2.27.0"
|
"zod-i18n-map": "^2.27.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.2.0",
|
"@primevue/auto-import-resolver": "^4.2.5",
|
||||||
"netlify-cli": "^17.37.2",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
"typescript": "~5.6.3",
|
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
||||||
"vite": "^5.4.11",
|
"typescript": "~5.7.3",
|
||||||
"vue-tsc": "^2.1.10"
|
"unocss": "^65.5.0",
|
||||||
|
"unplugin-vue-components": "^28.2.0",
|
||||||
|
"vite": "^6.1.0",
|
||||||
|
"vue-tsc": "^2.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
9621
pnpm-lock.yaml
generated
9621
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
109
src/App.vue
109
src/App.vue
@ -1,9 +1,110 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import TutorialForm from './tutorial-form/index.vue'
|
import { ref } from 'vue';
|
||||||
import ZodForm from './zod-form/index.vue'
|
import AllCustom from './all-custom/all-custom.vue';
|
||||||
|
import TutorialForm from './tutorial-form/index.vue';
|
||||||
|
import ZodForm from './zod-form/index.vue';
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ZodForm />
|
<div class="page-wrapper p-4">
|
||||||
<TutorialForm />
|
<ZodForm v-if="false" />
|
||||||
|
<TutorialForm v-if="false" />
|
||||||
|
|
||||||
|
<!-- <div class="flex flex-col md:flex-row items-start p-4 gap-4"> -->
|
||||||
|
<div class="p-4 w-full bg-white rounded-lg shadow-md dark:bg-gray-800 dark:text-white">
|
||||||
|
<AllCustom />
|
||||||
|
</div>
|
||||||
|
<!-- </div> -->
|
||||||
|
|
||||||
|
<div class="p-4 w-full bg-white rounded-lg shadow-md dark:bg-gray-800 dark:text-white mt-4">
|
||||||
|
|
||||||
|
<CascadeSelect
|
||||||
|
loading
|
||||||
|
v-model="selectedCity"
|
||||||
|
:options="countries"
|
||||||
|
optionLabel="cname"
|
||||||
|
optionGroupLabel="name"
|
||||||
|
:optionGroupChildren="['states', 'cities']"
|
||||||
|
class="w-56"
|
||||||
|
placeholder="Select a City"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import TimesIcon from '@primevue/icons/times';
|
||||||
|
import type { FileUploadStatus } from './types';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import type { BadgeProps } from 'primevue/badge';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
disabled: boolean;
|
||||||
|
url: string;
|
||||||
|
filename: string;
|
||||||
|
status?: FileUploadStatus;
|
||||||
|
progress?: number;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
defineEmits<{
|
||||||
|
remove: () => void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const bageValue = computed(() => {
|
||||||
|
switch (props.status) {
|
||||||
|
case 'pending':
|
||||||
|
return '待上传';
|
||||||
|
case 'uploading':
|
||||||
|
return '上传中';
|
||||||
|
case 'failed':
|
||||||
|
return '上传失败';
|
||||||
|
case 'uploaded':
|
||||||
|
return '已上传';
|
||||||
|
default:
|
||||||
|
return '未知状态';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const bageSeverity = computed<BadgeProps['severity']>(() => {
|
||||||
|
switch (props.status) {
|
||||||
|
case 'pending':
|
||||||
|
return 'info';
|
||||||
|
case 'uploading':
|
||||||
|
return 'warn';
|
||||||
|
case 'failed':
|
||||||
|
return 'danger';
|
||||||
|
case 'uploaded':
|
||||||
|
return 'success';
|
||||||
|
default:
|
||||||
|
return 'contrast';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="border border-[var(--p-fileupload-border-color)] p-2 rounded-md flex flex-wrap items-center gap-2">
|
||||||
|
<slot name="image">
|
||||||
|
<img
|
||||||
|
:src="url"
|
||||||
|
class="w-10 h-10 shrink-0 of-hidden"
|
||||||
|
/>
|
||||||
|
</slot>
|
||||||
|
<div class="break-all break-anywhere">{{ filename }}</div>
|
||||||
|
<Badge
|
||||||
|
:value="bageValue"
|
||||||
|
:severity="bageSeverity"
|
||||||
|
/>
|
||||||
|
<ProgressBar
|
||||||
|
v-if="status === 'uploading'"
|
||||||
|
:value="progress"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
:disabled="disabled"
|
||||||
|
text
|
||||||
|
:rounded="true"
|
||||||
|
severity="danger"
|
||||||
|
class="ml-auto"
|
||||||
|
@click="$emit('remove')"
|
||||||
|
>
|
||||||
|
<TimesIcon aria-hidden="true" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
237
src/__fk-inputs__/components/file-upload/file-upload.vue
Normal file
237
src/__fk-inputs__/components/file-upload/file-upload.vue
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { FormKitFrameworkContext } from '@formkit/core';
|
||||||
|
import FileUpload, { type FileUploadUploaderEvent } from 'primevue/fileupload';
|
||||||
|
import { computed, onMounted, useTemplateRef } from 'vue';
|
||||||
|
import FileUploadItem from './file-upload-item.vue';
|
||||||
|
import type { CustomRequest, FileExt, FileUploadInst, PropFilesToValue, PropValueToFiles } from './types';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
context: FormKitFrameworkContext & {
|
||||||
|
fileLimit?: number;
|
||||||
|
maxFileSize?: number;
|
||||||
|
customRequest?: CustomRequest;
|
||||||
|
valueToFiles?: PropValueToFiles;
|
||||||
|
filesToValue?: PropFilesToValue;
|
||||||
|
autoUpload?: boolean;
|
||||||
|
};
|
||||||
|
}>();
|
||||||
|
const formkitContext = props.context;
|
||||||
|
|
||||||
|
const customRequest = props.context.customRequest;
|
||||||
|
|
||||||
|
const fileUploadRef = useTemplateRef<FileUploadInst>('fileUploadRef');
|
||||||
|
|
||||||
|
const cmpt_disabled = computed(() => {
|
||||||
|
if (formkitContext.disabled === true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (fileUploadRef.value) {
|
||||||
|
// 有上传失败的文件
|
||||||
|
if (fileUploadRef.value.uploadedFiles.some((f) => f.status === 'failed')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已上传文件数量超过限制(这里是大于*等于*)
|
||||||
|
if (fileUploadRef.value.uploadedFileCount >= (formkitContext.fileLimit ?? Infinity)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已上传和待上传文件数量超过限制(这里是*大于*)
|
||||||
|
const uploaded_and_pending_count = fileUploadRef.value.uploadedFileCount + fileUploadRef.value.files.length;
|
||||||
|
if (uploaded_and_pending_count > (formkitContext.fileLimit ?? Infinity)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const cmpt_showUploadButton = computed(() => {
|
||||||
|
if (fileUploadRef.value?.files?.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const changeModelValue = () => {
|
||||||
|
const uploadedFiles = fileUploadRef.value!.uploadedFiles.filter((f) => f.status === 'uploaded');
|
||||||
|
if (!formkitContext.filesToValue) {
|
||||||
|
console.warn('[FileUpload] filesToValue is not defined');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (uploadedFiles.length === 0) {
|
||||||
|
formkitContext.node.input(null);
|
||||||
|
} else {
|
||||||
|
formkitContext.node.input(formkitContext.filesToValue(uploadedFiles));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const changeUploadedFiles = () => {
|
||||||
|
if (!formkitContext.valueToFiles) {
|
||||||
|
console.warn('[FileUpload] valueToFiles is not defined');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const files = formkitContext.valueToFiles(formkitContext._value);
|
||||||
|
if (files === null) return;
|
||||||
|
|
||||||
|
fileUploadRef.value!.uploadedFiles = files.map((f) => ({
|
||||||
|
name: f.name,
|
||||||
|
url: f.url,
|
||||||
|
status: f.status || 'uploaded',
|
||||||
|
progress: f.progress || 100,
|
||||||
|
}));
|
||||||
|
fileUploadRef.value!.uploadedFileCount = files.length;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[FileUpload] valueToFiles error:', error, 'value:', formkitContext._value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
changeUploadedFiles();
|
||||||
|
});
|
||||||
|
|
||||||
|
const onUploader = (event: FileUploadUploaderEvent) => {
|
||||||
|
if (!customRequest) {
|
||||||
|
console.warn('[FileUpload] customRequest is not defined');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = event.files as FileExt[];
|
||||||
|
for (const file of files) {
|
||||||
|
fileUploadRef.value!.uploadedFiles.push({
|
||||||
|
rawFile: file,
|
||||||
|
name: file.name,
|
||||||
|
url: '',
|
||||||
|
status: 'uploading',
|
||||||
|
progress: 0,
|
||||||
|
});
|
||||||
|
const fileItem = fileUploadRef.value!.uploadedFiles[fileUploadRef.value!.uploadedFiles.length - 1];
|
||||||
|
customRequest({
|
||||||
|
file,
|
||||||
|
onProgress: (percent) => {
|
||||||
|
fileItem.progress = percent;
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
fileItem.status = 'uploaded';
|
||||||
|
fileItem.url = result.url;
|
||||||
|
changeModelValue();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
fileItem.status = 'failed';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// fileUploadRef.value?.uploadedFiles
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<FileUpload
|
||||||
|
ref="fileUploadRef"
|
||||||
|
:auto="formkitContext.autoUpload"
|
||||||
|
:disabled="cmpt_disabled"
|
||||||
|
:showUploadButton="cmpt_showUploadButton"
|
||||||
|
:showCancelButton="false"
|
||||||
|
:customUpload="true"
|
||||||
|
mode="advanced"
|
||||||
|
:multiple="true"
|
||||||
|
accept="image/*"
|
||||||
|
:maxFileSize="formkitContext.maxFileSize"
|
||||||
|
invalidFileSizeMessage="文件 {0} 大小超过限制 {1}"
|
||||||
|
:fileLimit="formkitContext.fileLimit"
|
||||||
|
invalidFileLimitMessage="最多只能上传 {0} 个文件,请移除多余文件后点击上传"
|
||||||
|
@uploader="onUploader"
|
||||||
|
:chooseButtonProps="{ size: 'small' }"
|
||||||
|
:uploadButtonProps="{ size: 'small', severity: 'secondary' }"
|
||||||
|
:cancelButtonProps="{ size: 'small', severity: 'secondary' }"
|
||||||
|
@remove="changeModelValue()"
|
||||||
|
@removeUploadedFile="changeModelValue()"
|
||||||
|
>
|
||||||
|
<template #empty>
|
||||||
|
<Message
|
||||||
|
size="small"
|
||||||
|
severity="secondary"
|
||||||
|
>未上传附件</Message
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #content="{ messages, removeFileCallback, removeUploadedFileCallback }">
|
||||||
|
<Message
|
||||||
|
size="small"
|
||||||
|
v-for="msg of messages"
|
||||||
|
closable
|
||||||
|
@close="fileUploadRef!.messages = []"
|
||||||
|
:key="msg"
|
||||||
|
severity="error"
|
||||||
|
>{{ msg }}</Message
|
||||||
|
>
|
||||||
|
|
||||||
|
<!-- 已上传列表(上传中、上传成功、上传失败) -->
|
||||||
|
<template
|
||||||
|
v-for="(file, index) of fileUploadRef?.uploadedFiles"
|
||||||
|
:key="file.name + file.url"
|
||||||
|
>
|
||||||
|
<FileUploadItem
|
||||||
|
:disabled="formkitContext.disabled === true"
|
||||||
|
:url="file.url || file.rawFile?.objectURL || ''"
|
||||||
|
:filename="file.name || file.url || '未知文件'"
|
||||||
|
@remove="
|
||||||
|
() => {
|
||||||
|
fileUploadRef!.uploadedFileCount--;
|
||||||
|
removeUploadedFileCallback(index);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
:status="file.status"
|
||||||
|
:progress="file.progress"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
#image
|
||||||
|
v-if="formkitContext.slots.image"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="formkitContext.slots.image"
|
||||||
|
:url="file.url || file.rawFile?.objectURL || ''"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</FileUploadItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 待上传列表 -->
|
||||||
|
<template
|
||||||
|
v-for="(file, index) of fileUploadRef?.files"
|
||||||
|
:key="file.name + file.type + file.size"
|
||||||
|
>
|
||||||
|
<FileUploadItem
|
||||||
|
:disabled="formkitContext.disabled === true"
|
||||||
|
:url="file.objectURL"
|
||||||
|
:filename="file.name"
|
||||||
|
@remove="removeFileCallback(index)"
|
||||||
|
status="pending"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
#image
|
||||||
|
v-if="formkitContext.slots.image"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="formkitContext.slots.image"
|
||||||
|
:url="file.objectURL"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</FileUploadItem>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</FileUpload>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.p-floatlabel:has(.p-fileupload) label {
|
||||||
|
top: var(--p-floatlabel-over-active-top);
|
||||||
|
transform: translateY(0);
|
||||||
|
font-size: var(--p-floatlabel-active-font-size);
|
||||||
|
font-weight: var(--p-floatlabel-label-active-font-weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-fileupload-content .p-progressbar {
|
||||||
|
--p-fileupload-progressbar-height: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
28
src/__fk-inputs__/components/file-upload/types.ts
Normal file
28
src/__fk-inputs__/components/file-upload/types.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import type { FileUploadState } from 'primevue/fileupload';
|
||||||
|
|
||||||
|
export interface FileExt extends File {
|
||||||
|
objectURL: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FileUploadStatus = 'pending' | 'uploading' | 'uploaded' | 'failed';
|
||||||
|
|
||||||
|
export type UploadedFileInfo = {
|
||||||
|
rawFile?: FileExt;
|
||||||
|
name?: string;
|
||||||
|
url?: string;
|
||||||
|
status?: FileUploadStatus;
|
||||||
|
progress?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface FileUploadInst extends FileUploadState {
|
||||||
|
files: FileExt[];
|
||||||
|
uploadedFiles: UploadedFileInfo[];
|
||||||
|
chooseDisabled?: boolean;
|
||||||
|
}
|
||||||
|
export type CustomRequest = (options: {
|
||||||
|
file: File;
|
||||||
|
onProgress: (percent: number) => void;
|
||||||
|
}) => Promise<{ url: string }>;
|
||||||
|
|
||||||
|
export type PropValueToFiles = (value: unknown) => UploadedFileInfo[] | null;
|
||||||
|
export type PropFilesToValue = (filelist: UploadedFileInfo[]) => unknown;
|
31
src/__fk-inputs__/components/messages.vue
Normal file
31
src/__fk-inputs__/components/messages.vue
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<template>
|
||||||
|
<Message
|
||||||
|
severity="error"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<!-- <template #container> -->
|
||||||
|
<ul class="my-0 flex flex-col gap-1">
|
||||||
|
<li
|
||||||
|
v-for="message in context.messages"
|
||||||
|
:key="message.key"
|
||||||
|
:id="`${context.id}-${message.key}`"
|
||||||
|
:data-message-type="message.type"
|
||||||
|
>
|
||||||
|
{{ message.value }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<!-- </template> -->
|
||||||
|
</Message>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { type FormKitFrameworkContext } from '@formkit/core';
|
||||||
|
import Message from 'primevue/message';
|
||||||
|
defineProps<{ context: FormKitFrameworkContext }>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.p-floatlabel + .p-message {
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
</style>
|
50
src/__fk-inputs__/inputs/form.ts
Normal file
50
src/__fk-inputs__/inputs/form.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import type { FormKitTypeDefinition } from '@formkit/core';
|
||||||
|
import { createSection, disablesChildren, forms } from '@formkit/inputs';
|
||||||
|
import PButton from 'primevue/button';
|
||||||
|
import { markRaw } from 'vue';
|
||||||
|
import { messages } from '../sections/messages';
|
||||||
|
|
||||||
|
const formInput = createSection('form', () => ({
|
||||||
|
$el: 'form',
|
||||||
|
bind: '$attrs',
|
||||||
|
meta: {
|
||||||
|
autoAnimate: true,
|
||||||
|
},
|
||||||
|
attrs: {
|
||||||
|
'id': '$id',
|
||||||
|
'name': '$node.name',
|
||||||
|
'onSubmit': '$handlers.submit',
|
||||||
|
'data-loading': '$state.loading || undefined',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const button = createSection(
|
||||||
|
// https://formkit.com/playground?fkv=latest&fileTab=Playground.vue&files=jc%5B%28%27name%21%27Playground.vue%27%7Eeditor%21%27%3Cscripzsetup%3EBtestUgWasyncJEqawaiznew+Promise%7Br3%3E+setTimeout%7Br%2C+5000%7D%7D-%29-BsGW%5Bq%286%24formkit142*lX1LogU2%29-%5D%3B-B_W%28qY1Uput2sG8%5B6%286*%24cmp19xbUdZsHAttrsxprops8%286Nignore8true%2C6NY1sHx*OZOx*lXZsHLXx%29%2C6*V6N%22%24lXx*%286N*ifZOxN%24el1spanxNV%22+loadUg...%22%5D6N%29%2C6*%5D6%29q%5D%2C-%29-BC3JE%29--%2F%2F+Then+we+attach+a+library-C.library3JnodeEqifJnode.props.Y3%3D%3D+%224%22%7D+%286node.defUe%7B_%7Dq%29-%29--MscriptQ-%3Cj-+%3C96YRform76%3AplugUsR%5BC%5D76%3AactionsRfalse76%40sHRtestUg7q%3E6%3C9SG+%3AsGRsG7+%2F%3E6M9Q*M9QMj%27%7Eadded%21true%29%5D*++-%5Cn18%222%22%2CqW%3D4spUnUgSH6-N7%5C%278%3A+9FormKitB-conszCcustomInputsE%7D3%3E+%28GchemaHubmitJ+%7BM%3C%2FN**OdisabledQ%3E-R%3D7UinVchildren8%5BW3+XabelYtypeZ1%24_4DefUitionjtemplateQq-*x2Nzt+%01zxqj_ZYXWVURQONMJHGECB98764321-*_&imports=jc%28%27name%21%27ImportMap%27%7Eeditor%21%27%28*+1vue%5C%211https%3A%2F%2Fcdn.jsdelivr.net%2Fnpm%2Fvue%403%2Fdist%2Fvue.esm-browser.min.js0*%29*%27%29*%5Cn0%5C%271+0%0110*_&css-framework=genesis
|
||||||
|
'button',
|
||||||
|
() => ({
|
||||||
|
$cmp: markRaw(PButton) as never,
|
||||||
|
bind: '$submitAttrs',
|
||||||
|
props: {
|
||||||
|
type: 'submit',
|
||||||
|
label: '$submitLabel',
|
||||||
|
loading: '$state.loading || undefined',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const form: FormKitTypeDefinition = {
|
||||||
|
type: 'group',
|
||||||
|
schema: formInput(
|
||||||
|
'$slots.default',
|
||||||
|
messages(),
|
||||||
|
button(), //
|
||||||
|
),
|
||||||
|
props: [
|
||||||
|
'submitLabel',
|
||||||
|
'submitAttrs',
|
||||||
|
'submitBehavior',
|
||||||
|
'incompleteMessage', // 抱歉,部分字段未被正确填写。
|
||||||
|
],
|
||||||
|
features: [forms, disablesChildren],
|
||||||
|
schemaMemoKey: 'i90xrn9dyre', // Math.random().toString(36).substring(2, 15)
|
||||||
|
};
|
115
src/__fk-inputs__/inputs/p-cascade-select.tsx
Normal file
115
src/__fk-inputs__/inputs/p-cascade-select.tsx
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
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';
|
||||||
|
import { help } from '../sections/help';
|
||||||
|
|
||||||
|
// 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'),
|
||||||
|
),
|
||||||
|
help('$help'),
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
107
src/__fk-inputs__/inputs/p-date-picker.tsx
Normal file
107
src/__fk-inputs__/inputs/p-date-picker.tsx
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
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';
|
||||||
|
import { help } from '../sections/help';
|
||||||
|
|
||||||
|
// :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'),
|
||||||
|
),
|
||||||
|
help('$help'),
|
||||||
|
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'];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
58
src/__fk-inputs__/inputs/p-file-upload.tsx
Normal file
58
src/__fk-inputs__/inputs/p-file-upload.tsx
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import type { FormKitTypeDefinition } from '@formkit/core';
|
||||||
|
import type { FormKitInputs, FormKitSlotData } from '@formkit/inputs';
|
||||||
|
import { createSection, label, outer } from '@formkit/inputs';
|
||||||
|
import { markRaw } from 'vue';
|
||||||
|
import FileUploadComponent from '../components/file-upload/file-upload.vue';
|
||||||
|
import type { CustomRequest, PropFilesToValue, PropValueToFiles } from '../components/file-upload/types';
|
||||||
|
import { floatLabel } from '../sections/floatLabel';
|
||||||
|
import { help } from '../sections/help';
|
||||||
|
import { messages } from '../sections/messages';
|
||||||
|
|
||||||
|
const input = createSection('input', () => ({
|
||||||
|
$cmp: markRaw(FileUploadComponent) as never,
|
||||||
|
bind: '$attrs',
|
||||||
|
props: {
|
||||||
|
context: '$node.context',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const PFileUpload: FormKitTypeDefinition = {
|
||||||
|
type: 'input',
|
||||||
|
schema: outer(
|
||||||
|
floatLabel(
|
||||||
|
input(), //
|
||||||
|
label('$label'),
|
||||||
|
),
|
||||||
|
help('$help'),
|
||||||
|
messages(),
|
||||||
|
),
|
||||||
|
props: ['fileLimit', 'maxFileSize', 'customRequest', 'valueToFiles', 'filesToValue', 'autoUpload'],
|
||||||
|
schemaMemoKey: 'ihcxd4qdgh7', // 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>> {
|
||||||
|
PFileUpload: {
|
||||||
|
type: 'PFileUpload';
|
||||||
|
value?: unknown;
|
||||||
|
fileLimit?: number;
|
||||||
|
maxFileSize?: number;
|
||||||
|
customRequest?: CustomRequest;
|
||||||
|
valueToFiles?: PropValueToFiles;
|
||||||
|
filesToValue?: PropFilesToValue;
|
||||||
|
autoUpload?: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FormKitInputSlots<Props extends FormKitInputs<Props>> {
|
||||||
|
PFileUpload: {
|
||||||
|
image: FormKitSlotData<
|
||||||
|
Props,
|
||||||
|
{
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
45
src/__fk-inputs__/inputs/p-input-password.ts
Normal file
45
src/__fk-inputs__/inputs/p-input-password.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import type { FormKitTypeDefinition } from '@formkit/core';
|
||||||
|
import type { FormKitInputs } from '@formkit/inputs';
|
||||||
|
import { casts, createSection, label, outer } from '@formkit/inputs';
|
||||||
|
import SchemaComponent from 'primevue/password';
|
||||||
|
import { markRaw } from 'vue';
|
||||||
|
import { messages } from '../sections/messages';
|
||||||
|
import { floatLabel } from '../sections/floatLabel';
|
||||||
|
import { help } from '../sections/help';
|
||||||
|
|
||||||
|
const input = createSection('input', () => ({
|
||||||
|
$cmp: markRaw(SchemaComponent) as never,
|
||||||
|
bind: '$attrs',
|
||||||
|
props: {
|
||||||
|
invalid: '$state.invalid',
|
||||||
|
disabled: '$disabled',
|
||||||
|
modelValue: '$_value',
|
||||||
|
onInput: '$handlers.DOMInput',
|
||||||
|
onBlur: '$handlers.blur',
|
||||||
|
inputId: '$id',
|
||||||
|
fluid: true,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const PInputPassword: FormKitTypeDefinition = {
|
||||||
|
type: 'input',
|
||||||
|
schema: outer(
|
||||||
|
floatLabel(
|
||||||
|
input(), //
|
||||||
|
label('$label'),
|
||||||
|
),
|
||||||
|
help('$help'),
|
||||||
|
messages(),
|
||||||
|
),
|
||||||
|
features: [casts],
|
||||||
|
schemaMemoKey: '9h771fci93n', // 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>> {
|
||||||
|
PInputPassword: {
|
||||||
|
type: 'PInputPassword';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
47
src/__fk-inputs__/inputs/p-input-text.ts
Normal file
47
src/__fk-inputs__/inputs/p-input-text.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import type { FormKitTypeDefinition } from '@formkit/core';
|
||||||
|
import type { FormKitInputs } from '@formkit/inputs';
|
||||||
|
import { casts, createSection, label, outer } from '@formkit/inputs';
|
||||||
|
import SchemaComponent from 'primevue/inputtext';
|
||||||
|
import { markRaw } from 'vue';
|
||||||
|
import { floatLabel } from '../sections/floatLabel';
|
||||||
|
import { messages } from '../sections/messages';
|
||||||
|
import { help } from '../sections/help';
|
||||||
|
|
||||||
|
const input = createSection('input', () => ({
|
||||||
|
$cmp: markRaw(SchemaComponent) as never,
|
||||||
|
bind: '$attrs',
|
||||||
|
props: {
|
||||||
|
invalid: '$state.invalid',
|
||||||
|
type: '$type',
|
||||||
|
disabled: '$disabled',
|
||||||
|
name: '$node.name',
|
||||||
|
modelValue: '$_value',
|
||||||
|
onInput: '$handlers.DOMInput',
|
||||||
|
onBlur: '$handlers.blur',
|
||||||
|
id: '$id',
|
||||||
|
fluid: true,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const PInputText: FormKitTypeDefinition = {
|
||||||
|
type: 'input',
|
||||||
|
schema: outer(
|
||||||
|
floatLabel(
|
||||||
|
input(), //
|
||||||
|
label('$label'),
|
||||||
|
),
|
||||||
|
help('$help'),
|
||||||
|
messages(),
|
||||||
|
),
|
||||||
|
features: [casts],
|
||||||
|
schemaMemoKey: 'nnvujvlf2xr', // 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>> {
|
||||||
|
PInputText: {
|
||||||
|
type: 'PInputText';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
139
src/__fk-inputs__/inputs/p-select.tsx
Normal file
139
src/__fk-inputs__/inputs/p-select.tsx
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import type { FormKitFrameworkContext, FormKitTypeDefinition } from '@formkit/core';
|
||||||
|
import type { FormKitInputs } from '@formkit/inputs';
|
||||||
|
import { createSection, label, outer } from '@formkit/inputs';
|
||||||
|
import PrimevueSelect, { type SelectEmitsOptions, type SelectSlots } from 'primevue/select';
|
||||||
|
import { computed, defineComponent, markRaw, ref } from 'vue';
|
||||||
|
import { floatLabel } from '../sections/floatLabel';
|
||||||
|
import { messages } from '../sections/messages';
|
||||||
|
import { help } from '../sections/help';
|
||||||
|
|
||||||
|
// https://formkit.com/inputs/dropdown
|
||||||
|
|
||||||
|
type PrimevueSelectListeners = {
|
||||||
|
// 'onFocus': SelectEmitsOptions['focus'];
|
||||||
|
'onUpdate:modelValue': SelectEmitsOptions['update:modelValue'];
|
||||||
|
// 'onChange': SelectEmitsOptions['change'];
|
||||||
|
'onBlur': SelectEmitsOptions['blur'];
|
||||||
|
};
|
||||||
|
|
||||||
|
const SchemaComponent = defineComponent(
|
||||||
|
(vueProps: { context: FormKitFrameworkContext }) => {
|
||||||
|
const formkitContext = vueProps.context;
|
||||||
|
const primevueSelectInstance = ref<{ label: string } | undefined>();
|
||||||
|
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事件延迟一点,可以考虑优化。
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// [optionsLoader](https://github.com/formkit/formkit/blob/2d5387ba98597775cb2a752af65aee84bc438863/packages/inputs/src/features/options.ts#L125)
|
||||||
|
|
||||||
|
const p_options = ref();
|
||||||
|
const loading = ref(true);
|
||||||
|
|
||||||
|
const loadOptions = async () => {
|
||||||
|
try {
|
||||||
|
let result;
|
||||||
|
if (formkitContext.options instanceof Promise) {
|
||||||
|
result = await formkitContext.options;
|
||||||
|
} else if (typeof formkitContext.options === 'function') {
|
||||||
|
const funcResult = await (formkitContext.options as () => unknown).call(undefined);
|
||||||
|
result = funcResult instanceof Promise ? await funcResult : funcResult;
|
||||||
|
} else {
|
||||||
|
result = formkitContext.options;
|
||||||
|
}
|
||||||
|
p_options.value = result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load options:', error);
|
||||||
|
p_options.value = [];
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadOptions(); // 立即加载options
|
||||||
|
|
||||||
|
const valueSlot = computed(() => {
|
||||||
|
if (primevueSelectInstance.value?.label === 'p-emptylabel' && !!formkitContext._value) {
|
||||||
|
return ((slotProps) => {
|
||||||
|
return slotProps.value as never;
|
||||||
|
}) satisfies SelectSlots['value'];
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
return (
|
||||||
|
<PrimevueSelect
|
||||||
|
style={{
|
||||||
|
'border-color':
|
||||||
|
!formkitContext.state.invalid && valueSlot.value !== undefined ? 'var(--p-yellow-200)' : undefined,
|
||||||
|
}}
|
||||||
|
ref={primevueSelectInstance}
|
||||||
|
labelId={formkitContext.id}
|
||||||
|
fluid
|
||||||
|
invalid={formkitContext.state.invalid}
|
||||||
|
disabled={!!formkitContext.disabled}
|
||||||
|
loading={loading.value}
|
||||||
|
options={p_options.value}
|
||||||
|
modelValue={formkitContext._value}
|
||||||
|
optionLabel={formkitContext.optionLabel as never}
|
||||||
|
optionValue={formkitContext.optionValue as never}
|
||||||
|
{...listeners}
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
value: valueSlot.value,
|
||||||
|
}}
|
||||||
|
</PrimevueSelect>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// 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 PSelect: FormKitTypeDefinition = {
|
||||||
|
type: 'input',
|
||||||
|
schema: outer(
|
||||||
|
floatLabel(
|
||||||
|
input(), //
|
||||||
|
label('$label'),
|
||||||
|
),
|
||||||
|
help('$help'),
|
||||||
|
messages(),
|
||||||
|
),
|
||||||
|
props: ['options', 'optionLabel', 'optionValue'],
|
||||||
|
schemaMemoKey: 'nip9j70lb1', // Math.random().toString(36).substring(2, 15)
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
type OptionsItem = Record<string, any>;
|
||||||
|
type OptionsType = Array<OptionsItem>;
|
||||||
|
|
||||||
|
declare module '@formkit/inputs' {
|
||||||
|
// https://formkit.com/essentials/custom-inputs#typescript-support
|
||||||
|
interface FormKitInputProps<Props extends FormKitInputs<Props>> {
|
||||||
|
PSelect: {
|
||||||
|
type: 'PSelect';
|
||||||
|
options: OptionsType | Promise<OptionsType> | (() => OptionsType | Promise<OptionsType>);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
8
src/__fk-inputs__/sections/floatLabel.ts
Normal file
8
src/__fk-inputs__/sections/floatLabel.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { createSection } from '@formkit/inputs';
|
||||||
|
import FloatLabel from 'primevue/floatlabel';
|
||||||
|
import { markRaw } from 'vue';
|
||||||
|
|
||||||
|
export const floatLabel = createSection('floatLabel', () => ({
|
||||||
|
$cmp: markRaw(FloatLabel) as never,
|
||||||
|
bind: '$attrs',
|
||||||
|
}));
|
14
src/__fk-inputs__/sections/help.ts
Normal file
14
src/__fk-inputs__/sections/help.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { createSection } from '@formkit/inputs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Help section that shows help text
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export const help = createSection('help', () => ({
|
||||||
|
$el: 'div',
|
||||||
|
if: '$help',
|
||||||
|
attrs: {
|
||||||
|
id: '$: "help-" + $id',
|
||||||
|
},
|
||||||
|
}));
|
9
src/__fk-inputs__/sections/messages.ts
Normal file
9
src/__fk-inputs__/sections/messages.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import MessagesCmp from '../components/messages.vue';
|
||||||
|
import { createSection } from '@formkit/inputs';
|
||||||
|
import { markRaw } from 'vue';
|
||||||
|
|
||||||
|
export const messages = createSection('messages', () => ({
|
||||||
|
$cmp: markRaw(MessagesCmp) as unknown as never, // [element = node.$cmp](https://github.com/formkit/formkit/blob/2d5387ba98597775cb2a752af65aee84bc438863/packages/vue/src/FormKitSchema.ts#L449)
|
||||||
|
props: { context: '$node.context' },
|
||||||
|
if: '$defaultMessagePlacement && $fns.length($messages)',
|
||||||
|
}));
|
253
src/all-custom/all-custom.vue
Normal file
253
src/all-custom/all-custom.vue
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { FormKitNode } from '@formkit/core';
|
||||||
|
import { text } from '@formkit/inputs';
|
||||||
|
import Swal from 'sweetalert2';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { arrayToTree } from 'utils4u/array';
|
||||||
|
import { CustomRequest } from '@/__fk-inputs__/components/file-upload/types';
|
||||||
|
|
||||||
|
async function submit(formData: Record<string, any>, formNode: FormKitNode) {
|
||||||
|
console.group('submit');
|
||||||
|
console.log('formData :>> ', formData);
|
||||||
|
console.log('formNode :>> ', formNode);
|
||||||
|
console.groupEnd();
|
||||||
|
|
||||||
|
await new Promise(r => setTimeout(r, 2000))
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Submitted! 🎉',
|
||||||
|
icon: 'success',
|
||||||
|
showConfirmButton: false,
|
||||||
|
timer: 1500
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const K_OPTIONS = [
|
||||||
|
{ label: 'Option 1', value: 'Value 1' },
|
||||||
|
{ label: 'Option 2', value: 'Value 2' },
|
||||||
|
{ label: 'Option 3', value: 'Value 3' },
|
||||||
|
];
|
||||||
|
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;
|
||||||
|
} */
|
||||||
|
|
||||||
|
import axios from 'axios';
|
||||||
|
const customRequest: CustomRequest = (async ({ file, onProgress, }) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
return axios
|
||||||
|
.post('https://jsonplaceholder.typicode.com/posts', formData, {
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
|
onUploadProgress: (e) => {
|
||||||
|
onProgress(Math.round((e.loaded * 100) / (e.total || 1)));
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// onProgress(100);
|
||||||
|
return { url: 'https://picsum.photos/200/300' };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Fieldset
|
||||||
|
legend="表单"
|
||||||
|
toggleable
|
||||||
|
class="min-w-full"
|
||||||
|
pt:content:class="flex justify-center"
|
||||||
|
>
|
||||||
|
<!-- https://formkit.com/inputs/form#props-attributes -->
|
||||||
|
<FormKit
|
||||||
|
:value="{
|
||||||
|
a: '1', b: '2',
|
||||||
|
PInputText: 'PInputText default value from form',
|
||||||
|
notInForm: 'not in form',
|
||||||
|
}"
|
||||||
|
:config="{
|
||||||
|
classes: {
|
||||||
|
form: 'flex flex-col w-full py-6 gap-8',
|
||||||
|
outer: 'flex flex-col gap-2',
|
||||||
|
/* label: 'font-medium block' */
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
type="form"
|
||||||
|
#default="{ value }"
|
||||||
|
@submit="submit"
|
||||||
|
submit-label="提交 ✨"
|
||||||
|
:submit-attrs="{
|
||||||
|
'some-submit-attr': 'value',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<FormKit
|
||||||
|
type="PInputText"
|
||||||
|
name="PInputText"
|
||||||
|
label="输入框"
|
||||||
|
value="`default value from field`"
|
||||||
|
help="帮助信息帮助信息帮助信息帮助信息"
|
||||||
|
some="prop"
|
||||||
|
some-boolean-prop
|
||||||
|
validation="required"
|
||||||
|
>
|
||||||
|
</FormKit>
|
||||||
|
<FormKit
|
||||||
|
name="PFileUpload"
|
||||||
|
type="PFileUpload"
|
||||||
|
label="文件上传"
|
||||||
|
:maxFileSize="1024 * 1024 * 2"
|
||||||
|
:customRequest
|
||||||
|
:fileLimit="99"
|
||||||
|
:autoUpload="true"
|
||||||
|
:filesToValue="(files) => JSON.stringify(files.map(f => ({ name: f.name, url: f.url })))"
|
||||||
|
:valueToFiles="(value) => JSON.parse(value as string)"
|
||||||
|
value='[{"name":"2KB图片_副本.jpeg","url":"https://picsum.photos/200/300"}]'
|
||||||
|
/>
|
||||||
|
<FormKit
|
||||||
|
type="PInputPassword"
|
||||||
|
name="PInputPassword"
|
||||||
|
label="密码"
|
||||||
|
validation="required"
|
||||||
|
toggleMask
|
||||||
|
:feedback="false"
|
||||||
|
/>
|
||||||
|
<FormKit
|
||||||
|
type="PSelect"
|
||||||
|
name="PSelect_1"
|
||||||
|
label="选择框"
|
||||||
|
validation="required"
|
||||||
|
:options="K_OPTIONS"
|
||||||
|
optionLabel="label"
|
||||||
|
optionValue="value"
|
||||||
|
value="数据的值不在options里"
|
||||||
|
/>
|
||||||
|
<FormKit
|
||||||
|
type="PSelect"
|
||||||
|
name="PSelect_2"
|
||||||
|
label="选择框"
|
||||||
|
validation="required"
|
||||||
|
:options="promiseOptions"
|
||||||
|
optionLabel="label"
|
||||||
|
optionValue="value"
|
||||||
|
/>
|
||||||
|
<FormKit
|
||||||
|
type="PCascadeSelect"
|
||||||
|
name="PCascadeSelect"
|
||||||
|
label="级联选择框"
|
||||||
|
validation="required"
|
||||||
|
:options="promiseCascadeOptions"
|
||||||
|
optionLabel="dictLabel"
|
||||||
|
optionValue="dictValue"
|
||||||
|
/>
|
||||||
|
<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">
|
||||||
|
<FormKit
|
||||||
|
v-if="!value?.PInputPassword"
|
||||||
|
:type="text"
|
||||||
|
:preserve="false"
|
||||||
|
name="customType"
|
||||||
|
value="this input will display if the password is empty"
|
||||||
|
help="helppppppppppp"
|
||||||
|
>
|
||||||
|
<!-- <template #label>
|
||||||
|
<div>labelll</div>
|
||||||
|
</template> -->
|
||||||
|
</FormKit>
|
||||||
|
|
||||||
|
|
||||||
|
<FormKit
|
||||||
|
type="group"
|
||||||
|
name="group"
|
||||||
|
>
|
||||||
|
<FormKit
|
||||||
|
:type="text"
|
||||||
|
name="text-in-group-1"
|
||||||
|
>
|
||||||
|
</FormKit>
|
||||||
|
</FormKit>
|
||||||
|
|
||||||
|
<FormKit
|
||||||
|
type="group"
|
||||||
|
name="group"
|
||||||
|
>
|
||||||
|
<FormKit
|
||||||
|
:type="text"
|
||||||
|
name="text-in-group-2"
|
||||||
|
>
|
||||||
|
</FormKit>
|
||||||
|
</FormKit>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
label="Button"
|
||||||
|
@click="() => {
|
||||||
|
value!.button = value!.button || {};
|
||||||
|
(value!.button as any).clicked = 'clicked-这个值在@submit回调的formData里不会出现';
|
||||||
|
(value!.group as any)['text-in-group-1'] = 'changed-by-button';
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<pre class="font-mono text-sm p-4 bg-slate-100 dark:bg-gray-900 overflow-x-auto rounded-md shadow-inner dark:text-gray-300
|
||||||
|
">{{ value }}</pre>
|
||||||
|
<!-- <FormKitSummary /> -->
|
||||||
|
</FormKit>
|
||||||
|
</Fieldset>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.formkit-input) {
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,20 +1,15 @@
|
|||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color:#f3f4f6;
|
background-color: #f3f4f6;
|
||||||
color:#1f2937;
|
color: #1f2937;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
body {
|
body {
|
||||||
background-color:#1a202c;
|
background-color: #1a202c;
|
||||||
color:#e5e7eb;
|
color: #e5e7eb;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.attributes-group {
|
.attributes-group {
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
border: 1px solid #e5e7eb;
|
border: 1px solid #e5e7eb;
|
||||||
@ -28,4 +23,10 @@ body {
|
|||||||
border-color: #374151;
|
border-color: #374151;
|
||||||
background-color: #1f2937;
|
background-color: #1f2937;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* DEV: */
|
||||||
|
/* .formkit-outer {
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
} */
|
3
src/assets/tailwind.css
Normal file
3
src/assets/tailwind.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
|
Before Width: | Height: | Size: 496 B |
45
src/main.ts
45
src/main.ts
@ -1,13 +1,40 @@
|
|||||||
import { autoAnimatePlugin } from '@formkit/auto-animate/vue'
|
import Aura from '@primevue/themes/aura';
|
||||||
import { plugin, /* defaultConfig */ } from '@formkit/vue'
|
import zhCN from 'primelocale/zh-CN.json';
|
||||||
import { createApp } from 'vue'
|
import PrimeVue from 'primevue/config';
|
||||||
import formKitConfig from '../formkit.config'
|
import ToastService from 'primevue/toastservice';
|
||||||
import App from './App.vue'
|
import { createApp } from 'vue';
|
||||||
|
import { setupFormKit } from '../formkit.config';
|
||||||
|
import App from './App.vue';
|
||||||
|
|
||||||
const app = createApp(App)
|
// import '@unocss/reset/normalize.css'
|
||||||
|
// import '@unocss/reset/sanitize/sanitize.css'
|
||||||
|
// import '@unocss/reset/sanitize/assets.css'
|
||||||
|
// import '@unocss/reset/eric-meyer.css'
|
||||||
|
// import '@unocss/reset/tailwind-compat.css'
|
||||||
|
// import '@unocss/reset/tailwind.css'
|
||||||
|
|
||||||
|
import 'primeicons/primeicons.css';
|
||||||
|
import 'virtual:uno.css';
|
||||||
|
|
||||||
|
const app = createApp(App);
|
||||||
|
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);
|
||||||
|
|
||||||
// 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
|
||||||
app.use(autoAnimatePlugin) // v-auto-animate="{ duration: 100 }"
|
|
||||||
|
|
||||||
app.use(plugin, formKitConfig)
|
setupFormKit(app);
|
||||||
app.mount('#app')
|
|
||||||
|
app.mount('#app');
|
||||||
|
@ -1,79 +0,0 @@
|
|||||||
:root {
|
|
||||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
||||||
line-height: 1.5;
|
|
||||||
font-weight: 400;
|
|
||||||
|
|
||||||
color-scheme: light dark;
|
|
||||||
color: rgba(255, 255, 255, 0.87);
|
|
||||||
background-color: #242424;
|
|
||||||
|
|
||||||
font-synthesis: none;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-weight: 500;
|
|
||||||
color: #646cff;
|
|
||||||
text-decoration: inherit;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color: #535bf2;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
min-width: 320px;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 3.2em;
|
|
||||||
line-height: 1.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
padding: 0.6em 1.2em;
|
|
||||||
font-size: 1em;
|
|
||||||
font-weight: 500;
|
|
||||||
font-family: inherit;
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: border-color 0.25s;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
border-color: #646cff;
|
|
||||||
}
|
|
||||||
button:focus,
|
|
||||||
button:focus-visible {
|
|
||||||
outline: 4px auto -webkit-focus-ring-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
padding: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#app {
|
|
||||||
max-width: 1280px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
|
||||||
:root {
|
|
||||||
color: #213547;
|
|
||||||
background-color: #ffffff;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color: #747bff;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
|
||||||
}
|
|
@ -220,7 +220,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<div class="bg-gray-100 p-4 rounded-xl dark:bg-gray-800">
|
<div class="bg-gray-100 p-4 rounded-xl dark:bg-gray-800">
|
||||||
<h2 class="font-bold text-lg mb-4 color:gray-800 dark:text-white">Pro Inputs</h2>
|
<h2 class="font-bold text-lg mb-4 color:gray-800 dark:text-white">Pro Inputs</h2>
|
||||||
<FormKit type="toggle" />
|
<!-- <FormKit type="toggle" /> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</FormKit>
|
</FormKit>
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
export default {
|
export default {
|
||||||
prefix: '',
|
prefix: '',
|
||||||
content: [
|
content: [
|
||||||
"./index.html",
|
// "./index.html",
|
||||||
"./src/**/*.vue",
|
// "./src/**/*.vue",
|
||||||
"./formkit.config.theme.ts",
|
"./formkit.config.theme.ts",
|
||||||
"./formkit.config.ts",
|
"./formkit.config.ts",
|
||||||
],
|
],
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
|
||||||
/* Bundler mode */
|
/* Bundler mode */
|
||||||
"moduleResolution": "Bundler",
|
"moduleResolution": "Bundler",
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
@ -14,13 +14,17 @@
|
|||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
|
"jsxImportSource": "vue",
|
||||||
/* Linting */
|
/* Linting */
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"noUncheckedSideEffectImports": true
|
"noUncheckedSideEffectImports": true,
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"],
|
||||||
|
"__fk-inputs__/*": ["src/__fk-inputs__/*"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
|
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||||
}
|
}
|
||||||
|
16
unocss.config.ts
Normal file
16
unocss.config.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { extractorArbitraryVariants } from "@unocss/extractor-arbitrary-variants";
|
||||||
|
import {
|
||||||
|
defineConfig,
|
||||||
|
presetUno,
|
||||||
|
transformerVariantGroup
|
||||||
|
} from "unocss";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
presets: [
|
||||||
|
presetUno({
|
||||||
|
dark: "media",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
transformers: [transformerVariantGroup()],
|
||||||
|
extractors: [extractorArbitraryVariants()],
|
||||||
|
});
|
@ -1,10 +1,27 @@
|
|||||||
import { defineConfig } from 'vite'
|
import UnoCSS from 'unocss/vite';
|
||||||
import vue from '@vitejs/plugin-vue'
|
import { defineConfig } from 'vite';
|
||||||
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
import Components from 'unplugin-vue-components/vite';
|
||||||
|
import { PrimeVueResolver } from '@primevue/auto-import-resolver';
|
||||||
|
import VueJsx from '@vitejs/plugin-vue-jsx';
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()],
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
VueJsx(),
|
||||||
|
UnoCSS(),
|
||||||
|
Components({
|
||||||
|
resolvers: [PrimeVueResolver()],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'__fk-inputs__': '/src/__fk-inputs__',
|
||||||
|
'@': '/src',
|
||||||
|
},
|
||||||
|
},
|
||||||
build: {
|
build: {
|
||||||
sourcemap: true
|
sourcemap: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
Reference in New Issue
Block a user