config_tools: Add vue-json-schema-form and advanced custom component for IVSHMEM field
1. add Vue devtools support
2. update project dependencies
3. refactor configurator source code tree for private library hook
4. fix build issue
5. dynamic load scenario JSON schema(fix cache issue)
6. add vjsf 1.12.2 (latest) for private package dependencies
7. remove vjsf unnecessary files
8. use private vjsf as configurator dependencies
9. Add custom IVSHMEM_REGION widget
10. add a script to populate default values
11. get default values before export scenario xml
12. specify widgets in XML schema
13. add missing vjsf license file
14. populate default values to empty nodes
15. when user clicks save button, update formData with each field default value
16. fix when the user clicks the save button will collapse configFom
17. add success message for saving scenario XML
vue-json-schema-form 1.12.2 (latest)link: b30ea7c2d6/packages/lib
Tracked-On: #6691
Signed-off-by: Weiyi Feng <weiyix.feng@intel.com>
@ -4,6 +4,6 @@ python scenario_config/jsonschema/converter.py
|
||||
xmllint --xinclude schema/datachecks.xsd > schema/allchecks.xsd
|
||||
python -m build
|
||||
rem pip install .\dist\acrn_config_tools-3.0-py3-none-any.whl --force-reinstall
|
||||
del .\configurator\thirdLib\acrn_config_tools-3.0-py3-none-any.whl
|
||||
python .\configurator\thirdLib\manager.py install
|
||||
del .\configurator\packages\configurator\thirdLib\acrn_config_tools-3.0-py3-none-any.whl
|
||||
python .\configurator\packages\configurator\thirdLib\manager.py install
|
||||
echo build and install success
|
||||
|
@ -86,7 +86,7 @@ xmllint --xinclude schema/datachecks.xsd > schema/allchecks.xsd
|
||||
python -m build
|
||||
|
||||
cd configurator
|
||||
python thirdLib/manager.py install
|
||||
python packages/configurator/thirdLib/manager.py install
|
||||
yarn build
|
||||
```
|
||||
|
||||
|
@ -0,0 +1,3 @@
|
||||
# Do Not Delete
|
||||
# This file be used in configurator's wasm python env
|
||||
# See: https://stackoverflow.com/questions/42791179/why-does-pip-install-not-include-my-package-data-files
|
@ -1,5 +1,4 @@
|
||||
{
|
||||
"name": "acrn-configurator",
|
||||
"private": true,
|
||||
"version": "0.3.0",
|
||||
"author": {
|
||||
@ -8,34 +7,13 @@
|
||||
"url": "https://github.com/Weiyi-Feng"
|
||||
},
|
||||
"description": "ACRN Configurator",
|
||||
"workspaces": [
|
||||
"packages/configurator",
|
||||
"packages/vue-json-schema-form/**"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"tauri": "tauri"
|
||||
"dev": "yarn workspace acrn-configurator dev",
|
||||
"build": "yarn workspace acrn-configurator build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lljj/vue3-form-naive": "^1.12.2",
|
||||
"@popperjs/core": "^2.11.5",
|
||||
"@rollup/plugin-replace": "^4.0.0",
|
||||
"@tauri-apps/api": "^1.0.0-rc.3",
|
||||
"@vicons/fa": "^0.12.0",
|
||||
"@vicons/utils": "^0.1.4",
|
||||
"ajv-i18n": "^4.2.0",
|
||||
"bootstrap": "^5.1.3",
|
||||
"bootstrap-vue-3": "^0.1.10",
|
||||
"js-base64": "^3.7.2",
|
||||
"lodash": "^4.17.21",
|
||||
"naive-ui": "^2.28.1",
|
||||
"node-fetch": "2",
|
||||
"sass": "^1.50.0",
|
||||
"vfonts": "^0.0.3",
|
||||
"vue": "^3.2.25",
|
||||
"vue-router": "4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^1.0.0-rc.8",
|
||||
"@vitejs/plugin-vue": "^2.3.1",
|
||||
"vite": "^2.9.2"
|
||||
}
|
||||
}
|
||||
"dependencies": {}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "acrn-configurator",
|
||||
"private": true,
|
||||
"version": "0.3.0",
|
||||
"author": {
|
||||
"name": "Feng, Weiyi",
|
||||
"email": "weiyix.feng@intel.com",
|
||||
"url": "https://github.com/Weiyi-Feng"
|
||||
},
|
||||
"description": "ACRN Configurator",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lljj/vue3-form-naive": "^1.12.2",
|
||||
"@popperjs/core": "^2.11.5",
|
||||
"@rollup/plugin-replace": "^4.0.0",
|
||||
"@tauri-apps/api": "^1.0.0-rc.4",
|
||||
"@vicons/fa": "^0.12.0",
|
||||
"@vicons/utils": "^0.1.4",
|
||||
"ajv-i18n": "^4.2.0",
|
||||
"bootstrap": "^5.1.3",
|
||||
"bootstrap-vue-3": "^0.1.10",
|
||||
"js-base64": "^3.7.2",
|
||||
"lodash": "^4.17.21",
|
||||
"naive-ui": "^2.28.1",
|
||||
"node-fetch": "2",
|
||||
"sass": "^1.50.0",
|
||||
"vfonts": "^0.0.3",
|
||||
"vue": "^3.2.25",
|
||||
"vue-router": "4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^1.0.0-rc.9",
|
||||
"@types/node": "^16.11.33",
|
||||
"@vitejs/plugin-vue": "^2.3.1",
|
||||
"vite": "^2.9.2"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
@ -185,9 +185,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "attohttpc"
|
||||
version = "0.18.0"
|
||||
version = "0.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e69e13a99a7e6e070bb114f7ff381e58c7ccc188630121fc4c2fe4bcf24cd072"
|
||||
checksum = "262c3f7f5d61249d8c00e5546e2685cd15ebeeb1bc0f3cc5449350a1cb07319e"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"http",
|
||||
@ -351,7 +351,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74f89d248799e3f15f91b70917f65381062a01bb8e222700ea0e5a7ff9785f9c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"uuid",
|
||||
"uuid 0.8.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -387,12 +387,6 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||
|
||||
[[package]]
|
||||
name = "cocoa"
|
||||
version = "0.24.0"
|
||||
@ -1572,9 +1566,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.16"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
@ -2605,18 +2599,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.136"
|
||||
version = "1.0.137"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
|
||||
checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.136"
|
||||
version = "1.0.137"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
|
||||
checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -2625,9 +2619,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.79"
|
||||
version = "1.0.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
|
||||
checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
|
||||
dependencies = [
|
||||
"itoa 1.0.1",
|
||||
"ryu",
|
||||
@ -2954,9 +2948,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tao"
|
||||
version = "0.7.0"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b6a3359088d4c4735a13f933202f4ecd91f5991b41a8eb757f2449c044ce925"
|
||||
checksum = "3765f329d831aa461cd3f0f94b065a9fe37560fd7f8099d5bcf3e95c923071f0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cairo-rs",
|
||||
@ -3018,16 +3012,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "1.0.0-rc.6"
|
||||
version = "1.0.0-rc.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6d514a34b3f9a07e2002d95e1371b42a446636e3d571a59e974b21d6acf3007"
|
||||
checksum = "537978045ca229b9c1bb51ea85bc807b9d109a119721134fc5da24f94fd3074a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"attohttpc",
|
||||
"bincode",
|
||||
"cfg_aliases",
|
||||
"dirs-next",
|
||||
"either",
|
||||
"embed_plist",
|
||||
"flate2",
|
||||
"futures",
|
||||
@ -3035,9 +3027,9 @@ dependencies = [
|
||||
"glib",
|
||||
"glob",
|
||||
"gtk",
|
||||
"heck 0.4.0",
|
||||
"http",
|
||||
"ignore",
|
||||
"memchr",
|
||||
"notify-rust",
|
||||
"once_cell",
|
||||
"open",
|
||||
@ -3064,15 +3056,15 @@ dependencies = [
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"url",
|
||||
"uuid",
|
||||
"uuid 1.0.0",
|
||||
"windows 0.30.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-build"
|
||||
version = "1.0.0-rc.5"
|
||||
version = "1.0.0-rc.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ede6462a4692e2fd5030497ad576264dc90eea5fa337182492e77291d45fc78b"
|
||||
checksum = "7e6448e80778032b4f9dd86b5efc8214d5bfc81a11efa502bb5211b05d422b14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cargo_toml",
|
||||
@ -3083,9 +3075,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-codegen"
|
||||
version = "1.0.0-rc.4"
|
||||
version = "1.0.0-rc.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54193ebdb010e85824301ce5f0940742b680d66376203f6425d549d2f32ad499"
|
||||
checksum = "e4c2e553c2ceaf30f1feabc76abebbd5f9eddb99b643de0078e38037e43e3c2f"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"brotli",
|
||||
@ -3099,15 +3091,15 @@ dependencies = [
|
||||
"sha2",
|
||||
"tauri-utils",
|
||||
"thiserror",
|
||||
"uuid",
|
||||
"uuid 1.0.0",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-macros"
|
||||
version = "1.0.0-rc.4"
|
||||
version = "1.0.0-rc.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8b867ef4703cb8e50f128ee3c941895d94c01e0ebd9007a7b45ecca52516dbf"
|
||||
checksum = "d3e8af1367b1e1224edfa4117c88fe19717970fabfbc2555e957e077f0469248"
|
||||
dependencies = [
|
||||
"heck 0.4.0",
|
||||
"proc-macro2",
|
||||
@ -3119,9 +3111,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "0.3.4"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b289ac8eafc52a36425fcaf3de23febd0b2606d3cce2b39ac412a1817fae537"
|
||||
checksum = "27653d24a0d7e2c8e04838e975acbf7a5628746d8d60a916d33a9ccf8a06c4ea"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"http",
|
||||
@ -3131,22 +3123,22 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri-utils",
|
||||
"thiserror",
|
||||
"uuid",
|
||||
"uuid 1.0.0",
|
||||
"webview2-com",
|
||||
"windows 0.30.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime-wry"
|
||||
version = "0.3.5"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8bf16e0476a8249aa2c75e7b49ec4c059be5fb27d9f6514e30ed327e8e9fa2"
|
||||
checksum = "53d9b0922c27ea8a1430a2bbf666fe7789645dffb9d317f8d2dfca1a5dff2271"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"rand 0.8.5",
|
||||
"tauri-runtime",
|
||||
"tauri-utils",
|
||||
"uuid",
|
||||
"uuid 1.0.0",
|
||||
"webview2-com",
|
||||
"windows 0.30.0",
|
||||
"wry",
|
||||
@ -3154,9 +3146,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-utils"
|
||||
version = "1.0.0-rc.4"
|
||||
version = "1.0.0-rc.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a67fcf8fdd1340de4e75c01966fceab03057a8b0e97864eb39a21e420deed503"
|
||||
checksum = "a485f9fc0f381d3da0818c4260b3a04be86dc1844a12edaff68afb07bc55d735"
|
||||
dependencies = [
|
||||
"brotli",
|
||||
"ctor",
|
||||
@ -3165,13 +3157,13 @@ dependencies = [
|
||||
"html5ever",
|
||||
"json-patch",
|
||||
"kuchiki",
|
||||
"memchr",
|
||||
"phf 0.10.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"serialize-to-javascript",
|
||||
"thiserror",
|
||||
"url",
|
||||
"walkdir",
|
||||
@ -3417,6 +3409,12 @@ name = "uuid"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cfcd319456c4d6ea10087ed423473267e1a071f3bc0aa89f80d60997843c6f0"
|
||||
dependencies = [
|
||||
"getrandom 0.2.6",
|
||||
]
|
||||
@ -3916,9 +3914,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wry"
|
||||
version = "0.14.0"
|
||||
version = "0.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fd09ffc86ecea0a0d5f50cc8e4a8121a1bfc0b0825a160f86ac39e86979344c"
|
||||
checksum = "20b69cff9f50bab10b42e51bac9c2cf695484059f1b19e911754477ae703ef42"
|
||||
dependencies = [
|
||||
"block",
|
||||
"cocoa",
|
@ -12,16 +12,16 @@ rust-version = "1.57"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1.0.0-rc.5", features = [] }
|
||||
tauri-build = { version = "1.0.0-rc.7", features = [] }
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "1.0.0-rc.6", features = ["api-all", "devtools"] }
|
||||
log = "0.4"
|
||||
glob = "0.3"
|
||||
dirs = "4.0"
|
||||
itertools = "0.10"
|
||||
serde_json = "1.0.81"
|
||||
serde = { version = "1.0.137", features = ["derive"] }
|
||||
tauri = { version = "1.0.0-rc.8", features = ["api-all", "devtools"] }
|
||||
log = "0.4.17"
|
||||
glob = "0.3.0"
|
||||
dirs = "4.0.0"
|
||||
itertools = "0.10.3"
|
||||
|
||||
[features]
|
||||
# by default Tauri runs in production mode
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 974 B After Width: | Height: | Size: 974 B |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 903 B After Width: | Height: | Size: 903 B |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
@ -1 +1 @@
|
||||
{"package":{"productName":"acrn-configurator","version":"../package.json"},"build":{"distDir":"../build","devPath":"http://localhost:3000","beforeDevCommand":"","beforeBuildCommand":""},"tauri":{"bundle":{"active":true,"targets":"all","identifier":"com.projectacrn.configurator","icon":["icons/32x32.png","icons/128x128.png","icons/128x128@2x.png","icons/icon.icns","icons/icon.ico"],"resources":[],"externalBin":[],"copyright":"","category":"DeveloperTool","shortDescription":"","longDescription":"","deb":{"depends":[],"useBootstrapper":false},"macOS":{"frameworks":[],"useBootstrapper":false,"exceptionDomain":"","signingIdentity":null,"providerShortName":null,"entitlements":null},"windows":{"certificateThumbprint":null,"digestAlgorithm":"sha256","timestampUrl":""}},"updater":{"active":false},"allowlist":{"all":true},"windows":[{"title":"ACRN Configurator","width":1366,"height":768,"resizable":true,"fullscreen":false,"decorations":false}],"security":{"csp":null}}}
|
||||
{"package":{"productName":"acrn-configurator","version":"../package.json"},"build":{"distDir":"../build","devPath":"http://localhost:3000","beforeDevCommand":"","beforeBuildCommand":""},"tauri":{"bundle":{"active":true,"targets":"all","identifier":"com.projectacrn.configurator","icon":["icons/32x32.png","icons/128x128.png","icons/128x128@2x.png","icons/icon.icns","icons/icon.ico"],"resources":[],"externalBin":[],"copyright":"","category":"DeveloperTool","shortDescription":"","longDescription":"","deb":{"depends":[]},"macOS":{"frameworks":[],"exceptionDomain":"","signingIdentity":null,"providerShortName":null,"entitlements":null},"windows":{"certificateThumbprint":null,"digestAlgorithm":"sha256","timestampUrl":""}},"updater":{"active":false},"allowlist":{"all":true},"windows":[{"title":"ACRN Configurator","width":1366,"height":768,"resizable":true,"fullscreen":false,"decorations":false}],"security":{"csp":null}}}
|
@ -28,12 +28,10 @@
|
||||
"shortDescription": "",
|
||||
"longDescription": "",
|
||||
"deb": {
|
||||
"depends": [],
|
||||
"useBootstrapper": false
|
||||
"depends": []
|
||||
},
|
||||
"macOS": {
|
||||
"frameworks": [],
|
||||
"useBootstrapper": false,
|
||||
"exceptionDomain": "",
|
||||
"signingIdentity": null,
|
||||
"providerShortName": null,
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 299 B After Width: | Height: | Size: 299 B |
Before Width: | Height: | Size: 404 B After Width: | Height: | Size: 404 B |
Before Width: | Height: | Size: 399 B After Width: | Height: | Size: 399 B |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
@ -16,7 +16,12 @@
|
||||
"enum": [
|
||||
"y",
|
||||
"n"
|
||||
]
|
||||
],
|
||||
"ui:widget": "b-form-checkbox",
|
||||
"ui:options": {
|
||||
"value": "y",
|
||||
"unchecked-value": "n"
|
||||
}
|
||||
},
|
||||
"EnablementType": {
|
||||
"type": "string",
|
||||
@ -599,6 +604,7 @@
|
||||
"properties": {
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"default": 256,
|
||||
"title": "Memory size (MB)",
|
||||
"description": "<div class=\"document\">\n <p>\n Specify the physical memory size allocated to this VM in megabytes.\n </p>\n</div>\n"
|
||||
@ -739,18 +745,18 @@
|
||||
"properties": {
|
||||
"pci_dev": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"enum": {
|
||||
"type": "dynamicEnum",
|
||||
"function": "get_enum",
|
||||
"source": "board_xml",
|
||||
"selector": "//device[class]/@description",
|
||||
"sorted": "lambda s: (s.split(' ', maxsplit=1)[-1].split(':')[0], s.split(' ')[0])"
|
||||
}
|
||||
},
|
||||
"type": "array",
|
||||
"title": "PCI device assignment",
|
||||
"description": "<div class=\"document\">\n <p>\n Select the PCI devices you want to assign to this virtual machine.\n </p>\n</div>\n",
|
||||
"enum": {
|
||||
"type": "dynamicEnum",
|
||||
"function": "get_enum",
|
||||
"source": "board_xml",
|
||||
"selector": "//device[class]/@description",
|
||||
"sorted": "lambda s: (s.split(' ', maxsplit=1)[-1].split(':')[0], s.split(' ')[0])"
|
||||
}
|
||||
"description": "<div class=\"document\">\n <p>\n Select the PCI devices you want to assign to this virtual machine.\n </p>\n</div>\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1050,6 +1056,8 @@
|
||||
},
|
||||
"MAX_PT_IRQ_ENTRIES": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 1024,
|
||||
"default": 256,
|
||||
"title": "Max passthrough IRQ entries",
|
||||
"description": "<div class=\"document\">\n <p>\n Specify the maximum number of interrupt request (IRQ) entries from all passthrough devices.\n </p>\n</div>\n"
|
||||
@ -1339,7 +1347,7 @@
|
||||
"gpu": {
|
||||
"type": "string",
|
||||
"title": "Virtio GPU device",
|
||||
"description": "<div class=\"document\">\n <dl class=\"simple\">\n <dt>\n The virtio GPU device presents a GPU device to the VM.\n </dt>\n <dd>\n <p>\n This feature enables you to view the VM's GPU output in the Service VM.\n </p>\n </dd>\n </dl>\n</div>\n"
|
||||
"description": "<div class=\"document\">\n <p>\n The virtio GPU device presents a GPU device to the VM.\nThis feature enables you to view the VM's GPU output in the Service VM.\n </p>\n</div>\n"
|
||||
}
|
||||
},
|
||||
"title": "Virt-IO devices",
|
||||
@ -1630,6 +1638,7 @@
|
||||
"properties": {
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"default": 256,
|
||||
"title": "Memory size (MB)",
|
||||
"description": "<div class=\"document\">\n <p>\n Specify the physical memory size allocated to this VM in megabytes.\n </p>\n</div>\n"
|
||||
@ -1811,7 +1820,7 @@
|
||||
"gpu": {
|
||||
"type": "string",
|
||||
"title": "Virtio GPU device",
|
||||
"description": "<div class=\"document\">\n <dl class=\"simple\">\n <dt>\n The virtio GPU device presents a GPU device to the VM.\n </dt>\n <dd>\n <p>\n This feature enables you to view the VM's GPU output in the Service VM.\n </p>\n </dd>\n </dl>\n</div>\n"
|
||||
"description": "<div class=\"document\">\n <p>\n The virtio GPU device presents a GPU device to the VM.\nThis feature enables you to view the VM's GPU output in the Service VM.\n </p>\n</div>\n"
|
||||
}
|
||||
},
|
||||
"title": "Virt-IO devices",
|
||||
@ -1824,6 +1833,7 @@
|
||||
"properties": {
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"default": 256,
|
||||
"title": "Memory size (MB)",
|
||||
"description": "<div class=\"document\">\n <p>\n Specify the physical memory size allocated to this VM in megabytes.\n </p>\n</div>\n"
|
||||
@ -1975,7 +1985,7 @@
|
||||
"gpu": {
|
||||
"type": "string",
|
||||
"title": "Virtio GPU device",
|
||||
"description": "<div class=\"document\">\n <dl class=\"simple\">\n <dt>\n The virtio GPU device presents a GPU device to the VM.\n </dt>\n <dd>\n <p>\n This feature enables you to view the VM's GPU output in the Service VM.\n </p>\n </dd>\n </dl>\n</div>\n"
|
||||
"description": "<div class=\"document\">\n <p>\n The virtio GPU device presents a GPU device to the VM.\nThis feature enables you to view the VM's GPU output in the Service VM.\n </p>\n</div>\n"
|
||||
}
|
||||
},
|
||||
"title": "Virt-IO devices",
|
||||
@ -2181,7 +2191,7 @@
|
||||
"gpu": {
|
||||
"type": "string",
|
||||
"title": "Virtio GPU device",
|
||||
"description": "<div class=\"document\">\n <dl class=\"simple\">\n <dt>\n The virtio GPU device presents a GPU device to the VM.\n </dt>\n <dd>\n <p>\n This feature enables you to view the VM's GPU output in the Service VM.\n </p>\n </dd>\n </dl>\n</div>\n"
|
||||
"description": "<div class=\"document\">\n <p>\n The virtio GPU device presents a GPU device to the VM.\nThis feature enables you to view the VM's GPU output in the Service VM.\n </p>\n</div>\n"
|
||||
}
|
||||
},
|
||||
"title": "Virt-IO devices",
|
||||
@ -2501,6 +2511,8 @@
|
||||
},
|
||||
"MAX_PT_IRQ_ENTRIES": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 1024,
|
||||
"default": 256,
|
||||
"title": "Max passthrough IRQ entries",
|
||||
"description": "<div class=\"document\">\n <p>\n Specify the maximum number of interrupt request (IRQ) entries from all passthrough devices.\n </p>\n</div>\n"
|
@ -1,6 +1,5 @@
|
||||
import {dialog, invoke} from "@tauri-apps/api";
|
||||
import JSON2XML from "./json2xml"
|
||||
import scenarioSchema from "../assets/schema/scenario.json";
|
||||
import {OpenDialogOptions} from "@tauri-apps/api/dialog";
|
||||
|
||||
enum HistoryType {
|
||||
@ -34,6 +33,10 @@ class PythonObject {
|
||||
generateLaunchScript(boardXMLText, scenarioXMLText) {
|
||||
return this.api('generateLaunchScript', boardXMLText, scenarioXMLText)
|
||||
}
|
||||
|
||||
populateDefaultValues(scenarioXMLText) {
|
||||
return this.api('populateDefaultValues', scenarioXMLText)
|
||||
}
|
||||
}
|
||||
|
||||
class Configurator {
|
||||
@ -93,13 +96,8 @@ class Configurator {
|
||||
loadBoard(path: String) {
|
||||
return this.readFile(path)
|
||||
.then((fileContent) => {
|
||||
let params = JSON.stringify({
|
||||
boardXML: fileContent,
|
||||
scenarioSchema: scenarioSchema
|
||||
})
|
||||
return this.pythonObject.loadBoard(fileContent)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
loadScenario(path: String): Object {
|
@ -0,0 +1,59 @@
|
||||
const isTauri = !!window.__TAURI_IPC__;
|
||||
|
||||
if (isTauri) {
|
||||
let openCount = 0
|
||||
|
||||
function openDevTools() {
|
||||
openCount++;
|
||||
console.log(`openCount ${openCount} of 5`)
|
||||
if (openCount >= 5) {
|
||||
invoke('open_devtools', {})
|
||||
}
|
||||
}
|
||||
|
||||
window.openDevTools = openDevTools;
|
||||
} else {
|
||||
(async () => {
|
||||
// Patch Browser function to mock Tauri env
|
||||
let mockJS = await import('../tests/mock');
|
||||
mockJS.default();
|
||||
})()
|
||||
}
|
||||
|
||||
import {createApp} from 'vue';
|
||||
import App from './App.vue';
|
||||
|
||||
import router from "./router";
|
||||
import {invoke} from "@tauri-apps/api/tauri";
|
||||
import BootstrapVue3 from 'bootstrap-vue-3'
|
||||
import naive from 'naive-ui';
|
||||
|
||||
const app = createApp(App);
|
||||
app.use(BootstrapVue3);
|
||||
app.use(naive);
|
||||
app.use(router);
|
||||
app.config.unwrapInjectedRef = true
|
||||
|
||||
|
||||
async function main() {
|
||||
console.log("Pyodide Load Begin")
|
||||
let t1 = Date.now();
|
||||
let WASMPythonLoader = await import('./pyodide');
|
||||
await WASMPythonLoader.default()
|
||||
let t2 = Date.now();
|
||||
console.log("Pyodide Load Time: " + (t2 - t1) + "ms")
|
||||
|
||||
async function setWindowSystemInfo() {
|
||||
let homeDir = await invoke("get_home");
|
||||
let pathSplit = homeDir.indexOf("\\") > 0 ? "\\" : "/";
|
||||
window.systemInfo = {
|
||||
homeDir, pathSplit
|
||||
}
|
||||
}
|
||||
|
||||
await setWindowSystemInfo();
|
||||
|
||||
app.mount('#app')
|
||||
}
|
||||
|
||||
main();
|
@ -32,15 +32,18 @@
|
||||
</template>
|
||||
<Scenario :scenario="scenario" @scenarioUpdate="scenarioUpdate"/>
|
||||
</b-accordion-item>
|
||||
<Banner/>
|
||||
<Banner>
|
||||
<div style="position: relative">
|
||||
<button type="button" class="btn btn-primary btn-lg SaveButton" @click="saveScenario">
|
||||
Save Scenario and Launch Scripts
|
||||
</button>
|
||||
</div>
|
||||
</Banner>
|
||||
|
||||
<b-accordion-item visible>
|
||||
<template #title>
|
||||
<div class="p-1 ps-3 d-flex w-100 justify-content-between align-items-center">
|
||||
<div class="fs-4">3. Configure settings for scenario and launch scripts</div>
|
||||
<button type="button" class="btn btn-primary btn-lg" @click="saveScenario">
|
||||
Save Scenario and Launch Scripts
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -80,7 +83,7 @@ import Banner from '../components/common/Banner.vue';
|
||||
import Board from "./Config/Board.vue";
|
||||
import Scenario from "./Config/Scenario.vue";
|
||||
import TabBox from "./Config/ConfigForm/TabBox.vue";
|
||||
import ConfigForm from "./Config/ConfigForm/ConfigForm.vue";
|
||||
import ConfigForm from "./Config/ConfigForm.vue";
|
||||
|
||||
import configurator from "../lib/acrn";
|
||||
|
||||
@ -90,6 +93,7 @@ export default {
|
||||
props: ['WorkingFolder'],
|
||||
mounted() {
|
||||
this.updateCurrentFormSchema()
|
||||
window.getCurrentScenarioData = this.getCurrentScenarioData
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -119,7 +123,6 @@ export default {
|
||||
this.updateCurrentFormSchema()
|
||||
},
|
||||
updateCurrentFormSchema() {
|
||||
console.log(this.schemas)
|
||||
if (this.activeVMID === -1) {
|
||||
this.currentFormSchema = this.schemas.HV
|
||||
} else {
|
||||
@ -140,8 +143,10 @@ export default {
|
||||
this.updateCurrentFormSchema()
|
||||
this.updateCurrentFormData()
|
||||
},
|
||||
getCurrentScenarioData() {
|
||||
return this.scenario
|
||||
},
|
||||
updateCurrentFormData() {
|
||||
console.log(this.scenario)
|
||||
if (this.activeVMID === -1) {
|
||||
this.currentFormData = this.scenario.hv;
|
||||
}
|
||||
@ -249,6 +254,17 @@ export default {
|
||||
"acrn-config": JSON.parse(JSON.stringify(this.scenario))
|
||||
}
|
||||
);
|
||||
console.log(scenarioXMLData)
|
||||
// get scenario Defaults
|
||||
let scenarioWithDefault = configurator.pythonObject.populateDefaultValues(scenarioXMLData)
|
||||
console.log(scenarioWithDefault)
|
||||
// write defaults to frontend
|
||||
this.scenario = scenarioWithDefault.json['acrn-config']
|
||||
this.updateCurrentFormData()
|
||||
// get scenario XML with defaults
|
||||
scenarioXMLData = scenarioWithDefault.xml
|
||||
debugger
|
||||
// begin write down and verify
|
||||
configurator.writeFile(this.WorkingFolder + 'scenario.xml', scenarioXMLData)
|
||||
.then(() => {
|
||||
step = 1
|
||||
@ -279,5 +295,11 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.SaveButton {
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
top: 64px;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
</style>
|
@ -77,7 +77,7 @@ export default {
|
||||
default: {}
|
||||
}
|
||||
},
|
||||
emits:['boardUpdate'],
|
||||
emits: ['boardUpdate'],
|
||||
data() {
|
||||
return {
|
||||
boardHistory: [],
|
||||
@ -88,7 +88,10 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.getBoardHistory()
|
||||
//Todo: auto load board
|
||||
.then(() => {
|
||||
this.importBoard()
|
||||
})
|
||||
// Todo: auto load board
|
||||
},
|
||||
computed: {
|
||||
imported() {
|
||||
@ -123,7 +126,7 @@ export default {
|
||||
if (filepath.length > 0) {
|
||||
configurator.loadBoard(filepath)
|
||||
.then(({scenarioJSONSchema, boardInfo}) => {
|
||||
this.$emit('boardUpdate', boardInfo,scenarioJSONSchema);
|
||||
this.$emit('boardUpdate', boardInfo, scenarioJSONSchema);
|
||||
let boardFileNewPath = this.WorkingFolder + boardInfo.name;
|
||||
// Todo: use rust command writeBoard to fix bugs.
|
||||
configurator.writeFile(boardFileNewPath, boardInfo.content)
|
@ -56,7 +56,7 @@ import VueForm, {i18n} from "@lljj/vue3-form-naive"
|
||||
import {Icon} from "@vicons/utils";
|
||||
import {Minus} from "@vicons/fa"
|
||||
import localizeEn from 'ajv-i18n/localize/en';
|
||||
|
||||
import IVSHMEM_REGION from "./ConfigForm/CustomWidget/IVSHMEM_REGION.vue";
|
||||
|
||||
i18n.useLocal(localizeEn);
|
||||
export default {
|
||||
@ -77,7 +77,28 @@ export default {
|
||||
"labelWidth": "300px",
|
||||
"labelSuffix": ":"
|
||||
},
|
||||
uiSchema: {}
|
||||
uiSchema: {
|
||||
"FEATURES": {
|
||||
"IVSHMEM": {
|
||||
"ui:title": "InterVM shared memory",
|
||||
"IVSHMEM_REGION": {
|
||||
"ui:title": "",
|
||||
"ui:sortable": false,
|
||||
"ui:field": IVSHMEM_REGION,
|
||||
}
|
||||
}
|
||||
},
|
||||
"vuart_connections": {
|
||||
"vuart_connection": {
|
||||
"ui:sortable": false,
|
||||
"items": {
|
||||
"endpoint": {
|
||||
"ui:sortable": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
@ -0,0 +1,240 @@
|
||||
<template>
|
||||
<div class="IVSH_REGIONS" v-if="defaultVal && defaultVal.length>0">
|
||||
<div class="IVSH_REGION" v-for="(IVSHMEM_VMO, index) in defaultVal">
|
||||
<div class="IVSH_REGION_CONTENT">
|
||||
<b style="margin-bottom: 2rem">InterVM shared memory region {{ index + 1 }}</b>
|
||||
|
||||
<b-row class="align-items-center my-2 mt-4">
|
||||
<b-col md="2">
|
||||
<label>Region name: </label>
|
||||
</b-col>
|
||||
<b-col md="4">
|
||||
<b-form-input v-model="IVSHMEM_VMO.NAME"/>
|
||||
</b-col>
|
||||
</b-row>
|
||||
|
||||
<b-row class="align-items-center my-2">
|
||||
<b-col md="2">
|
||||
<label>Emulated by: </label>
|
||||
</b-col>
|
||||
<b-col md="4">
|
||||
<b-form-select v-model="IVSHMEM_VMO.PROVIDED_BY" :options="providerType"></b-form-select>
|
||||
</b-col>
|
||||
</b-row>
|
||||
|
||||
<b-row class="align-items-center my-2">
|
||||
<b-col md="2">
|
||||
<label>Size (MB): </label>
|
||||
</b-col>
|
||||
<b-col md="4">
|
||||
<b-form-select v-model="IVSHMEM_VMO.IVSHMEM_SIZE" :options="IVSHMEMSize"></b-form-select>
|
||||
</b-col>
|
||||
</b-row>
|
||||
|
||||
<div class="m-3 mt-4 d-flex flex-column gap-2">
|
||||
<b>Shared VMs</b>
|
||||
<p>Select all VMs that will use this shared memory region</p>
|
||||
<b-row>
|
||||
<b-col sm="2" offset-sm="6">
|
||||
Virtual BDF:
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row class="justify-content-between align-items-center"
|
||||
v-for="(IVSHMEM_VM,index) in IVSHMEM_VMO.IVSHMEM_VMS.IVSHMEM_VM">
|
||||
<b-col sm="1">
|
||||
<label>VM name:</label>
|
||||
</b-col>
|
||||
<b-col sm="3">
|
||||
<b-form-select v-model="IVSHMEM_VM.VM_NAME" :options="vmNames"></b-form-select>
|
||||
</b-col>
|
||||
<b-col sm="3">
|
||||
<b-form-input v-model="IVSHMEM_VM.VBDF"/>
|
||||
</b-col>
|
||||
<b-col sm="3">
|
||||
<div class="ToolSet">
|
||||
<div @click="removeSharedVM(IVSHMEM_VMO.IVSHMEM_VMS.IVSHMEM_VM,index)">
|
||||
<Icon size="18px">
|
||||
<Minus/>
|
||||
</Icon>
|
||||
</div>
|
||||
<div @click="addSharedVM(IVSHMEM_VMO.IVSHMEM_VMS.IVSHMEM_VM,index)">
|
||||
<Icon size="18px">
|
||||
<Plus/>
|
||||
</Icon>
|
||||
</div>
|
||||
</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="ToolSet">
|
||||
<div @click="removeIVSHMEM_VMO(index)">
|
||||
<Icon size="18px">
|
||||
<Minus/>
|
||||
</Icon>
|
||||
</div>
|
||||
<div @click="addIVSHMEM_VMO(index)">
|
||||
<Icon size="18px">
|
||||
<Plus/>
|
||||
</Icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="ToolSet">
|
||||
<div @click="addIVSHMEM_VMO">
|
||||
<Icon size="18px">
|
||||
<Plus/>
|
||||
</Icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash';
|
||||
import {Icon} from "@vicons/utils";
|
||||
import {Plus, Minus} from '@vicons/fa'
|
||||
import {fieldProps, vueUtils} from '@lljj/vue3-form-naive';
|
||||
|
||||
|
||||
export default {
|
||||
name: 'IVSHMEM_REGION',
|
||||
components: {Icon, Plus, Minus},
|
||||
props: {
|
||||
...fieldProps,
|
||||
// Todo: use ui:fieldProps to pass getScenarioData function
|
||||
fieldProps: {
|
||||
type: null,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
vmNames() {
|
||||
let currentScenarioData = window.getCurrentScenarioData()
|
||||
let vmNames = []
|
||||
for (let i = 0; i < currentScenarioData.vm.length; i++) {
|
||||
vmNames.push(currentScenarioData.vm[i].name)
|
||||
}
|
||||
return vmNames
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
providerType: this.rootSchema.definitions['ProviderType']['enum'],
|
||||
IVSHMEMSize: this.rootSchema.definitions['IVSHMEMSize']['enum'],
|
||||
defaultVal: vueUtils.getPathVal(this.rootFormData, this.curNodePath)
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
rootFormData: {
|
||||
handler(newValue, oldValue) {
|
||||
this.defaultVal = vueUtils.getPathVal(newValue, this.curNodePath)
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
defaultVal: {
|
||||
handler(newValue, oldValue) {
|
||||
// Note: `newValue` will be equal to `oldValue` here
|
||||
// on nested mutations as long as the object itself
|
||||
// hasn't been replaced.
|
||||
vueUtils.setPathVal(this.rootFormData, this.curNodePath, newValue);
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addSharedVM(vms, index) {
|
||||
// add new item after current item
|
||||
vms.splice(index + 1, 0, {
|
||||
"VM_NAME": "",
|
||||
"VBDF": ""
|
||||
})
|
||||
},
|
||||
removeSharedVM(vms, index) {
|
||||
if (vms.length <= 2) {
|
||||
return
|
||||
}
|
||||
vms.splice(index, 1)
|
||||
},
|
||||
removeIVSHMEM_VMO(index) {
|
||||
this.defaultVal.splice(index, 1);
|
||||
},
|
||||
addIVSHMEM_VMO(index) {
|
||||
if (!_.isArray(this.defaultVal)) {
|
||||
this.defaultVal = []
|
||||
}
|
||||
this.defaultVal.splice(index + 1, 0, {
|
||||
"NAME": "shm_region_" + this.defaultVal.length,
|
||||
"PROVIDED_BY": "Hypervisor",
|
||||
"IVSHMEM_SIZE": "2",
|
||||
"IVSHMEM_VMS": {
|
||||
"IVSHMEM_VM": [
|
||||
{
|
||||
"VM_NAME": "PRE_RT_VM0",
|
||||
"VBDF": ""
|
||||
},
|
||||
{
|
||||
"VM_NAME": "POST_STD_VM1",
|
||||
"VBDF": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
label:before {
|
||||
content: '*';
|
||||
color: red;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.IVSH_REGIONS {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.IVSH_REGION {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.IVSH_REGION_CONTENT {
|
||||
border: 1px solid gray;
|
||||
border-radius: 5px;
|
||||
padding: 25px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ToolSet {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
gap: 0.5rem;
|
||||
max-width: 5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ToolSet div {
|
||||
cursor: pointer;
|
||||
border: 1px solid gray;
|
||||
border-radius: 3px;
|
||||
background: #f9f9f9;
|
||||
padding: 5px 5px 3px;
|
||||
}
|
||||
</style>
|
@ -27,7 +27,7 @@
|
||||
<tr>
|
||||
<td>
|
||||
<div class="py-4 text-right">
|
||||
<button type="button" class="wel-btn btn btn-primary btn-lg" @click="loadScenario">
|
||||
<button type="button" class="wel-btn btn btn-primary btn-lg" @click="loadScenario(false)">
|
||||
Import Scenario
|
||||
</button>
|
||||
</div>
|
||||
@ -62,20 +62,27 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getScenarioHistory()
|
||||
this.getScenarioHistory().then(() => {
|
||||
// delay 2s for board loading
|
||||
setTimeout(() => {
|
||||
this.loadScenario(true)
|
||||
}, 2000);
|
||||
})
|
||||
// Todo: auto load scenario
|
||||
},
|
||||
methods: {
|
||||
newScenario(data) {
|
||||
this.$emit('scenarioUpdate', data)
|
||||
},
|
||||
loadScenario() {
|
||||
loadScenario(auto = false) {
|
||||
if (this.currentSelectedScenario.length > 0) {
|
||||
configurator.loadScenario(this.currentSelectedScenario)
|
||||
.then((scenarioConfig) => {
|
||||
console.log(scenarioConfig)
|
||||
this.$emit('scenarioUpdate', scenarioConfig['acrn-config'])
|
||||
alert(`Scenario ${this.currentSelectedScenario} loaded success!`)
|
||||
if (!auto) {
|
||||
alert(`Scenario ${this.currentSelectedScenario} loaded success!`)
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
alert(`Failed to open ${this.currentSelectedScenario}, file may not exist`)
|
||||
@ -96,7 +103,7 @@ export default {
|
||||
})
|
||||
},
|
||||
getScenarioHistory() {
|
||||
configurator.getHistory("Scenario")
|
||||
return configurator.getHistory("Scenario")
|
||||
.then((scenarioHistory) => {
|
||||
this.scenarioHistory = scenarioHistory
|
||||
if (this.scenarioHistory.length > 0) {
|
@ -0,0 +1,37 @@
|
||||
import {loadPyodide} from "/thirdLib/pyodide/pyodide";
|
||||
import scenarioJSONSchema from './assets/schema/scenario.json';
|
||||
|
||||
window.__dynamic__load__scenario__from__pyodide__ = () => {
|
||||
return JSON.stringify(scenarioJSONSchema)
|
||||
}
|
||||
|
||||
|
||||
export default async function () {
|
||||
let pyodide = await loadPyodide({
|
||||
indexURL: '/thirdLib/pyodide/'
|
||||
});
|
||||
await pyodide.loadPackage(['micropip', 'lxml', 'beautifulsoup4'])
|
||||
await pyodide.runPythonAsync(`
|
||||
import micropip
|
||||
await micropip.install([
|
||||
'./thirdLib/xmltodict-0.12.0-py2.py3-none-any.whl',
|
||||
'./thirdLib/elementpath-2.4.0-py3-none-any.whl',
|
||||
'./thirdLib/defusedxml-0.7.1-py2.py3-none-any.whl',
|
||||
'./thirdLib/xmlschema-1.9.2-py3-none-any.whl',
|
||||
'./thirdLib/acrn_config_tools-3.0-py3-none-any.whl'
|
||||
])
|
||||
`)
|
||||
|
||||
function test() {
|
||||
let result = pyodide.runPython(`
|
||||
import sys
|
||||
sys.version
|
||||
`)
|
||||
console.log(result);
|
||||
}
|
||||
|
||||
test()
|
||||
|
||||
// pyodide load success
|
||||
window.pyodide = pyodide;
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
import {mockIPC} from "@tauri-apps/api/mocks";
|
||||
import mockData from './data/data.json';
|
||||
|
||||
export default function mock() {
|
||||
|
||||
const origin_confirm = window.confirm;
|
||||
window.confirm = async (message) => origin_confirm(message);
|
||||
|
||||
// mock custom tauri command
|
||||
mockIPC(async (cmd, args) => {
|
||||
const packageInfo = await import('../package.json');
|
||||
|
||||
function handle() {
|
||||
switch (cmd) {
|
||||
case 'get_home':
|
||||
return 'C:\\Users\\Axel'
|
||||
case 'get_history':
|
||||
return JSON.stringify(mockData.history[args.historyType])
|
||||
case 'acrn_read':
|
||||
return mockData.files[args.filePath]
|
||||
default:
|
||||
if (args?.message?.cmd === "getAppVersion") {
|
||||
return packageInfo.version;
|
||||
}
|
||||
|
||||
console.log(cmd, args)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
return handle()
|
||||
})
|
||||
}
|
@ -13,7 +13,7 @@
|
||||
"install": [
|
||||
{
|
||||
"type": "copy",
|
||||
"from": "../../dist/acrn_config_tools-3.0-py3-none-any.whl",
|
||||
"from": "../../../../dist/acrn_config_tools-3.0-py3-none-any.whl",
|
||||
"to": "acrn_config_tools-3.0-py3-none-any.whl"
|
||||
}
|
||||
]
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
misc/config_tools/configurator/thirdLib/manager.py
|
||||
depend on misc/config_tools/configurator/thirdLib/library.json
|
||||
misc/config_tools/configurator/packages/configurator/thirdLib/manager.py
|
||||
depend on misc/config_tools/configurator/packages/configurator/thirdLib/library.json
|
||||
"""
|
||||
import argparse
|
||||
import os
|
@ -1,21 +1,12 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
import type {ConfigEnv, Plugin, ResolvedConfig} from "vite";
|
||||
// @ts-ignore
|
||||
import type {Plugin, ResolvedConfig} from "vite";
|
||||
import replace from "@rollup/plugin-replace";
|
||||
|
||||
// @ts-ignore
|
||||
import cli from "@tauri-apps/cli"
|
||||
import Config from "../src-tauri/types/config"
|
||||
|
||||
// @ts-ignore
|
||||
import tauriConf from "../src-tauri/tauri.json";
|
||||
|
||||
|
||||
interface Options {
|
||||
config?: (c: Config, e: ConfigEnv) => Config;
|
||||
}
|
||||
|
||||
function copyFolder(copiedPath, resultPath, direct) {
|
||||
if (!direct) {
|
||||
@ -68,17 +59,11 @@ function copyFolder(copiedPath, resultPath, direct) {
|
||||
}
|
||||
|
||||
|
||||
export default (options?: Options): Plugin => {
|
||||
let tauriConfig: Config = {...tauriConf};
|
||||
export default (): Plugin => {
|
||||
let viteConfig: ResolvedConfig;
|
||||
|
||||
const tauri = (mode: "dev" | "build"): Promise<any> => {
|
||||
// Generate `tauri.conf.json` by `tauri.json`.
|
||||
console.log("Generate `tauri.conf.json` by `tauri.json`.")
|
||||
let filePath = path.resolve(__dirname, '..', 'src-tauri', 'tauri.conf.json')
|
||||
let config = JSON.stringify(tauriConfig)
|
||||
try {
|
||||
fs.writeFileSync(filePath, config)
|
||||
return cli.run([mode], 'tauri')
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
@ -96,7 +81,6 @@ export default (options?: Options): Plugin => {
|
||||
server?.httpServer?.on("listening", () => {
|
||||
if (!process.env.TAURI_SERVE) {
|
||||
process.env.TAURI_SERVE = "true";
|
||||
delete tauriConfig["$schema"]
|
||||
tauri('dev').finally()
|
||||
}
|
||||
});
|
||||
@ -104,19 +88,12 @@ export default (options?: Options): Plugin => {
|
||||
closeBundle() {
|
||||
if (!process.env.TAURI_BUILD) {
|
||||
process.env.TAURI_BUILD = "true";
|
||||
delete tauriConfig["$schema"]
|
||||
copyFolder('../thirdLib', '../build/thirdLib', false)
|
||||
tauri('build').finally()
|
||||
}
|
||||
},
|
||||
config(viteConfig, env) {
|
||||
process.env.IS_TAURI = "true";
|
||||
if (options && options.config) {
|
||||
options.config(tauriConfig, env);
|
||||
}
|
||||
if (env.command === "build") {
|
||||
viteConfig.base = "/";
|
||||
}
|
||||
},
|
||||
configResolved(resolvedConfig) {
|
||||
viteConfig = resolvedConfig;
|
@ -8,6 +8,9 @@ import tauri from "./thirdLib/tauri-plugin";
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
plugins: [vue(), tauri()],
|
||||
resolve: {
|
||||
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
|
||||
},
|
||||
build: {
|
||||
outDir: path.resolve(__dirname, 'build')
|
||||
}
|
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
@ -0,0 +1,223 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.12.2](https://github.com/lljj-x/vue-json-schema-form/compare/v1.12.1...v1.12.2) (2022-04-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **lib:** ui:hidden 透传到widget组件 ([f63094e](https://github.com/lljj-x/vue-json-schema-form/commit/f63094ee85659d1fea45bd789321817c08664ffa)), closes [#170](https://github.com/lljj-x/vue-json-schema-form/issues/170)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.12.0](https://github.com/lljj-x/vue-json-schema-form/compare/v1.11.0...v1.12.0) (2022-03-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **lib:** 添加严格模式配置,更精准计算anyOf 默认值 ([10cdc08](https://github.com/lljj-x/vue-json-schema-form/commit/10cdc089087d83d8fe08e1fd379b7a1aaad0cd5d)), closes [#152](https://github.com/lljj-x/vue-json-schema-form/issues/152)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **lib:** 优化样式 ([e53291b](https://github.com/lljj-x/vue-json-schema-form/commit/e53291b8395fdceb971f15f72c9e809cdee8ec7e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.11.0](https://github.com/lljj-x/vue-json-schema-form/compare/v1.10.0...v1.11.0) (2022-02-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **lib:** 添加严格模式配置,更精准计算anyOf 默认值 ([2cd65bb](https://github.com/lljj-x/vue-json-schema-form/commit/2cd65bb5f275a021f1cc368e4c63387163c94d57)), closes [#157](https://github.com/lljj-x/vue-json-schema-form/issues/157)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.9.5](https://github.com/lljj-x/vue-json-schema-form/compare/v1.9.4...v1.9.5) (2021-11-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **lib:** 修复inline 布局样式问题 ([65a7143](https://github.com/lljj-x/vue-json-schema-form/commit/65a7143fc19105f9096afc24a25107c0ef27ac5f)), closes [#122](https://github.com/lljj-x/vue-json-schema-form/issues/122)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.9.3](https://github.com/lljj-x/vue-json-schema-form/compare/v1.9.2...v1.9.3) (2021-10-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **lib:** allOf merge 相同类型直接使用左边 ([c0bd0cd](https://github.com/lljj-x/vue-json-schema-form/commit/c0bd0cde9f15b4ca928fec84b4831a5cb459aa43)), closes [#116](https://github.com/lljj-x/vue-json-schema-form/issues/116)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.9.2](https://github.com/lljj-x/vue-json-schema-form/compare/v1.9.1...v1.9.2) (2021-09-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **lib:** 修复anyof 默认值计算可能丢失属性 ([44bcb44](https://github.com/lljj-x/vue-json-schema-form/commit/44bcb44af63a37847cd3df5614fad2f26bdf307d)), closes [#108](https://github.com/lljj-x/vue-json-schema-form/issues/108)
|
||||
* **lib:** 修复anyOf嵌套object 可能丢失部分校验规则的问题 ([5c06294](https://github.com/lljj-x/vue-json-schema-form/commit/5c06294d9a9c978bda1c3724710cfd4ba478af5b))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.9.1](https://github.com/lljj-x/vue-json-schema-form/compare/v1.9.0...v1.9.1) (2021-09-22)
|
||||
|
||||
**Note:** Version bump only for package @lljj/vjsf-utils
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.9.0](https://github.com/lljj-x/vue-json-schema-form/compare/v1.7.0...v1.9.0) (2021-09-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **vue2:** vue2 添加 widgetListeners 配置 ([50348c2](https://github.com/lljj-x/vue-json-schema-form/commit/50348c27e72813ea16fdcfcea46e6450ccf06018)), closes [#45](https://github.com/lljj-x/vue-json-schema-form/issues/45)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.8.0](https://github.com/lljj-x/vue-json-schema-form/compare/v1.7.0...v1.8.0) (2021-09-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **vue2:** vue2 添加 widgetListeners 配置 ([50348c2](https://github.com/lljj-x/vue-json-schema-form/commit/50348c27e72813ea16fdcfcea46e6450ccf06018)), closes [#45](https://github.com/lljj-x/vue-json-schema-form/issues/45)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.7.0](https://github.com/lljj-x/vue-json-schema-form/compare/v1.6.4...v1.7.0) (2021-08-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **lib:** 支持配置 slots ([27f1501](https://github.com/lljj-x/vue-json-schema-form/commit/27f1501eda01eabd4a723656be56904e9cb0f069)), closes [#45](https://github.com/lljj-x/vue-json-schema-form/issues/45)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.6.3](https://github.com/lljj-x/vue-json-schema-form/compare/v1.6.2...v1.6.3) (2021-07-12)
|
||||
|
||||
**Note:** Version bump only for package @lljj/vjsf-utils
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.6.2](https://github.com/lljj-x/vue-json-schema-form/compare/v1.6.1...v1.6.2) (2021-05-31)
|
||||
|
||||
**Note:** Version bump only for package @lljj/vjsf-utils
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.3.0](https://github.com/lljj-x/vue-json-schema-form/compare/v1.2.1...v1.3.0) (2021-04-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** 修复 anyof 后代存在 $ref可能无法回填的问题 ([4dd046b](https://github.com/lljj-x/vue-json-schema-form/commit/4dd046bee0e5c3589f2bfa64ba0d90ed7869067a)), closes [#59](https://github.com/lljj-x/vue-json-schema-form/issues/59)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **core:** widget 节点直接配置onChange ([2d2264b](https://github.com/lljj-x/vue-json-schema-form/commit/2d2264b004c3b6586e225c563bf03ca52fc5e53a))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.1](https://github.com/lljj-x/vue-json-schema-form/compare/v1.2.0...v1.2.1) (2021-04-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **lib:** 修复anyOf下多级对象初始值计算错误问题 ([6dd9780](https://github.com/lljj-x/vue-json-schema-form/commit/6dd97804573aa55001c2715da4a6ffcc5ee897b9)), closes [#57](https://github.com/lljj-x/vue-json-schema-form/issues/57)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.2.0](https://github.com/lljj-x/vue-json-schema-form/compare/v1.1.3...v1.2.0) (2021-03-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **lib:** 添加 fallback-label 参数 ([cd2d8c3](https://github.com/lljj-x/vue-json-schema-form/commit/cd2d8c3ed72b9bc03e44eb5b86eb1b18fe67c34c)), closes [#45](https://github.com/lljj-x/vue-json-schema-form/issues/45)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.1.3](https://github.com/lljj-x/vue-json-schema-form/compare/v1.1.2...v1.1.3) (2021-03-18)
|
||||
|
||||
**Note:** Version bump only for package @lljj/vjsf-utils
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.1.0](https://github.com/lljj-x/vue-json-schema-form/compare/v1.0.2...v1.1.0) (2021-03-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **vue3-ant:** 更新初始化 ([71a2810](https://github.com/lljj-x/vue-json-schema-form/commit/71a281045af11f215333050396aa546dd5e78b88)), closes [#27](https://github.com/lljj-x/vue-json-schema-form/issues/27) [#27](https://github.com/lljj-x/vue-json-schema-form/issues/27) [#27](https://github.com/lljj-x/vue-json-schema-form/issues/27) [#40](https://github.com/lljj-x/vue-json-schema-form/issues/40)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.0.2](https://github.com/lljj-x/vue-json-schema-form/compare/v1.0.1...v1.0.2) (2021-01-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **style:** 修复p标签等自带边距导致的样式问题 ([7b7e43e](https://github.com/lljj-x/vue-json-schema-form/commit/7b7e43eaa06c14a436b34c38d6d69aad27d67512))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.6.1](https://github.com/lljj-x/vue-json-schema-form/compare/v0.6.0...v0.6.1) (2021-01-19)
|
||||
|
||||
**Note:** Version bump only for package @lljj/vjsf-utils
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.6.0](https://github.com/lljj-x/vue-json-schema-form/compare/v0.5.0...v0.6.0) (2021-01-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **lib:** 修复 anyOf 类型,编辑时不能匹配正确选项 ([d747722](https://github.com/lljj-x/vue-json-schema-form/commit/d7477227d004e47c2b186c3eb956e4c83d7077ad)), closes [#31](https://github.com/lljj-x/vue-json-schema-form/issues/31)
|
||||
* **lib:** 解决打包后包含es6代码问题 ([f03352e](https://github.com/lljj-x/vue-json-schema-form/commit/f03352eb129c45963ad41e3e91eebe102c303913)), closes [#29](https://github.com/lljj-x/vue-json-schema-form/issues/29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **iview:** 添加iview i-switch 组件转换 ([8fae70c](https://github.com/lljj-x/vue-json-schema-form/commit/8fae70cb28f7fd02073d6d4318861b7f08f6199b))
|
||||
* **vue3:** 完成 vue3 form组件 ([1c5deba](https://github.com/lljj-x/vue-json-schema-form/commit/1c5debae4cb92f3f54de64d8f38c98396022a344))
|
@ -0,0 +1,40 @@
|
||||
# @lljj/vjsf-utils
|
||||
表单基础通用工具类,具体的参数可参见源码
|
||||
|
||||
## @lljj/vjsf-utils/i18n
|
||||
管理当前多语言
|
||||
|
||||
|
||||
## @lljj/vjsf-utils/schema/getDefaultFormState
|
||||
根据 jsonSchema 和 formData,计算当前schema value
|
||||
|
||||
## @lljj/vjsf-utils/schema/validate
|
||||
|
||||
```js
|
||||
import {
|
||||
ajvValidateFormData,
|
||||
validateFormDataAndTransformMsg,
|
||||
isValid
|
||||
} from '@lljj/vjsf-utils/schema/validate';
|
||||
|
||||
// 直接调用 ajv 验证schema,返回格式化后的结果
|
||||
ajvValidateFormData(...args);
|
||||
|
||||
// 校验数据并处理多语言(只处理当前节点)
|
||||
validateFormDataAndTransformMsg(...args);
|
||||
|
||||
// 返回数据是否校验成功
|
||||
isValid(...args);
|
||||
|
||||
// 返回数据是否校验成功
|
||||
isValid(...args);
|
||||
```
|
||||
|
||||
## @lljj/vjsf-utils/arrayUtils
|
||||
数组相关的工具类
|
||||
|
||||
## @lljj/vjsf-utils/formUtils
|
||||
表单相关的工具类
|
||||
|
||||
## @lljj/vjsf-utils/vueUtils
|
||||
Vue相关的工具类
|
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Created by Liu.Jun on 2020/4/25 10:53.
|
||||
*/
|
||||
|
||||
// 通过 index 上移
|
||||
export function moveUpAt(target, index) {
|
||||
if (index === 0) return false;
|
||||
const item = target[index];
|
||||
const newItems = [item, target[index - 1]];
|
||||
return target.splice(index - 1, 2, ...newItems);
|
||||
}
|
||||
|
||||
// 通过 index 下移动
|
||||
export function moveDownAt(target, index) {
|
||||
if (index === target.length - 1) return false;
|
||||
const item = target[index];
|
||||
const newItems = [target[index + 1], item];
|
||||
return target.splice(index, 2, ...newItems);
|
||||
}
|
||||
|
||||
// 移除
|
||||
export function removeAt(target, index) {
|
||||
// 移除数组中指定位置的元素,返回布尔表示成功与否
|
||||
return !!target.splice(index, 1).length;
|
||||
}
|
||||
|
||||
// 数组填充对象
|
||||
export function fillObj(target, data) {
|
||||
// 简单复制 异常直接抛错
|
||||
try {
|
||||
if (typeof data === 'object') {
|
||||
return target.fill(null).map(() => JSON.parse(JSON.stringify(data)));
|
||||
}
|
||||
} catch (e) {
|
||||
// nothing ...
|
||||
}
|
||||
|
||||
// 默认返回一个 undefined
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 切割分为多个数组
|
||||
export function cutOff(target, cutOffPointIndex) {
|
||||
return target.reduce((preVal, curVal, curIndex) => {
|
||||
preVal[curIndex > cutOffPointIndex ? 1 : 0].push(curVal);
|
||||
return preVal;
|
||||
}, [[], []]);
|
||||
}
|
||||
|
||||
// 数组交集
|
||||
export function intersection(arr1, arr2) {
|
||||
return arr1.filter(item => arr2.includes(item));
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div class="fieldGroupWrap">
|
||||
<h3
|
||||
v-if="showTitle && trueTitle"
|
||||
class="fieldGroupWrap_title"
|
||||
>
|
||||
{{ trueTitle }}
|
||||
</h3>
|
||||
<p
|
||||
v-if="showDescription && description"
|
||||
class="fieldGroupWrap_des"
|
||||
v-html="description"
|
||||
>
|
||||
</p>
|
||||
<div class="fieldGroupWrap_box">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FieldGroupWrap',
|
||||
inject: ['genFormProvide'],
|
||||
props: {
|
||||
// 当前节点路径
|
||||
curNodePath: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
showTitle: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showDescription: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
trueTitle() {
|
||||
const title = this.title;
|
||||
if (title) {
|
||||
return title;
|
||||
}
|
||||
|
||||
const genFormProvide = this.genFormProvide.value || this.genFormProvide;
|
||||
|
||||
const backTitle = genFormProvide.fallbackLabel && this.curNodePath.split('.').pop();
|
||||
if (backTitle !== `${Number(backTitle)}`) return backTitle;
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@ -0,0 +1,430 @@
|
||||
import retrieveSchema from './schema/retriev';
|
||||
import { getPathVal } from './vueUtils';
|
||||
|
||||
import { getSchemaType, isObject } from './utils';
|
||||
|
||||
// 通用的处理表达式方法
|
||||
// 这里打破 JSON Schema 规范
|
||||
const regExpression = /{{(.*)}}/;
|
||||
function handleExpression(rootFormData, curNodePath, expression, fallBack) {
|
||||
// 未配置
|
||||
if (undefined === expression) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 配置了 mustache 表达式
|
||||
const matchExpression = regExpression.exec(expression);
|
||||
regExpression.lastIndex = 0; // 重置索引
|
||||
if (matchExpression) {
|
||||
const code = matchExpression[1].trim();
|
||||
|
||||
// eslint-disable-next-line no-new-func
|
||||
const fn = new Function('parentFormData', 'rootFormData', `return ${code}`);
|
||||
|
||||
return fn(getPathVal(rootFormData, curNodePath, 1), rootFormData);
|
||||
}
|
||||
|
||||
// 回退
|
||||
return fallBack();
|
||||
}
|
||||
|
||||
export function replaceArrayIndex({ schema, uiSchema } = {}, index) {
|
||||
const itemUiOptions = getUiOptions({
|
||||
schema,
|
||||
uiSchema,
|
||||
containsSpec: false
|
||||
});
|
||||
|
||||
return ['title', 'description'].reduce((preVal, curItem) => {
|
||||
if (itemUiOptions[curItem]) {
|
||||
preVal[`ui:${curItem}`] = String(itemUiOptions[curItem]).replace(/\$index/g, index + 1);
|
||||
}
|
||||
return preVal;
|
||||
}, {});
|
||||
}
|
||||
|
||||
// 是否为 hidden Widget
|
||||
export function isHiddenWidget({
|
||||
schema = {},
|
||||
uiSchema = {},
|
||||
curNodePath = '',
|
||||
rootFormData = {}
|
||||
}) {
|
||||
const widget = uiSchema['ui:widget'] || schema['ui:widget'];
|
||||
const hiddenExpression = uiSchema['ui:hidden'] || schema['ui:hidden'];
|
||||
|
||||
// 支持配置 ui:hidden 表达式
|
||||
return widget === 'HiddenWidget'
|
||||
|| widget === 'hidden'
|
||||
|| !!handleExpression(rootFormData, curNodePath, hiddenExpression, () => {
|
||||
// 配置了函数 function
|
||||
if (typeof hiddenExpression === 'function') {
|
||||
return hiddenExpression(getPathVal(rootFormData, curNodePath, 1), rootFormData);
|
||||
}
|
||||
|
||||
// 配置了常量 ??
|
||||
return hiddenExpression;
|
||||
});
|
||||
}
|
||||
|
||||
// 解析当前节点 ui field
|
||||
export function getUiField(FIELDS_MAP, {
|
||||
schema = {},
|
||||
uiSchema = {},
|
||||
}) {
|
||||
const field = schema['ui:field'] || uiSchema['ui:field'];
|
||||
|
||||
// vue 组件,或者已注册的组件名
|
||||
if (typeof field === 'function' || typeof field === 'object' || typeof field === 'string') {
|
||||
return {
|
||||
field,
|
||||
fieldProps: uiSchema['ui:fieldProps'] || schema['ui:fieldProps'], // 自定义field ,支持传入额外的 props
|
||||
};
|
||||
}
|
||||
|
||||
// 类型默认 field
|
||||
const fieldCtor = FIELDS_MAP[getSchemaType(schema)];
|
||||
if (fieldCtor) {
|
||||
return {
|
||||
field: fieldCtor
|
||||
};
|
||||
}
|
||||
|
||||
// 如果包含 oneOf anyOf 返回空不异常
|
||||
// SchemaField 会附加onyOf anyOf信息
|
||||
if (!fieldCtor && (schema.anyOf || schema.oneOf)) {
|
||||
return {
|
||||
field: null
|
||||
};
|
||||
}
|
||||
|
||||
// 不支持的类型
|
||||
throw new Error(`不支持的field类型 ${schema.type}`);
|
||||
}
|
||||
|
||||
// 解析用户配置的 uiSchema options
|
||||
export function getUserUiOptions({
|
||||
schema = {},
|
||||
uiSchema = {},
|
||||
curNodePath, // undefined 不处理 表达式
|
||||
rootFormData = {}
|
||||
}) {
|
||||
// 支持 uiSchema配置在 schema文件中
|
||||
return Object.assign({}, ...[schema, uiSchema].map(itemSchema => Object.keys(itemSchema)
|
||||
.reduce((options, key) => {
|
||||
const value = itemSchema[key];
|
||||
// options 内外合并
|
||||
if (key === 'ui:options' && isObject(value)) {
|
||||
return { ...options, ...value };
|
||||
}
|
||||
|
||||
// https://github.com/lljj-x/vue-json-schema-form/issues/170
|
||||
// ui:hidden需要作为内置属性使用,不能直接透传给widget组件,如果组件需要只能在ui:options 中使用hidden传递
|
||||
if (key !== 'ui:hidden' && key.indexOf('ui:') === 0) {
|
||||
// 只对 ui:xxx 配置形式支持表达式
|
||||
return {
|
||||
...options,
|
||||
[key.substring(3)]: curNodePath === undefined ? value : handleExpression(rootFormData, curNodePath, value, () => value)
|
||||
};
|
||||
}
|
||||
|
||||
return options;
|
||||
}, {})));
|
||||
}
|
||||
|
||||
// 解析当前节点的ui options参数
|
||||
export function getUiOptions({
|
||||
schema = {},
|
||||
uiSchema = {},
|
||||
containsSpec = true,
|
||||
curNodePath,
|
||||
rootFormData,
|
||||
}) {
|
||||
const spec = {};
|
||||
if (containsSpec) {
|
||||
spec.readonly = !!schema.readOnly;
|
||||
if (undefined !== schema.multipleOf) {
|
||||
// 组件计数器步长
|
||||
spec.step = schema.multipleOf;
|
||||
}
|
||||
if (schema.minimum || schema.minimum === 0) {
|
||||
spec.min = schema.minimum;
|
||||
}
|
||||
if (schema.maximum || schema.maximum === 0) {
|
||||
spec.max = schema.maximum;
|
||||
}
|
||||
|
||||
if (schema.minLength || schema.minLength === 0) {
|
||||
spec.minlength = schema.minLength;
|
||||
}
|
||||
if (schema.maxLength || schema.maxLength === 0) {
|
||||
spec.maxlength = schema.maxLength;
|
||||
}
|
||||
|
||||
if (schema.format === 'date-time' || schema.format === 'date') {
|
||||
// 数组类型 时间区间
|
||||
// 打破了schema的规范,type array 配置了 format
|
||||
if (schema.type === 'array') {
|
||||
spec.isRange = true;
|
||||
spec.isNumberValue = !(schema.items && schema.items.type === 'string');
|
||||
} else {
|
||||
// 字符串 ISO 时间
|
||||
spec.isNumberValue = !(schema.type === 'string');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (schema.title) spec.title = schema.title;
|
||||
if (schema.description) spec.description = schema.description;
|
||||
|
||||
// 计算ui配置
|
||||
return {
|
||||
...spec,
|
||||
|
||||
// 用户配置最高优先级
|
||||
...getUserUiOptions({
|
||||
schema,
|
||||
uiSchema,
|
||||
curNodePath,
|
||||
rootFormData
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
// 获取当前节点的ui 配置 (options + widget)
|
||||
// 处理成 Widget 组件需要的格式
|
||||
export function getWidgetConfig({
|
||||
schema = {},
|
||||
uiSchema = {},
|
||||
curNodePath,
|
||||
rootFormData,
|
||||
}, fallback = null) {
|
||||
const uiOptions = getUiOptions({
|
||||
schema,
|
||||
uiSchema,
|
||||
curNodePath,
|
||||
rootFormData,
|
||||
});
|
||||
|
||||
// 没有配置 Widget ,各个Field组件根据类型判断
|
||||
if (!uiOptions.widget && fallback) {
|
||||
Object.assign(uiOptions, fallback({
|
||||
schema,
|
||||
uiSchema
|
||||
}));
|
||||
}
|
||||
|
||||
const {
|
||||
widget,
|
||||
title: label,
|
||||
labelWidth,
|
||||
description,
|
||||
attrs: widgetAttrs,
|
||||
class: widgetClass,
|
||||
style: widgetStyle,
|
||||
widgetListeners,
|
||||
fieldAttrs,
|
||||
fieldStyle,
|
||||
fieldClass,
|
||||
emptyValue,
|
||||
width,
|
||||
getWidget,
|
||||
renderScopedSlots,
|
||||
renderChildren,
|
||||
onChange,
|
||||
...uiProps
|
||||
} = uiOptions;
|
||||
|
||||
return {
|
||||
widget,
|
||||
label,
|
||||
labelWidth,
|
||||
description,
|
||||
widgetAttrs,
|
||||
widgetClass,
|
||||
widgetStyle,
|
||||
fieldAttrs,
|
||||
width,
|
||||
fieldStyle,
|
||||
fieldClass,
|
||||
emptyValue,
|
||||
getWidget,
|
||||
renderScopedSlots,
|
||||
renderChildren,
|
||||
onChange,
|
||||
widgetListeners,
|
||||
uiProps
|
||||
};
|
||||
}
|
||||
|
||||
// 解析用户配置的 errorSchema options
|
||||
export function getUserErrOptions({
|
||||
schema = {},
|
||||
uiSchema = {},
|
||||
errorSchema = {}
|
||||
}) {
|
||||
return Object.assign({}, ...[schema, uiSchema, errorSchema].map(itemSchema => Object.keys(itemSchema)
|
||||
.reduce((options, key) => {
|
||||
const value = itemSchema[key];
|
||||
// options 内外合并
|
||||
if (key === 'err:options' && isObject(value)) {
|
||||
return { ...options, ...value };
|
||||
}
|
||||
|
||||
if (key.indexOf('err:') === 0) {
|
||||
return { ...options, [key.substring(4)]: value };
|
||||
}
|
||||
|
||||
return options;
|
||||
}, {})));
|
||||
}
|
||||
|
||||
// ui:order object-> properties 排序
|
||||
export function orderProperties(properties, order) {
|
||||
if (!Array.isArray(order)) {
|
||||
return properties;
|
||||
}
|
||||
|
||||
const arrayToHash = arr => arr.reduce((prev, curr) => {
|
||||
prev[curr] = true;
|
||||
return prev;
|
||||
}, {});
|
||||
const errorPropList = arr => (arr.length > 1
|
||||
? `properties '${arr.join("', '")}'`
|
||||
: `property '${arr[0]}'`);
|
||||
const propertyHash = arrayToHash(properties);
|
||||
const orderFiltered = order.filter(
|
||||
prop => prop === '*' || propertyHash[prop]
|
||||
);
|
||||
const orderHash = arrayToHash(orderFiltered);
|
||||
|
||||
const rest = properties.filter(prop => !orderHash[prop]);
|
||||
const restIndex = orderFiltered.indexOf('*');
|
||||
if (restIndex === -1) {
|
||||
if (rest.length) {
|
||||
throw new Error(
|
||||
`uiSchema order list does not contain ${errorPropList(rest)}`
|
||||
);
|
||||
}
|
||||
return orderFiltered;
|
||||
}
|
||||
if (restIndex !== orderFiltered.lastIndexOf('*')) {
|
||||
throw new Error('uiSchema order list contains more than one wildcard item');
|
||||
}
|
||||
|
||||
const complete = [...orderFiltered];
|
||||
complete.splice(restIndex, 1, ...rest);
|
||||
return complete;
|
||||
}
|
||||
|
||||
/**
|
||||
* 单个匹配
|
||||
* 常量,或者只有一个枚举
|
||||
*/
|
||||
export function isConstant(schema) {
|
||||
return (
|
||||
(Array.isArray(schema.enum) && schema.enum.length === 1)
|
||||
|| schema.hasOwnProperty('const')
|
||||
);
|
||||
}
|
||||
|
||||
export function toConstant(schema) {
|
||||
if (Array.isArray(schema.enum) && schema.enum.length === 1) {
|
||||
return schema.enum[0];
|
||||
} if (schema.hasOwnProperty('const')) {
|
||||
return schema.const;
|
||||
}
|
||||
throw new Error('schema cannot be inferred as a constant');
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为选择列表
|
||||
* 枚举 或者 oneOf anyOf 每项都只有一个固定常量值
|
||||
* @param _schema
|
||||
* @param rootSchema
|
||||
* @returns {boolean|*}
|
||||
*/
|
||||
export function isSelect(_schema, rootSchema = {}) {
|
||||
const schema = retrieveSchema(_schema, rootSchema);
|
||||
const altSchemas = schema.oneOf || schema.anyOf;
|
||||
if (Array.isArray(schema.enum)) {
|
||||
return true;
|
||||
} if (Array.isArray(altSchemas)) {
|
||||
return altSchemas.every(altSchemasItem => isConstant(altSchemasItem));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// items 都为一个对象
|
||||
export function isFixedItems(schema) {
|
||||
return (
|
||||
Array.isArray(schema.items)
|
||||
&& schema.items.length > 0
|
||||
&& schema.items.every(item => isObject(item))
|
||||
);
|
||||
}
|
||||
|
||||
// 是否为多选
|
||||
export function isMultiSelect(schema, rootSchema = {}) {
|
||||
if (!schema.uniqueItems || !schema.items) {
|
||||
return false;
|
||||
}
|
||||
return isSelect(schema.items, rootSchema);
|
||||
}
|
||||
|
||||
// array additionalItems
|
||||
// https://json-schema.org/understanding-json-schema/reference/array.html#tuple-validation
|
||||
export function allowAdditionalItems(schema) {
|
||||
if (schema.additionalItems === true) {
|
||||
console.warn('additionalItems=true is currently not supported');
|
||||
}
|
||||
return isObject(schema.additionalItems);
|
||||
}
|
||||
|
||||
// 下拉选项
|
||||
export function optionsList(schema, uiSchema, curNodePath, rootFormData) {
|
||||
// enum
|
||||
if (schema.enum) {
|
||||
const uiOptions = getUserUiOptions({
|
||||
schema,
|
||||
uiSchema,
|
||||
curNodePath,
|
||||
rootFormData
|
||||
});
|
||||
|
||||
// ui配置 enumNames 优先
|
||||
const enumNames = uiOptions.enumNames || schema.enumNames;
|
||||
return schema.enum.map((value, i) => {
|
||||
const label = (enumNames && enumNames[i]) || String(value);
|
||||
return { label, value };
|
||||
});
|
||||
}
|
||||
|
||||
// oneOf | anyOf
|
||||
const altSchemas = schema.oneOf || schema.anyOf;
|
||||
const altUiSchemas = uiSchema.oneOf || uiSchema.anyOf;
|
||||
return altSchemas.map((curSchema, i) => {
|
||||
const uiOptions = (altUiSchemas && altUiSchemas[i]) ? getUserUiOptions({
|
||||
schema: curSchema,
|
||||
uiSchema: altUiSchemas[i],
|
||||
curNodePath,
|
||||
rootFormData
|
||||
}) : {};
|
||||
const value = toConstant(curSchema);
|
||||
const label = uiOptions.title || curSchema.title || String(value);
|
||||
return { label, value };
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export function fallbackLabel(oriLabel, isFallback, curNodePath) {
|
||||
if (oriLabel) return oriLabel;
|
||||
if (isFallback) {
|
||||
const backLabel = curNodePath.split('.').pop();
|
||||
|
||||
// 过滤纯数字字符串
|
||||
if (backLabel && (backLabel !== `${Number(backLabel)}`)) return backLabel;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Created by Liu.Jun on 2020/4/30 11:22.
|
||||
*/
|
||||
|
||||
// 使用 ajv-i18n 这里只为初始化默认可以设置语言
|
||||
// 也可以自己使用官方的语言包
|
||||
// https://github.com/epoberezkin/ajv-i18n/tree/master/localize
|
||||
|
||||
import localizeZh from './localize/zh';
|
||||
|
||||
export default {
|
||||
$$currentLocalizeFn: localizeZh,
|
||||
getCurrentLocalize() {
|
||||
return this.$$currentLocalizeFn;
|
||||
},
|
||||
useLocal(fn) {
|
||||
this.$$currentLocalizeFn = fn;
|
||||
}
|
||||
};
|
@ -0,0 +1,152 @@
|
||||
// https://github.com/epoberezkin/ajv-i18n
|
||||
export default function localizeZh(errors) {
|
||||
if (!(errors && errors.length)) return;
|
||||
for (let i = 0; i < errors.length; i += 1) {
|
||||
const e = errors[i];
|
||||
let out;
|
||||
let n;
|
||||
let cond;
|
||||
switch (e.keyword) {
|
||||
case '$ref':
|
||||
out = `无法找到引用${e.params.ref}`;
|
||||
break;
|
||||
case 'additionalItems':
|
||||
out = '';
|
||||
n = e.params.limit;
|
||||
out += `不允许超过${n}个元素`;
|
||||
break;
|
||||
case 'additionalProperties':
|
||||
out = '不允许有额外的属性';
|
||||
break;
|
||||
case 'anyOf':
|
||||
out = '数据应为 anyOf 所指定的其中一个';
|
||||
break;
|
||||
case 'const':
|
||||
out = '应当等于常量';
|
||||
break;
|
||||
case 'contains':
|
||||
out = '应当包含一个有效项';
|
||||
break;
|
||||
case 'custom':
|
||||
out = `应当通过 "${e.keyword} 关键词校验"`;
|
||||
break;
|
||||
case 'dependencies':
|
||||
out = '';
|
||||
n = e.params.depsCount;
|
||||
out += `应当拥有属性${e.params.property}的依赖属性${e.params.deps}`;
|
||||
break;
|
||||
case 'enum':
|
||||
out = '应当是预设定的枚举值之一';
|
||||
break;
|
||||
case 'exclusiveMaximum':
|
||||
out = '';
|
||||
cond = `${e.params.comparison} ${e.params.limit}`;
|
||||
out += `应当为 ${cond}`;
|
||||
break;
|
||||
case 'exclusiveMinimum':
|
||||
out = '';
|
||||
cond = `${e.params.comparison} ${e.params.limit}`;
|
||||
out += `应当为 ${cond}`;
|
||||
break;
|
||||
case 'false schema':
|
||||
out = '布尔模式出错';
|
||||
break;
|
||||
case 'format':
|
||||
out = `应当匹配格式 "${e.params.format}"`;
|
||||
break;
|
||||
case 'formatExclusiveMaximum':
|
||||
out = 'formatExclusiveMaximum 应当是布尔值';
|
||||
break;
|
||||
case 'formatExclusiveMinimum':
|
||||
out = 'formatExclusiveMinimum 应当是布尔值';
|
||||
break;
|
||||
case 'formatMaximum':
|
||||
out = '';
|
||||
cond = `${e.params.comparison} ${e.params.limit}`;
|
||||
out += `应当是 ${cond}`;
|
||||
break;
|
||||
case 'formatMinimum':
|
||||
out = '';
|
||||
cond = `${e.params.comparison} ${e.params.limit}`;
|
||||
out += `应当是 ${cond}`;
|
||||
break;
|
||||
case 'if':
|
||||
out = `应当匹配模式 "${e.params.failingKeyword}" `;
|
||||
break;
|
||||
case 'maximum':
|
||||
out = '';
|
||||
cond = `${e.params.comparison} ${e.params.limit}`;
|
||||
out += `应当为 ${cond}`;
|
||||
break;
|
||||
case 'maxItems':
|
||||
out = '';
|
||||
n = e.params.limit;
|
||||
out += `不应多于 ${n} 个项`;
|
||||
break;
|
||||
case 'maxLength':
|
||||
out = '';
|
||||
n = e.params.limit;
|
||||
out += `不应多于 ${n} 个字符`;
|
||||
break;
|
||||
case 'maxProperties':
|
||||
out = '';
|
||||
n = e.params.limit;
|
||||
out += `不应有多于 ${n} 个属性`;
|
||||
break;
|
||||
case 'minimum':
|
||||
out = '';
|
||||
cond = `${e.params.comparison} ${e.params.limit}`;
|
||||
out += `应当为 ${cond}`;
|
||||
break;
|
||||
case 'minItems':
|
||||
out = '';
|
||||
n = e.params.limit;
|
||||
out += `不应少于 ${n} 个项`;
|
||||
break;
|
||||
case 'minLength':
|
||||
out = '';
|
||||
n = e.params.limit;
|
||||
out += `不应少于 ${n} 个字符`;
|
||||
break;
|
||||
case 'minProperties':
|
||||
out = '';
|
||||
n = e.params.limit;
|
||||
out += `不应有少于 ${n} 个属性`;
|
||||
break;
|
||||
case 'multipleOf':
|
||||
out = `应当是 ${e.params.multipleOf} 的整数倍`;
|
||||
break;
|
||||
case 'not':
|
||||
out = '不应当匹配 "not" schema';
|
||||
break;
|
||||
case 'oneOf':
|
||||
out = '只能匹配一个 "oneOf" 中的 schema';
|
||||
break;
|
||||
case 'pattern':
|
||||
out = `应当匹配模式 "${e.params.pattern}"`;
|
||||
break;
|
||||
case 'patternRequired':
|
||||
out = `应当有属性匹配模式 ${e.params.missingPattern}`;
|
||||
break;
|
||||
case 'propertyNames':
|
||||
out = `属性名 '${e.params.propertyName}' 无效`;
|
||||
break;
|
||||
case 'required':
|
||||
out = `应当有必需属性 ${e.params.missingProperty}`;
|
||||
break;
|
||||
case 'switch':
|
||||
out = `由于 ${e.params.caseIndex} 失败,未通过 "switch" 校验, `;
|
||||
break;
|
||||
case 'type':
|
||||
out = `应当是 ${e.params.type} 类型`;
|
||||
break;
|
||||
case 'uniqueItems':
|
||||
out = `不应当含有重复项 (第 ${e.params.j} 项与第 ${e.params.i} 项是重复的)`;
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
e.message = out;
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<svg
|
||||
class="genFormIcon genFormIcon-down"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 1024 1024"
|
||||
>
|
||||
<path d="M840.4 300H183.6c-19.7 0-30.7 20.8-18.5 35l328.4 380.8c9.4 10.9 27.5 10.9 37 0L858.9 335c12.2-14.2 1.2-35-18.5-35z" />
|
||||
</svg>
|
||||
</template>
|
@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<svg
|
||||
class="genFormIcon genFormIcon-up"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 1024 1024"
|
||||
>
|
||||
<path
|
||||
d="M858.9 689L530.5 308.2c-9.4-10.9-27.5-10.9-37 0L165.1 689c-12.2 14.2-1.2 35 18.5 35h656.8c19.7 0 30.7-20.8 18.5-35z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<svg
|
||||
class="genFormIcon genFormIcon-close"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 1024 1024"
|
||||
>
|
||||
<path
|
||||
d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1
|
||||
191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0
|
||||
0 0 203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<Icon size="16px" class="labelInfoIcon">
|
||||
<InfoCircle/>
|
||||
</Icon>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {Icon} from "@vicons/utils";
|
||||
import {InfoCircle} from "@vicons/fa"
|
||||
|
||||
export default {
|
||||
name: "IconInfo",
|
||||
components: {Icon,InfoCircle}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.genFormLabel{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.labelInfoIcon{
|
||||
margin: 0 6px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<svg
|
||||
class="genFormIcon genFormIcon-plus"
|
||||
t="1551322312294"
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="10297"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="200"
|
||||
height="200"
|
||||
>
|
||||
<path
|
||||
d="M474 152m8 0l60 0q8 0 8 8l0 704q0 8-8 8l-60 0q-8 0-8-8l0-704q0-8 8-8Z"
|
||||
p-id="10298"
|
||||
/>
|
||||
<path
|
||||
d="M168 474m8 0l672 0q8 0 8 8l0 60q0 8-8 8l-672 0q-8 0-8-8l0-60q0-8 8-8Z"
|
||||
p-id="10299"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<svg
|
||||
class="genFormIcon genFormIcon-qs"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 1024 1024"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 708c-22.1
|
||||
0-40-17.9-40-40s17.9-40 40-40 40 17.9 40 40-17.9 40-40 40zm62.9-219.5a48.3 48.3 0 0
|
||||
0-30.9 44.8V620c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8v-21.5c0-23.1 6.7-45.9 19.9-64.9 12.9-18.6 30.9-32.8
|
||||
52.1-40.9 34-13.1 56-41.6 56-72.7 0-44.1-43.1-80-96-80s-96 35.9-96 80v7.6c0 4.4-3.6
|
||||
8-8 8h-48c-4.4 0-8-3.6-8-8V420c0-39.3 17.2-76 48.4-103.3C430.4 290.4 470 276 512 276s81.6 14.5 111.6
|
||||
40.7C654.8 344 672 380.7 672 420c0 57.8-38.1 109.8-97.1 132.5z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Created by Liu.Jun on 2021/3/6 2:58 下午.
|
||||
*/
|
||||
|
||||
import IconCaretDown from './IconCaretDown.vue';
|
||||
import IconCaretUp from './IconCaretUp.vue';
|
||||
import IconClose from './IconClose.vue';
|
||||
import IconPlus from './IconPlus.vue';
|
||||
import IconQuestion from './IconQuestion.vue';
|
||||
import IconInfo from "./IconInfo.vue";
|
||||
|
||||
export {
|
||||
IconCaretDown,
|
||||
IconCaretUp,
|
||||
IconClose,
|
||||
IconPlus,
|
||||
IconQuestion,
|
||||
IconInfo
|
||||
};
|
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "@lljj/vjsf-utils",
|
||||
"version": "1.12.2",
|
||||
"description": "vue json schema form 使用的基础utils工具类",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"keywords": [
|
||||
"@lljj/vjsf-utils",
|
||||
"vue",
|
||||
"vuejs",
|
||||
"form",
|
||||
"jsonSchema"
|
||||
],
|
||||
"dependencies": {
|
||||
"ajv": "^6.10.2"
|
||||
},
|
||||
"repository": "https://github.com/lljj-x/vue-json-schema-form",
|
||||
"homepage": "https://github.com/lljj-x/vue-json-schema-form",
|
||||
"license": "Apache-2.0",
|
||||
"author": "Liu.Jun",
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 8"
|
||||
],
|
||||
"gitHead": "92795075169c879e1c1fabfe26f1d3c10b861060"
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
// $ref 引用
|
||||
function getPathVal(obj, pathStr) {
|
||||
const pathArr = pathStr.split('/');
|
||||
for (let i = 0; i < pathArr.length; i += 1) {
|
||||
if (obj === undefined) return undefined;
|
||||
obj = pathArr[i] === '' ? obj : obj[pathArr[i]];
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
// 找到ref引用的schema
|
||||
export default function findSchemaDefinition($ref, rootSchema = {}) {
|
||||
const origRef = $ref;
|
||||
if ($ref.startsWith('#')) {
|
||||
// Decode URI fragment representation.
|
||||
$ref = decodeURIComponent($ref.substring(1));
|
||||
} else {
|
||||
throw new Error(`Could not find a definition for ${origRef}.`);
|
||||
}
|
||||
const current = getPathVal(rootSchema, $ref);
|
||||
|
||||
if (current === undefined) {
|
||||
throw new Error(`Could not find a definition for ${origRef}.`);
|
||||
}
|
||||
if (current.hasOwnProperty('$ref')) {
|
||||
return findSchemaDefinition(current.$ref, rootSchema);
|
||||
}
|
||||
return current;
|
||||
}
|
@ -0,0 +1,256 @@
|
||||
/**
|
||||
* 根据schema计算出formData的初始值
|
||||
* 源码来自:react-jsonschema-form 做了细节调整,重写了allOf实现逻辑
|
||||
* https://github.com/rjsf-team/react-jsonschema-form/blob/master/packages/core/src/utils.js#L283
|
||||
*/
|
||||
|
||||
import { getSchemaType, isObject, mergeObjects } from '../utils';
|
||||
import findSchemaDefinition from './findSchemaDefinition';
|
||||
import { getMatchingOption } from './validate';
|
||||
import { fillObj } from '../arrayUtils';
|
||||
import { isFixedItems, isMultiSelect } from '../formUtils';
|
||||
import retrieveSchema, { /* resolveDependencies, */ resolveAllOf } from './retriev';
|
||||
|
||||
/**
|
||||
* When merging defaults and form data, we want to merge in this specific way:
|
||||
* - objects are deeply merged
|
||||
* - arrays are merged in such a way that:
|
||||
* - when the array is set in form data, only array entries set in form data
|
||||
* are deeply merged; additional entries from the defaults are ignored
|
||||
* - when the array is not set in form data, the default is copied over
|
||||
* - scalars are overwritten/set by form data
|
||||
*/
|
||||
function mergeDefaultsWithFormData(defaults, formData) {
|
||||
if (Array.isArray(formData)) {
|
||||
if (!Array.isArray(defaults)) {
|
||||
defaults = [];
|
||||
}
|
||||
return formData.map((value, idx) => {
|
||||
if (defaults[idx]) {
|
||||
return mergeDefaultsWithFormData(defaults[idx], value);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
} if (isObject(formData)) {
|
||||
const acc = Object.assign({}, defaults); // Prevent mutation of source object.
|
||||
return Object.keys(formData).reduce((preAcc, key) => {
|
||||
preAcc[key] = mergeDefaultsWithFormData(
|
||||
defaults ? defaults[key] : {},
|
||||
formData[key]
|
||||
);
|
||||
return preAcc;
|
||||
}, acc);
|
||||
}
|
||||
return formData;
|
||||
}
|
||||
|
||||
function computeDefaults(
|
||||
_schema,
|
||||
parentDefaults,
|
||||
rootSchema,
|
||||
rawFormData = {},
|
||||
includeUndefinedValues = false,
|
||||
haveAllFields = false
|
||||
) {
|
||||
let schema = isObject(_schema) ? _schema : {};
|
||||
const formData = isObject(rawFormData) ? rawFormData : {};
|
||||
|
||||
// allOf 处理合并数据
|
||||
if ('allOf' in schema) {
|
||||
schema = resolveAllOf(schema, rootSchema, formData);
|
||||
}
|
||||
|
||||
// Compute the defaults recursively: give highest priority to deepest nodes.
|
||||
let defaults = parentDefaults;
|
||||
if (isObject(defaults) && isObject(schema.default)) {
|
||||
// For object defaults, only override parent defaults that are defined in
|
||||
// schema.default.
|
||||
defaults = mergeObjects(defaults, schema.default);
|
||||
} else if ('default' in schema) {
|
||||
// Use schema defaults for this node.
|
||||
defaults = schema.default;
|
||||
} else if ('$ref' in schema) {
|
||||
// Use referenced schema defaults for this node.
|
||||
const refSchema = findSchemaDefinition(schema.$ref, rootSchema);
|
||||
return computeDefaults(
|
||||
refSchema,
|
||||
defaults,
|
||||
rootSchema,
|
||||
formData,
|
||||
includeUndefinedValues,
|
||||
haveAllFields
|
||||
);
|
||||
} else if /* ('dependencies' in schema) {
|
||||
const resolvedSchema = resolveDependencies(schema, rootSchema, formData);
|
||||
return computeDefaults(
|
||||
resolvedSchema,
|
||||
defaults,
|
||||
rootSchema,
|
||||
formData,
|
||||
includeUndefinedValues,
|
||||
haveAllFields
|
||||
);
|
||||
} else if */ (isFixedItems(schema)) {
|
||||
defaults = schema.items.map((itemSchema, idx) => computeDefaults(
|
||||
itemSchema,
|
||||
Array.isArray(parentDefaults) ? parentDefaults[idx] : undefined,
|
||||
rootSchema,
|
||||
formData,
|
||||
includeUndefinedValues,
|
||||
haveAllFields
|
||||
));
|
||||
} else if ('oneOf' in schema) {
|
||||
const matchSchema = retrieveSchema(
|
||||
schema.oneOf[getMatchingOption(formData, schema.oneOf, rootSchema, haveAllFields)],
|
||||
rootSchema,
|
||||
formData
|
||||
);
|
||||
|
||||
schema = mergeObjects(schema, matchSchema);
|
||||
delete schema.oneOf;
|
||||
|
||||
// if (schema.properties && matchSchema.properties) {
|
||||
// // 对象 oneOf 需要合并原属性和 oneOf 属性
|
||||
// const mergeSchema = mergeObjects(schema, matchSchema);
|
||||
// delete mergeSchema.oneOf;
|
||||
// schema = mergeSchema;
|
||||
// } else {
|
||||
// schema = matchSchema;
|
||||
// }
|
||||
} else if ('anyOf' in schema) {
|
||||
const matchSchema = retrieveSchema(
|
||||
schema.anyOf[getMatchingOption(formData, schema.anyOf, rootSchema, haveAllFields)],
|
||||
rootSchema,
|
||||
formData
|
||||
);
|
||||
|
||||
schema = mergeObjects(schema, matchSchema);
|
||||
delete schema.anyOf;
|
||||
|
||||
// if (schema.properties && matchSchema.properties) {
|
||||
// // 对象 anyOf 需要合并原属性和 anyOf 属性
|
||||
// const mergeSchema = mergeObjects(schema, matchSchema);
|
||||
// delete mergeSchema.anyOf;
|
||||
// schema = mergeSchema;
|
||||
// } else {
|
||||
// schema = matchSchema;
|
||||
// }
|
||||
}
|
||||
// Not defaults defined for this node, fallback to generic typed ones.
|
||||
if (typeof defaults === 'undefined') {
|
||||
defaults = schema.default;
|
||||
}
|
||||
// eslint-disable-next-line default-case
|
||||
switch (getSchemaType(schema)) {
|
||||
case 'null':
|
||||
return null;
|
||||
|
||||
// We need to recur for object schema inner default values.
|
||||
case 'object':
|
||||
return Object.keys(schema.properties || {}).reduce((acc, key) => {
|
||||
// Compute the defaults for this node, with the parent defaults we might
|
||||
// have from a previous run: defaults[key].
|
||||
const computedDefault = computeDefaults(
|
||||
schema.properties[key],
|
||||
(defaults || {})[key],
|
||||
rootSchema,
|
||||
(formData || {})[key],
|
||||
includeUndefinedValues,
|
||||
haveAllFields
|
||||
);
|
||||
if (includeUndefinedValues || computedDefault !== undefined) {
|
||||
acc[key] = computedDefault;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
case 'array':
|
||||
// Inject defaults into existing array defaults
|
||||
if (Array.isArray(defaults)) {
|
||||
defaults = defaults.map((item, idx) => computeDefaults(
|
||||
schema.items[idx] || schema.additionalItems || {},
|
||||
item,
|
||||
rootSchema,
|
||||
{},
|
||||
includeUndefinedValues,
|
||||
haveAllFields
|
||||
));
|
||||
}
|
||||
|
||||
// Deeply inject defaults into already existing form data
|
||||
if (Array.isArray(rawFormData)) {
|
||||
defaults = rawFormData.map((item, idx) => computeDefaults(
|
||||
schema.items,
|
||||
(defaults || {})[idx],
|
||||
rootSchema,
|
||||
item,
|
||||
{},
|
||||
includeUndefinedValues,
|
||||
haveAllFields
|
||||
));
|
||||
}
|
||||
if (schema.minItems) {
|
||||
if (!isMultiSelect(schema, rootSchema)) {
|
||||
const defaultsLength = defaults ? defaults.length : 0;
|
||||
if (schema.minItems > defaultsLength) {
|
||||
const defaultEntries = defaults || [];
|
||||
// populate the array with the defaults
|
||||
const fillerSchema = Array.isArray(schema.items)
|
||||
? schema.additionalItems
|
||||
: schema.items;
|
||||
|
||||
const fillerEntries = fillObj(
|
||||
new Array(schema.minItems - defaultsLength), computeDefaults(
|
||||
fillerSchema, fillerSchema.defaults, rootSchema, {}, includeUndefinedValues, haveAllFields
|
||||
)
|
||||
);
|
||||
return defaultEntries.concat(fillerEntries);
|
||||
}
|
||||
} else {
|
||||
return defaults || [];
|
||||
}
|
||||
}
|
||||
|
||||
// undefined 默认一个空数组
|
||||
defaults = defaults === undefined ? [] : defaults;
|
||||
}
|
||||
return defaults;
|
||||
}
|
||||
|
||||
|
||||
// 获取默认form data
|
||||
export default function getDefaultFormState(
|
||||
_schema,
|
||||
formData,
|
||||
rootSchema = {},
|
||||
includeUndefinedValues = true,
|
||||
haveAllFields = false
|
||||
) {
|
||||
if (!isObject(_schema)) {
|
||||
throw new Error(`Invalid schema: ${_schema}`);
|
||||
}
|
||||
const schema = retrieveSchema(_schema, rootSchema, formData);
|
||||
|
||||
const defaults = computeDefaults(
|
||||
schema,
|
||||
_schema.default,
|
||||
rootSchema,
|
||||
formData,
|
||||
includeUndefinedValues,
|
||||
haveAllFields
|
||||
);
|
||||
|
||||
if (typeof formData === 'undefined') {
|
||||
// No form data? Use schema defaults.
|
||||
return defaults;
|
||||
}
|
||||
|
||||
// 传入formData时,合并传入数据
|
||||
if (isObject(formData) || Array.isArray(formData)) {
|
||||
return mergeDefaultsWithFormData(defaults, formData);
|
||||
}
|
||||
if (formData === 0 || formData === false || formData === '') {
|
||||
return formData;
|
||||
}
|
||||
return formData || defaults;
|
||||
}
|
@ -0,0 +1,424 @@
|
||||
/**
|
||||
* @param schema
|
||||
* @param rootSchema
|
||||
* @param formData
|
||||
* @returns {{properties: *}|{}|{properties: *}|{}|{properties: *}|{additionalProperties}|*|{}|{allOf}}
|
||||
* 源码来自:react-jsonschema-form
|
||||
* 做了细节和模块调整
|
||||
* 重写了allOf实现逻辑(解决使用allOf必须根节点同时存在,以及对json-schema-merge-allof依赖包过大)
|
||||
* 移除对lodash 、json-schema-merge-allof、jsonpointer 等依赖重新实现
|
||||
* https://github.com/rjsf-team/react-jsonschema-form/blob/master/packages/core/src/utils.js#L621
|
||||
*/
|
||||
|
||||
import findSchemaDefinition from './findSchemaDefinition';
|
||||
import { intersection } from '../arrayUtils';
|
||||
|
||||
import {
|
||||
/* guessType, mergeSchemas, */ isObject, scm
|
||||
} from '../utils';
|
||||
|
||||
// import { getMatchingOption, isValid } from './validate';
|
||||
|
||||
// 自动添加分割线
|
||||
|
||||
// export const ADDITIONAL_PROPERTY_FLAG = '__additional_property';
|
||||
|
||||
// resolve Schema - dependencies
|
||||
// https://json-schema.org/understanding-json-schema/reference/object.html#dependencies
|
||||
/*
|
||||
export function resolveDependencies(schema, rootSchema, formData) {
|
||||
// 从源模式中删除依赖项。
|
||||
const { dependencies = {} } = schema;
|
||||
let { ...resolvedSchema } = schema;
|
||||
if ('oneOf' in resolvedSchema) {
|
||||
resolvedSchema = resolvedSchema.oneOf[
|
||||
getMatchingOption(formData, resolvedSchema.oneOf, rootSchema)
|
||||
];
|
||||
} else if ('anyOf' in resolvedSchema) {
|
||||
resolvedSchema = resolvedSchema.anyOf[
|
||||
getMatchingOption(formData, resolvedSchema.anyOf, rootSchema)
|
||||
];
|
||||
}
|
||||
return processDependencies(
|
||||
dependencies,
|
||||
resolvedSchema,
|
||||
rootSchema,
|
||||
formData
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
// 处理依赖关系 dependencies
|
||||
// https://json-schema.org/understanding-json-schema/reference/object.html#dependencies
|
||||
/*
|
||||
|
||||
function processDependencies(
|
||||
dependencies,
|
||||
resolvedSchema,
|
||||
rootSchema,
|
||||
formData
|
||||
) {
|
||||
// Process dependencies updating the local schema properties as appropriate.
|
||||
for (const dependencyKey in dependencies) {
|
||||
// Skip this dependency if its trigger property is not present.
|
||||
if (formData[dependencyKey] === undefined) {
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
// Skip this dependency if it is not included in the schema (such as when dependencyKey is itself a hidden dependency.)
|
||||
if (
|
||||
resolvedSchema.properties
|
||||
&& !(dependencyKey in resolvedSchema.properties)
|
||||
) {
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
const {
|
||||
[dependencyKey]: dependencyValue,
|
||||
...remainingDependencies
|
||||
} = dependencies;
|
||||
if (Array.isArray(dependencyValue)) {
|
||||
resolvedSchema = withDependentProperties(resolvedSchema, dependencyValue);
|
||||
} else if (isObject(dependencyValue)) {
|
||||
resolvedSchema = withDependentSchema(
|
||||
resolvedSchema,
|
||||
rootSchema,
|
||||
formData,
|
||||
dependencyKey,
|
||||
dependencyValue
|
||||
);
|
||||
}
|
||||
return processDependencies(
|
||||
remainingDependencies,
|
||||
resolvedSchema,
|
||||
rootSchema,
|
||||
formData
|
||||
);
|
||||
}
|
||||
return resolvedSchema;
|
||||
}
|
||||
*/
|
||||
|
||||
// 属性依赖
|
||||
// https://json-schema.org/understanding-json-schema/reference/object.html#property-dependencies
|
||||
|
||||
/*
|
||||
function withDependentProperties(schema, additionallyRequired) {
|
||||
if (!additionallyRequired) {
|
||||
return schema;
|
||||
}
|
||||
const required = Array.isArray(schema.required)
|
||||
? Array.from(new Set([...schema.required, ...additionallyRequired]))
|
||||
: additionallyRequired;
|
||||
return { ...schema, required };
|
||||
}
|
||||
*/
|
||||
|
||||
// schema 依赖
|
||||
// https://json-schema.org/understanding-json-schema/reference/object.html#schema-dependencies
|
||||
/*
|
||||
function withDependentSchema(
|
||||
schema,
|
||||
rootSchema,
|
||||
formData,
|
||||
dependencyKey,
|
||||
dependencyValue
|
||||
) {
|
||||
const { oneOf, ...dependentSchema } = retrieveSchema(
|
||||
dependencyValue,
|
||||
rootSchema,
|
||||
formData
|
||||
);
|
||||
schema = mergeSchemas(schema, dependentSchema);
|
||||
// Since it does not contain oneOf, we return the original schema.
|
||||
if (oneOf === undefined) {
|
||||
return schema;
|
||||
} if (!Array.isArray(oneOf)) {
|
||||
throw new Error(`invalid: it is some ${typeof oneOf} instead of an array`);
|
||||
}
|
||||
// Resolve $refs inside oneOf.
|
||||
const resolvedOneOf = oneOf.map(subschema => (subschema.hasOwnProperty('$ref')
|
||||
? resolveReference(subschema, rootSchema, formData)
|
||||
: subschema));
|
||||
return withExactlyOneSubschema(
|
||||
schema,
|
||||
rootSchema,
|
||||
formData,
|
||||
dependencyKey,
|
||||
resolvedOneOf
|
||||
);
|
||||
}
|
||||
|
||||
function withExactlyOneSubschema(
|
||||
schema,
|
||||
rootSchema,
|
||||
formData,
|
||||
dependencyKey,
|
||||
oneOf
|
||||
) {
|
||||
// eslint-disable-next-line array-callback-return,consistent-return
|
||||
const validSubschemas = oneOf.filter((subschema) => {
|
||||
if (!subschema.properties) {
|
||||
return false;
|
||||
}
|
||||
const { [dependencyKey]: conditionPropertySchema } = subschema.properties;
|
||||
if (conditionPropertySchema) {
|
||||
const conditionSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[dependencyKey]: conditionPropertySchema,
|
||||
},
|
||||
};
|
||||
|
||||
return isValid(conditionSchema, formData);
|
||||
}
|
||||
});
|
||||
if (validSubschemas.length !== 1) {
|
||||
console.warn(
|
||||
"ignoring oneOf in dependencies because there isn't exactly one subschema that is valid"
|
||||
);
|
||||
return schema;
|
||||
}
|
||||
const subschema = validSubschemas[0];
|
||||
const {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
[dependencyKey]: conditionPropertySchema,
|
||||
...dependentSubschema
|
||||
} = subschema.properties;
|
||||
const dependentSchema = { ...subschema, properties: dependentSubschema };
|
||||
return mergeSchemas(
|
||||
schema,
|
||||
retrieveSchema(dependentSchema, rootSchema, formData)
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
// resolve Schema - $ref
|
||||
// https://json-schema.org/understanding-json-schema/structuring.html#using-id-with-ref
|
||||
function resolveReference(schema, rootSchema, formData) {
|
||||
// Retrieve the referenced schema definition.
|
||||
const $refSchema = findSchemaDefinition(schema.$ref, rootSchema);
|
||||
// Drop the $ref property of the source schema.
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { $ref, ...localSchema } = schema;
|
||||
// Update referenced schema definition with local schema properties.
|
||||
return retrieveSchema(
|
||||
{ ...$refSchema, ...localSchema },
|
||||
rootSchema,
|
||||
formData
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// 深度递归合并 合并allOf的每2项
|
||||
function mergeSchemaAllOf(...args) {
|
||||
if (args.length < 2) return args[0];
|
||||
|
||||
let preVal = {};
|
||||
const copyArgs = [...args];
|
||||
while (copyArgs.length >= 2) {
|
||||
const obj1 = isObject(copyArgs[0]) ? copyArgs[0] : {};
|
||||
const obj2 = isObject(copyArgs[1]) ? copyArgs[1] : {};
|
||||
|
||||
preVal = Object.assign({}, obj1);
|
||||
Object.keys(obj2).reduce((acc, key) => {
|
||||
const left = obj1[key];
|
||||
const right = obj2[key];
|
||||
|
||||
// 左右一边为object
|
||||
if (isObject(left) || isObject(right)) {
|
||||
|
||||
// 两边同时为object
|
||||
if (isObject(left) && isObject(right)) {
|
||||
acc[key] = mergeSchemaAllOf(left, right);
|
||||
} else {
|
||||
// 其中一边为 object
|
||||
const [objTypeData, baseTypeData] = isObject(left) ? [left, right] : [right, left];
|
||||
|
||||
if (key === 'additionalProperties') {
|
||||
// 适配类型: 一边配置了对象一边没配置或者true false
|
||||
// {
|
||||
// additionalProperties: {
|
||||
// type: 'string',
|
||||
// },
|
||||
// additionalProperties: false
|
||||
// }
|
||||
acc[key] = baseTypeData === true ? objTypeData : false; // default false
|
||||
} else {
|
||||
acc[key] = objTypeData;
|
||||
}
|
||||
}
|
||||
// 一边为array
|
||||
} else if (Array.isArray(left) || Array.isArray(right)) {
|
||||
|
||||
// 同为数组取交集
|
||||
if (Array.isArray(left) && Array.isArray(right)) {
|
||||
|
||||
// 数组里面嵌套对象不支持 因为我不知道该怎么合并
|
||||
if (isObject(left[0]) || isObject(right[0])) {
|
||||
throw new Error('暂不支持如上数组对象元素合并');
|
||||
}
|
||||
|
||||
// 交集
|
||||
const intersectionArray = intersection([].concat(left), [].concat(right));
|
||||
|
||||
// 没有交集
|
||||
if (intersectionArray.length <= 0) {
|
||||
throw new Error('无法合并如上数据');
|
||||
}
|
||||
|
||||
if (intersectionArray.length === 0 && key === 'type') {
|
||||
// 自己取出值
|
||||
acc[key] = intersectionArray[0];
|
||||
} else {
|
||||
acc[key] = intersectionArray;
|
||||
}
|
||||
} else {
|
||||
// 其中一边为 Array
|
||||
// 查找包含关系
|
||||
const [arrayTypeData, baseTypeData] = Array.isArray(left) ? [left, right] : [right, left];
|
||||
// 空值直接合并另一边
|
||||
if (baseTypeData === undefined) {
|
||||
acc[key] = arrayTypeData;
|
||||
} else {
|
||||
if (!arrayTypeData.includes(baseTypeData)) {
|
||||
throw new Error('无法合并如下数据');
|
||||
}
|
||||
acc[key] = baseTypeData;
|
||||
}
|
||||
}
|
||||
} else if (left !== undefined && right !== undefined) {
|
||||
// 两边都不是 undefined - 基础数据类型 string number boolean...
|
||||
if (key === 'maxLength' || key === 'maximum' || key === 'maxItems' || key === 'exclusiveMaximum' || key === 'maxProperties') {
|
||||
acc[key] = Math.min(left, right);
|
||||
} else if (key === 'minLength' || key === 'minimum' || key === 'minItems' || key === 'exclusiveMinimum' || key === 'minProperties') {
|
||||
acc[key] = Math.max(left, right);
|
||||
} else if (key === 'multipleOf') {
|
||||
// 获取最小公倍数
|
||||
acc[key] = scm(left, right);
|
||||
} else {
|
||||
// if (left !== right) {
|
||||
// throw new Error('无法合并如下数据');
|
||||
// }
|
||||
acc[key] = left;
|
||||
}
|
||||
} else {
|
||||
// 一边为undefined
|
||||
acc[key] = left === undefined ? right : left;
|
||||
}
|
||||
return acc;
|
||||
}, preVal);
|
||||
|
||||
// 先进先出
|
||||
copyArgs.splice(0, 2, preVal);
|
||||
}
|
||||
|
||||
return preVal;
|
||||
}
|
||||
|
||||
// resolve Schema - allOf
|
||||
export function resolveAllOf(schema, rootSchema, formData) {
|
||||
// allOf item中可能存在 $ref
|
||||
const resolvedAllOfRefSchema = {
|
||||
...schema,
|
||||
allOf: schema.allOf.map(allOfItem => retrieveSchema(allOfItem, rootSchema, formData)),
|
||||
};
|
||||
|
||||
try {
|
||||
const { allOf, ...originProperties } = resolvedAllOfRefSchema;
|
||||
return mergeSchemaAllOf(originProperties, ...allOf);
|
||||
} catch (e) {
|
||||
console.error(`无法合并allOf,丢弃allOf配置继续渲染: \n${e}`);
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { allOf: errAllOf, ...resolvedSchemaWithoutAllOf } = resolvedAllOfRefSchema;
|
||||
return resolvedSchemaWithoutAllOf;
|
||||
}
|
||||
}
|
||||
|
||||
// resolve Schema
|
||||
function resolveSchema(schema, rootSchema = {}, formData = {}) {
|
||||
// allOf 、$ref、dependencies 可能被同时配置
|
||||
|
||||
// allOf
|
||||
if (schema.hasOwnProperty('allOf')) {
|
||||
schema = resolveAllOf(schema, rootSchema, formData);
|
||||
}
|
||||
|
||||
// $ref
|
||||
if (schema.hasOwnProperty('$ref')) {
|
||||
schema = resolveReference(schema, rootSchema, formData);
|
||||
}
|
||||
|
||||
// dependencies
|
||||
/*
|
||||
if (schema.hasOwnProperty('dependencies')) {
|
||||
const resolvedSchema = resolveDependencies(schema, rootSchema, formData);
|
||||
schema = retrieveSchema(resolvedSchema, rootSchema, formData);
|
||||
}
|
||||
*/
|
||||
|
||||
// additionalProperties
|
||||
/*
|
||||
const hasAdditionalProperties = schema.hasOwnProperty('additionalProperties') && schema.additionalProperties !== false;
|
||||
if (hasAdditionalProperties) {
|
||||
return stubExistingAdditionalProperties(
|
||||
schema,
|
||||
rootSchema,
|
||||
formData
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
// 这个函数将为formData中的每个键创建新的“属性”项
|
||||
// 查找到附加属性统一到properties[key]格式 并且打上标准
|
||||
/* function stubExistingAdditionalProperties(
|
||||
schema,
|
||||
rootSchema = {},
|
||||
formData = {}
|
||||
) {
|
||||
// clone the schema so we don't ruin the consumer's original
|
||||
schema = {
|
||||
...schema,
|
||||
properties: { ...schema.properties },
|
||||
};
|
||||
|
||||
Object.keys(formData).forEach((key) => {
|
||||
if (schema.properties.hasOwnProperty(key)) {
|
||||
// No need to stub, our schema already has the property
|
||||
return;
|
||||
}
|
||||
|
||||
let additionalProperties;
|
||||
if (schema.additionalProperties.hasOwnProperty('$ref')) {
|
||||
additionalProperties = retrieveSchema(
|
||||
{ $ref: schema.additionalProperties.$ref },
|
||||
rootSchema,
|
||||
formData
|
||||
);
|
||||
} else if (schema.additionalProperties.hasOwnProperty('type')) {
|
||||
additionalProperties = { ...schema.additionalProperties };
|
||||
} else {
|
||||
additionalProperties = { type: guessType(formData[key]) };
|
||||
}
|
||||
|
||||
// The type of our new key should match the additionalProperties value;
|
||||
// 把追加进去的属性设置为标准 schema格式,同时打上标志
|
||||
schema.properties[key] = additionalProperties;
|
||||
// Set our additional property flag so we know it was dynamically added
|
||||
schema.properties[key][ADDITIONAL_PROPERTY_FLAG] = true;
|
||||
});
|
||||
|
||||
return schema;
|
||||
} */
|
||||
|
||||
// 索引当前节点
|
||||
export default function retrieveSchema(schema, rootSchema = {}, formData = {}) {
|
||||
if (!isObject(schema)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return resolveSchema(schema, rootSchema, formData);
|
||||
}
|
@ -0,0 +1,335 @@
|
||||
import Ajv from 'ajv';
|
||||
import i18n from '../i18n';
|
||||
import retrieveSchema from './retriev';
|
||||
|
||||
import {
|
||||
isObject, deepEquals
|
||||
} from '../utils';
|
||||
import { getUserErrOptions } from '../formUtils';
|
||||
|
||||
let ajv = createAjvInstance();
|
||||
|
||||
let formerCustomFormats = null;
|
||||
let formerMetaSchema = null;
|
||||
|
||||
// 创建实例
|
||||
function createAjvInstance() {
|
||||
const ajvInstance = new Ajv({
|
||||
errorDataPath: 'property',
|
||||
allErrors: true,
|
||||
multipleOfPrecision: 8,
|
||||
schemaId: 'auto',
|
||||
unknownFormats: 'ignore',
|
||||
});
|
||||
|
||||
// 添加base-64 format
|
||||
ajvInstance.addFormat(
|
||||
'data-url',
|
||||
/^data:([a-z]+\/[a-z0-9-+.]+)?;(?:name=(.*);)?base64,(.*)$/
|
||||
);
|
||||
|
||||
// 添加color format
|
||||
ajvInstance.addFormat(
|
||||
'color',
|
||||
// eslint-disable-next-line max-len
|
||||
/^(#?([0-9A-Fa-f]{3}){1,2}\b|aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow|(rgb\(\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*,\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*,\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*\))|(rgb\(\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*\)))$/
|
||||
);
|
||||
return ajvInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将错误输出从ajv转换为jsonschema使用的格式
|
||||
* At some point, components should be updated to support ajv.
|
||||
*/
|
||||
function transformAjvErrors(errors = []) {
|
||||
if (errors === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return errors.map((e) => {
|
||||
const {
|
||||
dataPath, keyword, message, params, schemaPath
|
||||
} = e;
|
||||
const property = `${dataPath}`;
|
||||
|
||||
// put data in expected format
|
||||
return {
|
||||
name: keyword,
|
||||
property,
|
||||
message,
|
||||
params, // specific to ajv
|
||||
stack: `${property} ${message}`.trim(),
|
||||
schemaPath,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 schema校验formData并返回错误信息
|
||||
* @param formData 校验的数据
|
||||
* @param schema
|
||||
* @param transformErrors function - 转换错误, 如个性化的配置
|
||||
* @param additionalMetaSchemas 数组 添加 ajv metaSchema
|
||||
* @param customFormats 添加 ajv 自定义 formats
|
||||
* @returns {{errors: ([]|{stack: string, schemaPath: *, name: *, property: string, message: *, params: *}[])}}
|
||||
*/
|
||||
export function ajvValidateFormData({
|
||||
formData,
|
||||
schema,
|
||||
transformErrors,
|
||||
additionalMetaSchemas = [],
|
||||
customFormats = {}
|
||||
} = {}) {
|
||||
const hasNewMetaSchemas = !deepEquals(formerMetaSchema, additionalMetaSchemas);
|
||||
const hasNewFormats = !deepEquals(formerCustomFormats, customFormats);
|
||||
|
||||
// 变更了 Meta或者调整了format配置重置新的实例
|
||||
if (hasNewMetaSchemas || hasNewFormats) {
|
||||
ajv = createAjvInstance();
|
||||
}
|
||||
|
||||
// 添加更多要验证的模式
|
||||
if (
|
||||
additionalMetaSchemas
|
||||
&& hasNewMetaSchemas
|
||||
&& Array.isArray(additionalMetaSchemas)
|
||||
) {
|
||||
ajv.addMetaSchema(additionalMetaSchemas);
|
||||
formerMetaSchema = additionalMetaSchemas;
|
||||
}
|
||||
|
||||
// 注册自定义的 formats - 没有变更只会注册一次 - 否则重新创建实例
|
||||
if (customFormats && hasNewFormats && isObject(customFormats)) {
|
||||
Object.keys(customFormats).forEach((formatName) => {
|
||||
ajv.addFormat(formatName, customFormats[formatName]);
|
||||
});
|
||||
|
||||
formerCustomFormats = customFormats;
|
||||
}
|
||||
|
||||
let validationError = null;
|
||||
try {
|
||||
ajv.validate(schema, formData);
|
||||
} catch (err) {
|
||||
validationError = err;
|
||||
}
|
||||
|
||||
// ajv 默认多语言处理
|
||||
i18n.getCurrentLocalize()(ajv.errors);
|
||||
|
||||
let errors = transformAjvErrors(ajv.errors);
|
||||
|
||||
// 清除错误
|
||||
ajv.errors = null;
|
||||
|
||||
// 处理异常
|
||||
const noProperMetaSchema = validationError
|
||||
&& validationError.message
|
||||
&& typeof validationError.message === 'string'
|
||||
&& validationError.message.includes('no schema with key or ref ');
|
||||
|
||||
if (noProperMetaSchema) {
|
||||
errors = [
|
||||
...errors,
|
||||
{
|
||||
stack: validationError.message,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// 转换错误, 如传入自定义的错误
|
||||
if (typeof transformErrors === 'function') {
|
||||
errors = transformErrors(errors);
|
||||
}
|
||||
|
||||
return {
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
||||
// 校验formData 并转换错误信息
|
||||
export function validateFormDataAndTransformMsg({
|
||||
formData,
|
||||
schema,
|
||||
uiSchema,
|
||||
transformErrors,
|
||||
additionalMetaSchemas = [],
|
||||
customFormats = {},
|
||||
errorSchema = {},
|
||||
required = false,
|
||||
propPath = '',
|
||||
isOnlyFirstError = true, // 只取第一条错误信息
|
||||
} = {}) {
|
||||
// 是否过滤根节点错误 固定只能根
|
||||
const filterRootNodeError = true;
|
||||
|
||||
// 校验required信息 isEmpty 校验
|
||||
// 如果数组类型针对配置了 format 的特殊处理
|
||||
const emptyArray = (schema.type === 'array' && Array.isArray(formData) && formData.length === 0);
|
||||
const isEmpty = formData === undefined || emptyArray;
|
||||
|
||||
if (required) {
|
||||
if (isEmpty) {
|
||||
const requireErrObj = {
|
||||
keyword: 'required',
|
||||
params: {
|
||||
missingProperty: propPath
|
||||
}
|
||||
};
|
||||
|
||||
// 用户设置校验信息
|
||||
const errSchemaMsg = getUserErrOptions({
|
||||
schema,
|
||||
uiSchema,
|
||||
errorSchema
|
||||
}).required;
|
||||
if (errSchemaMsg) {
|
||||
requireErrObj.message = errSchemaMsg;
|
||||
} else {
|
||||
// 处理多语言require提示信息 (ajv 修改原引用)
|
||||
i18n.getCurrentLocalize()([requireErrObj]);
|
||||
}
|
||||
return [requireErrObj];
|
||||
}
|
||||
} else if (isEmpty && !emptyArray) {
|
||||
// 非required 为空 校验通过
|
||||
return [];
|
||||
}
|
||||
|
||||
// 校验ajv错误信息
|
||||
let ajvErrors = ajvValidateFormData({
|
||||
formData,
|
||||
schema,
|
||||
transformErrors,
|
||||
additionalMetaSchemas,
|
||||
customFormats,
|
||||
}).errors;
|
||||
|
||||
// 过滤顶级错误
|
||||
if (filterRootNodeError) {
|
||||
ajvErrors = ajvErrors.filter(
|
||||
item => (item.property === ''
|
||||
&& (!item.schemaPath.includes('#/anyOf/') && !item.schemaPath.includes('#/oneOf/')))
|
||||
|| item.name === 'additionalProperties'
|
||||
);
|
||||
}
|
||||
|
||||
const userErrOptions = getUserErrOptions({
|
||||
schema,
|
||||
uiSchema,
|
||||
errorSchema
|
||||
});
|
||||
|
||||
return (isOnlyFirstError && ajvErrors.length > 0 ? [ajvErrors[0]] : ajvErrors).reduce((preErrors, errorItem) => {
|
||||
// 优先获取 errorSchema 配置
|
||||
errorItem.message = userErrOptions[errorItem.name] !== undefined ? userErrOptions[errorItem.name] : errorItem.message;
|
||||
preErrors.push(errorItem);
|
||||
return preErrors;
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据模式验证数据,如果数据有效则返回true,否则返回* false。如果模式无效,那么这个函数将返回* false。
|
||||
* @param schema
|
||||
* @param data
|
||||
* @returns {boolean|PromiseLike<any>}
|
||||
*/
|
||||
export function isValid(schema, data) {
|
||||
try {
|
||||
return ajv.validate(schema, data);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ajv valida
|
||||
export function ajvValid(schema, data) {
|
||||
return ajv.validate(schema, data);
|
||||
}
|
||||
|
||||
// 如果查找不到
|
||||
// return -1
|
||||
export function getMatchingIndex(formData, options, rootSchema, haveAllFields = false) {
|
||||
// eslint-disable-next-line no-plusplus
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
const option = retrieveSchema(options[i], rootSchema, formData);
|
||||
|
||||
// If the schema describes an object then we need to add slightly more
|
||||
// strict matching to the schema, because unless the schema uses the
|
||||
// "requires" keyword, an object will match the schema as long as it
|
||||
// doesn't have matching keys with a conflicting type. To do this we use an
|
||||
// "anyOf" with an array of requires. This augmentation expresses that the
|
||||
// schema should match if any of the keys in the schema are present on the
|
||||
// object and pass validation.
|
||||
if (option.properties) {
|
||||
// Create an "anyOf" schema that requires at least one of the keys in the
|
||||
// "properties" object
|
||||
const requiresAnyOf = {
|
||||
// 如果后代节点存在 $ref 需要正常引用
|
||||
...(rootSchema.definitions ? {
|
||||
definitions: rootSchema.definitions
|
||||
} : {}),
|
||||
anyOf: Object.keys(option.properties).map(key => ({
|
||||
required: [key],
|
||||
})),
|
||||
};
|
||||
|
||||
let augmentedSchema;
|
||||
|
||||
// If the "anyOf" keyword already exists, wrap the augmentation in an "allOf"
|
||||
if (option.anyOf) {
|
||||
// Create a shallow clone of the option
|
||||
const { ...shallowClone } = option;
|
||||
|
||||
if (!shallowClone.allOf) {
|
||||
shallowClone.allOf = [];
|
||||
} else {
|
||||
// If "allOf" already exists, shallow clone the array
|
||||
shallowClone.allOf = shallowClone.allOf.slice();
|
||||
}
|
||||
|
||||
shallowClone.allOf.push(requiresAnyOf);
|
||||
|
||||
augmentedSchema = shallowClone;
|
||||
} else {
|
||||
augmentedSchema = Object.assign({}, option, requiresAnyOf);
|
||||
}
|
||||
|
||||
// Remove the "required" field as it's likely that not all fields have
|
||||
// been filled in yet, which will mean that the schema is not valid
|
||||
|
||||
// 如果编辑回填数据的场景 可直接使用 required 判断
|
||||
if (!haveAllFields) delete augmentedSchema.required;
|
||||
|
||||
|
||||
if (isValid(augmentedSchema, formData)) {
|
||||
return i;
|
||||
}
|
||||
} else if (isValid(options[i], formData)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试查找const 配置
|
||||
if (options[0] && options[0].properties) {
|
||||
const constProperty = Object.keys(options[0].properties).find(k => options[0].properties[k].const);
|
||||
if (constProperty) {
|
||||
// eslint-disable-next-line no-plusplus
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
if (
|
||||
options[i].properties
|
||||
&& options[i].properties[constProperty]
|
||||
&& options[i].properties[constProperty].const === formData[constProperty]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// oneOf anyOf 通过formData的值来找到当前匹配项索引
|
||||
export function getMatchingOption(formData, options, rootSchema, haveAllFields = false) {
|
||||
const index = getMatchingIndex(formData, options, rootSchema, haveAllFields);
|
||||
return index === -1 ? 0 : index;
|
||||
}
|
@ -0,0 +1,322 @@
|
||||
.genFromComponent {
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
a,
|
||||
p,
|
||||
li,
|
||||
ul,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
p {
|
||||
font-size: 14px;
|
||||
}
|
||||
.genFormIcon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
vertical-align: top;
|
||||
}
|
||||
.genFormBtn {
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
border: 1px solid #dcdfe6;
|
||||
color: #606266;
|
||||
-webkit-appearance: none;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
margin: 0;
|
||||
transition: .1s;
|
||||
font-weight: 500;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
padding: 12px 20px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
&.is-plain{
|
||||
&:focus, &:hover {
|
||||
background: #fff;
|
||||
border-color: #409eff;
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
.hiddenWidget {
|
||||
display: none;
|
||||
}
|
||||
.fieldGroupWrap+.fieldGroupWrap {
|
||||
.fieldGroupWrap_title {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
.fieldGroupWrap_title {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
line-height: 26px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
border: 0;
|
||||
}
|
||||
.fieldGroupWrap_des {
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
margin-bottom: 10px;
|
||||
color: rgb(153, 153, 153);
|
||||
}
|
||||
.genFromWidget_des {
|
||||
padding: 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 2px;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
color: #999;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.formItemErrorBox {
|
||||
margin: 0 auto;
|
||||
color: #ff5757;
|
||||
padding-top: 2px;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
display: -webkit-box !important;
|
||||
line-height: 16px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
white-space: normal;
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
.genFormIcon-qs {
|
||||
fill: #606266;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-left: 2px;
|
||||
margin-top: -2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.genFormItemRequired {
|
||||
&:before {
|
||||
content: "*";
|
||||
color: #f56c6c;
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
/* oneOf anyOf - appendCombining_box*/
|
||||
.appendCombining_box {
|
||||
margin-bottom: 22px;
|
||||
.appendCombining_box {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
padding: 10px;
|
||||
background: rgba(242,242,242,0.8);
|
||||
box-shadow: 0 3px 1px -2px rgba(0,0,0,0.2), 0 0 3px 1px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
/* validateWidget 单独的校验不属于输入框的*/
|
||||
.validateWidget {
|
||||
margin-bottom: 0 !important;
|
||||
width: 100% !important;
|
||||
flex-basis: 100% !important;
|
||||
padding: 0 !important;
|
||||
.formItemErrorBox {
|
||||
padding: 5px 0;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
/* type array */
|
||||
.arrayField:not(.genFormItem){
|
||||
margin-bottom: 22px;
|
||||
.arrayField {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
.arrayOrderList {
|
||||
background: rgba(242,242,242,0.8);
|
||||
box-shadow: 0 3px 1px -2px rgba(0,0,0,0.2), 0 0 3px 1px rgba(0,0,0,0.1);
|
||||
}
|
||||
.arrayOrderList_item {
|
||||
position: relative;
|
||||
padding: 25px 10px 12px;
|
||||
border-radius: 2px;
|
||||
margin-bottom: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.arrayOrderList_bottomAddBtn {
|
||||
text-align: right;
|
||||
padding: 15px 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.bottomAddBtn {
|
||||
width: 40%;
|
||||
min-width: 10px;
|
||||
max-width: 180px;
|
||||
/*box-shadow: 0 3px 1px -2px rgba(0,0,0,0.2), 0 2px 2px 1px rgba(0,0,0,0.1);*/
|
||||
}
|
||||
.arrayListItem_content {
|
||||
padding-top: 15px;
|
||||
flex: 1;
|
||||
margin: 0 auto;
|
||||
box-shadow: 0 -1px 0 0 rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.arrayListItem_index, .arrayListItem_operateTool{
|
||||
position: absolute;
|
||||
height: 25px;
|
||||
}
|
||||
.arrayListItem_index {
|
||||
top: 6px;
|
||||
line-height: 18px;
|
||||
height: 18px;
|
||||
padding: 0 6px;
|
||||
background-color: rgba(0,0,0,.28);
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.arrayListItem_operateTool {
|
||||
width: 75px;
|
||||
right: 9px;
|
||||
top: -1px;
|
||||
text-align: right;
|
||||
font-size: 0;
|
||||
}
|
||||
.arrayListItem_btn {
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
padding: 6px;
|
||||
margin: 0;
|
||||
font-size: 0;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
outline: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
background: transparent;
|
||||
color: #666;
|
||||
&:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
&[disabled] {
|
||||
color: #999999;
|
||||
opacity: 0.3 !important;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
.arrayListItem_orderBtn-top {
|
||||
background-color: #f0f9eb;
|
||||
}
|
||||
.arrayListItem_orderBtn-bottom {
|
||||
background-color: #f0f9eb;
|
||||
}
|
||||
.arrayListItem_btn-delete {
|
||||
background-color: #fef0f0;
|
||||
}
|
||||
|
||||
.formFooter_item {
|
||||
text-align: right;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
&.formInlineFooter {
|
||||
&>.fieldGroupWrap{
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/*.arrayListItem_content .genFormItem {
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}*/
|
||||
&.formInline {
|
||||
/*.genFormItem {
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
vertical-align: top;
|
||||
}*/
|
||||
.validateWidget {
|
||||
margin-right: 0;
|
||||
}
|
||||
.formFooter_item {
|
||||
border-top: none;
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*popover 弹出可能appendtobody*/
|
||||
.genFromWidget_des_mini {
|
||||
font-size: 14px;
|
||||
line-height: 1.5715;
|
||||
}
|
||||
|
||||
/* 适配多列布局 */
|
||||
:root {
|
||||
--width-column-gutter : 10px;
|
||||
}
|
||||
|
||||
.layoutColumn {
|
||||
.layoutColumn_w100 {
|
||||
width: 100% !important;
|
||||
flex-basis: 100% !important;;
|
||||
}
|
||||
.fieldGroupWrap_box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
align-content: flex-start;
|
||||
&>div {
|
||||
width: 100%;
|
||||
flex-basis: 100%;
|
||||
}
|
||||
&>.genFormItem{
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
padding-right: var(--width-column-gutter);
|
||||
}
|
||||
}
|
||||
&.layoutColumn-1 {
|
||||
.fieldGroupWrap_box>.genFormItem {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
&.layoutColumn-2 {
|
||||
.fieldGroupWrap_box>.genFormItem {
|
||||
width: 50%;
|
||||
flex-basis: 50%;
|
||||
}
|
||||
}
|
||||
&.layoutColumn-3 {
|
||||
.fieldGroupWrap_box>.genFormItem{
|
||||
width: 33.333%;
|
||||
flex-basis: 33.333%;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,346 @@
|
||||
/**
|
||||
* Created by Liu.Jun on 2020/4/17 17:05.
|
||||
*/
|
||||
|
||||
// is object
|
||||
export function isObject(object) {
|
||||
return Object.prototype.toString.call(object) === '[object Object]';
|
||||
}
|
||||
|
||||
// is arguments
|
||||
function isArguments(object) {
|
||||
return Object.prototype.toString.call(object) === '[object Arguments]';
|
||||
}
|
||||
|
||||
// 定义的数据推导出schema 类型
|
||||
export const guessType = function guessType(value) {
|
||||
if (Array.isArray(value)) {
|
||||
return 'array';
|
||||
} if (typeof value === 'string') {
|
||||
return 'string';
|
||||
} if (value == null) {
|
||||
return 'null';
|
||||
} if (typeof value === 'boolean') {
|
||||
return 'boolean';
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
} if (!isNaN(value)) {
|
||||
return 'number';
|
||||
} if (typeof value === 'object') {
|
||||
return 'object';
|
||||
}
|
||||
// Default to string if we can't figure it out
|
||||
return 'string';
|
||||
};
|
||||
|
||||
export function union(arr1, arr2) {
|
||||
return [...new Set([...arr1, ...arr2])];
|
||||
}
|
||||
|
||||
// Recursively merge deeply nested schemas.
|
||||
// The difference between mergeSchemas and mergeObjects
|
||||
// is that mergeSchemas only concats arrays for
|
||||
// values under the "required" keyword, and when it does,
|
||||
// it doesn't include duplicate values.
|
||||
export function mergeSchemas(obj1, obj2) {
|
||||
const acc = Object.assign({}, obj1); // Prevent mutation of source object.
|
||||
// eslint-disable-next-line no-shadow
|
||||
return Object.keys(obj2).reduce((acc, key) => {
|
||||
const left = obj1 ? obj1[key] : {};
|
||||
const right = obj2[key];
|
||||
if (obj1 && obj1.hasOwnProperty(key) && isObject(right)) {
|
||||
acc[key] = mergeSchemas(left, right);
|
||||
} else if (
|
||||
obj1
|
||||
&& obj2
|
||||
&& (getSchemaType(obj1) === 'object' || getSchemaType(obj2) === 'object')
|
||||
&& key === 'required'
|
||||
&& Array.isArray(left)
|
||||
&& Array.isArray(right)
|
||||
) {
|
||||
// Don't include duplicate values when merging
|
||||
// "required" fields.
|
||||
acc[key] = union(left, right);
|
||||
} else {
|
||||
acc[key] = right;
|
||||
}
|
||||
return acc;
|
||||
}, acc);
|
||||
}
|
||||
|
||||
// 合并对象数据
|
||||
export function mergeObjects(obj1, obj2, concatArrays = false) {
|
||||
// Recursively merge deeply nested objects.
|
||||
const preAcc = Object.assign({}, obj1); // Prevent mutation of source object.
|
||||
if (!isObject(obj2)) return preAcc;
|
||||
|
||||
return Object.keys(obj2).reduce((acc, key) => {
|
||||
const left = obj1 ? obj1[key] : {};
|
||||
const right = obj2[key];
|
||||
if (obj1 && obj1.hasOwnProperty(key) && isObject(right)) {
|
||||
acc[key] = mergeObjects(left, right, concatArrays);
|
||||
} else if (concatArrays && Array.isArray(left) && Array.isArray(right)) {
|
||||
acc[key] = left.concat(right);
|
||||
} else {
|
||||
acc[key] = right;
|
||||
}
|
||||
return acc;
|
||||
}, preAcc);
|
||||
}
|
||||
|
||||
// 获取给定 schema 类型。
|
||||
export function getSchemaType(schema) {
|
||||
const { type } = schema;
|
||||
|
||||
// 通过const 申明的常量 做类型推断
|
||||
if (!type && schema.const) {
|
||||
return guessType(schema.const);
|
||||
}
|
||||
|
||||
// 枚举默认字符串
|
||||
if (!type && schema.enum) {
|
||||
return 'string';
|
||||
}
|
||||
|
||||
// items 推断为 array 类型
|
||||
if (!type && (schema.items)) {
|
||||
return 'array';
|
||||
}
|
||||
|
||||
// anyOf oneOf 不申明 type 字段
|
||||
if (!type && (schema.properties || schema.additionalProperties)) {
|
||||
return 'object';
|
||||
}
|
||||
|
||||
if (type instanceof Array && type.length === 2 && type.includes('null')) {
|
||||
return type.find(curType => curType !== 'null');
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
// 深度相等对比
|
||||
export function deepEquals(a, b, ca = [], cb = []) {
|
||||
// Partially extracted from node-deeper and adapted to exclude comparison
|
||||
// checks for functions.
|
||||
// https://github.com/othiym23/node-deeper
|
||||
if (a === b) {
|
||||
return true;
|
||||
} if (typeof a === 'function' || typeof b === 'function') {
|
||||
// Assume all functions are equivalent
|
||||
// see https://github.com/mozilla-services/react-jsonschema-form/issues/255
|
||||
return true;
|
||||
} if (typeof a !== 'object' || typeof b !== 'object') {
|
||||
return false;
|
||||
} if (a === null || b === null) {
|
||||
return false;
|
||||
} if (a instanceof Date && b instanceof Date) {
|
||||
return a.getTime() === b.getTime();
|
||||
} if (a instanceof RegExp && b instanceof RegExp) {
|
||||
return (
|
||||
a.source === b.source
|
||||
&& a.global === b.global
|
||||
&& a.multiline === b.multiline
|
||||
&& a.lastIndex === b.lastIndex
|
||||
&& a.ignoreCase === b.ignoreCase
|
||||
);
|
||||
} if (isArguments(a) || isArguments(b)) {
|
||||
if (!(isArguments(a) && isArguments(b))) {
|
||||
return false;
|
||||
}
|
||||
const slice = Array.prototype.slice;
|
||||
return deepEquals(slice.call(a), slice.call(b), ca, cb);
|
||||
}
|
||||
if (a.constructor !== b.constructor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ka = Object.keys(a);
|
||||
const kb = Object.keys(b);
|
||||
// don't bother with stack acrobatics if there's nothing there
|
||||
if (ka.length === 0 && kb.length === 0) {
|
||||
return true;
|
||||
}
|
||||
if (ka.length !== kb.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let cal = ca.length;
|
||||
// eslint-disable-next-line no-plusplus
|
||||
while (cal--) {
|
||||
if (ca[cal] === a) {
|
||||
return cb[cal] === b;
|
||||
}
|
||||
}
|
||||
ca.push(a);
|
||||
cb.push(b);
|
||||
|
||||
ka.sort();
|
||||
kb.sort();
|
||||
// eslint-disable-next-line no-plusplus
|
||||
for (let j = ka.length - 1; j >= 0; j--) {
|
||||
if (ka[j] !== kb[j]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let key;
|
||||
// eslint-disable-next-line no-plusplus
|
||||
for (let k = ka.length - 1; k >= 0; k--) {
|
||||
key = ka[k];
|
||||
if (!deepEquals(a[key], b[key], ca, cb)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ca.pop();
|
||||
cb.pop();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 只保证同时生成不重复
|
||||
export const genId = (function genIdFn() {
|
||||
let preKey = `${+new Date()}`;
|
||||
let key = 0;
|
||||
return () => {
|
||||
const curTimestamp = `${+new Date()}`;
|
||||
if (curTimestamp === preKey) {
|
||||
key += 1;
|
||||
} else {
|
||||
// 重置 key
|
||||
key = 0;
|
||||
}
|
||||
|
||||
preKey = curTimestamp;
|
||||
return `${preKey}x${key}`;
|
||||
};
|
||||
}());
|
||||
|
||||
// 空对象
|
||||
export function isEmptyObject(obj) {
|
||||
if (!obj) return true;
|
||||
|
||||
for (const key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 过滤和转换对象的key
|
||||
export function filterObject(obj, filterFn) {
|
||||
return Object.entries(obj).reduce((preVal, [key, value]) => {
|
||||
const newKey = filterFn(key, value);
|
||||
if (undefined !== newKey) {
|
||||
preVal[newKey] = value;
|
||||
}
|
||||
return preVal;
|
||||
}, {});
|
||||
}
|
||||
|
||||
const f = s => `0${s}`.substr(-2);
|
||||
export function parseDateString(dateString, includeTime = true) {
|
||||
if (!dateString) {
|
||||
return {
|
||||
year: -1,
|
||||
month: -1,
|
||||
day: -1,
|
||||
hour: includeTime ? -1 : 0,
|
||||
minute: includeTime ? -1 : 0,
|
||||
second: includeTime ? -1 : 0,
|
||||
};
|
||||
}
|
||||
const date = new Date(dateString);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
throw new Error(`Unable to parse date ${dateString}`);
|
||||
}
|
||||
return {
|
||||
year: date.getFullYear(),
|
||||
month: f(date.getMonth() + 1), // oh you, javascript.
|
||||
day: f(date.getDate()),
|
||||
hour: f(includeTime ? date.getHours() : 0),
|
||||
minute: f(includeTime ? date.getMinutes() : 0),
|
||||
second: f(includeTime ? date.getSeconds() : 0),
|
||||
};
|
||||
}
|
||||
|
||||
export function toDateString(
|
||||
{
|
||||
year, month, day, hour = 0, minute = 0, second = 0
|
||||
},
|
||||
time = true
|
||||
) {
|
||||
const utcTime = Date.UTC(year, month - 1, day, hour, minute, second);
|
||||
const datetime = new Date(utcTime).toJSON();
|
||||
return time ? datetime : datetime.slice(0, 10);
|
||||
}
|
||||
|
||||
export function pad(num, size) {
|
||||
let s = String(num);
|
||||
while (s.length < size) {
|
||||
s = `0${s}`;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// dataUrl 转 Blob文件对象
|
||||
export function dataURItoBlob(dataURI) {
|
||||
// Split metadata from data
|
||||
const splitted = dataURI.split(',');
|
||||
// Split params
|
||||
const params = splitted[0].split(';');
|
||||
// Get mime-type from params
|
||||
const type = params[0].replace('data:', '');
|
||||
// Filter the name property from params
|
||||
const properties = params.filter(param => param.split('=')[0] === 'name');
|
||||
// Look for the name and use unknown if no name property.
|
||||
let name;
|
||||
if (properties.length !== 1) {
|
||||
name = 'unknown';
|
||||
} else {
|
||||
// Because we filtered out the other property,
|
||||
// we only have the name case here.
|
||||
name = properties[0].split('=')[1];
|
||||
}
|
||||
|
||||
// Built the Uint8Array Blob parameter from the base64 string.
|
||||
const binary = atob(splitted[1]);
|
||||
const array = [];
|
||||
// eslint-disable-next-line no-plusplus
|
||||
for (let i = 0; i < binary.length; i++) {
|
||||
array.push(binary.charCodeAt(i));
|
||||
}
|
||||
// Create the blob object
|
||||
const blob = new window.Blob([new Uint8Array(array)], { type });
|
||||
|
||||
return { blob, name };
|
||||
}
|
||||
|
||||
// 字符串首字母小写
|
||||
export function lowerCase(str) {
|
||||
if (undefined === str) return str;
|
||||
return String(str).replace(/^./, s => s.toLocaleLowerCase());
|
||||
}
|
||||
|
||||
// 最大公约数
|
||||
export function gcd(a, b) {
|
||||
if (b === 0) return a;
|
||||
return gcd(b, a % b);
|
||||
}
|
||||
|
||||
// 最小公倍数
|
||||
export function scm(a, b) {
|
||||
return (a * b) / gcd(a, b);
|
||||
}
|
||||
|
||||
// 打开新页面
|
||||
export function openNewPage(url, target = '_blank') {
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.target = target;
|
||||
a.href = url;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Created by Liu.Jun on 2020/4/25 14:45.
|
||||
*/
|
||||
|
||||
import { defineComponent, h, resolveComponent as _resolveComponent } from 'vue';
|
||||
|
||||
export {
|
||||
nodePath2ClassName, isRootNodePath, computedCurPath, getPathVal, path2prop
|
||||
} from './vueUtils';
|
||||
|
||||
// 内部使用 . ,配置数据key不能出现.
|
||||
const pathSeparator = '.';
|
||||
|
||||
// 删除当前path值
|
||||
export function deletePathVal(vueData, name) {
|
||||
delete vueData[name];
|
||||
}
|
||||
|
||||
// 设置当前path值
|
||||
export function setPathVal(obj, path, value) {
|
||||
const pathArr = path.split(pathSeparator);
|
||||
for (let i = 0; i < pathArr.length; i += 1) {
|
||||
if (pathArr.length - i < 2) {
|
||||
// 倒数第一个数据
|
||||
obj[pathArr[pathArr.length - 1]] = value;
|
||||
break;
|
||||
}
|
||||
obj = obj[pathArr[i]];
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveComponent(component) {
|
||||
if (typeof component === 'string') return _resolveComponent(component);
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
// 转换antdv、naive等非moduleValue的v-model组件
|
||||
export const modelValueComponent = (component, {
|
||||
model = 'value'
|
||||
} = {}) => defineComponent({
|
||||
inheritAttrs: false,
|
||||
setup(props, { attrs, slots }) {
|
||||
return () => {
|
||||
const {
|
||||
modelValue: value,
|
||||
'onUpdate:modelValue': onUpdateValue,
|
||||
...otherAttrs
|
||||
} = attrs;
|
||||
|
||||
// eg: 'a-input'
|
||||
return h(resolveComponent(component), {
|
||||
[model]: value,
|
||||
[`onUpdate:${model}`]: onUpdateValue,
|
||||
...otherAttrs
|
||||
}, slots);
|
||||
};
|
||||
}
|
||||
});
|
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Created by Liu.Jun on 2020/4/25 14:45.
|
||||
*/
|
||||
|
||||
|
||||
// 内部使用 . ,配置数据key不能出现.
|
||||
const pathSeparator = '.';
|
||||
|
||||
// nodePath 转css类名
|
||||
export function nodePath2ClassName(path) {
|
||||
const rootPathName = '__pathRoot';
|
||||
return path ? `${rootPathName}.${path}`.replace(/\./g, '_') : rootPathName;
|
||||
}
|
||||
|
||||
// 是否为根节点
|
||||
export function isRootNodePath(path) {
|
||||
return path === '';
|
||||
}
|
||||
|
||||
// 计算当前节点path
|
||||
export function computedCurPath(prePath, curKey) {
|
||||
return prePath === '' ? curKey : [prePath, curKey].join(pathSeparator);
|
||||
}
|
||||
|
||||
// 获取当前path值
|
||||
export function getPathVal(obj, path, leftDeviation = 0) {
|
||||
const pathArr = path.split(pathSeparator);
|
||||
|
||||
for (let i = 0; i < pathArr.length - leftDeviation; i += 1) {
|
||||
// 错误路径或者undefined中断查找
|
||||
if (obj === undefined) return undefined;
|
||||
obj = pathArr[i] === '' ? obj : obj[pathArr[i]];
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
// path 等于props
|
||||
export function path2prop(path) {
|
||||
return path;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
/**/node_modules/*
|
||||
/**/dist/*
|
||||
/**/*.css
|
@ -0,0 +1,287 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.12.2](https://github.com/lljj-x/vue-json-schema-form/compare/v1.12.1...v1.12.2) (2022-04-11)
|
||||
|
||||
**Note:** Version bump only for package @lljj/vue3-form-core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.12.1](https://github.com/lljj-x/vue-json-schema-form/compare/v1.12.0...v1.12.1) (2022-04-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **lib:** anyOf onChange 参数 ([60cac99](https://github.com/lljj-x/vue-json-schema-form/commit/60cac995779c1eeb90b23f2cfeeb5deb8c350feb)), closes [#166](https://github.com/lljj-x/vue-json-schema-form/issues/166)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.12.0](https://github.com/lljj-x/vue-json-schema-form/compare/v1.11.0...v1.12.0) (2022-03-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **lib:** 优化样式 ([e53291b](https://github.com/lljj-x/vue-json-schema-form/commit/e53291b8395fdceb971f15f72c9e809cdee8ec7e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.11.0](https://github.com/lljj-x/vue-json-schema-form/compare/v1.10.0...v1.11.0) (2022-02-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **lib:** 添加严格模式配置,更精准计算anyOf 默认值 ([2cd65bb](https://github.com/lljj-x/vue-json-schema-form/commit/2cd65bb5f275a021f1cc368e4c63387163c94d57)), closes [#157](https://github.com/lljj-x/vue-json-schema-form/issues/157)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **lib:** 阻止表单默认submit事件 ([a882181](https://github.com/lljj-x/vue-json-schema-form/commit/a882181d65a9a152f8017e55367100658464aeba)), closes [#150](https://github.com/lljj-x/vue-json-schema-form/issues/150)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.10.0](https://github.com/lljj-x/vue-json-schema-form/compare/v1.9.5...v1.10.0) (2021-11-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **lib:** 添加 $$uiFormRef 属性,可在mounted 之直接访问子组件实例 ([08c6c4f](https://github.com/lljj-x/vue-json-schema-form/commit/08c6c4f2d247b4881e88fa380de8980c31cc5cd7)), closes [#127](https://github.com/lljj-x/vue-json-schema-form/issues/127)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.9.5](https://github.com/lljj-x/vue-json-schema-form/compare/v1.9.4...v1.9.5) (2021-11-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **lib:** 修复inline 布局样式问题 ([65a7143](https://github.com/lljj-x/vue-json-schema-form/commit/65a7143fc19105f9096afc24a25107c0ef27ac5f)), closes [#122](https://github.com/lljj-x/vue-json-schema-form/issues/122)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.9.4](https://github.com/lljj-x/vue-json-schema-form/compare/v1.9.3...v1.9.4) (2021-11-02)
|
||||
|
||||
**Note:** Version bump only for package @lljj/vue3-form-core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.9.3](https://github.com/lljj-x/vue-json-schema-form/compare/v1.9.2...v1.9.3) (2021-10-10)
|
||||
|
||||
**Note:** Version bump only for package @lljj/vue3-form-core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.9.2](https://github.com/lljj-x/vue-json-schema-form/compare/v1.9.1...v1.9.2) (2021-09-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **lib:** 修复antd表单不实时校验 ([5452491](https://github.com/lljj-x/vue-json-schema-form/commit/5452491354acf9884cd37691e766c0007c82f88a))
|
||||
* **lib:** 修复anyOf嵌套object 可能丢失部分校验规则的问题 ([5c06294](https://github.com/lljj-x/vue-json-schema-form/commit/5c06294d9a9c978bda1c3724710cfd4ba478af5b))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.9.1](https://github.com/lljj-x/vue-json-schema-form/compare/v1.9.0...v1.9.1) (2021-09-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **anyof:** 新值为object类型直接覆盖 ([d2f9791](https://github.com/lljj-x/vue-json-schema-form/commit/d2f9791ce7d35228edd07257049607177a95fc84)), closes [#77](https://github.com/lljj-x/vue-json-schema-form/issues/77)
|
||||
* 修复select组件无法实时校验 ([85d9545](https://github.com/lljj-x/vue-json-schema-form/commit/85d95451b56b9d985ca7094118fbfaca87342322)), closes [#105](https://github.com/lljj-x/vue-json-schema-form/issues/105)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.9.0](https://github.com/lljj-x/vue-json-schema-form/compare/v1.7.0...v1.9.0) (2021-09-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **vue3-core:** 允许传递props至formFooter ([60ee613](https://github.com/lljj-x/vue-json-schema-form/commit/60ee613bda30b818adccd98ad73949ff111df74c))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **vue3-core:** 优化 okBtnProps 的实现方式 ([adfe93e](https://github.com/lljj-x/vue-json-schema-form/commit/adfe93e58a9e8fedc2b0c26484be5691ccc3f65a))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.8.0](https://github.com/lljj-x/vue-json-schema-form/compare/v1.7.0...v1.8.0) (2021-09-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **vue3-core:** 允许传递props至formFooter ([60ee613](https://github.com/lljj-x/vue-json-schema-form/commit/60ee613bda30b818adccd98ad73949ff111df74c))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **vue3-core:** 优化 okBtnProps 的实现方式 ([adfe93e](https://github.com/lljj-x/vue-json-schema-form/commit/adfe93e58a9e8fedc2b0c26484be5691ccc3f65a))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.7.0](https://github.com/lljj-x/vue-json-schema-form/compare/v1.6.4...v1.7.0) (2021-08-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **lib:** 支持配置 slots ([27f1501](https://github.com/lljj-x/vue-json-schema-form/commit/27f1501eda01eabd4a723656be56904e9cb0f069)), closes [#45](https://github.com/lljj-x/vue-json-schema-form/issues/45)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.6.3](https://github.com/lljj-x/vue-json-schema-form/compare/v1.6.2...v1.6.3) (2021-07-12)
|
||||
|
||||
**Note:** Version bump only for package @lljj/vue3-form-core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.6.2](https://github.com/lljj-x/vue-json-schema-form/compare/v1.6.1...v1.6.2) (2021-05-31)
|
||||
|
||||
**Note:** Version bump only for package @lljj/vue3-form-core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.6.0](https://github.com/lljj-x/vue-json-schema-form/compare/v1.5.0...v1.6.0) (2021-05-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **lib:** form-mounted event 添加formData 参数 ([c54202c](https://github.com/lljj-x/vue-json-schema-form/commit/c54202c27304add9636a7062c05c80c60fc200a6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.5.0](https://github.com/lljj-x/vue-json-schema-form/compare/v1.4.0...v1.5.0) (2021-05-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **lib:** 优化anyOf切换选项值的复用,修复vue3 anyOf无法切换选项 ([6159160](https://github.com/lljj-x/vue-json-schema-form/commit/6159160d1727165e706343187aca129360dc011f))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.4.0](https://github.com/lljj-x/vue-json-schema-form/compare/v1.3.0...v1.4.0) (2021-04-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **lib:** 调整 widget onChange prop参数格式,添加 formData参数 ([4c441fc](https://github.com/lljj-x/vue-json-schema-form/commit/4c441fce239ade40b10a42bf361c3ee920a044ed)), closes [#45](https://github.com/lljj-x/vue-json-schema-form/issues/45)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.3.0](https://github.com/lljj-x/vue-json-schema-form/compare/v1.2.1...v1.3.0) (2021-04-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **core:** widget 节点直接配置onChange ([2d2264b](https://github.com/lljj-x/vue-json-schema-form/commit/2d2264b004c3b6586e225c563bf03ca52fc5e53a))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.1](https://github.com/lljj-x/vue-json-schema-form/compare/v1.2.0...v1.2.1) (2021-04-11)
|
||||
|
||||
**Note:** Version bump only for package @lljj/vue3-form-core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.2.0](https://github.com/lljj-x/vue-json-schema-form/compare/v1.1.3...v1.2.0) (2021-03-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **lib:** 添加 fallback-label 参数 ([cd2d8c3](https://github.com/lljj-x/vue-json-schema-form/commit/cd2d8c3ed72b9bc03e44eb5b86eb1b18fe67c34c)), closes [#45](https://github.com/lljj-x/vue-json-schema-form/issues/45)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.1.3](https://github.com/lljj-x/vue-json-schema-form/compare/v1.1.2...v1.1.3) (2021-03-18)
|
||||
|
||||
**Note:** Version bump only for package @lljj/vue3-form-core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.1.2](https://github.com/lljj-x/vue-json-schema-form/compare/v1.1.1...v1.1.2) (2021-03-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **vue3-antd:** 修复form label 双冒号问题 ([5b4f16c](https://github.com/lljj-x/vue-json-schema-form/commit/5b4f16c3c1a4f4b784c2fd5c1fbe7eec40cf8d7b)), closes [#46](https://github.com/lljj-x/vue-json-schema-form/issues/46)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.1.0](https://github.com/lljj-x/vue-json-schema-form/compare/v1.0.2...v1.1.0) (2021-03-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **vue3-ant:** 更新初始化 ([71a2810](https://github.com/lljj-x/vue-json-schema-form/commit/71a281045af11f215333050396aa546dd5e78b88)), closes [#27](https://github.com/lljj-x/vue-json-schema-form/issues/27) [#27](https://github.com/lljj-x/vue-json-schema-form/issues/27) [#27](https://github.com/lljj-x/vue-json-schema-form/issues/27) [#40](https://github.com/lljj-x/vue-json-schema-form/issues/40)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.0.2](https://github.com/lljj-x/vue-json-schema-form/compare/v1.0.1...v1.0.2) (2021-01-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **style:** 修复p标签等自带边距导致的样式问题 ([7b7e43e](https://github.com/lljj-x/vue-json-schema-form/commit/7b7e43eaa06c14a436b34c38d6d69aad27d67512))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.6.1](https://github.com/lljj-x/vue-json-schema-form/compare/v0.6.0...v0.6.1) (2021-01-19)
|
||||
|
||||
**Note:** Version bump only for package @lljj/vue3-form-core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.6.0](https://github.com/lljj-x/vue-json-schema-form/compare/v0.5.0...v0.6.0) (2021-01-19)
|
||||
|
||||
**Note:** Version bump only for package @lljj/vue3-form-core
|
@ -0,0 +1,124 @@
|
||||
# @lljj/vue3-form-core
|
||||
vue3 版本核心,可以基于此适配不同的 vue3 ui库。
|
||||
|
||||
适配的核心就是对应类型为自己的组件库,且处理默认 `props` 与自己组件库 props 之间的转换
|
||||
|
||||
> 适配方案可参见 [@lljj/vue3-form-element](https://github.com/lljj-x/vue-json-schema-form/tree/master/packages/lib/vue3/vue3-form-element) 、[@lljj/vue3-form-ant](https://github.com/lljj-x/vue-json-schema-form/tree/master/packages/lib/vue3/vue3-form-ant)
|
||||
|
||||
|
||||
## 兼容性
|
||||
npm 包直接为 es6+ 源码,需要在构建 lib 时通过babe转义
|
||||
|
||||
如配置 rollup babel plugin:
|
||||
|
||||
```js
|
||||
babel({
|
||||
exclude: /node_modules\/(?!(@lljj)\/).*/, // 忽略跳过 @lljj
|
||||
extensions: ['.js', '.vue'],
|
||||
})
|
||||
```
|
||||
|
||||
## 安装
|
||||
|
||||
```ssh
|
||||
## npm
|
||||
npm install --save @lljj/vue3-form-core
|
||||
|
||||
## yarn
|
||||
yarn add @lljj/vue3-form-core
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
按如下格式,配置对应组件在当前组件库中的映射关系,可以直接配置全局组件名或者组件构造函数,`默认组件 props 为elementUi格式,如果props格式不同需要中间组件来做转换`;
|
||||
|
||||
```js
|
||||
import createVue2Core from '@lljj/vue3-form-core';
|
||||
|
||||
const globalOptions = {
|
||||
// widget组件和现有组件库映射关系
|
||||
WIDGET_MAP: {
|
||||
// 默认按schema type 映射默认widget组件
|
||||
types: {
|
||||
// type boolean
|
||||
boolean: 'el-switch',
|
||||
|
||||
// type string
|
||||
string: 'el-input',
|
||||
|
||||
// type number
|
||||
number: 'el-input-number',
|
||||
|
||||
// type integer
|
||||
integer: 'el-input-number',
|
||||
},
|
||||
|
||||
// 按 schema format 映射默认widget组件,优先级高于 types
|
||||
formats: {
|
||||
// format: color
|
||||
color: 'el-color-picker',
|
||||
|
||||
// format: time
|
||||
time: TimePickerWidget, // 格式 20:20:39+00:00
|
||||
|
||||
// format: date
|
||||
date: DatePickerWidget, // 格式 2018-11-13
|
||||
|
||||
// format: date-time
|
||||
'date-time': DateTimePickerWidget, // 格式 2018-11-13T20:20:39+00:00
|
||||
},
|
||||
|
||||
// 一些公共常用类型
|
||||
common: {
|
||||
// select option
|
||||
select: SelectWidget,
|
||||
|
||||
// radio
|
||||
radioGroup: RadioWidget,
|
||||
|
||||
// checkout
|
||||
checkboxGroup: CheckboxesWidget,
|
||||
},
|
||||
|
||||
// 这里配置一些 为当前ui库适配过的组件,会在运行时自动注册为全局组件,不注册为全局也可不配置
|
||||
// Vue3 只有在组件内才能获取到当前的app,所以注册时机是在 form组件setup中,且只会注册一次。
|
||||
widgetComponents: {
|
||||
CheckboxesWidget,
|
||||
RadioWidget,
|
||||
SelectWidget,
|
||||
TimePickerWidget,
|
||||
DatePickerWidget,
|
||||
DateTimePickerWidget
|
||||
}
|
||||
},
|
||||
|
||||
// 其它表单相关组件映射关系
|
||||
COMPONENT_MAP: {
|
||||
// form组件
|
||||
form: 'el-form',
|
||||
|
||||
// formItem 组件
|
||||
formItem: 'el-form-item',
|
||||
|
||||
// button 组件
|
||||
button: 'el-button',
|
||||
|
||||
// popover,用在formLable 左右布局时鼠标移入显示description
|
||||
popover: 'el-popover'
|
||||
},
|
||||
HELPERS: {
|
||||
// 是否mini显示 description
|
||||
isMiniDes(formProps) {
|
||||
return formProps && ['left', 'right'].includes(formProps.labselPosition);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const mySchemaForm = createVue2Core(globalOptions);
|
||||
|
||||
```
|
||||
|
||||
适配一个新的ui框架只需要适配如上的组件即可
|
||||
|
||||
## License
|
||||
Apache-2.0
|
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "@lljj/vue3-form-core",
|
||||
"version": "1.12.2",
|
||||
"description": "基于 Vue3 、JsonSchema快速构建一个带完整校验的form表单,vue3版本基础框架",
|
||||
"main": "src/index.js",
|
||||
"module": "src/index.js",
|
||||
"keywords": [
|
||||
"vue",
|
||||
"vuejs",
|
||||
"form",
|
||||
"jsonSchema"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lljj/vjsf-utils": "1.12.2"
|
||||
},
|
||||
"repository": "https://github.com/lljj-x/vue-json-schema-form",
|
||||
"homepage": "https://github.com/lljj-x/vue-json-schema-form",
|
||||
"license": "Apache-2.0",
|
||||
"author": "Liu.Jun",
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 8"
|
||||
],
|
||||
"gitHead": "92795075169c879e1c1fabfe26f1d3c10b861060"
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Created by Liu.Jun on 2020/4/20 9:55 下午.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import ObjectField from './fields/ObjectField';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import StringField from './fields/StringField';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import NumberField from './fields/NumberField';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import IntegerField from './fields/IntegerField';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import BooleanField from './fields/BooleanField';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import ArrayField from './fields/ArrayField';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import AnyOfField from './fields/combiningSchemas/AnyOfField';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import OneOfField from './fields/combiningSchemas/OneOfField';
|
||||
|
||||
// 默认类型使用field映射关系
|
||||
const FIELDS_MAPS = {
|
||||
array: ArrayField,
|
||||
boolean: BooleanField,
|
||||
integer: IntegerField,
|
||||
number: NumberField,
|
||||
object: ObjectField,
|
||||
string: StringField,
|
||||
null: {
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
anyOf: AnyOfField,
|
||||
oneOf: OneOfField
|
||||
};
|
||||
|
||||
export default FIELDS_MAPS;
|
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Created by Liu.Jun on 2020/12/27 9:53 下午.
|
||||
*/
|
||||
|
||||
import { h } from 'vue';
|
||||
|
||||
import { resolveComponent } from '@lljj/vjsf-utils/vue3Utils';
|
||||
|
||||
export default {
|
||||
name: 'FormFooter',
|
||||
props: {
|
||||
okBtn: {
|
||||
type: String,
|
||||
default: '保存'
|
||||
},
|
||||
okBtnProps: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
cancelBtn: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
},
|
||||
formItemAttrs: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
globalOptions: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: ['cancel', 'submit'],
|
||||
setup(props, { emit }) {
|
||||
// globalOptions 不需要响应式
|
||||
const { globalOptions: { COMPONENT_MAP } } = props;
|
||||
|
||||
return () => h(resolveComponent(COMPONENT_MAP.formItem), {
|
||||
class: {
|
||||
formFooter_item: true
|
||||
},
|
||||
...props.formItemAttrs
|
||||
}, {
|
||||
default: () => [
|
||||
h(resolveComponent(COMPONENT_MAP.button), {
|
||||
onClick() {
|
||||
emit('cancel');
|
||||
}
|
||||
}, {
|
||||
default: () => props.cancelBtn
|
||||
}),
|
||||
h(resolveComponent(COMPONENT_MAP.button), {
|
||||
style: {
|
||||
marginLeft: '10px'
|
||||
},
|
||||
type: 'primary',
|
||||
onClick() {
|
||||
emit('submit');
|
||||
},
|
||||
...props.okBtnProps
|
||||
}, {
|
||||
default: () => props.okBtn
|
||||
})
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
@ -0,0 +1,365 @@
|
||||
/**
|
||||
* Created by Liu.Jun on 2020/4/23 11:24.
|
||||
*/
|
||||
|
||||
import {
|
||||
computed, h, ref, watch, inject
|
||||
} from 'vue';
|
||||
|
||||
import { IconInfo } from '@lljj/vjsf-utils/icons';
|
||||
|
||||
import { validateFormDataAndTransformMsg } from '@lljj/vjsf-utils/schema/validate';
|
||||
import { fallbackLabel } from '@lljj/vjsf-utils/formUtils';
|
||||
|
||||
import {
|
||||
isRootNodePath, path2prop, getPathVal, setPathVal, resolveComponent
|
||||
} from '@lljj/vjsf-utils/vue3Utils';
|
||||
|
||||
export default {
|
||||
name: 'Widget',
|
||||
props: {
|
||||
// 是否同步formData的值,默认表单元素都需要
|
||||
// oneOf anyOf 中的select属于formData之外的数据
|
||||
isFormData: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// isFormData = false时需要传入当前 value 否则会通过 curNodePath 自动计算
|
||||
curValue: {
|
||||
type: null,
|
||||
default: 0
|
||||
},
|
||||
schema: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
uiSchema: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
errorSchema: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
customFormats: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// 自定义校验
|
||||
customRule: {
|
||||
type: Function,
|
||||
default: null
|
||||
},
|
||||
widget: {
|
||||
type: [String, Function, Object],
|
||||
default: null
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 解决 JSON Schema和实际输入元素中空字符串 required 判定的差异性
|
||||
// 元素输入为 '' 使用 emptyValue 的值
|
||||
emptyValue: {
|
||||
type: null,
|
||||
default: undefined
|
||||
},
|
||||
rootFormData: {
|
||||
type: null
|
||||
},
|
||||
curNodePath: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// width -> formItem width
|
||||
width: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
labelWidth: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// Widget attrs
|
||||
widgetAttrs: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// Widget className
|
||||
widgetClass: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// Widget style
|
||||
widgetStyle: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// Field attrs
|
||||
fieldAttrs: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// Field className
|
||||
fieldClass: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// Field style
|
||||
fieldStyle: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// props
|
||||
uiProps: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
formProps: null,
|
||||
getWidget: null,
|
||||
renderScopedSlots: null, // 作用域插槽
|
||||
globalOptions: null, // 全局配置
|
||||
onChange: null
|
||||
},
|
||||
emits: ['otherDataChange'],
|
||||
inheritAttrs: true,
|
||||
setup(props, { emit }) {
|
||||
const genFormProvide = inject('genFormProvide');
|
||||
const widgetValue = computed({
|
||||
get() {
|
||||
if (props.isFormData) return getPathVal(props.rootFormData, props.curNodePath);
|
||||
|
||||
return props.curValue;
|
||||
},
|
||||
set(value) {
|
||||
// 大多组件删除为空值会重置为null。
|
||||
const trueValue = (value === '' || value === null) ? props.emptyValue : value;
|
||||
if (props.isFormData) {
|
||||
setPathVal(props.rootFormData, props.curNodePath, trueValue);
|
||||
} else {
|
||||
emit('otherDataChange', trueValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 枚举类型默认值为第一个选项
|
||||
if (props.uiProps.enumOptions
|
||||
&& props.uiProps.enumOptions.length > 0
|
||||
&& widgetValue.value === undefined
|
||||
&& widgetValue.value !== props.uiProps.enumOptions[0]
|
||||
) {
|
||||
// array 渲染为多选框时默认为空数组
|
||||
if (props.schema.items) {
|
||||
widgetValue.value = [];
|
||||
} else if (props.required) {
|
||||
widgetValue.value = props.uiProps.enumOptions[0].value;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取到widget组件实例
|
||||
const widgetRef = ref(null);
|
||||
// 提供一种特殊的配置 允许直接访问到 widget vm
|
||||
if (typeof props.getWidget === 'function') {
|
||||
watch(widgetRef, () => {
|
||||
props.getWidget.call(null, widgetRef.value);
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
// 判断是否为根节点
|
||||
const isRootNode = isRootNodePath(props.curNodePath);
|
||||
|
||||
const isMiniDes = props.formProps && props.formProps.isMiniDes;
|
||||
const miniDesModel = isMiniDes || props.globalOptions.HELPERS.isMiniDes(props.formProps);
|
||||
|
||||
const descriptionVNode = (props.description) ? h(
|
||||
'div',
|
||||
{
|
||||
innerHTML: props.description,
|
||||
class: {
|
||||
genFromWidget_des: true,
|
||||
genFromWidget_des_mini: miniDesModel
|
||||
}
|
||||
},
|
||||
) : null;
|
||||
|
||||
const { COMPONENT_MAP } = props.globalOptions;
|
||||
const miniDescriptionVNode = (miniDesModel && descriptionVNode) ? h(resolveComponent(COMPONENT_MAP.popover), {
|
||||
style: {
|
||||
margin: '0 2px',
|
||||
fontSize: '16px',
|
||||
cursor: 'pointer'
|
||||
},
|
||||
placement: 'top',
|
||||
trigger: 'hover'
|
||||
}, {
|
||||
default: () => descriptionVNode,
|
||||
reference: () => h(IconInfo)
|
||||
}) : null;
|
||||
|
||||
// form-item style
|
||||
const formItemStyle = {
|
||||
...props.fieldStyle,
|
||||
...(props.width ? {
|
||||
width: props.width,
|
||||
flexBasis: props.width,
|
||||
paddingRight: '10px'
|
||||
} : {})
|
||||
};
|
||||
|
||||
// 运行配置回退到 属性名
|
||||
const label = fallbackLabel(props.label, (props.widget && genFormProvide.value.fallbackLabel), props.curNodePath);
|
||||
return h(
|
||||
resolveComponent(COMPONENT_MAP.formItem),
|
||||
{
|
||||
class: {
|
||||
...props.fieldClass,
|
||||
genFormItem: true
|
||||
},
|
||||
style: formItemStyle,
|
||||
...props.fieldAttrs,
|
||||
|
||||
...props.labelWidth ? { labelWidth: props.labelWidth } : {},
|
||||
...props.isFormData ? {
|
||||
// 这里对根节点打特殊标志,绕过elementUi无prop属性不校验
|
||||
prop: isRootNode ? '__$$root' : path2prop(props.curNodePath),
|
||||
rules: [
|
||||
{
|
||||
validator(rule, value, callback) {
|
||||
if (isRootNode) value = props.rootFormData;
|
||||
|
||||
// 校验是通过对schema逐级展开校验 这里只捕获根节点错误
|
||||
const errors = validateFormDataAndTransformMsg({
|
||||
formData: value,
|
||||
schema: props.schema,
|
||||
uiSchema: props.uiSchema,
|
||||
customFormats: props.customFormats,
|
||||
errorSchema: props.errorSchema,
|
||||
required: props.required,
|
||||
propPath: path2prop(props.curNodePath)
|
||||
});
|
||||
|
||||
// 存在校验不通过字段
|
||||
if (errors.length > 0) {
|
||||
if (callback) return callback(errors[0].message);
|
||||
return Promise.reject(errors[0].message);
|
||||
}
|
||||
|
||||
// customRule 如果存在自定义校验
|
||||
const curCustomRule = props.customRule;
|
||||
if (curCustomRule && (typeof curCustomRule === 'function')) {
|
||||
return curCustomRule({
|
||||
field: props.curNodePath,
|
||||
value,
|
||||
rootFormData: props.rootFormData,
|
||||
callback
|
||||
});
|
||||
}
|
||||
|
||||
// 校验成功
|
||||
if (callback) return callback();
|
||||
return Promise.resolve();
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
} : {},
|
||||
},
|
||||
{
|
||||
// 错误只能显示一行,多余...
|
||||
error: slotProps => (slotProps.error ? h('div', {
|
||||
class: {
|
||||
formItemErrorBox: true
|
||||
},
|
||||
title: slotProps.error
|
||||
}, [slotProps.error]) : null),
|
||||
|
||||
// label
|
||||
/*
|
||||
TODO:这里slot如果从无到有会导致无法正常渲染出元素 怀疑是vue3 bug
|
||||
如果使用 error 的形式渲染,ElementPlus label labelWrap 未做判断,使用 slots.default?.() 会得到 undefined
|
||||
*/
|
||||
...label ? {
|
||||
label: () => h('span', {
|
||||
class: {
|
||||
genFormLabel: true,
|
||||
genFormItemRequired: props.required,
|
||||
},
|
||||
}, [
|
||||
...miniDescriptionVNode ? [miniDescriptionVNode] : [],
|
||||
`${label}`,
|
||||
`${(props.formProps && props.formProps.labelSuffix) || ''}`
|
||||
])
|
||||
} : {},
|
||||
|
||||
// default
|
||||
default: otherAttrs => [
|
||||
// description
|
||||
// 非mini模式显示 description
|
||||
...(!miniDesModel && descriptionVNode) ? [descriptionVNode] : [],
|
||||
|
||||
...props.widget ? [
|
||||
h( // 关键输入组件
|
||||
resolveComponent(props.widget),
|
||||
{
|
||||
style: props.widgetStyle,
|
||||
class: props.widgetClass,
|
||||
|
||||
...props.widgetAttrs,
|
||||
...props.uiProps,
|
||||
modelValue: widgetValue.value, // v-model
|
||||
ref: widgetRef,
|
||||
'onUpdate:modelValue': function updateModelValue(event) {
|
||||
const preVal = widgetValue.value;
|
||||
if (preVal !== event) {
|
||||
widgetValue.value = event;
|
||||
if (props.onChange) {
|
||||
props.onChange({
|
||||
curVal: event,
|
||||
preVal,
|
||||
parentFormData: getPathVal(props.rootFormData, props.curNodePath, 1),
|
||||
rootFormData: props.rootFormData
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
...otherAttrs ? (() => Object.keys(otherAttrs).reduce((pre, k) => {
|
||||
pre[k] = otherAttrs[k];
|
||||
|
||||
// 保证ui配置同名方法 ui方法先执行
|
||||
[
|
||||
props.widgetAttrs[k],
|
||||
props.uiProps[k]
|
||||
].forEach((uiConfFn) => {
|
||||
if (uiConfFn && typeof uiConfFn === 'function') {
|
||||
pre[k] = (...args) => {
|
||||
uiConfFn(...args);
|
||||
pre[k](...args);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return pre;
|
||||
}, {}))() : {}
|
||||
},
|
||||
{
|
||||
...(props.renderScopedSlots ? (
|
||||
typeof props.renderScopedSlots === 'function' ? props.renderScopedSlots() : props.renderScopedSlots
|
||||
) : {})
|
||||
}
|
||||
)
|
||||
] : []
|
||||
]
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
};
|
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Created by Liu.Jun on 2020/4/24 11:56.
|
||||
*/
|
||||
|
||||
import { h } from 'vue';
|
||||
|
||||
import {
|
||||
getWidgetConfig, optionsList
|
||||
} from '@lljj/vjsf-utils/formUtils';
|
||||
import retrieveSchema from '@lljj/vjsf-utils/schema/retriev';
|
||||
import vueProps from '../../props';
|
||||
|
||||
import Widget from '../../../components/Widget';
|
||||
|
||||
export default {
|
||||
name: 'ArrayFieldMultiSelect',
|
||||
props: {
|
||||
...vueProps
|
||||
},
|
||||
setup(props, { attrs }) {
|
||||
return () => {
|
||||
const {
|
||||
schema, rootSchema, uiSchema, curNodePath, rootFormData, globalOptions
|
||||
} = props;
|
||||
|
||||
// 这里需要索引当前节点,通过到schemaField组件的会统一处理
|
||||
const itemsSchema = retrieveSchema(schema.items, rootSchema);
|
||||
|
||||
const enumOptions = optionsList(itemsSchema, uiSchema, curNodePath, rootFormData);
|
||||
|
||||
const widgetConfig = getWidgetConfig({
|
||||
schema,
|
||||
uiSchema,
|
||||
curNodePath,
|
||||
rootFormData
|
||||
}, () => ({
|
||||
widget: globalOptions.WIDGET_MAP.common.checkboxGroup
|
||||
}));
|
||||
|
||||
// 存在枚举数据列表 传入 enumOptions
|
||||
widgetConfig.uiProps.multiple = true;
|
||||
|
||||
if (enumOptions && !widgetConfig.uiProps.enumOptions) {
|
||||
widgetConfig.uiProps.enumOptions = enumOptions;
|
||||
}
|
||||
|
||||
return h(
|
||||
Widget,
|
||||
{
|
||||
...attrs,
|
||||
...props,
|
||||
...widgetConfig
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
};
|