2 Commits

Author SHA1 Message Date
49a6823df0 feat: 在 devDependencies 中添加 netlify-cli 依赖
All checks were successful
/ netlify (push) Successful in 50s
/ surge (push) Successful in 40s
/ test (push) Successful in 33s
2024-11-22 14:29:25 +08:00
89da01a51b feat: 更新 GitHub Actions 工作流,简化部署步骤并添加 Netlify 部署支持
Some checks failed
/ netlify (push) Failing after -3s
/ surge (push) Failing after 13s
/ test (push) Failing after -3s
2024-11-22 14:27:59 +08:00
39 changed files with 8232 additions and 3308 deletions

2
.env
View File

@ -1,2 +0,0 @@
VITE_DEBUG_FORMKIT=true
# https://formkit.com/pro

View File

@ -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
View File

@ -22,8 +22,3 @@ dist-ssr
*.njsproj
*.sln
*.sw?
# Local Netlify folder
.netlify
components.d.ts

5
.npmrc
View File

@ -1,4 +1 @@
registry=https://nexus.oo1.dev/repository/npm/
use-node-version=22.14.0
shamefully-hoist=true
shamefully-hoist=true

View File

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

View File

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

View 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
}
})
}

View File

@ -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);
};
});
}

View File

@ -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();
});
}

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
};

View File

@ -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;
};
}
}

View File

@ -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'];
};
}
}

View File

@ -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;
}
>;
};
}
}

View File

@ -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';
};
}
}

View File

@ -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';
};
}
}

View File

@ -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>);
};
}
}

View File

@ -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',
}));

View File

@ -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',
},
}));

View File

@ -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)',
}));

View File

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

View File

@ -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;
} */
}

View File

@ -1,3 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

1
src/assets/vue.svg Normal file
View 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

View File

@ -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
View 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;
}
}

View File

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

View File

@ -2,8 +2,8 @@
export default {
prefix: '',
content: [
// "./index.html",
// "./src/**/*.vue",
"./index.html",
"./src/**/*.vue",
"./formkit.config.theme.ts",
"./formkit.config.ts",
],

View File

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

View File

@ -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()],
});

View File

@ -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
}
})