feat: implement JSON editor component for enhanced JSON data management in forms
This commit is contained in:
1 parent
7355edf999
commit
2f9a401381
10 files changed
+1710
-851
No files matched your search
Generated
+473
-6
@@ -33,6 +33,8 @@
|
||||
"sass": "^1.89.2",
|
||||
"terser": "^5.43.1",
|
||||
"typescript": "^5.8.3",
|
||||
"unplugin-auto-import": "^21.0.0",
|
||||
"unplugin-vue-components": "^31.0.0",
|
||||
"vite": "^5.0.0",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vitest": "^2.0.0",
|
||||
@@ -730,6 +732,17 @@
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/remapping": {
|
||||
"version": "2.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
||||
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
@@ -2328,9 +2341,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"version": "8.16.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
|
||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
@@ -2648,6 +2661,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/confbox": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
|
||||
"integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
@@ -3292,6 +3312,13 @@
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/exsolve": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz",
|
||||
"integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
@@ -3897,6 +3924,13 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
|
||||
"integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
@@ -3968,6 +4002,24 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/local-pkg": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz",
|
||||
"integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mlly": "^1.7.4",
|
||||
"pkg-types": "^2.3.0",
|
||||
"quansync": "^0.2.11"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||
@@ -4022,9 +4074,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.18",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz",
|
||||
"integrity": "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==",
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
@@ -4106,6 +4158,45 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/mlly": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.1.tgz",
|
||||
"integrity": "sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"acorn": "^8.16.0",
|
||||
"pathe": "^2.0.3",
|
||||
"pkg-types": "^1.3.1",
|
||||
"ufo": "^1.6.3"
|
||||
}
|
||||
},
|
||||
"node_modules/mlly/node_modules/confbox": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
|
||||
"integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mlly/node_modules/pathe": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mlly/node_modules/pkg-types": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
|
||||
"integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"confbox": "^0.1.8",
|
||||
"mlly": "^1.7.4",
|
||||
"pathe": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@@ -4172,6 +4263,17 @@
|
||||
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/obug": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
|
||||
"integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
"https://github.com/sponsors/sxzz",
|
||||
"https://opencollective.com/debug"
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
@@ -4368,6 +4470,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-types": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
|
||||
"integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"confbox": "^0.2.2",
|
||||
"exsolve": "^1.0.7",
|
||||
"pathe": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-types/node_modules/pathe": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||
@@ -4465,6 +4586,23 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/quansync": {
|
||||
"version": "0.2.11",
|
||||
"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",
|
||||
"integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/sxzz"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
@@ -4692,6 +4830,13 @@
|
||||
"@parcel/watcher": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/scule": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz",
|
||||
"integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
@@ -4840,6 +4985,19 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-literal": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz",
|
||||
"integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-tokens": "^9.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
@@ -4909,6 +5067,54 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.15",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fdir": "^6.5.0",
|
||||
"picomatch": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby/node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"picomatch": "^3 || ^4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"picomatch": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/tinypool": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
|
||||
@@ -5011,6 +5217,13 @@
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/ufo": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz",
|
||||
"integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
@@ -5018,6 +5231,65 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unimport": {
|
||||
"version": "5.7.0",
|
||||
"resolved": "https://registry.npmjs.org/unimport/-/unimport-5.7.0.tgz",
|
||||
"integrity": "sha512-njnL6sp8lEA8QQbZrt+52p/g4X0rw3bnGGmUcJnt1jeG8+iiqO779aGz0PirCtydAIVcuTBRlJ52F0u46z309Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"acorn": "^8.16.0",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"estree-walker": "^3.0.3",
|
||||
"local-pkg": "^1.1.2",
|
||||
"magic-string": "^0.30.21",
|
||||
"mlly": "^1.8.0",
|
||||
"pathe": "^2.0.3",
|
||||
"picomatch": "^4.0.3",
|
||||
"pkg-types": "^2.3.0",
|
||||
"scule": "^1.3.0",
|
||||
"strip-literal": "^3.1.0",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"unplugin": "^2.3.11",
|
||||
"unplugin-utils": "^0.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unimport/node_modules/escape-string-regexp": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
|
||||
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/unimport/node_modules/pathe": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unimport/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||
@@ -5028,6 +5300,194 @@
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin": {
|
||||
"version": "2.3.11",
|
||||
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz",
|
||||
"integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/remapping": "^2.3.5",
|
||||
"acorn": "^8.15.0",
|
||||
"picomatch": "^4.0.3",
|
||||
"webpack-virtual-modules": "^0.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-auto-import": {
|
||||
"version": "21.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-21.0.0.tgz",
|
||||
"integrity": "sha512-vWuC8SwqJmxZFYwPojhOhOXDb5xFhNNcEVb9K/RFkyk/3VnfaOjzitWN7v+8DEKpMjSsY2AEGXNgt6I0yQrhRQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"local-pkg": "^1.1.2",
|
||||
"magic-string": "^0.30.21",
|
||||
"picomatch": "^4.0.3",
|
||||
"unimport": "^5.6.0",
|
||||
"unplugin": "^2.3.11",
|
||||
"unplugin-utils": "^0.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nuxt/kit": "^4.0.0",
|
||||
"@vueuse/core": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@nuxt/kit": {
|
||||
"optional": true
|
||||
},
|
||||
"@vueuse/core": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-auto-import/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-utils": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz",
|
||||
"integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pathe": "^2.0.3",
|
||||
"picomatch": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sxzz"
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-utils/node_modules/pathe": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unplugin-utils/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-vue-components": {
|
||||
"version": "31.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-31.0.0.tgz",
|
||||
"integrity": "sha512-4ULwfTZTLuWJ7+S9P7TrcStYLsSRkk6vy2jt/WTfgUEUb0nW9//xxmrfhyHUEVpZ2UKRRwfRb8Yy15PDbVZf+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chokidar": "^5.0.0",
|
||||
"local-pkg": "^1.1.2",
|
||||
"magic-string": "^0.30.21",
|
||||
"mlly": "^1.8.0",
|
||||
"obug": "^2.1.1",
|
||||
"picomatch": "^4.0.3",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"unplugin": "^2.3.11",
|
||||
"unplugin-utils": "^0.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nuxt/kit": "^3.2.2 || ^4.0.0",
|
||||
"vue": "^3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@nuxt/kit": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-vue-components/node_modules/chokidar": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
|
||||
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readdirp": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-vue-components/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-vue-components/node_modules/readdirp": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
|
||||
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/uri-js": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||
@@ -5432,6 +5892,13 @@
|
||||
"typescript": ">=5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-virtual-modules": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
|
||||
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
||||
@@ -0,0 +1,505 @@
|
||||
<template>
|
||||
<div class="json-editor">
|
||||
<!-- 操作按钮 -->
|
||||
<div class="json-editor-actions">
|
||||
<el-button size="small" @click="addRootEntry">
|
||||
{{ t('jsonEditor.addItem') }}
|
||||
</el-button>
|
||||
<el-button size="small" text :disabled="!hasData" @click="clearAll">
|
||||
{{ t('jsonEditor.clearAll') }}
|
||||
</el-button>
|
||||
<el-button size="small" text :disabled="!hasData" @click="copyJson">
|
||||
{{ t('jsonEditor.copyJson') }}
|
||||
</el-button>
|
||||
<el-button size="small" text @click="openImportDialog">
|
||||
{{ t('jsonEditor.importJson') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- JSON 树形结构 -->
|
||||
<div class="json-tree">
|
||||
<JsonNode
|
||||
v-for="(node, index) in nodes"
|
||||
:key="node.id"
|
||||
:node="node"
|
||||
:depth="0"
|
||||
@update="handleNodeUpdate"
|
||||
@delete="handleNodeDelete(index)"
|
||||
@add-child="handleAddChild"
|
||||
@toggle-expand="handleToggleExpand"
|
||||
/>
|
||||
|
||||
<div v-if="!nodes.length" class="json-empty">
|
||||
{{ t('jsonEditor.emptyState') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- JSON 预览 -->
|
||||
<div class="json-preview">
|
||||
<div class="json-preview-label">{{ t('jsonEditor.preview') }}</div>
|
||||
<el-input
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
:model-value="jsonPreview"
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 导入弹窗 -->
|
||||
<el-dialog
|
||||
v-model="importDialogVisible"
|
||||
:title="t('jsonEditor.importTitle')"
|
||||
width="600px"
|
||||
destroy-on-close
|
||||
>
|
||||
<p class="import-dialog-tip">
|
||||
{{ t('jsonEditor.importDescription') }}
|
||||
</p>
|
||||
<el-input
|
||||
v-model="importDialogContent"
|
||||
type="textarea"
|
||||
:rows="10"
|
||||
:placeholder="t('jsonEditor.importPlaceholder')"
|
||||
/>
|
||||
<p v-if="importDialogError" class="json-editor-error">
|
||||
{{ importDialogError }}
|
||||
</p>
|
||||
<template #footer>
|
||||
<el-button @click="importDialogVisible = false">
|
||||
{{ t('jsonEditor.importCancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="handleImportConfirm">
|
||||
{{ t('jsonEditor.importConfirm') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, nextTick } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import JsonNode from './JsonNode.vue'
|
||||
|
||||
export type JsonValueType = 'string' | 'number' | 'boolean' | 'object' | 'array'
|
||||
|
||||
export interface JsonNodeData {
|
||||
id: string
|
||||
key: string
|
||||
value: string
|
||||
type: JsonValueType
|
||||
children: JsonNodeData[]
|
||||
expanded: boolean
|
||||
keyError?: string
|
||||
valueError?: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: Record<string, any> | string | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: Record<string, any> | string | null): void
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
// 响应式数据
|
||||
const nodes = ref<JsonNodeData[]>([])
|
||||
const importDialogVisible = ref(false)
|
||||
const importDialogContent = ref('')
|
||||
const importDialogError = ref('')
|
||||
|
||||
// 计算属性
|
||||
const hasData = computed(() => nodes.value.length > 0)
|
||||
|
||||
const jsonPreview = computed(() => {
|
||||
const data = buildJsonData(nodes.value)
|
||||
return data ? JSON.stringify(data, null, 2) : ''
|
||||
})
|
||||
|
||||
// 生成唯一ID
|
||||
const createNodeId = () => `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
||||
|
||||
// 创建空节点
|
||||
const createEmptyNode = (type: JsonValueType = 'string'): JsonNodeData => ({
|
||||
id: createNodeId(),
|
||||
key: '',
|
||||
value: type === 'object' || type === 'array' ? '' : '',
|
||||
type,
|
||||
children: [],
|
||||
expanded: true,
|
||||
keyError: '',
|
||||
valueError: ''
|
||||
})
|
||||
|
||||
// 构建 JSON 数据
|
||||
const buildJsonData = (nodeList: JsonNodeData[]): Record<string, any> | null => {
|
||||
if (!nodeList.length) return null
|
||||
|
||||
const result: Record<string, any> = {}
|
||||
for (const node of nodeList) {
|
||||
const trimmedKey = node.key.trim()
|
||||
if (!trimmedKey) continue
|
||||
|
||||
let value: any
|
||||
|
||||
switch (node.type) {
|
||||
case 'number':
|
||||
const num = Number(node.value)
|
||||
value = Number.isNaN(num) ? node.value : num
|
||||
break
|
||||
case 'boolean':
|
||||
value = node.value === 'true'
|
||||
break
|
||||
case 'object':
|
||||
value = buildJsonData(node.children)
|
||||
break
|
||||
case 'array':
|
||||
value = node.children.map(child => {
|
||||
if (child.type === 'object') {
|
||||
return buildJsonData(child.children)
|
||||
}
|
||||
if (child.type === 'number') {
|
||||
return Number(child.value)
|
||||
}
|
||||
if (child.type === 'boolean') {
|
||||
return child.value === 'true'
|
||||
}
|
||||
return child.value
|
||||
})
|
||||
break
|
||||
default:
|
||||
value = node.value
|
||||
}
|
||||
|
||||
result[trimmedKey] = value
|
||||
}
|
||||
|
||||
return Object.keys(result).length ? result : null
|
||||
}
|
||||
|
||||
// 解析 JSON 为节点
|
||||
const parseJsonToNodes = (data: unknown): JsonNodeData[] => {
|
||||
if (!data) return []
|
||||
if (typeof data === 'string') {
|
||||
try {
|
||||
data = JSON.parse(data)
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof data !== 'object' || data === null) return []
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
return data.map((item, index) => {
|
||||
const isObj = item !== null && typeof item === 'object'
|
||||
const isArr = Array.isArray(item)
|
||||
const itemType: JsonValueType = item === null ? 'string' : (isArr ? 'array' : (isObj ? 'object' : typeof item as JsonValueType))
|
||||
return {
|
||||
id: createNodeId(),
|
||||
key: String(index),
|
||||
value: isObj || isArr ? '' : String(item),
|
||||
type: itemType,
|
||||
children: (isObj || isArr) ? parseJsonToNodes(item) : [],
|
||||
expanded: true,
|
||||
keyError: '',
|
||||
valueError: ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return Object.entries(data as Record<string, any>).map(([key, value]) => {
|
||||
let type: JsonValueType = 'string'
|
||||
let children: JsonNodeData[] = []
|
||||
|
||||
if (value === null) {
|
||||
type = 'string'
|
||||
} else if (Array.isArray(value)) {
|
||||
type = 'array'
|
||||
children = value.map((item, index) => {
|
||||
const isObj = item !== null && typeof item === 'object'
|
||||
const isArr = Array.isArray(item)
|
||||
const itemType: JsonValueType = item === null ? 'string' : (isArr ? 'array' : (isObj ? 'object' : typeof item as JsonValueType))
|
||||
return {
|
||||
id: createNodeId(),
|
||||
key: String(index),
|
||||
value: isObj || isArr ? '' : String(item),
|
||||
type: itemType,
|
||||
children: (isObj || isArr) ? parseJsonToNodes(item) : [],
|
||||
expanded: true,
|
||||
keyError: '',
|
||||
valueError: ''
|
||||
}
|
||||
})
|
||||
} else if (typeof value === 'object') {
|
||||
type = 'object'
|
||||
children = parseJsonToNodes(value)
|
||||
} else if (typeof value === 'number') {
|
||||
type = 'number'
|
||||
} else if (typeof value === 'boolean') {
|
||||
type = 'boolean'
|
||||
}
|
||||
|
||||
return {
|
||||
id: createNodeId(),
|
||||
key,
|
||||
value: type === 'object' || type === 'array' ? '' : String(value),
|
||||
type,
|
||||
children,
|
||||
expanded: true,
|
||||
keyError: '',
|
||||
valueError: ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化节点
|
||||
const initializeNodes = (value: unknown) => {
|
||||
nodes.value = parseJsonToNodes(value)
|
||||
}
|
||||
|
||||
// 添加根节点
|
||||
const addRootEntry = () => {
|
||||
nodes.value.push(createEmptyNode('string'))
|
||||
emitUpdate()
|
||||
}
|
||||
|
||||
// 清空
|
||||
const clearAll = () => {
|
||||
nodes.value = []
|
||||
emitUpdate()
|
||||
}
|
||||
|
||||
// 复制 JSON
|
||||
const fallbackCopy = (value: string) => {
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.value = value
|
||||
textarea.style.position = 'fixed'
|
||||
textarea.style.opacity = '0'
|
||||
document.body.appendChild(textarea)
|
||||
textarea.focus()
|
||||
textarea.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(textarea)
|
||||
}
|
||||
|
||||
const copyJson = async () => {
|
||||
if (!jsonPreview.value) {
|
||||
ElMessage.info(t('jsonEditor.copyEmpty'))
|
||||
return
|
||||
}
|
||||
try {
|
||||
if (navigator?.clipboard?.writeText) {
|
||||
await navigator.clipboard.writeText(jsonPreview.value)
|
||||
} else {
|
||||
fallbackCopy(jsonPreview.value)
|
||||
}
|
||||
ElMessage.success(t('jsonEditor.copySuccess'))
|
||||
} catch (error) {
|
||||
console.error('复制JSON失败:', error)
|
||||
ElMessage.error(t('jsonEditor.copyError'))
|
||||
}
|
||||
}
|
||||
|
||||
// 导入弹窗
|
||||
const openImportDialog = () => {
|
||||
importDialogContent.value = jsonPreview.value
|
||||
importDialogError.value = ''
|
||||
importDialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleImportConfirm = () => {
|
||||
importDialogError.value = ''
|
||||
const content = importDialogContent.value.trim()
|
||||
|
||||
if (!content) {
|
||||
nodes.value = []
|
||||
importDialogVisible.value = false
|
||||
ElMessage.success(t('jsonEditor.importSuccess'))
|
||||
emitUpdate()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(content)
|
||||
nodes.value = parseJsonToNodes(parsed)
|
||||
importDialogVisible.value = false
|
||||
ElMessage.success(t('jsonEditor.importSuccess'))
|
||||
emitUpdate()
|
||||
} catch (error: any) {
|
||||
importDialogError.value = error?.message || t('jsonEditor.importFailed')
|
||||
}
|
||||
}
|
||||
|
||||
// 节点更新
|
||||
const handleNodeUpdate = () => {
|
||||
// 自动触发表单更新
|
||||
}
|
||||
|
||||
// 节点删除
|
||||
const handleNodeDelete = (index: number) => {
|
||||
nodes.value.splice(index, 1)
|
||||
emitUpdate()
|
||||
}
|
||||
|
||||
// 添加子节点
|
||||
const handleAddChild = (parentId: string) => {
|
||||
const findAndAdd = (nodeList: JsonNodeData[]): boolean => {
|
||||
for (const node of nodeList) {
|
||||
if (node.id === parentId) {
|
||||
if (node.type === 'array') {
|
||||
node.children.push(createEmptyNode('string'))
|
||||
} else if (node.type === 'object') {
|
||||
node.children.push(createEmptyNode('string'))
|
||||
}
|
||||
return true
|
||||
}
|
||||
if (node.children.length && findAndAdd(node.children)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
findAndAdd(nodes.value)
|
||||
}
|
||||
|
||||
// 切换展开
|
||||
const handleToggleExpand = (nodeId: string) => {
|
||||
const findAndToggle = (nodeList: JsonNodeData[]): boolean => {
|
||||
for (const node of nodeList) {
|
||||
if (node.id === nodeId) {
|
||||
node.expanded = !node.expanded
|
||||
return true
|
||||
}
|
||||
if (node.children.length && findAndToggle(node.children)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
findAndToggle(nodes.value)
|
||||
}
|
||||
|
||||
// 验证所有节点
|
||||
const validateAll = (): boolean => {
|
||||
let isValid = true
|
||||
const existingKeys = new Map<string, number>()
|
||||
|
||||
const validate = (nodeList: JsonNodeData[], parentPath: string = '') => {
|
||||
for (const node of nodeList) {
|
||||
node.keyError = ''
|
||||
node.valueError = ''
|
||||
|
||||
const trimmedKey = node.key.trim()
|
||||
const fullPath = parentPath ? `${parentPath}.${trimmedKey}` : trimmedKey
|
||||
|
||||
// Key 验证
|
||||
if (!trimmedKey) {
|
||||
node.keyError = t('jsonEditor.keyRequired')
|
||||
isValid = false
|
||||
} else {
|
||||
const keyCount = existingKeys.get(fullPath) || 0
|
||||
if (keyCount > 0) {
|
||||
node.keyError = t('jsonEditor.keyDuplicate')
|
||||
isValid = false
|
||||
}
|
||||
existingKeys.set(fullPath, keyCount + 1)
|
||||
}
|
||||
|
||||
// Value 验证
|
||||
if (node.type === 'number') {
|
||||
if (node.value && Number.isNaN(Number(node.value))) {
|
||||
node.valueError = t('jsonEditor.numberInvalid')
|
||||
isValid = false
|
||||
}
|
||||
}
|
||||
|
||||
// 递归验证子节点
|
||||
if (node.children.length) {
|
||||
validate(node.children, fullPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validate(nodes.value)
|
||||
return isValid
|
||||
}
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
validate: validateAll,
|
||||
getValue: () => buildJsonData(nodes.value),
|
||||
setValue: initializeNodes
|
||||
})
|
||||
|
||||
// 监听 modelValue 变化(仅用于初始化)
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newValue) => {
|
||||
initializeNodes(newValue)
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// 手动触发更新的方法(供内部操作调用)
|
||||
const emitUpdate = () => {
|
||||
nextTick(() => {
|
||||
const newValue = buildJsonData(nodes.value)
|
||||
emit('update:modelValue', newValue)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.json-editor {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.json-editor-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.json-tree {
|
||||
border: 1px solid var(--app-border-color);
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
margin-bottom: 16px;
|
||||
min-height: 100px;
|
||||
background: var(--app-content-bg);
|
||||
}
|
||||
|
||||
.json-empty {
|
||||
text-align: center;
|
||||
color: var(--app-text-secondary);
|
||||
padding: 20px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.json-preview {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.json-preview-label {
|
||||
font-size: 12px;
|
||||
color: var(--app-text-secondary);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.import-dialog-tip {
|
||||
font-size: 13px;
|
||||
color: var(--app-text-secondary);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.json-editor-error {
|
||||
color: #f56c6c;
|
||||
font-size: 12px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<div class="json-node" :class="{ 'is-child': depth > 0 }">
|
||||
<!-- 节点内容 -->
|
||||
<div class="json-node-content">
|
||||
<!-- 展开/折叠按钮 (对象和数组) -->
|
||||
<el-button
|
||||
v-if="isContainer"
|
||||
link
|
||||
class="expand-btn"
|
||||
@click="$emit('toggle-expand', node.id)"
|
||||
>
|
||||
<el-icon>
|
||||
<ArrowRight v-if="!node.expanded" />
|
||||
<ArrowDown v-else />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<span v-else class="expand-placeholder"></span>
|
||||
|
||||
<!-- Key 输入 -->
|
||||
<div class="node-field key-field">
|
||||
<el-input
|
||||
v-model="node.key"
|
||||
:placeholder="t('jsonEditor.keyPlaceholder')"
|
||||
size="small"
|
||||
@input="node.keyError = ''"
|
||||
/>
|
||||
<p v-if="node.keyError" class="node-error">{{ node.keyError }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 类型选择 -->
|
||||
<div class="node-field type-field">
|
||||
<el-select
|
||||
v-model="node.type"
|
||||
size="small"
|
||||
@change="handleTypeChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="type in typeOptions"
|
||||
:key="type"
|
||||
:label="t(`jsonEditor.types.${type}`)"
|
||||
:value="type"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<!-- Value 输入 -->
|
||||
<div class="node-field value-field">
|
||||
<!-- 布尔类型 -->
|
||||
<el-select
|
||||
v-if="node.type === 'boolean'"
|
||||
v-model="node.value"
|
||||
size="small"
|
||||
>
|
||||
<el-option :label="'true'" value="true" />
|
||||
<el-option :label="'false'" value="false" />
|
||||
</el-select>
|
||||
|
||||
<!-- 对象/数组类型 - 显示子节点 -->
|
||||
<template v-else-if="isContainer">
|
||||
<span class="container-label">
|
||||
<template v-if="node.type === 'array'">[{{ node.children.length }}]</template>
|
||||
<template v-else>{ {{ node.children.length }} }</template>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<!-- 数字类型 -->
|
||||
<el-input
|
||||
v-else-if="node.type === 'number'"
|
||||
v-model="node.value"
|
||||
:placeholder="t('jsonEditor.valuePlaceholder')"
|
||||
size="small"
|
||||
type="number"
|
||||
@input="node.valueError = ''"
|
||||
/>
|
||||
|
||||
<!-- 字符串类型 -->
|
||||
<el-input
|
||||
v-else
|
||||
v-model="node.value"
|
||||
:placeholder="t('jsonEditor.valuePlaceholder')"
|
||||
size="small"
|
||||
@input="node.valueError = ''"
|
||||
/>
|
||||
<p v-if="node.valueError" class="node-error">{{ node.valueError }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="node-actions">
|
||||
<el-button
|
||||
v-if="isContainer"
|
||||
link
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="$emit('add-child', node.id)"
|
||||
>
|
||||
{{ t('jsonEditor.addChild') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="depth > 0 || node.type === 'array'"
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="$emit('delete')"
|
||||
>
|
||||
{{ t('jsonEditor.remove') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 子节点容器 -->
|
||||
<div v-if="isContainer && node.expanded" class="json-node-children">
|
||||
<JsonNode
|
||||
v-for="(child, index) in node.children"
|
||||
:key="child.id"
|
||||
:node="child"
|
||||
:depth="depth + 1"
|
||||
@update="$emit('update')"
|
||||
@delete="removeChild(index)"
|
||||
@add-child="(id: string) => $emit('add-child', id)"
|
||||
@toggle-expand="(id: string) => $emit('toggle-expand', id)"
|
||||
/>
|
||||
|
||||
<div v-if="!node.children.length" class="child-empty">
|
||||
{{ node.type === 'array' ? t('jsonEditor.arrayEmpty') : t('jsonEditor.objectEmpty') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick } from 'vue'
|
||||
import { ArrowRight, ArrowDown } from '@element-plus/icons-vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { JsonNodeData, JsonValueType } from './JsonEditor.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
node: JsonNodeData
|
||||
depth: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update'): void
|
||||
(e: 'delete'): void
|
||||
(e: 'add-child', id: string): void
|
||||
(e: 'toggle-expand', id: string): void
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const typeOptions: JsonValueType[] = ['string', 'number', 'boolean', 'object', 'array']
|
||||
|
||||
const isContainer = computed(() => props.node.type === 'object' || props.node.type === 'array')
|
||||
|
||||
const handleTypeChange = (newType: JsonValueType) => {
|
||||
// 清空 children 如果切换到非容器类型
|
||||
if (newType !== 'object' && newType !== 'array') {
|
||||
props.node.children = []
|
||||
}
|
||||
// 清空 value 如果切换到容器类型
|
||||
if (newType === 'object' || newType === 'array') {
|
||||
props.node.value = ''
|
||||
}
|
||||
// 初始化空 children 如果切换到容器类型
|
||||
if ((newType === 'object' || newType === 'array') && !props.node.children.length) {
|
||||
props.node.children = []
|
||||
}
|
||||
nextTick(() => {
|
||||
emit('update')
|
||||
})
|
||||
}
|
||||
|
||||
const removeChild = (index: number) => {
|
||||
nextTick(() => {
|
||||
props.node.children.splice(index, 1)
|
||||
emit('update')
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.json-node {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.json-node.is-child {
|
||||
margin-left: 24px;
|
||||
padding-left: 12px;
|
||||
border-left: 1px dashed var(--app-border-color);
|
||||
}
|
||||
|
||||
.json-node-content {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.expand-btn {
|
||||
padding: 2px;
|
||||
min-width: 20px;
|
||||
}
|
||||
|
||||
.expand-placeholder {
|
||||
width: 20px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.node-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.key-field {
|
||||
flex: 1;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.type-field {
|
||||
flex: 0 0 100px;
|
||||
}
|
||||
|
||||
.value-field {
|
||||
flex: 1;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.container-label {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
background: var(--app-bg-color);
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--app-text-secondary);
|
||||
}
|
||||
|
||||
.node-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.node-error {
|
||||
color: #f56c6c;
|
||||
font-size: 11px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.json-node-children {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.child-empty {
|
||||
text-align: center;
|
||||
color: var(--app-text-secondary);
|
||||
font-size: 12px;
|
||||
padding: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -813,7 +813,7 @@
|
||||
"importTitleLimit": "Import Usage Limits JSON",
|
||||
"importTitleCustom": "Import Custom Parameters JSON",
|
||||
"importDescription": "Paste a JSON object whose values can be string, number, or boolean. Confirming will overwrite current entries.",
|
||||
"importPlaceholder": "{ \"feature_dashboard\": \"enabled\" }",
|
||||
"importPlaceholder": "Please enter a JSON object",
|
||||
"importCancel": "Cancel",
|
||||
"importConfirm": "Parse & Overwrite",
|
||||
"importSuccess": "JSON imported successfully",
|
||||
@@ -964,5 +964,42 @@
|
||||
"days": "{days} day(s)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"jsonEditor": {
|
||||
"addItem": "Add Field",
|
||||
"clearAll": "Clear",
|
||||
"copyJson": "Copy JSON",
|
||||
"importJson": "Import JSON",
|
||||
"emptyState": "No data yet, add fields or import JSON",
|
||||
"preview": "JSON Preview",
|
||||
"copyEmpty": "Nothing to copy",
|
||||
"copySuccess": "Copied successfully",
|
||||
"copyError": "Copy failed",
|
||||
"importTitle": "Import JSON",
|
||||
"importDescription": "Please enter valid JSON string, supports nested objects and arrays",
|
||||
"importPlaceholder": "Please enter a valid JSON string",
|
||||
"importCancel": "Cancel",
|
||||
"importConfirm": "Import",
|
||||
"importSuccess": "Imported successfully",
|
||||
"importFailed": "Invalid JSON format",
|
||||
"keyLabel": "Key",
|
||||
"keyPlaceholder": "Enter key",
|
||||
"valueLabel": "Value",
|
||||
"valuePlaceholder": "Enter value",
|
||||
"typeLabel": "Type",
|
||||
"keyRequired": "Key is required",
|
||||
"keyDuplicate": "Duplicate key",
|
||||
"numberInvalid": "Please enter a valid number",
|
||||
"addChild": "Add Child",
|
||||
"remove": "Delete",
|
||||
"arrayEmpty": "Array is empty, click to add child",
|
||||
"objectEmpty": "Object is empty, click to add child",
|
||||
"types": {
|
||||
"string": "String",
|
||||
"number": "Number",
|
||||
"boolean": "Boolean",
|
||||
"object": "Object",
|
||||
"array": "Array"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -812,7 +812,7 @@
|
||||
"importTitleLimit": "利用制限 JSON をインポート",
|
||||
"importTitleCustom": "カスタムパラメータ JSON をインポート",
|
||||
"importDescription": "キーを設定名とするオブジェクト形式の JSON を貼り付けてください。値は文字列・数値・ブールをサポートします。確定すると現在のリストを上書きします。",
|
||||
"importPlaceholder": "{ \"feature_dashboard\": \"enabled\" }",
|
||||
"importPlaceholder": "JSONオブジェクトを入力してください",
|
||||
"importCancel": "キャンセル",
|
||||
"importConfirm": "解析して上書き",
|
||||
"importSuccess": "JSON を取り込みました",
|
||||
@@ -963,5 +963,42 @@
|
||||
"days": "{days}日"
|
||||
}
|
||||
}
|
||||
},
|
||||
"jsonEditor": {
|
||||
"addItem": "フィールド追加",
|
||||
"clearAll": "クリア",
|
||||
"copyJson": "JSONをコピー",
|
||||
"importJson": "JSONをインポート",
|
||||
"emptyState": "データがありません。フィールドを追加するかJSONをインポートしてください",
|
||||
"preview": "JSONプレビュー",
|
||||
"copyEmpty": "コピーするJSONがありません",
|
||||
"copySuccess": "コピー成功",
|
||||
"copyError": "コピー失敗",
|
||||
"importTitle": "JSONをインポート",
|
||||
"importDescription": "有効なJSON文字列を入力してください。ネストされたオブジェクトと配列をサポートします",
|
||||
"importPlaceholder": "有効なJSON文字列を入力してください",
|
||||
"importCancel": "キャンセル",
|
||||
"importConfirm": "インポート",
|
||||
"importSuccess": "インポート成功",
|
||||
"importFailed": "JSON形式が無効です",
|
||||
"keyLabel": "キー",
|
||||
"keyPlaceholder": "キーを入力",
|
||||
"valueLabel": "値",
|
||||
"valuePlaceholder": "値を入力",
|
||||
"typeLabel": "タイプ",
|
||||
"keyRequired": "キーを入力してください",
|
||||
"keyDuplicate": "キーが重複しています",
|
||||
"numberInvalid": "有効な数値を入力してください",
|
||||
"addChild": "子要素を追加",
|
||||
"remove": "削除",
|
||||
"arrayEmpty": "配列が空です。クリックして子要素を追加",
|
||||
"objectEmpty": "オブジェクトが空です。クリックして子要素を追加",
|
||||
"types": {
|
||||
"string": "文字列",
|
||||
"number": "数値",
|
||||
"boolean": "真偽値",
|
||||
"object": "オブジェクト",
|
||||
"array": "配列"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -846,7 +846,7 @@
|
||||
"importTitleLimit": "导入使用限制 JSON",
|
||||
"importTitleCustom": "导入自定义参数 JSON",
|
||||
"importDescription": "粘贴一个对象格式的 JSON,键为配置项,值支持字符串、数字或布尔类型。提交后将覆盖当前列表。",
|
||||
"importPlaceholder": "{ \"feature_dashboard\": \"enabled\" }",
|
||||
"importPlaceholder": "请输入 JSON 对象",
|
||||
"importCancel": "取消",
|
||||
"importConfirm": "解析并覆盖",
|
||||
"importSuccess": "JSON 解析成功",
|
||||
@@ -997,5 +997,42 @@
|
||||
"days": "{days}天"
|
||||
}
|
||||
}
|
||||
},
|
||||
"jsonEditor": {
|
||||
"addItem": "添加字段",
|
||||
"clearAll": "清空",
|
||||
"copyJson": "复制JSON",
|
||||
"importJson": "导入JSON",
|
||||
"emptyState": "暂无数据,请添加字段或导入JSON",
|
||||
"preview": "JSON 预览",
|
||||
"copyEmpty": "没有可复制的JSON",
|
||||
"copySuccess": "复制成功",
|
||||
"copyError": "复制失败",
|
||||
"importTitle": "导入 JSON",
|
||||
"importDescription": "请输入有效的 JSON 字符串,支持嵌套对象和数组",
|
||||
"importPlaceholder": "请输入有效的 JSON 字符串",
|
||||
"importCancel": "取消",
|
||||
"importConfirm": "导入",
|
||||
"importSuccess": "导入成功",
|
||||
"importFailed": "JSON 格式不正确",
|
||||
"keyLabel": "键名",
|
||||
"keyPlaceholder": "请输入键名",
|
||||
"valueLabel": "值",
|
||||
"valuePlaceholder": "请输入值",
|
||||
"typeLabel": "类型",
|
||||
"keyRequired": "请输入键名",
|
||||
"keyDuplicate": "键名重复",
|
||||
"numberInvalid": "请输入有效数字",
|
||||
"addChild": "添加子项",
|
||||
"remove": "删除",
|
||||
"arrayEmpty": "数组为空,点击添加子项",
|
||||
"objectEmpty": "对象为空,点击添加子项",
|
||||
"types": {
|
||||
"string": "字符串",
|
||||
"number": "数字",
|
||||
"boolean": "布尔",
|
||||
"object": "对象",
|
||||
"array": "数组"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,295 +213,24 @@
|
||||
|
||||
<!-- 功能配置 -->
|
||||
<div class="license-form">
|
||||
<div class="key-value-section-header">
|
||||
<h3 class="section-title">{{ t('pages.licenses.form.sections.featureConfig') }}</h3>
|
||||
<div class="key-value-section-actions">
|
||||
<el-button size="small" @click="addFeatureConfigEntry">
|
||||
{{ t('pages.licenses.form.keyValue.addItem') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
text
|
||||
:disabled="!featureConfigEntries.length"
|
||||
@click="clearFeatureConfigEntries"
|
||||
>
|
||||
{{ t('pages.licenses.form.keyValue.clearAll') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
text
|
||||
:disabled="!featureConfigPreview"
|
||||
@click="copyJson(featureConfigPreview)"
|
||||
>
|
||||
{{ t('pages.licenses.form.keyValue.copyJson') }}
|
||||
</el-button>
|
||||
<el-button size="small" text @click="openImportDialog('feature')">
|
||||
{{ t('pages.licenses.form.keyValue.importJson') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="key-value-list">
|
||||
<div v-for="(item, index) in featureConfigEntries" :key="item.id" class="key-value-row">
|
||||
<div class="key-value-field">
|
||||
<label>{{ t('pages.licenses.form.keyValue.keyLabel') }}</label>
|
||||
<el-input
|
||||
v-model="item.key"
|
||||
:placeholder="t('pages.licenses.form.keyValue.keyPlaceholder')"
|
||||
@input="item.keyError = ''"
|
||||
/>
|
||||
<p v-if="item.keyError" class="key-value-error">{{ item.keyError }}</p>
|
||||
</div>
|
||||
<div class="key-value-field type-field">
|
||||
<label>{{ t('pages.licenses.form.keyValue.typeLabel') }}</label>
|
||||
<el-select v-model="item.type" @change="handleEntryTypeChange(item)">
|
||||
<el-option
|
||||
v-for="type in typeOptions"
|
||||
:key="type"
|
||||
:label="t(`pages.licenses.form.keyValue.typeOptions.${type}`)"
|
||||
:value="type"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="key-value-field">
|
||||
<label>{{ t('pages.licenses.form.keyValue.valueLabel') }}</label>
|
||||
<template v-if="item.type === 'bool'">
|
||||
<el-select v-model="item.value" @change="item.valueError = ''">
|
||||
<el-option
|
||||
v-for="option in boolOptions"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
<el-input
|
||||
v-else
|
||||
v-model="item.value"
|
||||
:placeholder="t('pages.licenses.form.keyValue.valuePlaceholder')"
|
||||
@input="item.valueError = ''"
|
||||
:inputmode="item.type === 'number' ? 'decimal' : 'text'"
|
||||
/>
|
||||
<p v-if="item.valueError" class="key-value-error">{{ item.valueError }}</p>
|
||||
</div>
|
||||
<el-button link type="danger" @click="removeFeatureConfigEntry(index)">
|
||||
{{ t('pages.licenses.form.keyValue.remove') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div v-if="!featureConfigEntries.length" class="key-value-empty">
|
||||
{{ t('pages.licenses.form.keyValue.emptyState') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="key-value-preview">
|
||||
<div class="key-value-preview-label">
|
||||
{{ t('pages.licenses.form.keyValue.preview') }}
|
||||
</div>
|
||||
<el-input type="textarea" :rows="3" :model-value="featureConfigPreview" readonly />
|
||||
</div>
|
||||
<JsonEditor ref="featureConfigRef" v-model="featureConfigData" />
|
||||
</div>
|
||||
|
||||
<!-- 限制设置 -->
|
||||
<div class="license-form">
|
||||
<div class="key-value-section-header">
|
||||
<h3 class="section-title">{{ t('pages.licenses.form.sections.usageLimits') }}</h3>
|
||||
<div class="key-value-section-actions">
|
||||
<el-button size="small" @click="addLimitEntry">
|
||||
{{ t('pages.licenses.form.keyValue.addItem') }}
|
||||
</el-button>
|
||||
<el-button size="small" text :disabled="!limitEntries.length" @click="clearLimitEntries">
|
||||
{{ t('pages.licenses.form.keyValue.clearAll') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
text
|
||||
:disabled="!usageJsonPreview"
|
||||
@click="copyJson(usageJsonPreview)"
|
||||
>
|
||||
{{ t('pages.licenses.form.keyValue.copyJson') }}
|
||||
</el-button>
|
||||
<el-button size="small" text @click="openImportDialog('limit')">
|
||||
{{ t('pages.licenses.form.keyValue.importJson') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="key-value-list">
|
||||
<div v-for="(item, index) in limitEntries" :key="item.id" class="key-value-row">
|
||||
<div class="key-value-field">
|
||||
<label>{{ t('pages.licenses.form.keyValue.keyLabel') }}</label>
|
||||
<el-input
|
||||
v-model="item.key"
|
||||
:placeholder="t('pages.licenses.form.keyValue.keyPlaceholder')"
|
||||
@input="item.keyError = ''"
|
||||
/>
|
||||
<p v-if="item.keyError" class="key-value-error">{{ item.keyError }}</p>
|
||||
</div>
|
||||
<div class="key-value-field type-field">
|
||||
<label>{{ t('pages.licenses.form.keyValue.typeLabel') }}</label>
|
||||
<el-select v-model="item.type" @change="handleEntryTypeChange(item)">
|
||||
<el-option
|
||||
v-for="type in typeOptions"
|
||||
:key="type"
|
||||
:label="t(`pages.licenses.form.keyValue.typeOptions.${type}`)"
|
||||
:value="type"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="key-value-field">
|
||||
<label>{{ t('pages.licenses.form.keyValue.valueLabel') }}</label>
|
||||
<template v-if="item.type === 'bool'">
|
||||
<el-select v-model="item.value" @change="item.valueError = ''">
|
||||
<el-option
|
||||
v-for="option in boolOptions"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
<el-input
|
||||
v-else
|
||||
v-model="item.value"
|
||||
:placeholder="t('pages.licenses.form.keyValue.valuePlaceholder')"
|
||||
@input="item.valueError = ''"
|
||||
:inputmode="item.type === 'number' ? 'decimal' : 'text'"
|
||||
/>
|
||||
<p v-if="item.valueError" class="key-value-error">{{ item.valueError }}</p>
|
||||
</div>
|
||||
<el-button link type="danger" @click="removeLimitEntry(index)">
|
||||
{{ t('pages.licenses.form.keyValue.remove') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div v-if="!limitEntries.length" class="key-value-empty">
|
||||
{{ t('pages.licenses.form.keyValue.emptyState') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="key-value-preview">
|
||||
<div class="key-value-preview-label">
|
||||
{{ t('pages.licenses.form.keyValue.preview') }}
|
||||
</div>
|
||||
<el-input type="textarea" :rows="3" :model-value="usageJsonPreview" readonly />
|
||||
</div>
|
||||
<JsonEditor ref="usageLimitsRef" v-model="usageLimitsData" />
|
||||
</div>
|
||||
|
||||
<!-- 自定义参数 -->
|
||||
<div class="license-form">
|
||||
<div class="key-value-section-header">
|
||||
<h3 class="section-title">{{ t('pages.licenses.form.sections.customParameters') }}</h3>
|
||||
<div class="key-value-section-actions">
|
||||
<el-button size="small" @click="addCustomEntry">
|
||||
{{ t('pages.licenses.form.keyValue.addItem') }}
|
||||
</el-button>
|
||||
<el-button size="small" text :disabled="!customEntries.length" @click="clearCustomEntries">
|
||||
{{ t('pages.licenses.form.keyValue.clearAll') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
text
|
||||
:disabled="!customJsonPreview"
|
||||
@click="copyJson(customJsonPreview)"
|
||||
>
|
||||
{{ t('pages.licenses.form.keyValue.copyJson') }}
|
||||
</el-button>
|
||||
<el-button size="small" text @click="openImportDialog('custom')">
|
||||
{{ t('pages.licenses.form.keyValue.importJson') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="key-value-list">
|
||||
<div v-for="(item, index) in customEntries" :key="item.id" class="key-value-row">
|
||||
<div class="key-value-field">
|
||||
<label>{{ t('pages.licenses.form.keyValue.keyLabel') }}</label>
|
||||
<el-input
|
||||
v-model="item.key"
|
||||
:placeholder="t('pages.licenses.form.keyValue.keyPlaceholder')"
|
||||
@input="item.keyError = ''"
|
||||
/>
|
||||
<p v-if="item.keyError" class="key-value-error">{{ item.keyError }}</p>
|
||||
</div>
|
||||
<div class="key-value-field type-field">
|
||||
<label>{{ t('pages.licenses.form.keyValue.typeLabel') }}</label>
|
||||
<el-select v-model="item.type" @change="handleEntryTypeChange(item)">
|
||||
<el-option
|
||||
v-for="type in typeOptions"
|
||||
:key="type"
|
||||
:label="t(`pages.licenses.form.keyValue.typeOptions.${type}`)"
|
||||
:value="type"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="key-value-field">
|
||||
<label>{{ t('pages.licenses.form.keyValue.valueLabel') }}</label>
|
||||
<template v-if="item.type === 'bool'">
|
||||
<el-select v-model="item.value" @change="item.valueError = ''">
|
||||
<el-option
|
||||
v-for="option in boolOptions"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
<el-input
|
||||
v-else
|
||||
v-model="item.value"
|
||||
:placeholder="t('pages.licenses.form.keyValue.valuePlaceholder')"
|
||||
@input="item.valueError = ''"
|
||||
:inputmode="item.type === 'number' ? 'decimal' : 'text'"
|
||||
/>
|
||||
<p v-if="item.valueError" class="key-value-error">{{ item.valueError }}</p>
|
||||
</div>
|
||||
<el-button link type="danger" @click="removeCustomEntry(index)">
|
||||
{{ t('pages.licenses.form.keyValue.remove') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div v-if="!customEntries.length" class="key-value-empty">
|
||||
{{ t('pages.licenses.form.keyValue.emptyState') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="key-value-preview">
|
||||
<div class="key-value-preview-label">
|
||||
{{ t('pages.licenses.form.keyValue.preview') }}
|
||||
</div>
|
||||
<el-input type="textarea" :rows="3" :model-value="customJsonPreview" readonly />
|
||||
</div>
|
||||
<JsonEditor ref="customParamsRef" v-model="customParamsData" />
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
v-model="importDialogVisible"
|
||||
:title="importDialogTitle"
|
||||
width="520px"
|
||||
destroy-on-close
|
||||
>
|
||||
<p class="import-dialog-tip">
|
||||
{{ t('pages.licenses.form.keyValue.importDescription') }}
|
||||
</p>
|
||||
<el-input
|
||||
v-model="importDialogContent"
|
||||
type="textarea"
|
||||
:rows="8"
|
||||
:placeholder="t('pages.licenses.form.keyValue.importPlaceholder')"
|
||||
/>
|
||||
<p v-if="importDialogError" class="key-value-error import-error">{{ importDialogError }}</p>
|
||||
<template #footer>
|
||||
<el-button @click="importDialogVisible = false">
|
||||
{{ t('pages.licenses.form.keyValue.importCancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="handleImportConfirm">
|
||||
{{ t('pages.licenses.form.keyValue.importConfirm') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -519,58 +248,34 @@ import {
|
||||
} from '@/api/license'
|
||||
import { getCustomers, type Customer } from '@/api/customer'
|
||||
import { getEnumOptions, type RawEnumItem } from '@/api/enum'
|
||||
|
||||
type KeyValueType = 'string' | 'number' | 'bool'
|
||||
|
||||
interface KeyValueItem {
|
||||
id: string
|
||||
key: string
|
||||
value: string
|
||||
type: KeyValueType
|
||||
keyError?: string
|
||||
valueError?: string
|
||||
}
|
||||
|
||||
const KEY_PATTERN = /^[a-z0-9-]{1,32}$/
|
||||
import JsonEditor from '@/components/common/JsonEditor.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { t } = useI18n()
|
||||
const boolOptions = computed(() => [
|
||||
{ label: t('pages.licenses.form.keyValue.booleanTrue'), value: 'true' },
|
||||
{ label: t('pages.licenses.form.keyValue.booleanFalse'), value: 'false' }
|
||||
])
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
// JsonEditor 组件引用
|
||||
const featureConfigRef = ref<InstanceType<typeof JsonEditor>>()
|
||||
const usageLimitsRef = ref<InstanceType<typeof JsonEditor>>()
|
||||
const customParamsRef = ref<InstanceType<typeof JsonEditor>>()
|
||||
|
||||
// 响应式数据
|
||||
const submitting = ref(false)
|
||||
const customerLoading = ref(false)
|
||||
const customerOptions = ref<Customer[]>([])
|
||||
|
||||
const featureConfigEntries = ref<KeyValueItem[]>([])
|
||||
const limitEntries = ref<KeyValueItem[]>([])
|
||||
const customEntries = ref<KeyValueItem[]>([])
|
||||
const importDialogVisible = ref(false)
|
||||
const importDialogTarget = ref<'feature' | 'limit' | 'custom'>('feature')
|
||||
const importDialogTitle = computed(() => {
|
||||
if (importDialogTarget.value === 'limit') {
|
||||
return t('pages.licenses.form.keyValue.importTitleLimit')
|
||||
}
|
||||
if (importDialogTarget.value === 'custom') {
|
||||
return t('pages.licenses.form.keyValue.importTitleCustom')
|
||||
}
|
||||
return t('pages.licenses.form.keyValue.importTitleFeature')
|
||||
})
|
||||
const importDialogContent = ref('')
|
||||
const importDialogError = ref('')
|
||||
// JsonEditor 数据
|
||||
const featureConfigData = ref<Record<string, any> | null>(null)
|
||||
const usageLimitsData = ref<Record<string, any> | null>(null)
|
||||
const customParamsData = ref<Record<string, any> | null>(null)
|
||||
|
||||
// 枚举选项
|
||||
const deploymentTypeOptions = ref<RawEnumItem[]>([])
|
||||
const encryptionTypeOptions = ref<RawEnumItem[]>([])
|
||||
|
||||
const typeOptions: KeyValueType[] = ['string', 'number', 'bool']
|
||||
// 表单数据
|
||||
const formData = reactive<
|
||||
AuthorizationCodeCreateRequest & {
|
||||
@@ -594,10 +299,6 @@ const formData = reactive<
|
||||
// 独立的响应式变量
|
||||
const dateRange = ref<[string, string] | null>(null)
|
||||
|
||||
const featureConfigPreview = computed(() => buildJsonString(featureConfigEntries.value))
|
||||
const usageJsonPreview = computed(() => buildJsonString(limitEntries.value))
|
||||
const customJsonPreview = computed(() => buildJsonString(customEntries.value))
|
||||
|
||||
// 计算属性
|
||||
const isEdit = computed(() => {
|
||||
return route.name === 'licenses-edit' && route.params.id
|
||||
@@ -744,277 +445,6 @@ const formRules: FormRules = {
|
||||
]
|
||||
}
|
||||
|
||||
const createEntryId = () => `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
||||
|
||||
const createEmptyEntry = (): KeyValueItem => ({
|
||||
id: createEntryId(),
|
||||
key: '',
|
||||
value: '',
|
||||
type: 'string',
|
||||
keyError: '',
|
||||
valueError: ''
|
||||
})
|
||||
|
||||
const convertValueByType = (value: string, type: KeyValueType) => {
|
||||
if (type === 'number') {
|
||||
const num = Number(value)
|
||||
if (Number.isNaN(num)) {
|
||||
throw new Error('invalid number')
|
||||
}
|
||||
return num
|
||||
}
|
||||
if (type === 'bool') {
|
||||
return value === 'true'
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
const buildJsonRecord = (entries: KeyValueItem[]) => {
|
||||
if (!entries.length) {
|
||||
return null
|
||||
}
|
||||
const result: Record<string, string | number | boolean> = {}
|
||||
entries.forEach(item => {
|
||||
const trimmedKey = item.key.trim()
|
||||
if (trimmedKey) {
|
||||
try {
|
||||
result[trimmedKey] = convertValueByType(item.value, item.type)
|
||||
} catch {
|
||||
// 如果转换失败,则忽略该条目,等待校验提示
|
||||
}
|
||||
}
|
||||
})
|
||||
return Object.keys(result).length ? result : null
|
||||
}
|
||||
|
||||
const buildJsonString = (entries: KeyValueItem[]) => {
|
||||
const record = buildJsonRecord(entries)
|
||||
return record ? JSON.stringify(record) : ''
|
||||
}
|
||||
|
||||
const addFeatureConfigEntry = () => {
|
||||
featureConfigEntries.value.push(createEmptyEntry())
|
||||
}
|
||||
|
||||
const addLimitEntry = () => {
|
||||
limitEntries.value.push(createEmptyEntry())
|
||||
}
|
||||
|
||||
const addCustomEntry = () => {
|
||||
customEntries.value.push(createEmptyEntry())
|
||||
}
|
||||
|
||||
const removeFeatureConfigEntry = (index: number) => {
|
||||
featureConfigEntries.value.splice(index, 1)
|
||||
}
|
||||
|
||||
const removeLimitEntry = (index: number) => {
|
||||
limitEntries.value.splice(index, 1)
|
||||
}
|
||||
|
||||
const removeCustomEntry = (index: number) => {
|
||||
customEntries.value.splice(index, 1)
|
||||
}
|
||||
|
||||
const clearFeatureConfigEntries = () => {
|
||||
featureConfigEntries.value = []
|
||||
}
|
||||
|
||||
const clearLimitEntries = () => {
|
||||
limitEntries.value = []
|
||||
}
|
||||
|
||||
const clearCustomEntries = () => {
|
||||
customEntries.value = []
|
||||
}
|
||||
|
||||
const handleEntryTypeChange = (entry: KeyValueItem) => {
|
||||
if (entry.type === 'bool') {
|
||||
entry.value = entry.value === 'false' ? 'false' : 'true'
|
||||
} else if (entry.type === 'number') {
|
||||
if (entry.value && Number.isNaN(Number(entry.value))) {
|
||||
entry.value = ''
|
||||
}
|
||||
}
|
||||
entry.valueError = ''
|
||||
}
|
||||
|
||||
const fallbackCopy = (value: string) => {
|
||||
if (typeof document === 'undefined') {
|
||||
throw new Error('document is not available')
|
||||
}
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.value = value
|
||||
textarea.style.position = 'fixed'
|
||||
textarea.style.opacity = '0'
|
||||
document.body.appendChild(textarea)
|
||||
textarea.focus()
|
||||
textarea.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(textarea)
|
||||
}
|
||||
|
||||
const copyJson = async (value: string) => {
|
||||
if (!value) {
|
||||
ElMessage.info(t('pages.licenses.form.keyValue.copyEmpty'))
|
||||
return
|
||||
}
|
||||
try {
|
||||
if (navigator?.clipboard?.writeText) {
|
||||
await navigator.clipboard.writeText(value)
|
||||
} else {
|
||||
fallbackCopy(value)
|
||||
}
|
||||
ElMessage.success(t('pages.licenses.form.keyValue.copySuccess'))
|
||||
} catch (error) {
|
||||
console.error('复制JSON失败:', error)
|
||||
ElMessage.error(t('pages.licenses.form.keyValue.copyError'))
|
||||
}
|
||||
}
|
||||
|
||||
const validateKeyValueEntries = (entries: KeyValueItem[]) => {
|
||||
let isValid = true
|
||||
const existingKeys = new Set<string>()
|
||||
|
||||
entries.forEach(item => {
|
||||
item.keyError = ''
|
||||
item.valueError = ''
|
||||
|
||||
const trimmedKey = item.key.trim()
|
||||
if (!trimmedKey) {
|
||||
item.keyError = t('pages.licenses.form.keyValue.keyRequired')
|
||||
isValid = false
|
||||
} else if (!KEY_PATTERN.test(trimmedKey)) {
|
||||
item.keyError = t('pages.licenses.form.keyValue.keyFormat')
|
||||
isValid = false
|
||||
} else if (existingKeys.has(trimmedKey)) {
|
||||
item.keyError = t('pages.licenses.form.keyValue.keyDuplicate')
|
||||
isValid = false
|
||||
} else {
|
||||
existingKeys.add(trimmedKey)
|
||||
}
|
||||
|
||||
if (item.value.trim() === '') {
|
||||
item.valueError = t('pages.licenses.form.keyValue.valueRequired')
|
||||
isValid = false
|
||||
} else if (item.type === 'number') {
|
||||
const num = Number(item.value)
|
||||
if (Number.isNaN(num)) {
|
||||
item.valueError = t('pages.licenses.form.keyValue.numberRequired')
|
||||
isValid = false
|
||||
}
|
||||
} else if (item.type === 'bool') {
|
||||
if (item.value !== 'true' && item.value !== 'false') {
|
||||
item.valueError = t('pages.licenses.form.keyValue.boolRequired')
|
||||
isValid = false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return isValid
|
||||
}
|
||||
|
||||
const openImportDialog = (target: 'feature' | 'limit' | 'custom') => {
|
||||
importDialogTarget.value = target
|
||||
importDialogContent.value =
|
||||
target === 'feature'
|
||||
? featureConfigPreview.value
|
||||
: target === 'limit'
|
||||
? usageJsonPreview.value
|
||||
: customJsonPreview.value
|
||||
importDialogError.value = ''
|
||||
importDialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleImportConfirm = () => {
|
||||
importDialogError.value = ''
|
||||
try {
|
||||
const content = importDialogContent.value.trim()
|
||||
if (!content) {
|
||||
if (importDialogTarget.value === 'feature') {
|
||||
featureConfigEntries.value = []
|
||||
} else if (importDialogTarget.value === 'limit') {
|
||||
limitEntries.value = []
|
||||
} else {
|
||||
customEntries.value = []
|
||||
}
|
||||
} else {
|
||||
const entries = parseJsonToEntries(content, { strict: true })
|
||||
if (!entries.length) {
|
||||
throw new Error(t('pages.licenses.form.keyValue.importEmpty'))
|
||||
}
|
||||
if (importDialogTarget.value === 'feature') {
|
||||
featureConfigEntries.value = entries
|
||||
} else if (importDialogTarget.value === 'limit') {
|
||||
limitEntries.value = entries
|
||||
} else {
|
||||
customEntries.value = entries
|
||||
}
|
||||
}
|
||||
importDialogVisible.value = false
|
||||
ElMessage.success(t('pages.licenses.form.keyValue.importSuccess'))
|
||||
} catch (error: any) {
|
||||
importDialogError.value =
|
||||
error?.message || t('pages.licenses.form.keyValue.importFailed')
|
||||
}
|
||||
}
|
||||
|
||||
const mapObjectToEntries = (data: Record<string, any>): KeyValueItem[] => {
|
||||
return Object.entries(data).map(([key, value]) => {
|
||||
let type: KeyValueType = 'string'
|
||||
let normalizedValue = ''
|
||||
if (typeof value === 'number') {
|
||||
type = 'number'
|
||||
normalizedValue = String(value)
|
||||
} else if (typeof value === 'boolean') {
|
||||
type = 'bool'
|
||||
normalizedValue = value ? 'true' : 'false'
|
||||
} else {
|
||||
normalizedValue = value === undefined || value === null ? '' : String(value)
|
||||
}
|
||||
|
||||
return {
|
||||
id: createEntryId(),
|
||||
key,
|
||||
value: normalizedValue,
|
||||
type,
|
||||
keyError: '',
|
||||
valueError: ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const parseJsonToEntries = (raw: unknown, options?: { strict?: boolean }): KeyValueItem[] => {
|
||||
if (!raw) return []
|
||||
|
||||
let parsed: unknown = raw
|
||||
if (typeof raw === 'string') {
|
||||
if (!raw.trim()) return []
|
||||
try {
|
||||
parsed = JSON.parse(raw)
|
||||
} catch (error) {
|
||||
if (options?.strict) {
|
||||
throw error
|
||||
}
|
||||
console.warn('解析JSON失败:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||
if (options?.strict) {
|
||||
throw new Error('JSON payload must be an object with key-value pairs')
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
return mapObjectToEntries(parsed as Record<string, any>)
|
||||
}
|
||||
|
||||
const hydrateEntriesFromField = (raw: unknown, target: Ref<KeyValueItem[]>) => {
|
||||
target.value = parseJsonToEntries(raw)
|
||||
}
|
||||
|
||||
// 方法
|
||||
const loadEnumOptions = async () => {
|
||||
try {
|
||||
@@ -1201,9 +631,16 @@ const loadLicenseDetail = async () => {
|
||||
]
|
||||
}
|
||||
|
||||
hydrateEntriesFromField(data.feature_config, featureConfigEntries)
|
||||
hydrateEntriesFromField(data.usage_limits, limitEntries)
|
||||
hydrateEntriesFromField(data.custom_parameters, customEntries)
|
||||
// 使用 JsonEditor 加载数据
|
||||
featureConfigData.value = typeof data.feature_config === 'string'
|
||||
? JSON.parse(data.feature_config || '{}')
|
||||
: (data.feature_config || {})
|
||||
usageLimitsData.value = typeof data.usage_limits === 'string'
|
||||
? JSON.parse(data.usage_limits || '{}')
|
||||
: (data.usage_limits || {})
|
||||
customParamsData.value = typeof data.custom_parameters === 'string'
|
||||
? JSON.parse(data.custom_parameters || '{}')
|
||||
: (data.custom_parameters || {})
|
||||
} else {
|
||||
throw new Error(response.message || t('pages.licenses.form.messages.loadDetailError'))
|
||||
}
|
||||
@@ -1226,17 +663,18 @@ const handleSubmit = async () => {
|
||||
// 执行表单验证
|
||||
await formRef.value.validate()
|
||||
|
||||
const featureConfigValid = validateKeyValueEntries(featureConfigEntries.value)
|
||||
const limitValid = validateKeyValueEntries(limitEntries.value)
|
||||
const customValid = validateKeyValueEntries(customEntries.value)
|
||||
if (!featureConfigValid || !limitValid || !customValid) {
|
||||
// 验证 JsonEditor 数据
|
||||
const featureValid = featureConfigRef.value?.validate() ?? true
|
||||
const limitValid = usageLimitsRef.value?.validate() ?? true
|
||||
const customValid = customParamsRef.value?.validate() ?? true
|
||||
if (!featureValid || !limitValid || !customValid) {
|
||||
ElMessage.error(t('pages.licenses.form.keyValue.validationFailed'))
|
||||
return
|
||||
}
|
||||
|
||||
formData.feature_config = buildJsonRecord(featureConfigEntries.value) || {}
|
||||
formData.usage_limits = usageJsonPreview.value
|
||||
formData.custom_parameters = customJsonPreview.value
|
||||
formData.feature_config = featureConfigData.value || {}
|
||||
formData.usage_limits = JSON.stringify(usageLimitsData.value || {})
|
||||
formData.custom_parameters = JSON.stringify(customParamsData.value || {})
|
||||
|
||||
submitting.value = true
|
||||
|
||||
@@ -1325,30 +763,6 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
limitEntries,
|
||||
() => {
|
||||
formData.usage_limits = usageJsonPreview.value
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
featureConfigEntries,
|
||||
() => {
|
||||
formData.feature_config = buildJsonRecord(featureConfigEntries.value) || {}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
customEntries,
|
||||
() => {
|
||||
formData.custom_parameters = customJsonPreview.value
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
// 生命周期
|
||||
onMounted(async () => {
|
||||
await loadEnumOptions()
|
||||
|
||||
@@ -176,6 +176,9 @@ const parseKeyValueData = (source: unknown): KeyValueItem[] => {
|
||||
} else if (typeof value === 'boolean') {
|
||||
type = 'bool'
|
||||
normalizedValue = value ? 'true' : 'false'
|
||||
} else if (typeof value === 'object' && value !== null) {
|
||||
// 对象或数组用 JSON.stringify 处理
|
||||
normalizedValue = JSON.stringify(value)
|
||||
} else {
|
||||
normalizedValue = value === undefined || value === null ? '' : String(value)
|
||||
}
|
||||
|
||||
@@ -138,51 +138,15 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('pages.licenses.detail.updateDialog.fields.featureConfig')">
|
||||
<div class="kv-entries">
|
||||
<div v-for="(entry, idx) in featureConfigEntries" :key="entry.id" class="kv-row">
|
||||
<el-input v-model="entry.key" placeholder="key" style="width:40%" />
|
||||
<el-select v-model="entry.type" placeholder="type" style="width:14%;margin:0 8px">
|
||||
<el-option label="string" value="string" />
|
||||
<el-option label="number" value="number" />
|
||||
<el-option label="bool" value="bool" />
|
||||
</el-select>
|
||||
<el-input v-model="entry.value" placeholder="value" style="width:40%" />
|
||||
<el-button type="text" icon="el-icon-delete" @click="removeFeatureConfigEntry(idx)" />
|
||||
</div>
|
||||
<el-button size="small" @click="addFeatureConfigEntry">{{ t('pages.licenses.form.keyValue.addItem') }}</el-button>
|
||||
</div>
|
||||
<JsonEditor ref="featureConfigRef" v-model="featureConfigData" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('pages.licenses.detail.updateDialog.fields.usageLimits')">
|
||||
<div class="kv-entries">
|
||||
<div v-for="(entry, idx) in usageLimitsEntries" :key="entry.id" class="kv-row">
|
||||
<el-input v-model="entry.key" placeholder="key" style="width:40%" />
|
||||
<el-select v-model="entry.type" placeholder="type" style="width:14%;margin:0 8px">
|
||||
<el-option label="string" value="string" />
|
||||
<el-option label="number" value="number" />
|
||||
<el-option label="bool" value="bool" />
|
||||
</el-select>
|
||||
<el-input v-model="entry.value" placeholder="value" style="width:40%" />
|
||||
<el-button type="text" icon="el-icon-delete" @click="removeUsageLimitEntry(idx)" />
|
||||
</div>
|
||||
<el-button size="small" @click="addUsageLimitEntry">{{ t('pages.licenses.form.keyValue.addItem') }}</el-button>
|
||||
</div>
|
||||
<JsonEditor ref="usageLimitsRef" v-model="usageLimitsData" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('pages.licenses.detail.updateDialog.fields.customParameters')">
|
||||
<div class="kv-entries">
|
||||
<div v-for="(entry, idx) in customParametersEntries" :key="entry.id" class="kv-row">
|
||||
<el-input v-model="entry.key" placeholder="key" style="width:40%" />
|
||||
<el-select v-model="entry.type" placeholder="type" style="width:14%;margin:0 8px">
|
||||
<el-option label="string" value="string" />
|
||||
<el-option label="number" value="number" />
|
||||
<el-option label="bool" value="bool" />
|
||||
</el-select>
|
||||
<el-input v-model="entry.value" placeholder="value" style="width:40%" />
|
||||
<el-button type="text" icon="el-icon-delete" @click="removeCustomParamEntry(idx)" />
|
||||
</div>
|
||||
<el-button size="small" @click="addCustomParamEntry">{{ t('pages.licenses.form.keyValue.addItem') }}</el-button>
|
||||
</div>
|
||||
<JsonEditor ref="customParamsRef" v-model="customParamsData" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('pages.licenses.detail.updateDialog.fields.reason')">
|
||||
@@ -223,6 +187,7 @@ import BasicInfo from './components/BasicInfo.vue'
|
||||
import AuthorizationInfo from './components/AuthorizationInfo.vue'
|
||||
import LicenseInfo from './components/LicenseInfo.vue'
|
||||
import ChangeHistory from './components/ChangeHistory.vue'
|
||||
import JsonEditor from '@/components/common/JsonEditor.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
@@ -385,12 +350,10 @@ const handleConfirmChangeValidity = async () => {
|
||||
if (updateMaxActivations.value !== null && updateMaxActivations.value !== undefined) {
|
||||
payload.max_activations = updateMaxActivations.value
|
||||
}
|
||||
const fc = buildJsonRecord(featureConfigEntries.value)
|
||||
if (fc) payload.feature_config = fc
|
||||
const ul = buildJsonRecord(usageLimitsEntries.value)
|
||||
if (ul) payload.usage_limits = ul
|
||||
const cp = buildJsonRecord(customParametersEntries.value)
|
||||
if (cp) payload.custom_parameters = cp
|
||||
// 使用 JsonEditor 数据
|
||||
payload.feature_config = featureConfigData.value || {}
|
||||
payload.usage_limits = usageLimitsData.value || {}
|
||||
payload.custom_parameters = customParamsData.value || {}
|
||||
payload.change_type = updateChangeType.value || payload.change_type
|
||||
// if reason in update dialog provided, override
|
||||
if (updateReason.value && updateReason.value.trim()) {
|
||||
@@ -417,121 +380,37 @@ const handleConfirmChangeValidity = async () => {
|
||||
|
||||
// --- update authorization dialog ---
|
||||
import type { RawEnumItem } from '@/api/enum'
|
||||
type KeyValueType = 'string' | 'number' | 'bool'
|
||||
interface KeyValueItem {
|
||||
id: string
|
||||
key: string
|
||||
value: string
|
||||
type: KeyValueType
|
||||
}
|
||||
|
||||
const updateDialogVisible = ref(false)
|
||||
const changeTypeOptions = ref<RawEnumItem[]>([])
|
||||
const updateChangeType = ref<string>('other')
|
||||
const updateMaxActivations = ref<number | null>(null)
|
||||
const featureConfigEntries = ref<KeyValueItem[]>([])
|
||||
const usageLimitsEntries = ref<KeyValueItem[]>([])
|
||||
const customParametersEntries = ref<KeyValueItem[]>([])
|
||||
const featureConfigData = ref<Record<string, any>>({})
|
||||
const usageLimitsData = ref<Record<string, any>>({})
|
||||
const customParamsData = ref<Record<string, any>>({})
|
||||
const updateReason = ref<string>('')
|
||||
const updateSubmitting = ref(false)
|
||||
|
||||
const createEntryId = () => `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
||||
const createEmptyEntry = (): KeyValueItem => ({
|
||||
id: createEntryId(),
|
||||
key: '',
|
||||
value: '',
|
||||
type: 'string'
|
||||
})
|
||||
|
||||
const addFeatureConfigEntry = () => featureConfigEntries.value.push(createEmptyEntry())
|
||||
const addUsageLimitEntry = () => usageLimitsEntries.value.push(createEmptyEntry())
|
||||
const addCustomParamEntry = () => customParametersEntries.value.push(createEmptyEntry())
|
||||
const removeFeatureConfigEntry = (i: number) => featureConfigEntries.value.splice(i, 1)
|
||||
const removeUsageLimitEntry = (i: number) => usageLimitsEntries.value.splice(i, 1)
|
||||
const removeCustomParamEntry = (i: number) => customParametersEntries.value.splice(i, 1)
|
||||
|
||||
const convertValueByType = (value: string, type: KeyValueType) => {
|
||||
if (type === 'number') {
|
||||
const num = Number(value)
|
||||
if (Number.isNaN(num)) throw new Error('invalid number')
|
||||
return num
|
||||
}
|
||||
if (type === 'bool') return value === 'true'
|
||||
return value
|
||||
}
|
||||
|
||||
const buildJsonRecord = (entries: KeyValueItem[]) => {
|
||||
if (!entries.length) return null
|
||||
const result: Record<string, any> = {}
|
||||
entries.forEach(item => {
|
||||
const k = item.key.trim()
|
||||
if (!k) return
|
||||
try {
|
||||
result[k] = convertValueByType(item.value, item.type)
|
||||
} catch {
|
||||
// ignore here; validation will surface
|
||||
}
|
||||
})
|
||||
return Object.keys(result).length ? result : null
|
||||
}
|
||||
|
||||
// Validate key-value entries: ensure key present and value matches selected type
|
||||
const validateKeyValueEntries = (entries: KeyValueItem[]): { ok: boolean; msg?: string } => {
|
||||
for (const item of entries) {
|
||||
const key = item.key?.trim()
|
||||
const val = item.value
|
||||
if (!key) {
|
||||
return { ok: false, msg: t('pages.licenses.form.keyValue.keyRequired') }
|
||||
}
|
||||
if (item.type === 'number') {
|
||||
if (val === '' || val === null || val === undefined || Number.isNaN(Number(val))) {
|
||||
return { ok: false, msg: t('pages.licenses.form.keyValue.numberRequired') }
|
||||
}
|
||||
}
|
||||
if (item.type === 'bool') {
|
||||
const lower = String(val).toLowerCase()
|
||||
if (lower !== 'true' && lower !== 'false') {
|
||||
return { ok: false, msg: t('pages.licenses.form.keyValue.boolRequired') }
|
||||
}
|
||||
}
|
||||
}
|
||||
return { ok: true }
|
||||
}
|
||||
// JsonEditor 组件引用
|
||||
const featureConfigRef = ref<InstanceType<typeof JsonEditor>>()
|
||||
const usageLimitsRef = ref<InstanceType<typeof JsonEditor>>()
|
||||
const customParamsRef = ref<InstanceType<typeof JsonEditor>>()
|
||||
|
||||
const openUpdateDialog = async () => {
|
||||
updateChangeType.value = 'other'
|
||||
updateReason.value = ''
|
||||
updateMaxActivations.value = licenseData.value?.max_activations || null
|
||||
// populate entries from existing JSON if present
|
||||
featureConfigEntries.value = []
|
||||
usageLimitsEntries.value = []
|
||||
customParametersEntries.value = []
|
||||
try {
|
||||
if (licenseData.value?.feature_config) {
|
||||
const obj = typeof licenseData.value.feature_config === 'string' ? JSON.parse(licenseData.value.feature_config) : licenseData.value.feature_config
|
||||
for (const [k, v] of Object.entries(obj || {})) {
|
||||
featureConfigEntries.value.push({ id: createEntryId(), key: k, value: String(v), type: typeof v === 'number' ? 'number' : typeof v === 'boolean' ? 'bool' : 'string' })
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore parse errors
|
||||
}
|
||||
try {
|
||||
if (licenseData.value?.usage_limits) {
|
||||
const obj = typeof licenseData.value.usage_limits === 'string' ? JSON.parse(licenseData.value.usage_limits) : licenseData.value.usage_limits
|
||||
for (const [k, v] of Object.entries(obj || {})) {
|
||||
usageLimitsEntries.value.push({ id: createEntryId(), key: k, value: String(v), type: typeof v === 'number' ? 'number' : typeof v === 'boolean' ? 'bool' : 'string' })
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
try {
|
||||
if (licenseData.value?.custom_parameters) {
|
||||
const obj = typeof licenseData.value.custom_parameters === 'string' ? JSON.parse(licenseData.value.custom_parameters) : licenseData.value.custom_parameters
|
||||
for (const [k, v] of Object.entries(obj || {})) {
|
||||
customParametersEntries.value.push({ id: createEntryId(), key: k, value: String(v), type: typeof v === 'number' ? 'number' : typeof v === 'boolean' ? 'bool' : 'string' })
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
|
||||
// 使用 JsonEditor 加载数据
|
||||
featureConfigData.value = typeof licenseData.value?.feature_config === 'string'
|
||||
? JSON.parse(licenseData.value?.feature_config || '{}')
|
||||
: (licenseData.value?.feature_config || {})
|
||||
usageLimitsData.value = typeof licenseData.value?.usage_limits === 'string'
|
||||
? JSON.parse(licenseData.value?.usage_limits || '{}')
|
||||
: (licenseData.value?.usage_limits || {})
|
||||
customParamsData.value = typeof licenseData.value?.custom_parameters === 'string'
|
||||
? JSON.parse(licenseData.value?.custom_parameters || '{}')
|
||||
: (licenseData.value?.custom_parameters || {})
|
||||
|
||||
// load change type options from backend enum
|
||||
try {
|
||||
@@ -563,7 +442,17 @@ const handleConfirmUpdate = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
// build payload
|
||||
// 验证 JsonEditor 数据
|
||||
const featureValid = featureConfigRef.value?.validate() ?? true
|
||||
const limitValid = usageLimitsRef.value?.validate() ?? true
|
||||
const customValid = customParamsRef.value?.validate() ?? true
|
||||
if (!featureValid || !limitValid || !customValid) {
|
||||
ElMessage.error(t('pages.licenses.form.keyValue.validationFailed'))
|
||||
changeSubmitting.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// 构建 payload
|
||||
const payload: any = {
|
||||
change_type: updateChangeType.value,
|
||||
reason: updateReason.value
|
||||
@@ -572,32 +461,10 @@ const handleConfirmUpdate = async () => {
|
||||
payload.max_activations = updateMaxActivations.value
|
||||
}
|
||||
|
||||
// build JSON object from key-value entries
|
||||
const fc = buildJsonRecord(featureConfigEntries.value)
|
||||
if (fc) payload.feature_config = fc
|
||||
const ul = buildJsonRecord(usageLimitsEntries.value)
|
||||
if (ul) payload.usage_limits = ul
|
||||
const cp = buildJsonRecord(customParametersEntries.value)
|
||||
if (cp) payload.custom_parameters = cp
|
||||
// validate KV entries before sending
|
||||
const v1 = validateKeyValueEntries(featureConfigEntries.value)
|
||||
if (!v1.ok) {
|
||||
ElMessage.error(v1.msg || t('pages.licenses.detail.updateDialog.validation.featureConfigInvalid'))
|
||||
changeSubmitting.value = false
|
||||
return
|
||||
}
|
||||
const v2 = validateKeyValueEntries(usageLimitsEntries.value)
|
||||
if (!v2.ok) {
|
||||
ElMessage.error(v2.msg || t('pages.licenses.detail.updateDialog.validation.usageLimitsInvalid'))
|
||||
changeSubmitting.value = false
|
||||
return
|
||||
}
|
||||
const v3 = validateKeyValueEntries(customParametersEntries.value)
|
||||
if (!v3.ok) {
|
||||
ElMessage.error(v3.msg || t('pages.licenses.detail.updateDialog.validation.customParametersInvalid'))
|
||||
changeSubmitting.value = false
|
||||
return
|
||||
}
|
||||
// 使用 JsonEditor 数据
|
||||
payload.feature_config = featureConfigData.value || {}
|
||||
payload.usage_limits = usageLimitsData.value || {}
|
||||
payload.custom_parameters = customParamsData.value || {}
|
||||
|
||||
updateSubmitting.value = true
|
||||
try {
|
||||
|
||||
+285
-51
@@ -42,16 +42,16 @@
|
||||
resolved "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz"
|
||||
integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==
|
||||
|
||||
"@esbuild/android-arm64@0.21.5":
|
||||
version "0.21.5"
|
||||
resolved "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz"
|
||||
integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==
|
||||
|
||||
"@esbuild/android-arm@0.21.5":
|
||||
version "0.21.5"
|
||||
resolved "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz"
|
||||
integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==
|
||||
|
||||
"@esbuild/android-arm64@0.21.5":
|
||||
version "0.21.5"
|
||||
resolved "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz"
|
||||
integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==
|
||||
|
||||
"@esbuild/android-x64@0.21.5":
|
||||
version "0.21.5"
|
||||
resolved "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz"
|
||||
@@ -77,16 +77,16 @@
|
||||
resolved "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz"
|
||||
integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==
|
||||
|
||||
"@esbuild/linux-arm64@0.21.5":
|
||||
version "0.21.5"
|
||||
resolved "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz"
|
||||
integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==
|
||||
|
||||
"@esbuild/linux-arm@0.21.5":
|
||||
version "0.21.5"
|
||||
resolved "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz"
|
||||
integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==
|
||||
|
||||
"@esbuild/linux-arm64@0.21.5":
|
||||
version "0.21.5"
|
||||
resolved "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz"
|
||||
integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==
|
||||
|
||||
"@esbuild/linux-ia32@0.21.5":
|
||||
version "0.21.5"
|
||||
resolved "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz"
|
||||
@@ -257,6 +257,14 @@
|
||||
"@jridgewell/sourcemap-codec" "^1.5.0"
|
||||
"@jridgewell/trace-mapping" "^0.3.24"
|
||||
|
||||
"@jridgewell/remapping@^2.3.5":
|
||||
version "2.3.5"
|
||||
resolved "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz"
|
||||
integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==
|
||||
dependencies:
|
||||
"@jridgewell/gen-mapping" "^0.3.5"
|
||||
"@jridgewell/trace-mapping" "^0.3.24"
|
||||
|
||||
"@jridgewell/resolve-uri@^3.1.0":
|
||||
version "3.1.2"
|
||||
resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz"
|
||||
@@ -291,7 +299,7 @@
|
||||
"@nodelib/fs.stat" "2.0.5"
|
||||
run-parallel "^1.1.9"
|
||||
|
||||
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
|
||||
"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
|
||||
version "2.0.5"
|
||||
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
|
||||
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
|
||||
@@ -508,12 +516,12 @@
|
||||
resolved "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.0.tgz"
|
||||
integrity sha512-xMmiWRR8sp72Zqwjgtf3QbZfF1wdh8X2ABu3EaozvZcyHJeU0r+XAnXdKgs4cCAp6ORoYoCygipYP1mjmbjrsg==
|
||||
|
||||
"@types/estree@1.0.8", "@types/estree@^1.0.0":
|
||||
"@types/estree@^1.0.0", "@types/estree@1.0.8":
|
||||
version "1.0.8"
|
||||
resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz"
|
||||
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
|
||||
|
||||
"@types/lodash-es@^4.17.6":
|
||||
"@types/lodash-es@*", "@types/lodash-es@^4.17.6":
|
||||
version "4.17.12"
|
||||
resolved "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz"
|
||||
integrity sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==
|
||||
@@ -525,7 +533,7 @@
|
||||
resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz"
|
||||
integrity sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==
|
||||
|
||||
"@types/node@^22.0.0":
|
||||
"@types/node@^18.0.0 || >=20.0.0", "@types/node@^22.0.0":
|
||||
version "22.18.0"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-22.18.0.tgz"
|
||||
integrity sha512-m5ObIqwsUp6BZzyiy4RdZpzWGub9bqLJMvZDD0QMXhxjqMHMENlj+SqF5QxoUwaQNFe+8kz8XM8ZQhqkQPTgMQ==
|
||||
@@ -567,7 +575,7 @@
|
||||
natural-compare "^1.4.0"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/parser@^7.1.1":
|
||||
"@typescript-eslint/parser@^7.0.0", "@typescript-eslint/parser@^7.1.1":
|
||||
version "7.18.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz"
|
||||
integrity sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==
|
||||
@@ -578,7 +586,7 @@
|
||||
"@typescript-eslint/visitor-keys" "7.18.0"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/parser@^8.0.0":
|
||||
"@typescript-eslint/parser@^8.0.0", "@typescript-eslint/parser@^8.41.0":
|
||||
version "8.41.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.41.0.tgz"
|
||||
integrity sha512-gTtSdWX9xiMPA/7MV9STjJOOYtWwIJIYxkQxnSV1U3xcE+mnJSH3f6zI0RYP+ew66WSlZ5ed+h0VCxsvdC1jJg==
|
||||
@@ -614,7 +622,7 @@
|
||||
"@typescript-eslint/types" "8.41.0"
|
||||
"@typescript-eslint/visitor-keys" "8.41.0"
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@8.41.0", "@typescript-eslint/tsconfig-utils@^8.41.0":
|
||||
"@typescript-eslint/tsconfig-utils@^8.41.0", "@typescript-eslint/tsconfig-utils@8.41.0":
|
||||
version "8.41.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.41.0.tgz"
|
||||
integrity sha512-TDhxYFPUYRFxFhuU5hTIJk+auzM/wKvWgoNYOPcOf6i4ReYlOoYN8q1dV5kOTjNQNJgzWN3TUUQMtlLOcUgdUw==
|
||||
@@ -640,16 +648,16 @@
|
||||
debug "^4.3.4"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/types@^8.41.0", "@typescript-eslint/types@8.41.0":
|
||||
version "8.41.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.41.0.tgz"
|
||||
integrity sha512-9EwxsWdVqh42afLbHP90n2VdHaWU/oWgbH2P0CfcNfdKL7CuKpwMQGjwev56vWu9cSKU7FWSu6r9zck6CVfnag==
|
||||
|
||||
"@typescript-eslint/types@7.18.0":
|
||||
version "7.18.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz"
|
||||
integrity sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==
|
||||
|
||||
"@typescript-eslint/types@8.41.0", "@typescript-eslint/types@^8.41.0":
|
||||
version "8.41.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.41.0.tgz"
|
||||
integrity sha512-9EwxsWdVqh42afLbHP90n2VdHaWU/oWgbH2P0CfcNfdKL7CuKpwMQGjwev56vWu9cSKU7FWSu6r9zck6CVfnag==
|
||||
|
||||
"@typescript-eslint/typescript-estree@7.18.0":
|
||||
version "7.18.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz"
|
||||
@@ -745,7 +753,7 @@
|
||||
estree-walker "^3.0.3"
|
||||
magic-string "^0.30.12"
|
||||
|
||||
"@vitest/pretty-format@2.1.9", "@vitest/pretty-format@^2.1.9":
|
||||
"@vitest/pretty-format@^2.1.9", "@vitest/pretty-format@2.1.9":
|
||||
version "2.1.9"
|
||||
resolved "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz"
|
||||
integrity sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==
|
||||
@@ -817,7 +825,7 @@
|
||||
estree-walker "^2.0.2"
|
||||
source-map-js "^1.2.1"
|
||||
|
||||
"@vue/compiler-dom@3.5.20", "@vue/compiler-dom@^3.5.0":
|
||||
"@vue/compiler-dom@^3.5.0", "@vue/compiler-dom@3.5.20":
|
||||
version "3.5.20"
|
||||
resolved "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.20.tgz"
|
||||
integrity sha512-whB44M59XKjqUEYOMPYU0ijUV0G+4fdrHVKDe32abNdX/kJe1NUEMqsi4cwzXa9kyM9w5S8WqFsrfo1ogtBZGQ==
|
||||
@@ -899,7 +907,7 @@
|
||||
dependencies:
|
||||
"@vue/shared" "3.5.20"
|
||||
|
||||
"@vue/runtime-core@3.5.20":
|
||||
"@vue/runtime-core@^3.0.0", "@vue/runtime-core@3.5.20":
|
||||
version "3.5.20"
|
||||
resolved "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.20.tgz"
|
||||
integrity sha512-vyQRiH5uSZlOa+4I/t4Qw/SsD/gbth0SW2J7oMeVlMFMAmsG1rwDD6ok0VMmjXY3eI0iHNSSOBilEDW98PLRKw==
|
||||
@@ -925,12 +933,12 @@
|
||||
"@vue/compiler-ssr" "3.5.20"
|
||||
"@vue/shared" "3.5.20"
|
||||
|
||||
"@vue/shared@3.5.20", "@vue/shared@^3.5.0":
|
||||
"@vue/shared@^3.5.0", "@vue/shared@3.5.20":
|
||||
version "3.5.20"
|
||||
resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.5.20.tgz"
|
||||
integrity sha512-SoRGP596KU/ig6TfgkCMbXkr4YJ91n/QSdMuqeP5r3hVIYA3CPHUBCc7Skak0EAKV+5lL4KyIh61VA/pK1CIAA==
|
||||
|
||||
"@vueuse/core@^9.1.0":
|
||||
"@vueuse/core@*", "@vueuse/core@^9.1.0":
|
||||
version "9.13.0"
|
||||
resolved "https://registry.npmjs.org/@vueuse/core/-/core-9.13.0.tgz"
|
||||
integrity sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==
|
||||
@@ -957,10 +965,10 @@ acorn-jsx@^5.3.2:
|
||||
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
|
||||
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
||||
|
||||
acorn@^8.14.0, acorn@^8.15.0, acorn@^8.9.0:
|
||||
version "8.15.0"
|
||||
resolved "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz"
|
||||
integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==
|
||||
"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.14.0, acorn@^8.15.0, acorn@^8.16.0, acorn@^8.9.0:
|
||||
version "8.16.0"
|
||||
resolved "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz"
|
||||
integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==
|
||||
|
||||
ajv@^6.12.4:
|
||||
version "6.12.6"
|
||||
@@ -1109,6 +1117,13 @@ chokidar@^4.0.0:
|
||||
dependencies:
|
||||
readdirp "^4.0.1"
|
||||
|
||||
chokidar@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz"
|
||||
integrity sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==
|
||||
dependencies:
|
||||
readdirp "^5.0.0"
|
||||
|
||||
cliui@^8.0.1:
|
||||
version "8.0.1"
|
||||
resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz"
|
||||
@@ -1147,6 +1162,16 @@ concat-map@0.0.1:
|
||||
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
|
||||
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
|
||||
|
||||
confbox@^0.1.8:
|
||||
version "0.1.8"
|
||||
resolved "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz"
|
||||
integrity sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==
|
||||
|
||||
confbox@^0.2.2:
|
||||
version "0.2.4"
|
||||
resolved "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz"
|
||||
integrity sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==
|
||||
|
||||
cross-spawn@^7.0.2:
|
||||
version "7.0.6"
|
||||
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz"
|
||||
@@ -1231,7 +1256,7 @@ dunder-proto@^1.0.1:
|
||||
es-errors "^1.3.0"
|
||||
gopd "^1.2.0"
|
||||
|
||||
echarts@^5.6.0:
|
||||
echarts@^5.5.1, echarts@^5.6.0:
|
||||
version "5.6.0"
|
||||
resolved "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz"
|
||||
integrity sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==
|
||||
@@ -1346,7 +1371,12 @@ escape-string-regexp@^4.0.0:
|
||||
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz"
|
||||
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
|
||||
|
||||
eslint-config-prettier@^9.0.0:
|
||||
escape-string-regexp@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz"
|
||||
integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
|
||||
|
||||
eslint-config-prettier@^9.0.0, "eslint-config-prettier@>= 7.0.0 <10.0.0 || >=10.1.0":
|
||||
version "9.1.2"
|
||||
resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.2.tgz"
|
||||
integrity sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==
|
||||
@@ -1399,7 +1429,7 @@ eslint-visitor-keys@^4.2.0, eslint-visitor-keys@^4.2.1:
|
||||
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz"
|
||||
integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==
|
||||
|
||||
eslint@^8.57.1:
|
||||
"eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", eslint@^8.56.0, "eslint@^8.57.0 || ^9.0.0", eslint@^8.57.1, "eslint@>= 8.0.0", eslint@>=6.0.0, eslint@>=7.0.0, eslint@>=8.0.0:
|
||||
version "8.57.1"
|
||||
resolved "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz"
|
||||
integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==
|
||||
@@ -1502,6 +1532,11 @@ expect-type@^1.1.0:
|
||||
resolved "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz"
|
||||
integrity sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==
|
||||
|
||||
exsolve@^1.0.7:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz"
|
||||
integrity sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==
|
||||
|
||||
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
|
||||
@@ -1540,6 +1575,11 @@ fastq@^1.6.0:
|
||||
dependencies:
|
||||
reusify "^1.0.4"
|
||||
|
||||
fdir@^6.5.0:
|
||||
version "6.5.0"
|
||||
resolved "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz"
|
||||
integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
|
||||
|
||||
file-entry-cache@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz"
|
||||
@@ -1734,7 +1774,12 @@ he@^1.2.0:
|
||||
resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz"
|
||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||
|
||||
ignore@^5.2.0, ignore@^5.3.1:
|
||||
ignore@^5.2.0:
|
||||
version "5.3.2"
|
||||
resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz"
|
||||
integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
|
||||
|
||||
ignore@^5.3.1:
|
||||
version "5.3.2"
|
||||
resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz"
|
||||
integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
|
||||
@@ -1819,6 +1864,11 @@ isexe@^2.0.0:
|
||||
resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
|
||||
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
|
||||
|
||||
js-tokens@^9.0.1:
|
||||
version "9.0.1"
|
||||
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz"
|
||||
integrity sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==
|
||||
|
||||
js-yaml@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz"
|
||||
@@ -1865,6 +1915,15 @@ levn@^0.4.1:
|
||||
prelude-ls "^1.2.1"
|
||||
type-check "~0.4.0"
|
||||
|
||||
local-pkg@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz"
|
||||
integrity sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==
|
||||
dependencies:
|
||||
mlly "^1.7.4"
|
||||
pkg-types "^2.3.0"
|
||||
quansync "^0.2.11"
|
||||
|
||||
locate-path@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz"
|
||||
@@ -1872,7 +1931,7 @@ locate-path@^6.0.0:
|
||||
dependencies:
|
||||
p-locate "^5.0.0"
|
||||
|
||||
lodash-es@^4.17.21:
|
||||
lodash-es@*, lodash-es@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz"
|
||||
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
|
||||
@@ -1887,7 +1946,7 @@ lodash.merge@^4.6.2:
|
||||
resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"
|
||||
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
||||
|
||||
lodash@^4.17.21:
|
||||
lodash@*, lodash@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
@@ -1897,10 +1956,10 @@ loupe@^3.1.0, loupe@^3.1.2:
|
||||
resolved "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz"
|
||||
integrity sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==
|
||||
|
||||
magic-string@^0.30.12, magic-string@^0.30.17:
|
||||
version "0.30.18"
|
||||
resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz"
|
||||
integrity sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==
|
||||
magic-string@^0.30.12, magic-string@^0.30.17, magic-string@^0.30.21:
|
||||
version "0.30.21"
|
||||
resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz"
|
||||
integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==
|
||||
dependencies:
|
||||
"@jridgewell/sourcemap-codec" "^1.5.5"
|
||||
|
||||
@@ -1939,7 +1998,21 @@ mime-types@^2.1.12:
|
||||
dependencies:
|
||||
mime-db "1.52.0"
|
||||
|
||||
minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
||||
minimatch@^3.0.5:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz"
|
||||
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimatch@^3.1.1:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz"
|
||||
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimatch@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz"
|
||||
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
|
||||
@@ -1953,6 +2026,16 @@ minimatch@^9.0.3, minimatch@^9.0.4:
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
mlly@^1.7.4, mlly@^1.8.0:
|
||||
version "1.8.1"
|
||||
resolved "https://registry.npmjs.org/mlly/-/mlly-1.8.1.tgz"
|
||||
integrity sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ==
|
||||
dependencies:
|
||||
acorn "^8.16.0"
|
||||
pathe "^2.0.3"
|
||||
pkg-types "^1.3.1"
|
||||
ufo "^1.6.3"
|
||||
|
||||
ms@^2.1.3:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
|
||||
@@ -1990,6 +2073,11 @@ nth-check@^2.1.1:
|
||||
dependencies:
|
||||
boolbase "^1.0.0"
|
||||
|
||||
obug@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz"
|
||||
integrity sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==
|
||||
|
||||
once@^1.3.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz"
|
||||
@@ -2069,6 +2157,11 @@ pathe@^1.1.2:
|
||||
resolved "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz"
|
||||
integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==
|
||||
|
||||
pathe@^2.0.1, pathe@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz"
|
||||
integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==
|
||||
|
||||
pathval@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz"
|
||||
@@ -2084,6 +2177,11 @@ picomatch@^2.3.1:
|
||||
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
|
||||
"picomatch@^3 || ^4", picomatch@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz"
|
||||
integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==
|
||||
|
||||
picomatch@^4.0.2:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz"
|
||||
@@ -2097,6 +2195,24 @@ pinia@^2.3.1:
|
||||
"@vue/devtools-api" "^6.6.3"
|
||||
vue-demi "^0.14.10"
|
||||
|
||||
pkg-types@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz"
|
||||
integrity sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==
|
||||
dependencies:
|
||||
confbox "^0.1.8"
|
||||
mlly "^1.7.4"
|
||||
pathe "^2.0.1"
|
||||
|
||||
pkg-types@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz"
|
||||
integrity sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==
|
||||
dependencies:
|
||||
confbox "^0.2.2"
|
||||
exsolve "^1.0.7"
|
||||
pathe "^2.0.3"
|
||||
|
||||
postcss-selector-parser@^6.0.15:
|
||||
version "6.1.2"
|
||||
resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz"
|
||||
@@ -2126,7 +2242,7 @@ prettier-linter-helpers@^1.0.0:
|
||||
dependencies:
|
||||
fast-diff "^1.1.2"
|
||||
|
||||
prettier@^3.0.0:
|
||||
prettier@^3.0.0, "prettier@>= 3.0.0", prettier@>=3.0.0:
|
||||
version "3.6.2"
|
||||
resolved "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz"
|
||||
integrity sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==
|
||||
@@ -2141,6 +2257,11 @@ punycode@^2.1.0:
|
||||
resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz"
|
||||
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
|
||||
|
||||
quansync@^0.2.11:
|
||||
version "0.2.11"
|
||||
resolved "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz"
|
||||
integrity sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==
|
||||
|
||||
queue-microtask@^1.2.2:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
|
||||
@@ -2151,6 +2272,11 @@ readdirp@^4.0.1:
|
||||
resolved "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz"
|
||||
integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==
|
||||
|
||||
readdirp@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz"
|
||||
integrity sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==
|
||||
|
||||
require-directory@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz"
|
||||
@@ -2183,7 +2309,7 @@ rollup-plugin-visualizer@^5.12.0:
|
||||
source-map "^0.7.4"
|
||||
yargs "^17.5.1"
|
||||
|
||||
rollup@^4.20.0:
|
||||
rollup@^4.20.0, "rollup@2.x || 3.x || 4.x":
|
||||
version "4.50.0"
|
||||
resolved "https://registry.npmjs.org/rollup/-/rollup-4.50.0.tgz"
|
||||
integrity sha512-/Zl4D8zPifNmyGzJS+3kVoyXeDeT/GrsJM94sACNg9RtUE0hrHa1bNPtRSrfHTMH5HjRzce6K7rlTh3Khiw+pw==
|
||||
@@ -2220,7 +2346,7 @@ run-parallel@^1.1.9:
|
||||
dependencies:
|
||||
queue-microtask "^1.2.2"
|
||||
|
||||
sass@^1.89.2:
|
||||
sass@*, sass@^1.89.2:
|
||||
version "1.91.0"
|
||||
resolved "https://registry.npmjs.org/sass/-/sass-1.91.0.tgz"
|
||||
integrity sha512-aFOZHGf+ur+bp1bCHZ+u8otKGh77ZtmFyXDo4tlYvT7PWql41Kwd8wdkPqhhT+h2879IVblcHFglIMofsFd1EA==
|
||||
@@ -2231,6 +2357,11 @@ sass@^1.89.2:
|
||||
optionalDependencies:
|
||||
"@parcel/watcher" "^2.4.1"
|
||||
|
||||
scule@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz"
|
||||
integrity sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==
|
||||
|
||||
semver@^7.3.6, semver@^7.6.0, semver@^7.6.3:
|
||||
version "7.7.2"
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz"
|
||||
@@ -2258,7 +2389,7 @@ slash@^3.0.0:
|
||||
resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz"
|
||||
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
|
||||
|
||||
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2, source-map-js@^1.2.1:
|
||||
source-map-js@^1.0.2, source-map-js@^1.2.1, "source-map-js@>=0.6.2 <2.0.0":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz"
|
||||
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
|
||||
@@ -2312,6 +2443,13 @@ strip-json-comments@^3.1.1:
|
||||
resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
|
||||
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
|
||||
|
||||
strip-literal@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz"
|
||||
integrity sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==
|
||||
dependencies:
|
||||
js-tokens "^9.0.1"
|
||||
|
||||
supports-color@^7.1.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"
|
||||
@@ -2326,7 +2464,7 @@ synckit@^0.11.7:
|
||||
dependencies:
|
||||
"@pkgr/core" "^0.2.9"
|
||||
|
||||
terser@^5.43.1:
|
||||
terser@^5.4.0, terser@^5.43.1:
|
||||
version "5.43.1"
|
||||
resolved "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz"
|
||||
integrity sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==
|
||||
@@ -2351,6 +2489,14 @@ tinyexec@^0.3.1:
|
||||
resolved "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz"
|
||||
integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==
|
||||
|
||||
tinyglobby@^0.2.15:
|
||||
version "0.2.15"
|
||||
resolved "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz"
|
||||
integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==
|
||||
dependencies:
|
||||
fdir "^6.5.0"
|
||||
picomatch "^4.0.3"
|
||||
|
||||
tinypool@^1.0.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz"
|
||||
@@ -2400,21 +2546,91 @@ type-fest@^0.20.2:
|
||||
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz"
|
||||
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
|
||||
|
||||
typescript@^5.8.3:
|
||||
typescript@*, typescript@^5.8.3, typescript@>=4.2.0, typescript@>=4.4.4, typescript@>=4.7.4, typescript@>=4.8.4, "typescript@>=4.8.4 <6.0.0", typescript@>=5.0.0:
|
||||
version "5.9.2"
|
||||
resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz"
|
||||
integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==
|
||||
|
||||
ufo@^1.6.3:
|
||||
version "1.6.3"
|
||||
resolved "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz"
|
||||
integrity sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==
|
||||
|
||||
undici-types@~6.21.0:
|
||||
version "6.21.0"
|
||||
resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz"
|
||||
integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==
|
||||
|
||||
unimport@^5.6.0:
|
||||
version "5.7.0"
|
||||
resolved "https://registry.npmjs.org/unimport/-/unimport-5.7.0.tgz"
|
||||
integrity sha512-njnL6sp8lEA8QQbZrt+52p/g4X0rw3bnGGmUcJnt1jeG8+iiqO779aGz0PirCtydAIVcuTBRlJ52F0u46z309Q==
|
||||
dependencies:
|
||||
acorn "^8.16.0"
|
||||
escape-string-regexp "^5.0.0"
|
||||
estree-walker "^3.0.3"
|
||||
local-pkg "^1.1.2"
|
||||
magic-string "^0.30.21"
|
||||
mlly "^1.8.0"
|
||||
pathe "^2.0.3"
|
||||
picomatch "^4.0.3"
|
||||
pkg-types "^2.3.0"
|
||||
scule "^1.3.0"
|
||||
strip-literal "^3.1.0"
|
||||
tinyglobby "^0.2.15"
|
||||
unplugin "^2.3.11"
|
||||
unplugin-utils "^0.3.1"
|
||||
|
||||
universalify@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz"
|
||||
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
|
||||
|
||||
unplugin-auto-import@^21.0.0:
|
||||
version "21.0.0"
|
||||
resolved "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-21.0.0.tgz"
|
||||
integrity sha512-vWuC8SwqJmxZFYwPojhOhOXDb5xFhNNcEVb9K/RFkyk/3VnfaOjzitWN7v+8DEKpMjSsY2AEGXNgt6I0yQrhRQ==
|
||||
dependencies:
|
||||
local-pkg "^1.1.2"
|
||||
magic-string "^0.30.21"
|
||||
picomatch "^4.0.3"
|
||||
unimport "^5.6.0"
|
||||
unplugin "^2.3.11"
|
||||
unplugin-utils "^0.3.1"
|
||||
|
||||
unplugin-utils@^0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz"
|
||||
integrity sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==
|
||||
dependencies:
|
||||
pathe "^2.0.3"
|
||||
picomatch "^4.0.3"
|
||||
|
||||
unplugin-vue-components@^31.0.0:
|
||||
version "31.0.0"
|
||||
resolved "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-31.0.0.tgz"
|
||||
integrity sha512-4ULwfTZTLuWJ7+S9P7TrcStYLsSRkk6vy2jt/WTfgUEUb0nW9//xxmrfhyHUEVpZ2UKRRwfRb8Yy15PDbVZf+Q==
|
||||
dependencies:
|
||||
chokidar "^5.0.0"
|
||||
local-pkg "^1.1.2"
|
||||
magic-string "^0.30.21"
|
||||
mlly "^1.8.0"
|
||||
obug "^2.1.1"
|
||||
picomatch "^4.0.3"
|
||||
tinyglobby "^0.2.15"
|
||||
unplugin "^2.3.11"
|
||||
unplugin-utils "^0.3.1"
|
||||
|
||||
unplugin@^2.3.11:
|
||||
version "2.3.11"
|
||||
resolved "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz"
|
||||
integrity sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==
|
||||
dependencies:
|
||||
"@jridgewell/remapping" "^2.3.5"
|
||||
acorn "^8.15.0"
|
||||
picomatch "^4.0.3"
|
||||
webpack-virtual-modules "^0.6.2"
|
||||
|
||||
uri-js@^4.2.2:
|
||||
version "4.4.1"
|
||||
resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz"
|
||||
@@ -2447,7 +2663,7 @@ vite-plugin-compression@^0.5.1:
|
||||
debug "^4.3.3"
|
||||
fs-extra "^10.0.0"
|
||||
|
||||
vite@^5.0.0:
|
||||
vite@^5.0.0, "vite@^5.0.0 || ^6.0.0", vite@>=2.0.0:
|
||||
version "5.4.19"
|
||||
resolved "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz"
|
||||
integrity sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==
|
||||
@@ -2518,7 +2734,20 @@ vue-eslint-parser@^10.0.0:
|
||||
esquery "^1.6.0"
|
||||
semver "^7.6.3"
|
||||
|
||||
vue-eslint-parser@^9.3.1, vue-eslint-parser@^9.4.3:
|
||||
vue-eslint-parser@^9.3.1:
|
||||
version "9.4.3"
|
||||
resolved "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz"
|
||||
integrity sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==
|
||||
dependencies:
|
||||
debug "^4.3.4"
|
||||
eslint-scope "^7.1.1"
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
espree "^9.3.1"
|
||||
esquery "^1.4.0"
|
||||
lodash "^4.17.21"
|
||||
semver "^7.3.6"
|
||||
|
||||
vue-eslint-parser@^9.4.3:
|
||||
version "9.4.3"
|
||||
resolved "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz"
|
||||
integrity sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==
|
||||
@@ -2555,7 +2784,7 @@ vue-tsc@^2.0.0:
|
||||
"@volar/typescript" "2.4.15"
|
||||
"@vue/language-core" "2.2.12"
|
||||
|
||||
vue@^3.5.0:
|
||||
"vue@^2.7.0 || ^3.1.1", "vue@^2.7.0 || ^3.5.11", vue@^3.0.0, "vue@^3.0.0-0 || ^2.6.0", vue@^3.2.0, vue@^3.2.25, vue@^3.5.0, vue@3.5.20:
|
||||
version "3.5.20"
|
||||
resolved "https://registry.npmjs.org/vue/-/vue-3.5.20.tgz"
|
||||
integrity sha512-2sBz0x/wis5TkF1XZ2vH25zWq3G1bFEPOfkBcx2ikowmphoQsPH6X0V3mmPCXA2K1N/XGTnifVyDQP4GfDDeQw==
|
||||
@@ -2566,6 +2795,11 @@ vue@^3.5.0:
|
||||
"@vue/server-renderer" "3.5.20"
|
||||
"@vue/shared" "3.5.20"
|
||||
|
||||
webpack-virtual-modules@^0.6.2:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz"
|
||||
integrity sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==
|
||||
|
||||
which@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz"
|
||||
|
||||
Reference in new issue
Block a user