usePage增加许多配置项,另外增加一些回调函数、插槽等等,修改逻辑增强配置灵活性zk authored at 2023-11-21 17:42:23
diff --git a/.env.development b/.env.development
index 5e4721a..c78524c 100644
--- a/.env.development
+++ b/.env.development
@@ -3,4 +3,7 @@ ENV = development
# 本地环境接口地址
# VITE_API_URL = https://cube3.newlifex.com
-VITE_API_URL = /base-api
\ No newline at end of file
+VITE_API_URL = /base-api
+
+# 图片前缀地址
+VITE_IMG_BASE_URL = https://rengleme.oss-cn-beijing.aliyuncs.com/
\ No newline at end of file
diff --git a/.env.production b/.env.production
index 6f65e04..75fba85 100644
--- a/.env.production
+++ b/.env.production
@@ -3,4 +3,8 @@ ENV = production
# 线上环境接口地址
# VITE_API_URL = https://cube3.newlifex.com
-VITE_API_URL = http://120.78.152.40:5000
\ No newline at end of file
+# VITE_API_URL = http://120.78.152.40:5000
+VITE_API_URL =
+
+# 图片前缀地址
+VITE_IMG_BASE_URL = https://rengleme.oss-cn-beijing.aliyuncs.com/
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index aafc793..76033f6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,11 +1,11 @@
{
- "name": "vue-next-admin",
+ "name": "NewLife.QuickVue",
"version": "2.4.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
- "name": "vue-next-admin",
+ "name": "NewLife.QuickVue",
"version": "2.4.3",
"license": "MIT",
"dependencies": {
@@ -33,7 +33,7 @@
"sortablejs": "^1.15.0",
"splitpanes": "^3.1.5",
"ts-enum-util": "^4.0.2",
- "vue": "^3.2.47",
+ "vue": "^3.3.8",
"vue-clipboard3": "^2.0.0",
"vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.2.2",
@@ -67,9 +67,9 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.21.1",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.1.tgz",
- "integrity": "sha512-JzhBFpkuhBNYUY7qs+wTzNmyCWUHEaAFpQQD2YfU1rPL38/L43Wvid0fFkiOCnHvsGncRZgEPyGnltABLcVDTg==",
+ "version": "7.23.4",
+ "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.23.4.tgz",
+ "integrity": "sha512-vf3Xna6UEprW+7t6EtOmFpHNAuxw3xqPZghy+brsnusscJRW5BMUzzHZc5ICjULee81WeUV2jjakG09MDglJXQ==",
"bin": {
"parser": "bin/babel-parser.js"
},
@@ -869,6 +869,11 @@
"node": ">= 14"
}
},
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.15",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
+ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1224,49 +1229,60 @@
}
},
"node_modules/@vue/compiler-core": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.47.tgz",
- "integrity": "sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.3.8.tgz",
+ "integrity": "sha512-hN/NNBUECw8SusQvDSqqcVv6gWq8L6iAktUR0UF3vGu2OhzRqcOiAno0FmBJWwxhYEXRlQJT5XnoKsVq1WZx4g==",
"dependencies": {
- "@babel/parser": "^7.16.4",
- "@vue/shared": "3.2.47",
+ "@babel/parser": "^7.23.0",
+ "@vue/shared": "3.3.8",
"estree-walker": "^2.0.2",
- "source-map": "^0.6.1"
+ "source-map-js": "^1.0.2"
}
},
"node_modules/@vue/compiler-dom": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz",
- "integrity": "sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.3.8.tgz",
+ "integrity": "sha512-+PPtv+p/nWDd0AvJu3w8HS0RIm/C6VGBIRe24b9hSyNWOAPEUosFZ5diwawwP8ip5sJ8n0Pe87TNNNHnvjs0FQ==",
"dependencies": {
- "@vue/compiler-core": "3.2.47",
- "@vue/shared": "3.2.47"
+ "@vue/compiler-core": "3.3.8",
+ "@vue/shared": "3.3.8"
}
},
"node_modules/@vue/compiler-sfc": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.47.tgz",
- "integrity": "sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==",
- "dependencies": {
- "@babel/parser": "^7.16.4",
- "@vue/compiler-core": "3.2.47",
- "@vue/compiler-dom": "3.2.47",
- "@vue/compiler-ssr": "3.2.47",
- "@vue/reactivity-transform": "3.2.47",
- "@vue/shared": "3.2.47",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.3.8.tgz",
+ "integrity": "sha512-WMzbUrlTjfYF8joyT84HfwwXo+8WPALuPxhy+BZ6R4Aafls+jDBnSz8PDz60uFhuqFbl3HxRfxvDzrUf3THwpA==",
+ "dependencies": {
+ "@babel/parser": "^7.23.0",
+ "@vue/compiler-core": "3.3.8",
+ "@vue/compiler-dom": "3.3.8",
+ "@vue/compiler-ssr": "3.3.8",
+ "@vue/reactivity-transform": "3.3.8",
+ "@vue/shared": "3.3.8",
"estree-walker": "^2.0.2",
- "magic-string": "^0.25.7",
- "postcss": "^8.1.10",
- "source-map": "^0.6.1"
+ "magic-string": "^0.30.5",
+ "postcss": "^8.4.31",
+ "source-map-js": "^1.0.2"
+ }
+ },
+ "node_modules/@vue/compiler-sfc/node_modules/magic-string": {
+ "version": "0.30.5",
+ "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.5.tgz",
+ "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.15"
+ },
+ "engines": {
+ "node": ">=12"
}
},
"node_modules/@vue/compiler-ssr": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.47.tgz",
- "integrity": "sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.3.8.tgz",
+ "integrity": "sha512-hXCqQL/15kMVDBuoBYpUnSYT8doDNwsjvm3jTefnXr+ytn294ySnT8NlsFHmTgKNjwpuFy7XVV8yTeLtNl/P6w==",
"dependencies": {
- "@vue/compiler-dom": "3.2.47",
- "@vue/shared": "3.2.47"
+ "@vue/compiler-dom": "3.3.8",
+ "@vue/shared": "3.3.8"
}
},
"node_modules/@vue/devtools-api": {
@@ -1275,60 +1291,71 @@
"integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q=="
},
"node_modules/@vue/reactivity": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.47.tgz",
- "integrity": "sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.3.8.tgz",
+ "integrity": "sha512-ctLWitmFBu6mtddPyOKpHg8+5ahouoTCRtmAHZAXmolDtuZXfjL2T3OJ6DL6ezBPQB1SmMnpzjiWjCiMYmpIuw==",
"dependencies": {
- "@vue/shared": "3.2.47"
+ "@vue/shared": "3.3.8"
}
},
"node_modules/@vue/reactivity-transform": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.47.tgz",
- "integrity": "sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.3.8.tgz",
+ "integrity": "sha512-49CvBzmZNtcHua0XJ7GdGifM8GOXoUMOX4dD40Y5DxI3R8OUhMlvf2nvgUAcPxaXiV5MQQ1Nwy09ADpnLQUqRw==",
"dependencies": {
- "@babel/parser": "^7.16.4",
- "@vue/compiler-core": "3.2.47",
- "@vue/shared": "3.2.47",
+ "@babel/parser": "^7.23.0",
+ "@vue/compiler-core": "3.3.8",
+ "@vue/shared": "3.3.8",
"estree-walker": "^2.0.2",
- "magic-string": "^0.25.7"
+ "magic-string": "^0.30.5"
+ }
+ },
+ "node_modules/@vue/reactivity-transform/node_modules/magic-string": {
+ "version": "0.30.5",
+ "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.5.tgz",
+ "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.15"
+ },
+ "engines": {
+ "node": ">=12"
}
},
"node_modules/@vue/runtime-core": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.47.tgz",
- "integrity": "sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.3.8.tgz",
+ "integrity": "sha512-qurzOlb6q26KWQ/8IShHkMDOuJkQnQcTIp1sdP4I9MbCf9FJeGVRXJFr2mF+6bXh/3Zjr9TDgURXrsCr9bfjUw==",
"dependencies": {
- "@vue/reactivity": "3.2.47",
- "@vue/shared": "3.2.47"
+ "@vue/reactivity": "3.3.8",
+ "@vue/shared": "3.3.8"
}
},
"node_modules/@vue/runtime-dom": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.47.tgz",
- "integrity": "sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.3.8.tgz",
+ "integrity": "sha512-Noy5yM5UIf9UeFoowBVgghyGGPIDPy1Qlqt0yVsUdAVbqI8eeMSsTqBtauaEoT2UFXUk5S64aWVNJN4MJ2vRdA==",
"dependencies": {
- "@vue/runtime-core": "3.2.47",
- "@vue/shared": "3.2.47",
- "csstype": "^2.6.8"
+ "@vue/runtime-core": "3.3.8",
+ "@vue/shared": "3.3.8",
+ "csstype": "^3.1.2"
}
},
"node_modules/@vue/server-renderer": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.47.tgz",
- "integrity": "sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.3.8.tgz",
+ "integrity": "sha512-zVCUw7RFskvPuNlPn/8xISbrf0zTWsTSdYTsUTN1ERGGZGVnRxM2QZ3x1OR32+vwkkCm0IW6HmJ49IsPm7ilLg==",
"dependencies": {
- "@vue/compiler-ssr": "3.2.47",
- "@vue/shared": "3.2.47"
+ "@vue/compiler-ssr": "3.3.8",
+ "@vue/shared": "3.3.8"
},
"peerDependencies": {
- "vue": "3.2.47"
+ "vue": "3.3.8"
}
},
"node_modules/@vue/shared": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.47.tgz",
- "integrity": "sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ=="
+ "version": "3.3.8",
+ "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.8.tgz",
+ "integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw=="
},
"node_modules/@vueuse/core": {
"version": "9.13.0",
@@ -1982,9 +2009,9 @@
}
},
"node_modules/csstype": {
- "version": "2.6.21",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz",
- "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w=="
+ "version": "3.1.2",
+ "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.2.tgz",
+ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
},
"node_modules/d": {
"version": "1.0.1",
@@ -2485,7 +2512,7 @@
},
"node_modules/estree-walker": {
"version": "2.0.2",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"node_modules/esutils": {
@@ -3159,6 +3186,7 @@
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
"integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+ "dev": true,
"dependencies": {
"sourcemap-codec": "^1.4.8"
}
@@ -3252,9 +3280,9 @@
"integrity": "sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g=="
},
"node_modules/nanoid": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
- "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
+ "version": "3.3.7",
+ "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@@ -3529,11 +3557,11 @@
}
},
"node_modules/postcss": {
- "version": "8.4.21",
- "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.21.tgz",
- "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
+ "version": "8.4.31",
+ "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.31.tgz",
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"dependencies": {
- "nanoid": "^3.3.4",
+ "nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
@@ -4021,7 +4049,8 @@
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
- "deprecated": "Please use @jridgewell/sourcemap-codec instead"
+ "deprecated": "Please use @jridgewell/sourcemap-codec instead",
+ "dev": true
},
"node_modules/splitpanes": {
"version": "3.1.5",
@@ -4320,15 +4349,23 @@
}
},
"node_modules/vue": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.47.tgz",
- "integrity": "sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmmirror.com/vue/-/vue-3.3.8.tgz",
+ "integrity": "sha512-5VSX/3DabBikOXMsxzlW8JyfeLKlG9mzqnWgLQLty88vdZL7ZJgrdgBOmrArwxiLtmS+lNNpPcBYqrhE6TQW5w==",
"dependencies": {
- "@vue/compiler-dom": "3.2.47",
- "@vue/compiler-sfc": "3.2.47",
- "@vue/runtime-dom": "3.2.47",
- "@vue/server-renderer": "3.2.47",
- "@vue/shared": "3.2.47"
+ "@vue/compiler-dom": "3.3.8",
+ "@vue/compiler-sfc": "3.3.8",
+ "@vue/runtime-dom": "3.3.8",
+ "@vue/server-renderer": "3.3.8",
+ "@vue/shared": "3.3.8"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
}
},
"node_modules/vue-clipboard3": {
@@ -4542,9 +4579,9 @@
},
"dependencies": {
"@babel/parser": {
- "version": "7.21.1",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.1.tgz",
- "integrity": "sha512-JzhBFpkuhBNYUY7qs+wTzNmyCWUHEaAFpQQD2YfU1rPL38/L43Wvid0fFkiOCnHvsGncRZgEPyGnltABLcVDTg=="
+ "version": "7.23.4",
+ "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.23.4.tgz",
+ "integrity": "sha512-vf3Xna6UEprW+7t6EtOmFpHNAuxw3xqPZghy+brsnusscJRW5BMUzzHZc5ICjULee81WeUV2jjakG09MDglJXQ=="
},
"@babel/runtime": {
"version": "7.21.0",
@@ -5047,6 +5084,11 @@
"@intlify/shared": "9.2.2"
}
},
+ "@jridgewell/sourcemap-codec": {
+ "version": "1.4.15",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
+ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
+ },
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -5290,49 +5332,59 @@
"requires": {}
},
"@vue/compiler-core": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.47.tgz",
- "integrity": "sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.3.8.tgz",
+ "integrity": "sha512-hN/NNBUECw8SusQvDSqqcVv6gWq8L6iAktUR0UF3vGu2OhzRqcOiAno0FmBJWwxhYEXRlQJT5XnoKsVq1WZx4g==",
"requires": {
- "@babel/parser": "^7.16.4",
- "@vue/shared": "3.2.47",
+ "@babel/parser": "^7.23.0",
+ "@vue/shared": "3.3.8",
"estree-walker": "^2.0.2",
- "source-map": "^0.6.1"
+ "source-map-js": "^1.0.2"
}
},
"@vue/compiler-dom": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz",
- "integrity": "sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.3.8.tgz",
+ "integrity": "sha512-+PPtv+p/nWDd0AvJu3w8HS0RIm/C6VGBIRe24b9hSyNWOAPEUosFZ5diwawwP8ip5sJ8n0Pe87TNNNHnvjs0FQ==",
"requires": {
- "@vue/compiler-core": "3.2.47",
- "@vue/shared": "3.2.47"
+ "@vue/compiler-core": "3.3.8",
+ "@vue/shared": "3.3.8"
}
},
"@vue/compiler-sfc": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.47.tgz",
- "integrity": "sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==",
- "requires": {
- "@babel/parser": "^7.16.4",
- "@vue/compiler-core": "3.2.47",
- "@vue/compiler-dom": "3.2.47",
- "@vue/compiler-ssr": "3.2.47",
- "@vue/reactivity-transform": "3.2.47",
- "@vue/shared": "3.2.47",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.3.8.tgz",
+ "integrity": "sha512-WMzbUrlTjfYF8joyT84HfwwXo+8WPALuPxhy+BZ6R4Aafls+jDBnSz8PDz60uFhuqFbl3HxRfxvDzrUf3THwpA==",
+ "requires": {
+ "@babel/parser": "^7.23.0",
+ "@vue/compiler-core": "3.3.8",
+ "@vue/compiler-dom": "3.3.8",
+ "@vue/compiler-ssr": "3.3.8",
+ "@vue/reactivity-transform": "3.3.8",
+ "@vue/shared": "3.3.8",
"estree-walker": "^2.0.2",
- "magic-string": "^0.25.7",
- "postcss": "^8.1.10",
- "source-map": "^0.6.1"
+ "magic-string": "^0.30.5",
+ "postcss": "^8.4.31",
+ "source-map-js": "^1.0.2"
+ },
+ "dependencies": {
+ "magic-string": {
+ "version": "0.30.5",
+ "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.5.tgz",
+ "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==",
+ "requires": {
+ "@jridgewell/sourcemap-codec": "^1.4.15"
+ }
+ }
}
},
"@vue/compiler-ssr": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.47.tgz",
- "integrity": "sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.3.8.tgz",
+ "integrity": "sha512-hXCqQL/15kMVDBuoBYpUnSYT8doDNwsjvm3jTefnXr+ytn294ySnT8NlsFHmTgKNjwpuFy7XVV8yTeLtNl/P6w==",
"requires": {
- "@vue/compiler-dom": "3.2.47",
- "@vue/shared": "3.2.47"
+ "@vue/compiler-dom": "3.3.8",
+ "@vue/shared": "3.3.8"
}
},
"@vue/devtools-api": {
@@ -5341,57 +5393,67 @@
"integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q=="
},
"@vue/reactivity": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.47.tgz",
- "integrity": "sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.3.8.tgz",
+ "integrity": "sha512-ctLWitmFBu6mtddPyOKpHg8+5ahouoTCRtmAHZAXmolDtuZXfjL2T3OJ6DL6ezBPQB1SmMnpzjiWjCiMYmpIuw==",
"requires": {
- "@vue/shared": "3.2.47"
+ "@vue/shared": "3.3.8"
}
},
"@vue/reactivity-transform": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.47.tgz",
- "integrity": "sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.3.8.tgz",
+ "integrity": "sha512-49CvBzmZNtcHua0XJ7GdGifM8GOXoUMOX4dD40Y5DxI3R8OUhMlvf2nvgUAcPxaXiV5MQQ1Nwy09ADpnLQUqRw==",
"requires": {
- "@babel/parser": "^7.16.4",
- "@vue/compiler-core": "3.2.47",
- "@vue/shared": "3.2.47",
+ "@babel/parser": "^7.23.0",
+ "@vue/compiler-core": "3.3.8",
+ "@vue/shared": "3.3.8",
"estree-walker": "^2.0.2",
- "magic-string": "^0.25.7"
+ "magic-string": "^0.30.5"
+ },
+ "dependencies": {
+ "magic-string": {
+ "version": "0.30.5",
+ "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.5.tgz",
+ "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==",
+ "requires": {
+ "@jridgewell/sourcemap-codec": "^1.4.15"
+ }
+ }
}
},
"@vue/runtime-core": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.47.tgz",
- "integrity": "sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.3.8.tgz",
+ "integrity": "sha512-qurzOlb6q26KWQ/8IShHkMDOuJkQnQcTIp1sdP4I9MbCf9FJeGVRXJFr2mF+6bXh/3Zjr9TDgURXrsCr9bfjUw==",
"requires": {
- "@vue/reactivity": "3.2.47",
- "@vue/shared": "3.2.47"
+ "@vue/reactivity": "3.3.8",
+ "@vue/shared": "3.3.8"
}
},
"@vue/runtime-dom": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.47.tgz",
- "integrity": "sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.3.8.tgz",
+ "integrity": "sha512-Noy5yM5UIf9UeFoowBVgghyGGPIDPy1Qlqt0yVsUdAVbqI8eeMSsTqBtauaEoT2UFXUk5S64aWVNJN4MJ2vRdA==",
"requires": {
- "@vue/runtime-core": "3.2.47",
- "@vue/shared": "3.2.47",
- "csstype": "^2.6.8"
+ "@vue/runtime-core": "3.3.8",
+ "@vue/shared": "3.3.8",
+ "csstype": "^3.1.2"
}
},
"@vue/server-renderer": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.47.tgz",
- "integrity": "sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.3.8.tgz",
+ "integrity": "sha512-zVCUw7RFskvPuNlPn/8xISbrf0zTWsTSdYTsUTN1ERGGZGVnRxM2QZ3x1OR32+vwkkCm0IW6HmJ49IsPm7ilLg==",
"requires": {
- "@vue/compiler-ssr": "3.2.47",
- "@vue/shared": "3.2.47"
+ "@vue/compiler-ssr": "3.3.8",
+ "@vue/shared": "3.3.8"
}
},
"@vue/shared": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.47.tgz",
- "integrity": "sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ=="
+ "version": "3.3.8",
+ "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.8.tgz",
+ "integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw=="
},
"@vueuse/core": {
"version": "9.13.0",
@@ -5842,9 +5904,9 @@
"dev": true
},
"csstype": {
- "version": "2.6.21",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz",
- "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w=="
+ "version": "3.1.2",
+ "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.2.tgz",
+ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
},
"d": {
"version": "1.0.1",
@@ -6242,7 +6304,7 @@
},
"estree-walker": {
"version": "2.0.2",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"esutils": {
@@ -6763,6 +6825,7 @@
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
"integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+ "dev": true,
"requires": {
"sourcemap-codec": "^1.4.8"
}
@@ -6841,9 +6904,9 @@
"integrity": "sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g=="
},
"nanoid": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
- "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw=="
+ "version": "3.3.7",
+ "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g=="
},
"natural-compare": {
"version": "1.4.0",
@@ -7025,11 +7088,11 @@
}
},
"postcss": {
- "version": "8.4.21",
- "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.21.tgz",
- "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
+ "version": "8.4.31",
+ "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.31.tgz",
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"requires": {
- "nanoid": "^3.3.4",
+ "nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
}
@@ -7341,7 +7404,8 @@
"sourcemap-codec": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
- "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
+ "dev": true
},
"splitpanes": {
"version": "3.1.5",
@@ -7541,15 +7605,15 @@
}
},
"vue": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.47.tgz",
- "integrity": "sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==",
- "requires": {
- "@vue/compiler-dom": "3.2.47",
- "@vue/compiler-sfc": "3.2.47",
- "@vue/runtime-dom": "3.2.47",
- "@vue/server-renderer": "3.2.47",
- "@vue/shared": "3.2.47"
+ "version": "3.3.8",
+ "resolved": "https://registry.npmmirror.com/vue/-/vue-3.3.8.tgz",
+ "integrity": "sha512-5VSX/3DabBikOXMsxzlW8JyfeLKlG9mzqnWgLQLty88vdZL7ZJgrdgBOmrArwxiLtmS+lNNpPcBYqrhE6TQW5w==",
+ "requires": {
+ "@vue/compiler-dom": "3.3.8",
+ "@vue/compiler-sfc": "3.3.8",
+ "@vue/runtime-dom": "3.3.8",
+ "@vue/server-renderer": "3.3.8",
+ "@vue/shared": "3.3.8"
}
},
"vue-clipboard3": {
diff --git a/package.json b/package.json
index a913505..8951143 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,7 @@
"sortablejs": "^1.15.0",
"splitpanes": "^3.1.5",
"ts-enum-util": "^4.0.2",
- "vue": "^3.2.47",
+ "vue": "^3.3.8",
"vue-clipboard3": "^2.0.0",
"vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.2.2",
diff --git a/README.md b/README.md
index 2b3fdda..2511e0e 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ https://antd.newlifex.com
3. 页面路由规则从接口读取,所有路由自动引用 view/modules/index 文件,当页面需要手工修改时,可按后台路由规则在项目里建立文件复制模板代码(view/modules/index 文件)进行修改,无需在前端代码另外配置路由
-4. 完成动态表单、表格组件的初步封装,当接口配置不足以满足页面需求时,调用 usePageSetting 函数即可对页面进行组件配置
+4. 完成动态表单、表格组件的初步封装,当接口配置不足以满足页面需求时,调用 usePage 函数即可对页面进行组件配置
5. 组件不够时,可继续封装组件并配置到 form/component.ts 文件里即可引用
@@ -45,7 +45,7 @@ https://antd.newlifex.com
</script>
```
-2. usePageSetting 函数参数配置
+2. usePage 函数参数配置
```javascript
{
@@ -85,11 +85,47 @@ https://antd.newlifex.com
width?: string | number;
// 是否勾选显示
isCheck?: boolean;
- }
+ // 是否可排序
+ sort?: boolean;
+ },
+ /** 表格配置(支持element-plus table的所有配置) */
+ tableConfig?: Partial<TableProps<EmptyObjectType>> & Partial<TableMoreProps> & {
+ api?: (...props: EmptyArrayType) => Promise<EmptyObjectType | Array<EmptyObjectType>>;
+ url?: string;
+ requestProps?: EmptyObjectType;
+ handleWidth?: number;
+ },
+ /** 详情相关配置 */
+ detailConfig?: {
+ api?: (...props: EmptyArrayType) => Promise<EmptyObjectType | Array<EmptyObjectType>>;
+ url?: string;
+ requestProps?: EmptyObjectType | ((row: EmptyObjectType) => EmptyObjectType);
+ },
+ /** 添加相关配置 */
+ addConfig?: {
+ api?: (...props: EmptyArrayType) => Promise<EmptyObjectType | Array<EmptyObjectType>>;
+ url?: string;
+ requestProps?: EmptyObjectType;
+ },
+ /** 编辑相关配置 */
+ editConfig?: {
+ api?: (...props: EmptyArrayType) => Promise<EmptyObjectType | Array<EmptyObjectType>>;
+ url?: string;
+ requestProps?: EmptyObjectType;
+ },
+ /** 添加修改删除请求点击以及执行前后的回调钩子 */
+ onAddClick?: () => void;
+ onAddBefore?: (data: EmptyObjectType, addFun: Function) => void;
+ onAddAfter?: (data: EmptyObjectType) => void;
+ onEditClick?: (data: EmptyObjectType) => void;
+ onEditBefore?: (data: EmptyObjectType, editFun: Function) => void;
+ onEditAfter?: (data: EmptyObjectType) => void;
+ onDelBefore?: (data: EmptyObjectType, delFun: Function) => void;
+ onDelAfter?: (data: EmptyObjectType) => void;
}
```
-3. usePageSetting 返回值
+3. usePage 返回值
```javascript
{
@@ -102,6 +138,8 @@ https://antd.newlifex.com
// 表单相关
searchForm: Ref<EmptyObjectType>;
infoForm: Ref<EmptyObjectType>;
+ // 相关操作
+ handle: PageHandle
}
```
@@ -150,9 +188,9 @@ https://antd.newlifex.com
</template>
<script setup lang="ts">
-import usePageSetting from '/@/hook/usePageSetting'
+import usePage from '/@/hook/usePage'
import { ColumnKind, usePageApi } from '/@/api/page';
-const { columns, forms } = usePageSetting({
+const { columns, forms } = usePage({
columns: [
{
in: ColumnKind.ADD,
diff --git a/src/api/page.ts b/src/api/page.ts
index cf60317..d42b348 100644
--- a/src/api/page.ts
+++ b/src/api/page.ts
@@ -34,12 +34,23 @@ export function usePageApi() {
params
});
},
- getTableDetail: <T extends {}>(type: string, id: number) => {
+ getTableDetail: <T extends {}>(type: string, id: number, data?: EmptyObjectType) => {
return request<T>({
url: type + '/Detail',
method: 'get',
params: {
- id
+ id,
+ ...data
+ }
+ });
+ },
+ getTableDetailByUrl: <T extends {}>(url: string, id: number, data?: EmptyObjectType) => {
+ return request<T>({
+ url: url,
+ method: 'get',
+ params: {
+ id,
+ ...data
}
});
},
@@ -76,17 +87,18 @@ export function usePageApi() {
});
},
upload: (data: { r: string, file: File }) => {
- // console.log('upload')
const formData = new FormData();
- formData.append('r', data.r);
formData.append('file', data.file);
return request<{ [k in string]: EmptyObjectType[] }>({
- url: '/Admin/File/Upload',
+ url: '/Admin/File/UploadLayui',
method: 'post',
headers: {
- "Content-Type": 'multipart/form-data;charset=UTF-8'
+ "Content-Type": 'multipart/form-data'
},
- params: formData
+ data: formData,
+ params: {
+ r: 'user'
+ }
});
}
};
diff --git a/src/api/project.ts b/src/api/project.ts
new file mode 100644
index 0000000..f903f18
--- /dev/null
+++ b/src/api/project.ts
@@ -0,0 +1,20 @@
+import request from '/@/utils/request';
+
+/**
+ * (不建议写成 request.post(xxx),因为这样 post 时,无法 params 与 data 同时传参)
+ *
+ * 登录api接口集合
+ * @method signIn 用户登录
+ * @method signOut 用户退出登录
+ */
+export function useProjectApi() {
+ return {
+ workExamine: (data: { id: number; examine: 0 | 1 | 2 }) => {
+ return request({
+ url: '/Projects/Work/WorkExamine',
+ method: 'post',
+ data,
+ });
+ },
+ };
+}
\ No newline at end of file
diff --git a/src/components/cascader/index.vue b/src/components/cascader/index.vue
new file mode 100644
index 0000000..a16876b
--- /dev/null
+++ b/src/components/cascader/index.vue
@@ -0,0 +1,108 @@
+<template>
+ <el-cascader class="w-full" :props="caProps" v-model="caValue" />
+</template>
+
+<script setup lang="ts" name="cascader">
+import { useVModel } from '@vueuse/core';
+import type { CascaderProps } from 'element-plus'
+import { computed, ref, useAttrs, watch } from 'vue';
+import { usePageApi } from '/@/api/page';
+import { deepMerge } from '/@/utils/other';
+type PropApi = (...arr: any) => Promise<Array<EmptyObjectType>>
+interface Props {
+ // 读取store缓存
+ storeKey?: string;
+ // 值字段名
+ valueKey?: string;
+ // 名称字段名
+ labelKey?: string;
+ // 子集字段名
+ childrenKey?: string;
+ // 请求返回值字段名
+ resultKey?: string;
+ // 父级地段名
+ parentKey?: string;
+ // 用与将数组拆分赋值给多个字段
+ modelKeys?: string[];
+ // 绑定的值,可绑定表单对象或者属性值,当绑定表单对象时,需要搭配modelKeys一起使用
+ modelValue?: any;
+ // 请求方法
+ api?: PropApi | Array<PropApi>
+ // 请求地址
+ url?: string | Array<string>
+ // 总层级
+ level?: number;
+ // 最后以及标识字段
+ leafKey?: string;
+ // 请求携带的参数
+ requestProps?: EmptyObjectType;
+}
+interface Emits {
+ (e: 'update:model-value', val: any): void;
+ (e: 'optionRequestAfter', val: EmptyArrayType, level: number): void;
+}
+const props = withDefaults(defineProps<Props>(), {
+ valueKey: 'id',
+ labelKey: 'name',
+ resultKey: 'data',
+ childrenKey: 'children',
+ parentKey: 'parentId'
+})
+const emits = defineEmits<Emits>()
+
+const vModel = useVModel(props, 'modelValue', emits)
+
+const caValue = computed({
+ get () {
+ if (props.modelKeys) {
+ let val = props.modelKeys.map(key => props.modelValue[key])
+ for (let i = val.length - 1; i >= 0; i--) {
+ if (!val[i]) val.splice(i, 1)
+ else break;
+ }
+ return val
+ }
+ return props.modelValue
+ },
+ set (val: Array<string | number>) {
+ let value = val || []
+ if (props.modelKeys) {
+ props.modelKeys.forEach((key, i) => {
+ vModel.value[key] = value[i]
+ })
+ } else {
+ vModel.value = value
+ }
+ }
+})
+
+const attrs = useAttrs();
+const caProps = (attrs.props || {}) as CascaderProps;
+if (props.api || props.url) {
+ caProps.lazy = true;
+ caProps.value = props.valueKey!;
+ caProps.label = props.labelKey!;
+ caProps.children = props.childrenKey!;
+ const { getTableData } = usePageApi()
+ caProps.lazyLoad = async (node, resolve) => {
+ const { level, value } = node
+ let res
+ const requestProps = deepMerge(props.requestProps, level ? {[props.parentKey]: value} : {})
+ if (props.api) {
+ res = await Array.isArray(props.api) ? props.api[level](requestProps) : (props.api as PropApi)(requestProps)
+ } else {
+ res = await getTableData(Array.isArray(props.url) ? props.url[level] : props.url!, requestProps)
+ }
+ emits('optionRequestAfter', res[props.resultKey], level)
+ const nodes = res[props.resultKey].map((item: EmptyObjectType) => ({
+ ...item,
+ leaf: props.leafKey ? item[props.leafKey] : props.level ? level >= props.level : true
+ }))
+ resolve(nodes)
+ }
+}
+</script>
+
+<style scoped>
+
+</style>
\ No newline at end of file
diff --git a/src/components/checkboxGroup/index.vue b/src/components/checkboxGroup/index.vue
index 5163beb..1513a97 100644
--- a/src/components/checkboxGroup/index.vue
+++ b/src/components/checkboxGroup/index.vue
@@ -4,13 +4,13 @@
{{item[props.labelKey]}}
</el-checkbox>
</el-checkbox-group>
-
</template>
<script setup lang="ts">
import useOptions from '/@/hook/useOptions';
-import { optionProps } from '/@/utils/optionProps';
+import { OptionEmits, optionProps } from '/@/utils/optionProps';
const props = defineProps(optionProps);
-const { myOptions } = useOptions(props)
+const emits = defineEmits<OptionEmits>()
+const { myOptions } = useOptions(props, emits)
</script>
diff --git a/src/components/form/component.ts b/src/components/form/component.ts
index 642dade..f77e5c8 100644
--- a/src/components/form/component.ts
+++ b/src/components/form/component.ts
@@ -1,12 +1,12 @@
-import { ElAutocomplete, ElCascader, ElCheckbox, ElColorPicker, ElDatePicker, ElDivider, ElInput, ElInputNumber, ElRadio, ElRate, ElSlider, ElSwitch, ElTimePicker, ElTimeSelect } from "element-plus";
+import { ElAutocomplete, ElCheckbox, ElColorPicker, ElDatePicker, ElDivider, ElInput, ElInputNumber, ElRadio, ElRate, ElSlider, ElSwitch, ElTimePicker, ElTimeSelect } from "element-plus";
import Select from '../select/index.vue'
import CheckboxGroup from '../checkboxGroup/index.vue'
import RadioGroup from '../radioGroup/index.vue'
import Editor from '../editor/index.vue'
import Upload from '../upload/index.vue'
+import Cascader from '../cascader/index.vue'
export const forms = {
autocomplete: ElAutocomplete,
- cascader: ElCascader,
checkbox: ElCheckbox,
checkboxGroup: CheckboxGroup,
colorPicker: ElColorPicker,
@@ -24,4 +24,5 @@ export const forms = {
editor: Editor,
divider: ElDivider,
upload: Upload,
+ cascader: Cascader,
}
diff --git a/src/components/form/hooks/useForm.ts b/src/components/form/hooks/useForm.ts
index f4a2889..7331273 100644
--- a/src/components/form/hooks/useForm.ts
+++ b/src/components/form/hooks/useForm.ts
@@ -27,6 +27,6 @@ export default function useForm (props: FormProps, emits: FormEmits) {
formEl,
formValue,
onChange,
- getColBind
+ getColBind,
}
}
\ No newline at end of file
diff --git a/src/components/form/index.vue b/src/components/form/index.vue
index 3e18ada..8191416 100644
--- a/src/components/form/index.vue
+++ b/src/components/form/index.vue
@@ -1,10 +1,10 @@
<template>
<component :is="layoutComponent" v-bind="layoutBind" v-model="layoutVisible" class="form-wrapper" :class="{'h-full': wrapper !== 'dialog'}">
<div class="h-full flex flex-col overflow-auto form-body">
- <el-tabs type="border-card" :class="{'border-t-0': wrapper === 'drawer'}" v-model="activeName" v-if="tabs.length">
+ <el-tabs type="border-card" :class="{'border-t-0': wrapper === 'drawer'}" v-model="activeName" v-if="tabs.length > 1">
<el-tab-pane :name="item" :key="item" v-for="item in tabs">
<template #label>
- {{ item || '常规' }}
+ {{ item || DEFAULT_TAB_NAME }}
</template>
</el-tab-pane>
</el-tabs>
@@ -12,17 +12,21 @@
<el-form ref="formEl" :model="formValue" size="default" label-width="120px" class="table-form">
<el-row>
<template v-for="(item, key) in config" :key="key">
- <el-col v-bind="getColBind(item.col)" v-if="item.if === undefined ? true : item.if" v-show="getItemVShow(item)">
- <el-form-item
- class="form-item"
- :label="item.label"
- :prop="item.prop"
- :rules="[{ required: item.required, message: `${item.label}不能为空`, trigger: item.component === 'input' || item.component === 'inputNumber' ? 'blur' : 'change' }]"
- >
- <slot v-if="item.slot" :name="item.slot" :model="formValue" :prop="item.prop"></slot>
- <component v-else v-bind="item.props" v-model="formValue[item.prop]" :is="forms[item.component || 'input']" @change="onChange"></component>
- </el-form-item>
- </el-col>
+ <template v-if="item.if === undefined || (typeof item.if === 'function' ? item.if(formValue) : item.if)">
+ <el-divider v-if="item.component === 'divider'" v-show="getItemVShow(item, formValue)" v-bind="item.props">{{ item.label }}</el-divider>
+ <el-col v-bind="getColBind(item.col)" v-else v-show="getItemVShow(item, formValue)">
+ <el-form-item
+ class="form-item"
+ :label="item.label"
+ :prop="item.prop.toString()"
+ :rules="[{ required: item.required, message: `${item.label}不能为空`, trigger: item.component === 'input' || item.component === 'inputNumber' ? 'blur' : 'change' }]"
+ >
+ <slot v-if="item.slot" :name="item.slot" :model="formValue" :prop="item.prop"></slot>
+ <cascader v-else-if="Array.isArray(item.prop) && item.component === 'cascader'" v-bind="item.props" v-model="formValue" :modelKeys="item.prop" @change="onChange"></cascader>
+ <component v-else v-bind="item.props" :model-value="getFormValueByKey(formValue, item.prop as string)" @update:model-value="(val: any) => setFormValueByKey(val, item.prop as string)" :is="forms[item.component || 'input']" @change="onChange"></component>
+ </el-form-item>
+ </el-col>
+ </template>
</template>
<slot name="form-after"></slot>
</el-row>
@@ -49,6 +53,10 @@ import useFormWrapper from './hooks/useFormWrapper';
import useForm from './hooks/useForm';
import { EditWrapper } from '../page/model';
import { computed, ref, watch } from 'vue';
+import Cascader from '../../components/cascader/index.vue';
+import { isObjTrue } from '/@/utils/other';
+
+const DEFAULT_TAB_NAME = '常规'
interface Props {
wrapper?: EditWrapper;
@@ -82,7 +90,14 @@ const { formEl, formValue, onChange, getColBind } = useForm(props, emits)
const activeName = ref('');
const tabs = computed(() => {
- let groups = props.groups || [...new Set(props.config.map(item => item.group))]
+ let groups = props.groups || [
+ ...new Set(
+ props.config.filter(
+ item => (typeof item.if === 'function' ? item.if(formValue.value) : isObjTrue(item.if)) && (typeof item.show === 'function' ? item.show(formValue.value) : isObjTrue(item.show))
+ ).map(item => item.group || DEFAULT_TAB_NAME)
+ )
+ ]
+ // console.log('groups', groups)
if (!groups.length || (groups.length === 1 && !groups[0])) {
return []
}
@@ -102,10 +117,13 @@ watch(tabs, (val) => {
// activeName.value = tabs.value[tabs.value.findIndex(name => name === activeName.value) - 1]!
// }
-const getItemVShow = (item: ColumnConfig) => {
- const show = item.show === undefined ? true : item.show
- return tabs.value.length ? item.group === activeName.value && show : show
+const getItemVShow = (item: ColumnConfig, formData: EmptyObjectType) => {
+ // const show = item.show === undefined ? true : item.show
+ const show = typeof item.show === 'function' ? item.show(formData) : isObjTrue(item.show)
+ const group = item.group || ""
+ return tabs.value.length ? (group === activeName.value || (!group && activeName.value === DEFAULT_TAB_NAME)) && show : show
}
+
const submit = async () => {
if (!formEl.value) return
await formEl.value.validate((valid, fields) => {
@@ -113,8 +131,7 @@ const submit = async () => {
emits('submit', formValue.value)
layoutVisible.value = false;
} else {
- // console.log('error submit!', fields)
- ElMessage.error(fields)
+ ElMessage.error(Object.values(fields || {}).reduce((total, item) => total.concat(item), []).map(item => item.message).join('\n'))
}
})
}
@@ -122,6 +139,21 @@ const cancel = () => {
layoutVisible.value = false;
emits('cancel')
}
+
+const getFormValueByKey = (value: EmptyObjectType, key: string) => {
+ return key.split('.').reduce((total, key) => total ? total[key] : undefined, value)
+}
+const setFormValueByKey = (val: any, key: string) => {
+ let keys = key.split('.')
+ keys.reduce((total, key, i) => {
+ if (i === keys.length - 1) {
+ total[key] = val
+ } else if (!total[key]) {
+ total[key] = {}
+ }
+ return total[key]
+ }, formValue.value)
+}
defineExpose({
formEl
})
diff --git a/src/components/form/model/form.ts b/src/components/form/model/form.ts
index f5f3690..abc7d06 100644
--- a/src/components/form/model/form.ts
+++ b/src/components/form/model/form.ts
@@ -7,29 +7,29 @@ export type InstanceProps<T extends DefineComponent> = InstanceType<T>['$props']
export type ColumnProp<T> = T extends keyof FormType ? InstanceType<FormType[T]>['$props'] : undefined;
export interface ColumnConfig {
- // 字段名
- prop: string;
- // 字段中文名称
+ /** 字段名 */
+ prop: string | string[];
+ /** 字段中文名称 */
label?: string;
- // 组件
+ /** 组件 */
component?: keyof FormType;
- // 是否渲染
- if?: boolean;
- // 是否显示
- show?: boolean;
- // 自定义组件插槽
+ /** 是否渲染 */
+ if?: boolean | ((data: EmptyObjectType) => boolean);
+ /** 是否显示 */
+ show?: boolean | ((data: EmptyObjectType) => boolean);
+ /** 自定义组件插槽 */
slot?: string;
- // 必填
+ /** 必填 */
required?: boolean;
- // 组件参数
+ /** 组件参数 */
props?: ColumnProp<ColumnConfig['component']>;
- // 校验规则
+ /** 校验规则 */
rules?: FormRule[];
- // 排序下标
+ /** 排序下标 */
index?: number;
- // 所占列数
+ /** 所占列数 */
col?: number | Col;
- // 所属分组
+ /** 所属分组 */
group?: string;
// isCheck?: boolean;
}
diff --git a/src/components/page/edit.vue b/src/components/page/edit.vue
index 5340a96..50df5b1 100644
--- a/src/components/page/edit.vue
+++ b/src/components/page/edit.vue
@@ -1,20 +1,21 @@
<template>
<Form :config="config" v-model="formData" v-model:visible="layoutVisible" :wrapper="wrapper" :title="myIsUpdate?'修改':'添加'" @submit="submit">
- <template v-for="item in config.filter(item => item.slot)" :key="item.prop" #[`${item.slot!}`]="data">
+ <template v-for="item in config.filter(item => item.slot)" :key="item.prop.toString()" #[`${item.slot!}`]="data">
<slot :name="item.slot" :model="data.model" :prop="data.prop"></slot>
</template>
</Form>
</template>
<script setup lang="ts">
-import { computed, ref, watch } from 'vue';
+import { computed, inject, ref, watch } from 'vue';
import { usePageApi } from '/@/api/page';
import Form from '/@/components/form/index.vue';
import { ColumnConfig } from '../form/model/form';
import { ElMessage } from 'element-plus';
-import { EditWrapper } from './model';
+import { EditWrapper, UsePageProps } from './model';
+import providePageKey from './provide/key';
interface Props {
- type: string;
+ type?: string;
wrapper?: EditWrapper;
visible: boolean;
modelValue?: EmptyObjectType;
@@ -52,6 +53,9 @@ watch(() => props.isUpdate, (val) => {
myIsUpdate.value = val
})
const pageApi = usePageApi();
+const providePage = inject(providePageKey)
+const { onAddBefore, onAddAfter, onEditBefore, onEditAfter } = providePage?.pageProps || {}
+let { detailConfig, addConfig, editConfig } = providePage?.pageProps || ({} as UsePageProps)
const handleAdd = () => {
if (myIsUpdate.value) {
@@ -60,24 +64,59 @@ const handleAdd = () => {
myIsUpdate.value = false;
layoutVisible.value = true;
}
-const handleEdit = (id: number) => {
+const handleEdit = async (row: EmptyObjectType) => {
formData.value = {}
myIsUpdate.value = true;
layoutVisible.value = true;
loading.value = true
- pageApi.getTableDetail(props.type, id).then(res => {
+ let res;
+ try {
+ const requestProps = typeof detailConfig?.requestProps === 'function' ? detailConfig.requestProps(row) : detailConfig?.requestProps;
+ if (detailConfig?.api) {
+ res = await detailConfig?.api({ id: row.id, ...requestProps })
+ } else if (detailConfig?.url) {
+ res = await pageApi.getTableDetailByUrl(detailConfig?.url, row.id, requestProps)
+ } else {
+ res = await pageApi.getTableDetail(props.type!, row.id, requestProps)
+ }
loading.value = false
- formData.value = res.data
- }).catch(() => {
+ formData.value = res.data
+ } catch (error) {
loading.value = false
- })
+ }
}
const submit = () => {
- const submitApi = myIsUpdate.value ? pageApi.setTableItem : pageApi.addTableItem
- submitApi(props.type, formData.value).then(() => {
- ElMessage.success('保存成功!')
+ async function submitFun () {
+ if (myIsUpdate.value) {
+ const requestProps = typeof editConfig?.requestProps === 'function' ? editConfig.requestProps(formData.value) : editConfig?.requestProps;
+ if (editConfig?.api) {
+ await editConfig?.api({ ...formData.value, ...requestProps })
+ } else if (editConfig?.url) {
+ await pageApi.setTableItem(editConfig?.url, { ...formData.value, ...requestProps })
+ } else {
+ await pageApi.setTableItem(props.type!, { ...formData.value, ...requestProps })
+ }
+ onEditAfter && onEditAfter(formData);
+ } else {
+ const requestProps = typeof addConfig?.requestProps === 'function' ? addConfig.requestProps(formData.value) : addConfig?.requestProps;
+ if (addConfig?.api) {
+ await addConfig?.api({ ...formData.value, ...requestProps })
+ } else if (addConfig?.url) {
+ await pageApi.addTableItem(addConfig?.url, { ...formData.value, ...requestProps })
+ } else {
+ await pageApi.addTableItem(props.type!, { ...formData.value, ...requestProps })
+ }
+ onAddAfter && onAddAfter(formData);
+ }
emits('submitSuccess')
- })
+ }
+ if (myIsUpdate.value) {
+ if (onEditBefore) onEditBefore(formData, submitFun)
+ else submitFun()
+ } else {
+ if (onAddBefore) onAddBefore(formData, submitFun)
+ else submitFun()
+ }
}
defineExpose({
handleAdd,
@@ -87,4 +126,4 @@ defineExpose({
<style scoped>
-</style>
\ No newline at end of file
+</style>
diff --git a/src/components/page/hook/useGetColumnsForm.ts b/src/components/page/hook/useGetColumnsForm.ts
index 3a92193..7113d41 100644
--- a/src/components/page/hook/useGetColumnsForm.ts
+++ b/src/components/page/hook/useGetColumnsForm.ts
@@ -1,20 +1,20 @@
import { inject, ref, Ref, unref, watch } from "vue";
import { ColumnConfig } from "../../form/model/form";
import { TableColumn } from "../../table/type";
-import { EventSettingRef } from "../model";
-import providePageKey from "../provide/key";
+import { ColumnIn, UsePageProps, EventSettingRef, ProvidePage } from "../model";
+// import providePageKey from "../provide/key";
import { ColumnKind, usePageApi } from "/@/api/page";
import { deepMerge, getInfoFields, getSearchFields, getTableFields, toCamelCase } from "/@/utils/other";
interface Props {
- type: string;
+ type?: string;
searchData?: EmptyObjectType;
}
interface Emits {
(e: 'update:searchData', val: EmptyObjectType): void;
}
-export default function useGetColumnsForm (props: Props, emits: Emits) {
- const providePage = inject(providePageKey)
+export default function useGetColumnsForm (props: Props, emits: Emits, providePage?: ProvidePage) {
+ // const providePage = inject(providePageKey)
const editConfig = (providePage?.editColumns || ref([])) as Ref<ColumnConfig[]>;
const addConfig = (providePage?.addColumns || ref([])) as Ref<ColumnConfig[]>;
@@ -32,26 +32,41 @@ export default function useGetColumnsForm (props: Props, emits: Emits) {
// 配置修改
const setting = (oldSetting: EventSettingRef) => {
- if (providePage?.newSettings.columns) {
- let newArr = providePage.newSettings.columns.filter(item => item.in === undefined || item.in === oldSetting.type || (Array.isArray(item.in) && item.in.some(v => v === oldSetting.type)))
- let oldArrVal: Array<ColumnConfig | TableColumn> = unref(oldSetting.config)
- newArr.forEach((newItem) => {
- let oldItem = oldArrVal.find(v => v.prop === newItem.prop)
+ // 当prop配置数组时需要拆分成单个的配置(cascader组件除外),若存在同名配置则合并配置
+ let columns = providePage?.pageProps.columns?.reduce((total, item) => {
+ if (item.component === 'cascader') {
+ total.push(item)
+ } else {
+ let propArray = Array.isArray(item.prop) ? item.prop : [item.prop]
+ propArray.forEach((prop, i) => {
+ total.push({ ...item, prop, index: item.index !== undefined ? item.index + i : undefined})
+ })
+ }
+ return total
+ }, [] as Array<(ColumnConfig | TableColumn) & ColumnIn>) || []
+ let newArr = columns.filter(item => item.in === undefined || item.in === oldSetting.type || (Array.isArray(item.in) && item.in.some(v => v === oldSetting.type)))
+ let oldArrVal: Ref<Array<ColumnConfig | TableColumn>> = oldSetting.config
+ newArr.forEach((newItem) => {
+ if (newItem.component === 'cascader' && Array.isArray(newItem.prop)) {
+ oldArrVal.value = oldArrVal.value.filter(oldItem => (newItem.prop as string[]).every(v => v !== oldItem.prop))
+ oldArrVal.value.push(newItem)
+ } else {
+ let oldItem = oldArrVal.value.find(v => v.prop === newItem.prop)
if (oldItem) {
// 合并配置
oldItem = deepMerge(oldItem, newItem)
- } else if (newItem.in) {
+ } else {
// 追加配置
- oldArrVal.push(newItem)
+ oldArrVal.value.push(newItem)
}
- })
- // 排序
- let sortArr = oldArrVal.filter(item => item.index !== undefined).sort((item1, item2) => item1.index! - item2.index!)
- oldArrVal = oldArrVal.filter(item => item.index === undefined)
- sortArr.forEach(item => {
- oldArrVal.splice(item.index!, 0, item)
- })
- }
+ }
+ })
+ // 排序
+ let sortArr = oldArrVal.value.filter(item => item.index !== undefined).sort((item1, item2) => item1.index! - item2.index!)
+ oldArrVal.value = oldArrVal.value.filter(item => item.index === undefined)
+ sortArr.forEach(item => {
+ oldArrVal.value.splice(item.index!, 0, item)
+ })
}
const pageApi = usePageApi()
@@ -66,6 +81,7 @@ export default function useGetColumnsForm (props: Props, emits: Emits) {
pageApi.getColumns(props.type, ColumnKind.EDIT).then(res => {
editConfig.value = getInfoFields(res.data)
setting({ type: ColumnKind.EDIT, config: editConfig })
+ console.log('editConfig', editConfig.value)
})
pageApi.getColumns(props.type, ColumnKind.ADD).then(res => {
addConfig.value = getInfoFields(res.data)
diff --git a/src/components/page/index.vue b/src/components/page/index.vue
index 117e7dc..36e829a 100644
--- a/src/components/page/index.vue
+++ b/src/components/page/index.vue
@@ -3,34 +3,53 @@
<div class="table-demo-padding layout-padding-view layout-padding-auto h-full">
<Table
v-if="wrapper !== 'div'"
+ class="table-demo"
ref="tableRef"
+ v-bind="config"
+ v-model:columns="columns"
+ v-model:search-data="searchForm"
:authId="authId"
:data="data"
- :config="config"
- v-model:columns="columns"
:search="search"
:param="param"
:pager-visible="pagerVisible"
- v-model:search-data="searchForm"
- class="table-demo"
@del="onTableDelRow"
@add="onTableAdd"
@edit="onTableEdit"
@search="onSearch"
@sortHeader="onSortHeader">
- <template v-for="item in search.filter(v => v.slot)" :key="item.prop" #[`${item.slot!}`]="data">
+ <template v-for="item in search.filter(v => v.slot)" :key="item.prop.toString()" #[`${item.slot!}`]="data">
<slot :name="item.slot" :model="data.model" :prop="data.prop"></slot>
</template>
<template v-for="item in columns.filter(v => v.slot)" :key="item.prop" #[`${item.slot!}`]="data">
<slot :name="item.slot" :scope="data.scope"></slot>
</template>
- </Table>
- <Edit ref="editEl" v-model:visible="editVisible" :type="type" :wrapper="wrapper" :config="isUpdate ? editConfig : addConfig" :key="type" v-model="editForm" :isUpdate="isUpdate" @submit-success="getTableData">
- <template v-for="item in editConfig.filter(item => item.slot)" :key="item.prop" #[`${item.slot!}`]="data">
- <slot v-if="isUpdate" :name="item.slot" :model="data.model" :prop="data.prop"></slot>
+ <template #table-top>
+ <slot name="table-top"></slot>
+ </template>
+ <template #table-handle-before="{ scope }">
+ <slot name="table-handle-before" :scope="scope"></slot>
+ </template>
+ <template #table-handle-after="{ scope }">
+ <slot name="table-handle-after" :scope="scope"></slot>
</template>
- <template v-for="item in addConfig.filter(item => item.slot)" :key="item.prop" #[`${item.slot!}`]="data">
- <slot v-if="!isUpdate" :name="item.slot" :model="data.model" :prop="data.prop"></slot>
+ </Table>
+ <Edit
+ ref="editEl"
+ v-model:visible="editVisible"
+ :type="type"
+ :wrapper="wrapper"
+ :config="isUpdate ? editConfig : addConfig"
+ :key="type"
+ v-model="editForm"
+ :isUpdate="isUpdate"
+ @submit-success="getTableData"
+ @add-before="$attrs.onAddBefore"
+ @add-after="$attrs.onAddAfter"
+ @edit-before="$attrs.onEditBefore"
+ @edit-after="$attrs.onEditAfter">
+ <template v-for="item in slots" :key="item.prop.toString()" #[`${item.slot!}`]="data">
+ <slot v-if="(isUpdate && item.in?.indexOf(ColumnKind.EDIT) !== -1) || (!isUpdate && item.in?.indexOf(ColumnKind.ADD) !== -1)" :name="item.slot" :model="data.model" :prop="data.prop"></slot>
</template>
</Edit>
</div>
@@ -38,20 +57,22 @@
</template>
<script setup lang="ts">
-import { defineAsyncComponent, ref } from 'vue';
+import { computed, defineAsyncComponent, inject, ref } from 'vue';
import { ElMessage } from 'element-plus';
-import { usePageApi } from '../../api/page';
+import { ColumnKind, usePageApi } from '../../api/page';
import Edit from './edit.vue';
import { TableColumn } from '../table/type';
import { EditWrapper } from './model';
import useGetColumnsForm from './hook/useGetColumnsForm';
+import { ColumnConfig } from '../form/model/form';
+import providePageKey from './provide/key';
interface Props {
- type: string;
+ type?: string;
authId?: number;
searchData?: EmptyObjectType;
editWrapper?: EditWrapper;
- tableConfig?: TableConfigType;
+ // tableConfig?: TableConfigType;
}
interface Emits {
(e: 'update:searchData', val: EmptyObjectType): void;
@@ -65,8 +86,28 @@ const editVisible = ref(false);
// 引入组件
const Table = defineAsyncComponent(() => import('/@/components/table/index.vue'));
const wrapper = ref<EditWrapper>(props.editWrapper || 'drawer');
+
+const providePage = inject(providePageKey)
+const tableConfig = providePage?.pageProps.tableConfig || {}
+
// 初始化配置项数据与表单数据
-const { searchForm, editForm, editConfig, addConfig, search, columns } = useGetColumnsForm(props, emits)
+const { searchForm, editForm, editConfig, addConfig, search, columns } = useGetColumnsForm(props, emits, providePage)
+const { onAddClick, onEditClick, onDelBefore, onDelAfter } = providePage?.pageProps || {};
+
+const slots = computed(() => {
+ let values: Array<ColumnConfig & { in?: ColumnKind[] }> = editConfig.value.filter(item => item.slot).map(item => ({ ...item, in: [ColumnKind.EDIT] }))
+ addConfig.value.forEach(item => {
+ if (item.slot) {
+ let i = values.findIndex(val => val.prop === item.prop)
+ if (i !== -1) {
+ values[i].in?.push(ColumnKind.ADD)
+ } else {
+ values.push({...item, in: [ColumnKind.ADD]})
+ }
+ }
+ })
+ return values
+})
// 定义变量内容
const tableRef = ref<RefType>();
@@ -81,18 +122,28 @@ const config = ref<TableConfigType>({
isSerialNo: false, // 是否显示表格序号
isSelection: true, // 是否显示表格多选
isOperate: true, // 是否显示表格操作栏
- ...props.tableConfig
+ ...providePage?.pageProps.tableConfig
})
const param = ref({
pageIndex: 1,
pageSize: 20,
+ sort: '',
+ desc: false
})
// 初始化列表数据
-const getTableData = () => {
+const getTableData = async () => {
config.value.loading = true;
data.value = [];
- pageApi.getTableData(props.type, { ...param.value, ...searchForm.value }).then(res => {
+ let res;
+ try {
+ const requestProps = tableConfig?.requestProps;
+ if (tableConfig?.api) {
+ res = await tableConfig?.api({ ...requestProps, ...param.value, ...searchForm.value })
+ } else {
+ let url = tableConfig?.url || props.type!
+ res = await pageApi.getTableData(url, { ...requestProps, ...param.value, ...searchForm.value })
+ }
if (Array.isArray(res.data)) {
data.value = res.data || []
} else if (typeof res.data === 'object') {
@@ -105,31 +156,40 @@ const getTableData = () => {
config.value.total = Number(res.page.totalCount)
}
config.value.loading = false;
- }).catch(() => {
+ } catch (error) {
config.value.loading = false;
- })
+ }
};
// 删除当前项回调
const onTableDelRow = (item: EmptyObjectType) => {
- pageApi.delTableItem(props.type, item.id).then(() =>{
- ElMessage.success(`删除成功!`);
- getTableData();
- })
- getTableData();
+ function delFun () {
+ pageApi.delTableItem(props.type!, item.id).then(() =>{
+ ElMessage.success(`删除成功!`);
+ getTableData();
+ onDelAfter && onDelAfter(item);
+ })
+ }
+ // emits('delBefore', item, delFun)
+ if (onDelBefore) onDelBefore(item, delFun)
+ else delFun()
};
const onTableAdd = () => {
isUpdate.value = false
editEl.value?.handleAdd();
+ onAddClick && onAddClick()
}
const onTableEdit = (item: EmptyObjectType) => {
isUpdate.value = true
- editEl.value?.handleEdit(item.id);
+ editEl.value?.handleEdit(item);
+ onEditClick && onEditClick(item);
}
// 分页改变时回调
const onSearch = (page: TableDemoPageType) => {
param.value.pageIndex = page.pageIndex;
param.value.pageSize = page.pageSize;
+ param.value.sort = page.sort;
+ param.value.desc = page.desc;
getTableData();
};
// 拖动显示列排序回调
@@ -138,6 +198,11 @@ const onSortHeader = (data: TableColumn[]) => {
};
getTableData();
+defineExpose({
+ getTableData
+})
+providePage && (providePage.handle.reload = getTableData)
+
</script>
<style scoped lang="scss">
diff --git a/src/components/page/model/index.ts b/src/components/page/model/index.ts
index 29e8562..a0fdc6a 100644
--- a/src/components/page/model/index.ts
+++ b/src/components/page/model/index.ts
@@ -1,6 +1,7 @@
+import { TableProps } from "element-plus";
import { Ref } from "vue";
import { ColumnConfig } from "../../form/model/form";
-import { TableColumn } from "../../table/type";
+import { TableColumn, TableMoreProps } from "../../table/type";
import { ColumnKind } from "/@/api/page";
export type EditWrapper = 'div' | 'dialog' | 'drawer';
@@ -33,16 +34,58 @@ export interface EventSettingRef {
formData?: Ref<EmptyObjectType>;
}
-// type EventSettingPropsList = {
+// type UsePagePropsList = {
// in: ColumnKind.LIST
// } & TableColumn
-// type EventSettingPropsForm = {
+// type UsePagePropsForm = {
// in: ColumnKind.ADD | ColumnKind.DETAIL | ColumnKind.EDIT | ColumnKind.SEARCH
// } & ColumnConfig
-// type EventSettingPropsCommon = ({in?: ColumnKind[]} & TableColumn) | ({ in?: ColumnKind[]} & ColumnConfig);
+// type UsePagePropsCommon = ({in?: ColumnKind[]} & TableColumn) | ({ in?: ColumnKind[]} & ColumnConfig);
-export interface EventSettingProps {
- columns?: Array<({ in?: ColumnKind | Array<ColumnKind> } & TableColumn) | ({ in?: ColumnKind | Array<ColumnKind> } & ColumnConfig)>
+export interface ColumnIn {
+ in?: ColumnKind | Array<ColumnKind>;
+}
+export interface MoreProp extends ColumnIn {
+ /**参数名 */
+ prop: string | Array<string>;
+}
+export interface UsePageProps {
+ /** 表单配置 */
+ columns?: Array<(Omit<TableColumn, 'prop'> & MoreProp) | (Omit<ColumnConfig, 'prop'> & MoreProp)>;
+ /** 表格配置 */
+ tableConfig?: Partial<TableProps<EmptyObjectType>> & Partial<TableMoreProps> & {
+ api?: (...props: EmptyArrayType) => Promise<EmptyObjectType | Array<EmptyObjectType>>;
+ url?: string;
+ requestProps?: EmptyObjectType;
+ handleWidth?: number;
+ },
+ detailConfig?: {
+ api?: (...props: EmptyArrayType) => Promise<EmptyObjectType | Array<EmptyObjectType>>;
+ url?: string;
+ requestProps?: EmptyObjectType | ((row: EmptyObjectType) => EmptyObjectType);
+ },
+ addConfig?: {
+ api?: (...props: EmptyArrayType) => Promise<EmptyObjectType | Array<EmptyObjectType>>;
+ url?: string;
+ requestProps?: EmptyObjectType;
+ },
+ editConfig?: {
+ api?: (...props: EmptyArrayType) => Promise<EmptyObjectType | Array<EmptyObjectType>>;
+ url?: string;
+ requestProps?: EmptyObjectType;
+ },
+ onAddClick?: () => void;
+ onAddBefore?: (data: EmptyObjectType, addFun: Function) => void;
+ onAddAfter?: (data: EmptyObjectType) => void;
+ onEditClick?: (data: EmptyObjectType) => void;
+ onEditBefore?: (data: EmptyObjectType, editFun: Function) => void;
+ onEditAfter?: (data: EmptyObjectType) => void;
+ onDelBefore?: (data: EmptyObjectType, delFun: Function) => void;
+ onDelAfter?: (data: EmptyObjectType) => void;
+}
+
+export interface PageHandle {
+ reload: () => void;
}
export interface ProvidePage {
@@ -53,5 +96,6 @@ export interface ProvidePage {
detailColumns: Ref<ColumnConfig[]>;
searchForm: Ref<EmptyObjectType>;
infoForm: Ref<EmptyObjectType>;
- newSettings: EventSettingProps;
-}
\ No newline at end of file
+ pageProps: UsePageProps;
+ handle: PageHandle,
+}
diff --git a/src/components/radioGroup/index.vue b/src/components/radioGroup/index.vue
index 6a6e985..d3ffac5 100644
--- a/src/components/radioGroup/index.vue
+++ b/src/components/radioGroup/index.vue
@@ -9,8 +9,9 @@
<script setup lang="ts">
import useOptions from '/@/hook/useOptions';
-import { optionProps } from '/@/utils/optionProps';
+import { OptionEmits, optionProps } from '/@/utils/optionProps';
const props = defineProps(optionProps);
-const { myOptions } = useOptions(props)
+const emits = defineEmits<OptionEmits>()
+const { myOptions } = useOptions(props, emits)
</script>
diff --git a/src/components/select/index.vue b/src/components/select/index.vue
index c2723fb..aa8087c 100644
--- a/src/components/select/index.vue
+++ b/src/components/select/index.vue
@@ -11,10 +11,11 @@
<script setup lang="ts">
import useOptions from '/@/hook/useOptions';
-import { optionProps } from '/@/utils/optionProps';
+import { OptionEmits, optionProps } from '/@/utils/optionProps';
const props = defineProps(optionProps);
-const { myOptions } = useOptions(props)
+const emits = defineEmits<OptionEmits>()
+const { myOptions } = useOptions(props, emits)
</script>
diff --git a/src/components/table/index.vue b/src/components/table/index.vue
index 84905a8..e56cdcf 100644
--- a/src/components/table/index.vue
+++ b/src/components/table/index.vue
@@ -8,29 +8,33 @@
<slot :name="item.slot" :model="data.model" :prop="data.prop"></slot>
</template>
</Search>
+ <slot name="table-top"></slot>
<el-table
+ style="width: 100%"
+ row-key="id"
+ stripe
:data="data"
:border="setBorder"
v-bind="$attrs"
- row-key="id"
- stripe
- style="width: 100%"
- v-loading="config.loading"
- @selection-change="onSelectionChange">
- <el-table-column type="selection" :reserve-selection="true" width="40" fixed="left" v-if="config.isSelection && tableColumns.length" />
- <el-table-column type="index" label="序号" width="60" v-if="config.isSerialNo && tableColumns.length" />
+ v-loading="loading"
+ @selection-change="onSelectionChange"
+ @sort-change="onChangeSort">
+ <el-table-column type="selection" :reserve-selection="true" width="40" fixed="left" v-if="isSelection && tableColumns.length" />
+ <el-table-column type="index" label="序号" width="60" v-if="isSerialNo && tableColumns.length" />
<template v-for="(item, index) in tableColumns" :key="index">
<el-table-column
show-overflow-tooltip
:prop="item.prop"
:width="item.width"
:label="item.label"
+ :sortable="item.sort ? 'custom' : false"
v-if="(item.if === undefined || item.if) && (item.isCheck === undefined || item.isCheck)"
>
<template v-slot="scope">
<slot v-if="item.slot" :name="item.slot" :scope="scope" :model="scope.row" :prop="item.prop"></slot>
<template v-else-if="item.component === 'upload'">
- <img :src="scope.row[item.prop]" width="50px" height="50px" />
+ <!-- <img :src="scope.row[item.prop]" width="50px" height="50px" /> -->
+ <el-image :src="imgBaseUrl + scope.row[item.prop]" class="w-12 h-12 rounded" :preview-src-list="[imgBaseUrl + scope.row[item.prop]]" preview-teleported fit="fill"></el-image>
</template>
<template v-else-if="item.component === 'switch'">
<el-tag :type="scope.row[item.prop] ? '' : 'info'" effect="dark">
@@ -48,17 +52,19 @@
</el-table-column>
</template>
<el-table-column
- v-if="config.isOperate && tableColumns.length && auths(getBtnAuth(Auth.SET, Auth.DEL))"
+ v-if="isOperate && tableColumns.length && auths(getBtnAuth(Auth.SET, Auth.DEL))"
label="操作"
:width="operateWidth"
fixed="right">
<template v-slot="scope">
+ <slot name="table-handle-before" :scope="scope"></slot>
<el-button v-auths="getBtnAuth(Auth.SET)" text type="primary" @click="onEdit(scope.row)">修改</el-button>
<el-popconfirm title="确定删除吗?" @confirm="onDel(scope.row)">
<template #reference>
<el-button v-auths="getBtnAuth(Auth.DEL)" text type="danger">删除</el-button>
</template>
</el-popconfirm>
+ <slot name="table-handle-after" :scope="scope"></slot>
</template>
</el-table-column>
<template #empty>
@@ -73,7 +79,7 @@
v-model:page-size="state.page.pageSize"
:pager-count="5"
:page-sizes="[10, 20, 30]"
- :total="config.total"
+ :total="total"
layout="total, sizes, prev, pager, next, jumper"
background
@size-change="onHandleSizeChange"
@@ -138,7 +144,7 @@
<script setup lang="ts" name="netxTable">
import { reactive, computed, nextTick, watch, Ref } from 'vue';
-import { ElMessage } from 'element-plus';
+import { ColumnSortHandler, ColumnSortParams, ElMessage } from 'element-plus';
import table2excel from 'js-table2excel';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
@@ -154,33 +160,42 @@ import { useEnumOptions } from '/@/stores/enumOptions';
interface Param {
pageIndex?: number;
pageSize?: number;
+ sort?: string,
+ desc?: boolean,
}
-interface Props {
+export interface Props {
+ total?: number;
+ loading?: boolean;
+ isBorder?: boolean;
+ isSerialNo?: boolean;
+ isSelection?: boolean;
+ isOperate?: boolean;
+ operateWidth?: number;
authId?: number;
data: EmptyObjectType[];
columns: TableColumn[];
- config: TableConfigType;
search: ColumnConfig[];
param: Param;
pagerVisible: boolean;
searchData: EmptyObjectType;
}
+const imgBaseUrl = import.meta.env.VITE_IMG_BASE_URL
// 定义父组件传过来的值
const props = withDefaults(defineProps<Props>(), {
+ total: 0, // 列表总数
+ loading: true, // loading 加载
+ isBorder: false, // 是否显示表格边框
+ isSerialNo: false, // 是否显示表格序号
+ isSelection: true, // 是否显示表格多选
+ isOperate: true, // 是否显示表格操作栏
data: () => [],
columns: () => [],
- config: () => ({
- total: 0, // 列表总数
- loading: true, // loading 加载
- isBorder: false, // 是否显示表格边框
- isSerialNo: false, // 是否显示表格序号
- isSelection: true, // 是否显示表格多选
- isOperate: true, // 是否显示表格操作栏
- }),
search: () => [],
param: () => ({
pageIndex: 1,
pageSize: 10,
+ sort: '',
+ desc: false,
}),
pagerVisible: true,
searchData: () => ({})
@@ -189,8 +204,8 @@ const props = withDefaults(defineProps<Props>(), {
const { options } = useEnumOptions()
const operateWidth = computed(() => {
- if (props.config.operateWidth)
- return props.config.operateWidth
+ if (props.operateWidth)
+ return props.operateWidth
return (auths(getBtnAuth(Auth.SET)) ? 55 : 0) + (auths(getBtnAuth(Auth.DEL)) ? 55 : 0)
})
@@ -219,11 +234,14 @@ const searchDataRef = computed({
// 设置边框显示/隐藏
const setBorder = computed(() => {
- return props.config.isBorder ? true : false;
+ return props.isBorder ? true : false;
});
// 获取父组件 配置项(必传)
const getConfig = computed(() => {
- return props.config;
+ return {
+ isSerialNo: props.isSerialNo,
+ isSelection: props.isSelection,
+ };
});
// 设置 tool columns 数据
// const setHeader = computed(() => {
@@ -291,6 +309,11 @@ const onImportTable = () => {
const onRefreshTable = () => {
emit('search', state.page);
};
+const onChangeSort = (data: { prop: string; order: 'descending' | 'ascending' | null }) => {
+ state.page.sort = data.order ? data.prop : '';
+ state.page.desc = data.order === 'descending';
+ emit('search', state.page);
+}
// 设置
const onSetTable = () => {
nextTick(() => {
diff --git a/src/components/table/search.vue b/src/components/table/search.vue
index ff436e6..eaf9476 100644
--- a/src/components/table/search.vue
+++ b/src/components/table/search.vue
@@ -36,6 +36,7 @@
import { reactive, ref, onMounted, computed } from 'vue';
import Form from '/@/components/form/index.vue'
import { ColumnConfig } from '../form/model/form';
+import { isObjTrue } from '/@/utils/other';
interface Props {
search: ColumnConfig[];
modelValue: EmptyObjectType;
@@ -60,7 +61,10 @@ const formData = computed({
}
})
const config = computed(() => {
- return props.search.map((item, index) => ({
+ return props.search.filter(item =>
+ (typeof item.if === 'function' ? item.if(formData.value) : isObjTrue(item.if)) &&
+ (typeof item.show === 'function' ? item.show(formData.value) : isObjTrue(item.show))
+ ).map((item, index) => ({
...item,
show: index === 0 || state.isToggle
}))
@@ -87,7 +91,7 @@ const onReset = () => {
// 初始化 form 字段,取自父组件 search.prop
const initFormField = () => {
if (props.search.length <= 0) return false;
- props.search.forEach((v) => (formData.value[v.prop] = ''));
+ props.search.forEach((v) => (formData.value[v.prop.toString()] = ''));
};
// 页面加载时
onMounted(() => {
diff --git a/src/components/table/type.ts b/src/components/table/type.ts
index 4dc006c..47aa09b 100644
--- a/src/components/table/type.ts
+++ b/src/components/table/type.ts
@@ -1,5 +1,5 @@
import { FormRule } from "@form-create/element-ui";
-import { ColumnProp, FormType } from "../form/model/form";
+import { ColumnConfig, ColumnProp, FormType } from "../form/model/form";
export interface TableColumn {
// 字段名
@@ -26,6 +26,8 @@ export interface TableColumn {
index?: number;
// 是否渲染
if?: boolean;
+ // 是否可排序
+ sort?: boolean;
}
export enum Auth {
@@ -35,3 +37,25 @@ export enum Auth {
'DEL' = 8,
'ALL' = 255,
}
+
+interface Param {
+ pageIndex?: number;
+ pageSize?: number;
+}
+
+export interface TableMoreProps {
+ total?: number;
+ loading?: boolean;
+ isBorder?: boolean;
+ isSerialNo?: boolean;
+ isSelection?: boolean;
+ isOperate?: boolean;
+ operateWidth?: number;
+ authId?: number;
+ data: EmptyObjectType[];
+ columns: TableColumn[];
+ search: ColumnConfig[];
+ param: Param;
+ pagerVisible: boolean;
+ searchData: EmptyObjectType;
+}
diff --git a/src/components/upload/index.vue b/src/components/upload/index.vue
index ff35d3a..bbef816 100644
--- a/src/components/upload/index.vue
+++ b/src/components/upload/index.vue
@@ -1,58 +1,66 @@
<template>
<el-upload
class="avatar-uploader"
- :http-request="request"
- :show-file-list="fileListVisible"
+ :action="action"
+ :show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
>
- <img v-if="imageUrl" :src="imageUrl" class="avatar" />
+ <img v-if="imageUrl" :src="baseUrl + imageUrl" class="avatar" />
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
</template>
<script lang="ts" setup>
-import { ref } from 'vue'
-import { ElMessage, UploadRequestOptions } from 'element-plus'
+import { computed, ref } from 'vue'
+import { ElMessage } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
-
import type { UploadProps } from 'element-plus'
-import { Session } from '/@/utils/storage';
-import { usePageApi } from '/@/api/page';
interface Props {
+ modelValue?: string;
maxSize?: number;
+ limit?: number;
+ url?: string;
+ resultKey?: string;
}
-const headers = {
- Authorization: `${Session.get('token')}`,
- // 'Content-Type': 'multipart/form-data'
+interface Emits {
+ (e: 'update:modelValue', val: string): void
}
-const props = defineProps<Props>()
-const action = import.meta.env.VITE_API_URL + '/Admin/File/Upload'
+const props = withDefaults(defineProps<Props>(), {
+ limit: 1
+})
+const emits = defineEmits<Emits>()
-const imageUrl = ref('')
-const fileListVisible = ref(false)
+const action = import.meta.env.VITE_API_URL + props.url
+const baseUrl = import.meta.env.VITE_IMG_BASE_URL
-const { upload } = usePageApi()
-const request = (options: UploadRequestOptions) => {
- return upload({ r: 'user', file: options.file })
-}
+const imageUrl = computed({
+ get () {
+ return props.modelValue
+ },
+ set (val) {
+ emits('update:modelValue', val || '')
+ }
+})
+// const imageUrl = ref('')
const handleAvatarSuccess: UploadProps['onSuccess'] = (
response,
uploadFile
) => {
- imageUrl.value = URL.createObjectURL(uploadFile.raw!)
+ // console.log('response', response)
+ // imageUrl.value = URL.createObjectURL(uploadFile.raw!)
+ imageUrl.value = response.data.shortPath
}
const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
- // if (rawFile.type !== 'image/jpeg') {
- // ElMessage.error('Avatar picture must be JPG format!')
- // return false
- // } else
- if (props.maxSize && rawFile.size / 1024 / 1024 > props.maxSize) {
- ElMessage.error('最大不超过 2MB!')
+ if (rawFile.type !== 'image/jpeg' && rawFile.type !== 'image/jpg' && rawFile.type !== 'image/png') {
+ ElMessage.error('图片格式不支持!')
+ return false
+ } else if (rawFile.size / 1024 / 1024 > 2) {
+ ElMessage.error('Avatar picture size can not exceed 2MB!')
return false
}
return true
@@ -61,10 +69,9 @@ const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
<style scoped>
.avatar-uploader .avatar {
- width: 100px;
- height: 100px;
+ width: 178px;
+ height: 178px;
display: block;
- object-fit: cover;
}
</style>
@@ -85,8 +92,8 @@ const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
- width: 100px;
- height: 100px;
+ width: 178px;
+ height: 178px;
text-align: center;
}
</style>
\ No newline at end of file
diff --git a/src/hook/useOptions.ts b/src/hook/useOptions.ts
index 7228ca4..36e38a1 100644
--- a/src/hook/useOptions.ts
+++ b/src/hook/useOptions.ts
@@ -2,24 +2,28 @@ import { storeToRefs } from "pinia";
import { Ref, ref, toRef, toRefs, watch } from "vue";
import { usePageApi } from "../api/page";
import { useEnumOptions } from "../stores/enumOptions";
-import { OptionProps } from "../utils/optionProps";
+import { OptionEmits, OptionProps } from "../utils/optionProps";
-export default function useOptions (props: OptionProps) {
+export default function useOptions (props: OptionProps, emits: OptionEmits) {
const enumOptions = useEnumOptions()
// console.log('enumOptions', toRef(enumOptions.options, props.storeKey), props.storeKey)
const myOptions = props.storeKey ? (toRef(enumOptions.options, props.storeKey) as Ref<EmptyObjectType[] | undefined>) : ref(props.options);
- watch(() => props.options, (val) => {
+ watch(() => props.options, (val: EmptyArrayType) => {
myOptions.value = val
})
if (props.storeKey) {
- enumOptions.setOptions(props.storeKey)
+ enumOptions.setOptions(props.storeKey).then((res: EmptyObjectType) => {
+ emits('optionRequestAfter', myOptions)
+ })
} else if (props.api) {
- props.api().then(res => {
+ props.api().then((res: EmptyObjectType) => {
myOptions.value = props.resultKey ? getByKey(res, props.resultKey) : res
+ emits('optionRequestAfter', myOptions)
})
} else if (props.url) {
usePageApi().getTableData(props.url, { pageIndex: 0 }).then(res => {
myOptions.value = props.resultKey ? getByKey(res, props.resultKey) : res
+ emits('optionRequestAfter', myOptions)
})
}
return {
diff --git a/src/hook/usePageSetting.ts b/src/hook/usePage.ts
similarity index 85%
rename from src/hook/usePageSetting.ts
rename to src/hook/usePage.ts
index 6f23cc2..94b61ca 100644
--- a/src/hook/usePageSetting.ts
+++ b/src/hook/usePage.ts
@@ -1,16 +1,18 @@
import { provide, ref, Ref } from "vue";
import { ColumnConfig } from "../components/form/model/form";
import { TableColumn } from "../components/table/type";
-import { EventSettingProps } from "../components/page/model";
+import { PageHandle, UsePageProps } from "../components/page/model";
import providePageKey from "../components/page/provide/key";
+import { getCurrentInstance } from "vue";
+import { reactive } from "vue";
// 各区域配置项(响应式)
// 各区域表单值(响应式)
/**
* 页面配置
- * @param newSettings 配置规则
+ * @param pageProps 配置规则
* @returns setting: 回调方法; columns: 配置; forms: 表单;
*/
-export default function usePageSetting (newSettings: EventSettingProps) {
+export default function usePage (props: UsePageProps) {
const tableColumns = ref([]) as Ref<TableColumn[]>;
const searchColumns = ref([]) as Ref<ColumnConfig[]>;
const editColumns = ref([]) as Ref<ColumnConfig[]>;
@@ -19,8 +21,11 @@ export default function usePageSetting (newSettings: EventSettingProps) {
const searchForm = ref<EmptyObjectType>({});
const infoForm = ref<EmptyObjectType>({});
+
+ const handle = reactive({} as PageHandle)
provide(providePageKey, {
+ pageProps: props,
tableColumns,
searchColumns,
editColumns,
@@ -28,7 +33,7 @@ export default function usePageSetting (newSettings: EventSettingProps) {
detailColumns,
searchForm,
infoForm,
- newSettings
+ handle,
});
return {
@@ -41,5 +46,6 @@ export default function usePageSetting (newSettings: EventSettingProps) {
// 表单相关
searchForm,
infoForm,
+ handle,
}
}
\ No newline at end of file
diff --git a/src/layout/routerView/parent.vue b/src/layout/routerView/parent.vue
index 4124f7f..bca5f56 100644
--- a/src/layout/routerView/parent.vue
+++ b/src/layout/routerView/parent.vue
@@ -3,7 +3,7 @@
<router-view v-slot="{ Component }">
<transition :name="setTransitionName" mode="out-in">
<keep-alive :include="getKeepAliveNames">
- <component :is="Component" :key="state.refreshRouterViewKey" class="w100" v-show="!isIframePage" />
+ <component :is="Component" :key="state.refreshRouterViewKey" class="w100" v-show="!isIframePage"/>
</keep-alive>
</transition>
</router-view>
diff --git a/src/router/backEnd.ts b/src/router/backEnd.ts
index 586be93..91d0b99 100644
--- a/src/router/backEnd.ts
+++ b/src/router/backEnd.ts
@@ -219,7 +219,7 @@ export function backEndComponent(routes: Menu[]): Array<RouteRecordRaw> {
meta: {
title: item.displayName,
isLink: "",
- isHide: false,
+ isHide: !item.visible,
isKeepAlive: true,
isAffix: false,
isIframe: item.newWindow,
diff --git a/src/stores/enumOptions.ts b/src/stores/enumOptions.ts
index 1d80a47..6f7e0cd 100644
--- a/src/stores/enumOptions.ts
+++ b/src/stores/enumOptions.ts
@@ -19,14 +19,16 @@ export const useEnumOptions = defineStore('enumOptions', {
}),
actions: {
async setOptions(type: string) {
- if (!this[type]) {
+ if (!this.options[type]) {
this[type] = [];
- usePageApi().lookUp(type).then(res => {
+ return usePageApi().lookUp(type).then(res => {
this.options[type] = res.data[toCamelCase(type)]
+ return this.options[type]
}).catch(() => {
this.options[type] = undefined
})
}
+ return Promise.resolve(this.options[type])
},
},
});
diff --git a/src/theme/element.scss b/src/theme/element.scss
index fa4e78b..9b04220 100644
--- a/src/theme/element.scss
+++ b/src/theme/element.scss
@@ -1,5 +1,8 @@
@import 'mixins/index.scss';
+.el-upload--picture-card {
+ --el-upload-picture-card-size: 126px !important;
+}
/* Button 按钮
------------------------------- */
// 第三方字体图标大小
diff --git a/src/types/component.d.ts b/src/types/component.d.ts
new file mode 100644
index 0000000..5304973
--- /dev/null
+++ b/src/types/component.d.ts
@@ -0,0 +1,8 @@
+import Page from '../components/page/index.vue'
+
+// 声明全局组件
+declare module 'vue' {
+ export interface GlobalComponents {
+ Page: typeof Page
+ }
+}
diff --git a/src/types/global.d.ts b/src/types/global.d.ts
index cf808ab..129fe3a 100644
--- a/src/types/global.d.ts
+++ b/src/types/global.d.ts
@@ -110,4 +110,4 @@ declare interface TableType<T = any> {
};
}
-type ReverseMap<T> = T[keyof T];
\ No newline at end of file
+type ReverseMap<T> = T[keyof T];
diff --git a/src/types/views.d.ts b/src/types/views.d.ts
index ff2e68d..1de979d 100644
--- a/src/types/views.d.ts
+++ b/src/types/views.d.ts
@@ -291,6 +291,8 @@ declare type WorkflowDrawerState<T = any> = {
declare type TableDemoPageType = {
pageIndex: number;
pageSize: number;
+ sort: string;
+ desc: boolean;
};
declare type TableSearchType = {
diff --git a/src/utils/optionProps.ts b/src/utils/optionProps.ts
index abb70dd..74b3c89 100644
--- a/src/utils/optionProps.ts
+++ b/src/utils/optionProps.ts
@@ -1,4 +1,5 @@
import type { PropType, ExtractPropTypes } from 'vue';
+import { Ref } from 'vue';
export const optionProps = {
options: {
type: Object as PropType<Array<EmptyObjectType>>,
@@ -26,3 +27,8 @@ export const optionProps = {
}
};
export type OptionProps = ExtractPropTypes<typeof optionProps >;
+
+export type OptionEmits = {
+ (e: 'optionRequestAfter', options: Ref<EmptyArrayType>): void;
+ (e: 'optionRequestBefore', options: Ref<EmptyArrayType>): void;
+}
diff --git a/src/utils/other.ts b/src/utils/other.ts
index 2226114..5ad72d9 100644
--- a/src/utils/other.ts
+++ b/src/utils/other.ts
@@ -200,7 +200,7 @@ export function getComponentBaseField (item: Column) {
String: 'input',
Boolean: 'switch',
DateTime: 'datePicker',
- }
+ }
const contents = {
mail: 'input',
mobile: 'input',
@@ -258,7 +258,78 @@ export function deepMerge<T = any>(src: any = {}, ...targets: Array<any>): T {
});
return src;
}
+export function decomposePowerOfTwo (num: number) {
+ let powers = [];
+ let n = 0;
+ while(num > 0) {
+ if ((num & 1) === 1) {
+ // 如果当前位是1,就添加对应的2的n次幂
+ powers.push(Math.pow(2, n));
+ }
+ num >>= 1; // 右移一位
+ n++;
+ }
+ return powers;
+}
+
+export function isObjTrue (prop?: boolean) {
+ return prop || (prop === undefined)
+}
+export function toSomeLevelJSON(obj: EmptyObjectType) {
+ const result = {};
+ for (const key in obj) {
+ const keys = key.split('.');
+ let currentLevel = result;
+
+ for (let i = 0; i < keys.length; i++) {
+ const subKey = keys[i];
+ if (!currentLevel[subKey]) {
+ if (i === keys.length - 1) {
+ currentLevel[subKey] = obj[key];
+ } else {
+ currentLevel[subKey] = {};
+ }
+ }
+ currentLevel = currentLevel[subKey];
+ }
+ }
+
+ return result;
+}
+export function toOneLevelJSON(obj: EmptyObjectType, parentKey = '') {
+ const result = {};
+
+ for (const key in obj) {
+ const newKey = parentKey ? `${parentKey}.${key}` : key;
+ if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
+ const nestedObj = toOneLevelJSON(obj[key], newKey);
+ Object.assign(result, nestedObj);
+ } else {
+ result[newKey] = obj[key];
+ }
+ }
+
+ return result;
+}
+export function assignJSON(target: EmptyObjectType, source: EmptyObjectType) {
+ for (const key in target) {
+ if (!(key in source)) {
+ delete target[key];
+ }
+ }
+
+ for (const key in source) {
+ if (typeof source[key] === 'object' && !Array.isArray(source[key])) {
+ if (!target[key]) {
+ target[key] = {};
+ }
+ assignJSON(target[key], source[key]);
+ } else {
+ target[key] = source[key];
+ }
+ }
+}
/**
* 统一批量导出
* @method elSvg 导出全局注册 element plus svg 图标
@@ -304,7 +375,12 @@ const other = {
},
deepMerge (src: any = {}, ...targets: Array<any>) {
return deepMerge(src, ...targets)
- }
+ },
+ isObjTrue,
+ decomposePowerOfTwo,
+ toSomeLevelJSON,
+ toOneLevelJSON,
+ assignJSON
};
// 统一批量导出
diff --git a/src/views/modules/admin/role.vue b/src/views/modules/admin/role.vue
index 9a083d4..0fe4b38 100644
--- a/src/views/modules/admin/role.vue
+++ b/src/views/modules/admin/role.vue
@@ -1,23 +1,103 @@
<template>
- <Page></Page>
+ <Page>
+ <template #permission>
+ <el-checkbox-group v-model="permission" class="w-full">
+ <el-table :data="menus" style="width: 100%; margin-bottom: 20px" row-key="id" size="small" border default-expand-all>
+ <el-table-column prop="name" label="标识" width="150" />
+ <el-table-column prop="displayName" label="菜单名" width="150" />
+ <el-table-column label="全部授权" width="70" align="center">
+ <template #default="scope">
+ <el-checkbox
+ @change="(val: boolean) => changeCheckboxAll(scope.row, val)"
+ :label="`${scope.row.id}#255`"
+ :load="loadCheckbox(scope.row)"
+ ><span></span
+ ></el-checkbox>
+ </template>
+ </el-table-column>
+ <el-table-column label="权限">
+ <template #default="scope">
+ <el-checkbox :label="`${scope.row.id}#${index}`" v-for="(item, index) in scope.row.permissions" :key="index">{{ item }}</el-checkbox>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-checkbox-group>
+ </template>
+ </Page>
</template>
<script setup lang="ts">
+import { computed, ref } from 'vue';
+import { useMenuApi } from '/@/api/menu';
import { ColumnKind } from '/@/api/page';
-import usePageSetting from '/@/hook/usePageSetting';
+import usePage from '/@/hook/usePage';
-const { tableColumns } = usePageSetting({
- columns: [
- {
- in: ColumnKind.ADD,
- prop: 'sort',
- component: 'select',
- label: '33333',
- props: {
- options: [{id: 1, name: '1111'}]
- }
- }
- ]
-})
-console.log('tableColumns', tableColumns)
+const { infoForm } = usePage({
+ columns: [
+ {
+ in: ColumnKind.ADD,
+ prop: 'sort',
+ component: 'select',
+ label: '33333',
+ props: {
+ options: [{ id: 1, name: '1111' }],
+ },
+ },
+ {
+ prop: 'permission',
+ slot: 'permission',
+ in: [ColumnKind.EDIT, ColumnKind.ADD],
+ },
+ ],
+});
+
+const loadCheckbox = (row: EmptyObjectType) => {
+ if (!row.permissionsArr) {
+ row.permissionsArr = Object.keys(row.permissions).map((key) => `${row.id}#${key}`);
+ }
+};
+
+const permission = computed({
+ get() {
+ let value = infoForm.value.permission ? infoForm.value.permission.split(',') : [];
+ let menuStr = JSON.stringify(menus.value);
+ value.forEach((val: string) => {
+ let auth = val.split('#');
+ if (Number(auth[1]) === 255) {
+ let arr = menuStr.match(new RegExp(auth[0] + '#[0-9][0-9]*', 'g'));
+ value = Array.from(new Set(value.concat(arr)));
+ }
+ });
+ return value;
+ },
+ set(val) {
+ infoForm.value.permission = val.toString();
+ },
+});
+
+const menus = ref<EmptyObjectType[]>([]);
+useMenuApi()
+ .getMenu({ pageSize: 0 })
+ .then((res) => {
+ menus.value = res.data;
+ });
+
+const changeCheckboxAll = (row: EmptyObjectType, val: boolean) => {
+ if (val) {
+ permission.value = Array.from(new Set(permission.value.concat(row.permissionsArr)));
+ if (row.children) {
+ row.children.forEach((child: EmptyObjectType) => {
+ permission.value = Array.from(new Set(permission.value.concat([child.id + '#255'])));
+ changeCheckboxAll(child, true);
+ });
+ }
+ } else {
+ permission.value = permission.value.filter((val: string) => val.indexOf(row.id + '#') !== 0);
+ if (row.children) {
+ permission.value = permission.value.filter((val: string) =>
+ row.children.every((item: EmptyObjectType) => item.id !== Number(val.split('#')[0]))
+ );
+ }
+ }
+};
</script>
diff --git a/src/views/modules/admin/test.vue b/src/views/modules/admin/test.vue
index ef29ad6..0cd54cf 100644
--- a/src/views/modules/admin/test.vue
+++ b/src/views/modules/admin/test.vue
@@ -1,31 +1,32 @@
<template>
- <Page></Page>
+ <Page></Page>
</template>
<script setup lang="ts">
-import usePageSetting from '/@/hook/usePageSetting';
+import usePage from '/@/hook/usePage';
-usePageSetting({
- columns: [
- {
- prop: 'enable',
- component: 'radioGroup',
- props: {
- options: [
- { id: 1, name: '开' }, { id: 2, name: '关' }
- ]
- }
- },
- {
- prop: 'upload',
- if: false
- },
- {
- prop: 'checkbox',
- props: {
- label: '测试'
- }
- }
- ]
-})
+usePage({
+ columns: [
+ {
+ prop: 'enable',
+ component: 'radioGroup',
+ props: {
+ options: [
+ { id: 1, name: '开' },
+ { id: 2, name: '关' },
+ ],
+ },
+ },
+ {
+ prop: 'upload',
+ if: false,
+ },
+ {
+ prop: 'checkbox',
+ props: {
+ label: '测试',
+ },
+ },
+ ],
+});
</script>
diff --git a/src/views/modules/admin/user/index.vue b/src/views/modules/admin/user/index.vue
index 0385e6a..ee475e5 100644
--- a/src/views/modules/admin/user/index.vue
+++ b/src/views/modules/admin/user/index.vue
@@ -1,53 +1,66 @@
<template>
- <Page>
- <template #mail>
- 测试
- </template>
- </Page>
+ <Page>
+ <template #mail> 测试 </template>
+ <template #row-handle> </template>
+ </Page>
</template>
<script setup lang="ts">
-import usePageSetting from '/@/hook/usePageSetting'
+import usePage from '/@/hook/usePage';
import { ColumnKind, usePageApi } from '/@/api/page';
-const { columns, forms } = usePageSetting({
- columns: [
- // {
- // in: ColumnKind.ADD,
- // prop: 'sex',
- // component: 'radioGroup',
- // props: {
- // options: [{ id: 1, name: '男' }, { id: 2, name: '女' }]
- // },
- // },
- // {
- // in: [ColumnKind.SEARCH, ColumnKind.LIST, ColumnKind.ADD],
- // prop: 'mail',
- // slot: 'mail',
- // },
- {
- prop: 'departmentID',
- component: 'select',
- props: {
- api: () => usePageApi().getTableData('/admin/department', { pageIndex: 0 })
- }
- },
- {
- prop: 'roleID',
- component: 'select',
- props: {
- url: '/admin/role'
- }
- },
- // {
- // in: [ColumnKind.ADD, ColumnKind.EDIT],
- // prop: 'name',
- // props: {
- // onChange: (val: string) => {
- // forms.data!.mail = val
- // columns.add!.find(item => item.prop === 'sex')!.if = !val
- // }
- // }
- // }
- ]
-})
+const { columns, forms } = usePage({
+ columns: [
+ // {
+ // in: ColumnKind.ADD,
+ // prop: 'sex',
+ // component: 'radioGroup',
+ // props: {
+ // options: [{ id: 1, name: '男' }, { id: 2, name: '女' }]
+ // },
+ // },
+ // {
+ // in: [ColumnKind.SEARCH, ColumnKind.LIST, ColumnKind.ADD],
+ // prop: 'mail',
+ // slot: 'mail',
+ // },
+ {
+ prop: 'departmentID',
+ component: 'select',
+ props: {
+ api: () => usePageApi().getTableData('/admin/department', { pageIndex: 0 }),
+ },
+ },
+ {
+ prop: 'roleID',
+ component: 'select',
+ props: {
+ url: '/admin/role',
+ },
+ },
+ {
+ prop: 'avatar',
+ component: 'upload',
+ props: {
+ url: '/Projects/Work/UploadFileService',
+ data: {
+ directory: 'works'
+ }
+ }
+ }
+ // {
+ // in: [ColumnKind.ADD, ColumnKind.EDIT],
+ // prop: 'name',
+ // props: {
+ // onChange: (val: string) => {
+ // forms.data!.mail = val
+ // columns.add!.find(item => item.prop === 'sex')!.if = !val
+ // }
+ // }
+ // }
+ ],
+ onEditBefore: (data, fun) => {
+ data.value.ceshi = 123;
+ fun();
+ },
+});
</script>
diff --git a/src/views/modules/index.vue b/src/views/modules/index.vue
index 11581ac..e7300a5 100644
--- a/src/views/modules/index.vue
+++ b/src/views/modules/index.vue
@@ -1,6 +1,3 @@
<template>
- <Page></Page>
+ <Page></Page>
</template>
-
-<script setup lang="ts">
-</script>
diff --git a/tsconfig.json b/tsconfig.json
index 98f1fdc..46c1575 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -49,7 +49,7 @@
} /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */,
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
- "types": ["vite/client"] /* Type declaration files to be included in compilation. */,
+ "types": ["vite/client", "element-plus/global"] /* Type declaration files to be included in compilation. */,
"allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
diff --git a/vite.config.ts b/vite.config.ts
index efc09aa..7cacb8d 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -29,8 +29,7 @@ const viteConfig = defineConfig((mode: ConfigEnv) => {
hmr: true,
proxy: {
'/base-api': {
- // target: 'https://cube3.newlifex.com',
- target: 'http://120.78.152.40:5000',
+ target: 'https://cube3.newlifex.com',
ws: true,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/base-api/, ''),