Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
49a6823df0 | |||
89da01a51b |
12
.github/workflows/ci.yaml
vendored
12
.github/workflows/ci.yaml
vendored
@ -3,7 +3,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- unocss-fk
|
||||
|
||||
jobs:
|
||||
surge:
|
||||
@ -17,3 +16,14 @@ jobs:
|
||||
cp dist/index.html dist/200.html
|
||||
npx surge --project ./dist --domain $DEPLOY_DOMAIN --token d843de16b331c626f10771245c56ed93 # npx surge token
|
||||
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,8 +22,3 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Local Netlify folder
|
||||
.netlify
|
||||
|
||||
components.d.ts
|
5
.npmrc
5
.npmrc
@ -1,4 +1 @@
|
||||
registry=https://nexus.oo1.dev/repository/npm/
|
||||
use-node-version=22.14.0
|
||||
|
||||
shamefully-hoist=true
|
||||
shamefully-hoist=true
|
||||
|
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
@ -3,12 +3,9 @@
|
||||
"source.fixAll": "explicit"
|
||||
},
|
||||
"[vue]": {
|
||||
"editor.defaultFormatter": "Vue.volar"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
"editor.defaultFormatter": "Vue.volar",
|
||||
},
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnSaveMode": "file",
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
}
|
11
README.md
11
README.md
@ -1,30 +1,21 @@
|
||||
# [Vue FormKit Example](https://formkit.com)
|
||||
|
||||
## Knowledge
|
||||
|
||||
- https://youtu.be/v8Sb1mXDWa8
|
||||
- [sections](https://formkit.com/inputs/text#sections)
|
||||
- [sections](https://formkit.com/essentials/inputs#sections)
|
||||
- [useformkitcontext](https://formkit.com/inputs/form#useformkitcontext)
|
||||
- [debouncing](https://formkit.com/essentials/inputs#debouncing)
|
||||
|
||||
延迟 prop 的默认值是 20 毫秒。然而,group 和 list 输入默认使用 0 毫秒,以防止防抖延迟在每个深度级别“累积”。
|
||||
|
||||
## Custom Input
|
||||
|
||||
- https://formkit.com/guides/export-and-restructure-inputs
|
||||
- `npx formkit@latest export`
|
||||
|
||||
## Example
|
||||
|
||||
- https://formkit.com/essentials/examples
|
||||
|
||||
## Theme
|
||||
|
||||
- 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)
|
||||
- [issues: Tailwind prefix support for regenesis theme](https://github.com/formkit/formkit/issues/1157)
|
||||
|
||||
## 3rd Party
|
||||
|
||||
- https://github.com/mathsgod/formkit-element
|
||||
- https://github.com/mathsgod/formkit-element
|
39
formkit.addAsteriskPlugin.ts
Normal file
39
formkit.addAsteriskPlugin.ts
Normal file
@ -0,0 +1,39 @@
|
||||
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
|
||||
}
|
||||
})
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
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);
|
||||
};
|
||||
});
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
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,79 +1,56 @@
|
||||
import { form } from '@/__fk-inputs__/inputs/form';
|
||||
import { PCascadeSelect } from '@/__fk-inputs__/inputs/p-cascade-select';
|
||||
import { PDatePicker } from '@/__fk-inputs__/inputs/p-date-picker';
|
||||
import { PFileUpload } from '@/__fk-inputs__/inputs/p-file-upload';
|
||||
import { PInputPassword } from '@/__fk-inputs__/inputs/p-input-password';
|
||||
import { PInputText } from '@/__fk-inputs__/inputs/p-input-text';
|
||||
import { PSelect } from '@/__fk-inputs__/inputs/p-select';
|
||||
import { createAutoAnimatePlugin, createAutoHeightTextareaPlugin } from '@formkit/addons';
|
||||
import { autoAnimatePlugin } from '@formkit/auto-animate/vue';
|
||||
import type { FormKitOptions, FormKitPlugin } from '@formkit/core';
|
||||
import { register as decodeErrors } from '@formkit/dev';
|
||||
import { createI18nPlugin, zh } from '@formkit/i18n';
|
||||
import { createLibraryPlugin, group, list, submit } from '@formkit/inputs';
|
||||
import * as defaultRules from '@formkit/rules';
|
||||
import { createValidationPlugin } from '@formkit/validation';
|
||||
import { /* defaultConfig, */ bindings, plugin /* defaultConfig */ } from '@formkit/vue';
|
||||
import type { App } from 'vue';
|
||||
import { addAsteriskPlugin } from './formkit.config.plugin.addAsteriskPlugin';
|
||||
import { debugPlugin } from './formkit.config.plugin.debug';
|
||||
import { createAutoAnimatePlugin, createAutoHeightTextareaPlugin } from '@formkit/addons'
|
||||
import type { FormKitOptions } from '@formkit/core'
|
||||
import { createI18nPlugin, zh } from '@formkit/i18n'
|
||||
import { genesisIcons } from '@formkit/icons'
|
||||
import { checkbox, createLibraryPlugin, form, group, list, number, range, submit, text, textarea, } from '@formkit/inputs'
|
||||
import { createProPlugin, toggle } from '@formkit/pro'
|
||||
import * as defaultRules from '@formkit/rules'
|
||||
import { createThemePlugin } from '@formkit/themes'
|
||||
import { createValidationPlugin } from '@formkit/validation'
|
||||
import { /* defaultConfig, */ bindings, createInput } from '@formkit/vue'
|
||||
import { addAsteriskPlugin } from './formkit.addAsteriskPlugin'
|
||||
import { rootClasses } from "./formkit.config.theme"
|
||||
import HeadlessuiToggle from "./src/headlessui-switch.vue";
|
||||
|
||||
|
||||
const library = createLibraryPlugin({
|
||||
text, form, submit, group, checkbox, range, list, number, textarea,
|
||||
'headlessuiSwitch': createInput(HeadlessuiToggle)
|
||||
})
|
||||
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://github.com/formkit/auto-animate/
|
||||
// https://github.com/formkit/formkit/blob/46d64d05c1b37875fc6227853f2bcfa987550c91/packages/addons/src/plugins/autoAnimatePlugin.ts
|
||||
{
|
||||
duration: 250,
|
||||
easing: 'ease-in-out',
|
||||
},
|
||||
{
|
||||
/* optional animation targets object */
|
||||
// default:
|
||||
global: ['outer', 'inner'],
|
||||
form: ['form'],
|
||||
repeater: ['items'],
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
if (import.meta.env.VITE_DEBUG_FORMKIT === 'true') plugins.push(debugPlugin);
|
||||
|
||||
const config: FormKitOptions = {
|
||||
plugins,
|
||||
config: {},
|
||||
};
|
||||
|
||||
export function setupFormKit(app: App) {
|
||||
decodeErrors();
|
||||
app.use(plugin, config);
|
||||
app.use(autoAnimatePlugin); // v-auto-animate="{ duration: 100 }"
|
||||
}
|
||||
createAutoAnimatePlugin(
|
||||
{
|
||||
duration: 250,
|
||||
easing: 'ease-in-out',
|
||||
},
|
||||
// {
|
||||
// /* optional animation targets object */
|
||||
// // default:
|
||||
// global: ['outer', 'inner'],
|
||||
// form: ['form'],
|
||||
// repeater: ['items'],
|
||||
// }
|
||||
)
|
||||
],
|
||||
config: { rootClasses },
|
||||
} satisfies FormKitOptions
|
91
index.html
91
index.html
@ -1,69 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<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"
|
||||
/>
|
||||
|
||||
<script>
|
||||
function setAppHeight() {
|
||||
const app = document.getElementById('app');
|
||||
app.style.minHeight = `${window.innerHeight}px`;
|
||||
}
|
||||
<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"
|
||||
/>
|
||||
<title>Vue FormKit Example</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="/src/assets/main.css"
|
||||
>
|
||||
</head>
|
||||
|
||||
window.addEventListener('resize', setAppHeight);
|
||||
window.addEventListener('load', setAppHeight);
|
||||
</script>
|
||||
<style>
|
||||
#app {
|
||||
min-height: 100vh;
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script
|
||||
type="module"
|
||||
src="/src/main.ts"
|
||||
></script>
|
||||
</body>
|
||||
|
||||
@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>
|
||||
</html>
|
46
package.json
46
package.json
@ -7,42 +7,30 @@
|
||||
"scripts": {
|
||||
"dev": "vite --port 1115",
|
||||
"build": "vue-tsc -b && vite build",
|
||||
"preview": "vite preview",
|
||||
"preview:netlify": "netlify deploy --dir dist"
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@formkit/addons": "^1.6.9",
|
||||
"@formkit/core": "^1.6.9",
|
||||
"@formkit/icons": "^1.6.9",
|
||||
"@formkit/pro": "^0.127.21",
|
||||
"@formkit/themes": "^1.6.9",
|
||||
"@formkit/vue": "1.6.10-fix.202501212134",
|
||||
"@formkit/core": "latest",
|
||||
"@formkit/icons": "latest",
|
||||
"@formkit/pro": "^0.127.7",
|
||||
"@formkit/themes": "latest",
|
||||
"@formkit/vue": "latest",
|
||||
"@formkit/zod": "^1.6.9",
|
||||
"@headlessui/vue": "^1.7.23",
|
||||
"@primevue/forms": "^4.2.5",
|
||||
"@primevue/themes": "^4.2.5",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"axios": "^1.7.9",
|
||||
"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",
|
||||
"autoprefixer": "latest",
|
||||
"i18next": "^23.16.6",
|
||||
"postcss": "latest",
|
||||
"sweetalert2": "^11.14.5",
|
||||
"tailwindcss": "latest",
|
||||
"vue": "^3.5.13",
|
||||
"zod-i18n-map": "^2.27.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@primevue/auto-import-resolver": "^4.2.5",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
||||
"typescript": "~5.7.3",
|
||||
"unocss": "^65.5.0",
|
||||
"unplugin-vue-components": "^28.2.0",
|
||||
"vite": "^6.1.0",
|
||||
"vue-tsc": "^2.2.2"
|
||||
"@vitejs/plugin-vue": "^5.2.0",
|
||||
"netlify-cli": "^17.37.2",
|
||||
"typescript": "~5.6.3",
|
||||
"vite": "^5.4.11",
|
||||
"vue-tsc": "^2.1.10"
|
||||
}
|
||||
}
|
||||
}
|
9609
pnpm-lock.yaml
generated
9609
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
109
src/App.vue
109
src/App.vue
@ -1,110 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from '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' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
import TutorialForm from './tutorial-form/index.vue'
|
||||
import ZodForm from './zod-form/index.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-wrapper p-4">
|
||||
<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>
|
||||
<ZodForm />
|
||||
<TutorialForm />
|
||||
</template>
|
||||
|
@ -1,77 +0,0 @@
|
||||
<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>
|
@ -1,237 +0,0 @@
|
||||
<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>
|
@ -1,28 +0,0 @@
|
||||
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;
|
@ -1,31 +0,0 @@
|
||||
<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>
|
@ -1,50 +0,0 @@
|
||||
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)
|
||||
};
|
@ -1,115 +0,0 @@
|
||||
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;
|
||||
};
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
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'];
|
||||
};
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
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;
|
||||
}
|
||||
>;
|
||||
};
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
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';
|
||||
};
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
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';
|
||||
};
|
||||
}
|
||||
}
|
@ -1,139 +0,0 @@
|
||||
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>);
|
||||
};
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
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',
|
||||
}));
|
@ -1,14 +0,0 @@
|
||||
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',
|
||||
},
|
||||
}));
|
@ -1,9 +0,0 @@
|
||||
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)',
|
||||
}));
|
@ -1,253 +0,0 @@
|
||||
<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,15 +1,20 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
body {
|
||||
background-color: #f3f4f6;
|
||||
color: #1f2937;
|
||||
background-color:#f3f4f6;
|
||||
color:#1f2937;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #1a202c;
|
||||
color: #e5e7eb;
|
||||
background-color:#1a202c;
|
||||
color:#e5e7eb;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.attributes-group {
|
||||
padding: 1.5rem;
|
||||
border: 1px solid #e5e7eb;
|
||||
@ -23,10 +28,4 @@ body {
|
||||
border-color: #374151;
|
||||
background-color: #1f2937;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* DEV: */
|
||||
/* .formkit-outer {
|
||||
border: 1px solid #e5e7eb;
|
||||
} */
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
1
src/assets/vue.svg
Normal file
1
src/assets/vue.svg
Normal file
@ -0,0 +1 @@
|
||||
<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>
|
After Width: | Height: | Size: 496 B |
45
src/main.ts
45
src/main.ts
@ -1,40 +1,13 @@
|
||||
import Aura from '@primevue/themes/aura';
|
||||
import zhCN from 'primelocale/zh-CN.json';
|
||||
import PrimeVue from 'primevue/config';
|
||||
import ToastService from 'primevue/toastservice';
|
||||
import { createApp } from 'vue';
|
||||
import { setupFormKit } from '../formkit.config';
|
||||
import App from './App.vue';
|
||||
import { autoAnimatePlugin } from '@formkit/auto-animate/vue'
|
||||
import { plugin, /* defaultConfig */ } from '@formkit/vue'
|
||||
import { createApp } from 'vue'
|
||||
import formKitConfig from '../formkit.config'
|
||||
import App from './App.vue'
|
||||
|
||||
// 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);
|
||||
const app = createApp(App)
|
||||
|
||||
// https://github.dev/formkit/auto-animate/blob/master/docs/src/examples/formkit/ActualFormKit.vue
|
||||
app.use(autoAnimatePlugin) // v-auto-animate="{ duration: 100 }"
|
||||
|
||||
setupFormKit(app);
|
||||
|
||||
app.mount('#app');
|
||||
app.use(plugin, formKitConfig)
|
||||
app.mount('#app')
|
||||
|
79
src/style.css
Normal file
79
src/style.css
Normal file
@ -0,0 +1,79 @@
|
||||
: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">
|
||||
<h2 class="font-bold text-lg mb-4 color:gray-800 dark:text-white">Pro Inputs</h2>
|
||||
<!-- <FormKit type="toggle" /> -->
|
||||
<FormKit type="toggle" />
|
||||
</div>
|
||||
|
||||
</FormKit>
|
||||
|
@ -2,8 +2,8 @@
|
||||
export default {
|
||||
prefix: '',
|
||||
content: [
|
||||
// "./index.html",
|
||||
// "./src/**/*.vue",
|
||||
"./index.html",
|
||||
"./src/**/*.vue",
|
||||
"./formkit.config.theme.ts",
|
||||
"./formkit.config.ts",
|
||||
],
|
||||
|
@ -1,12 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "Bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
@ -14,17 +14,13 @@
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "preserve",
|
||||
"jsxImportSource": "vue",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true,
|
||||
"paths": {
|
||||
"@/*": ["src/*"],
|
||||
"__fk-inputs__/*": ["src/__fk-inputs__/*"]
|
||||
}
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||
}
|
||||
|
@ -1,16 +0,0 @@
|
||||
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,27 +1,10 @@
|
||||
import UnoCSS from 'unocss/vite';
|
||||
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';
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
VueJsx(),
|
||||
UnoCSS(),
|
||||
Components({
|
||||
resolvers: [PrimeVueResolver()],
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'__fk-inputs__': '/src/__fk-inputs__',
|
||||
'@': '/src',
|
||||
},
|
||||
},
|
||||
plugins: [vue()],
|
||||
build: {
|
||||
sourcemap: true,
|
||||
},
|
||||
});
|
||||
sourcemap: true
|
||||
}
|
||||
})
|
||||
|
Reference in New Issue
Block a user