提交 e73fba5f authored 作者: 王鹏飞's avatar 王鹏飞

chore: update

上级 fb04024b
...@@ -17,14 +17,14 @@ ...@@ -17,14 +17,14 @@
"blueimp-md5": "^2.19.0", "blueimp-md5": "^2.19.0",
"countup.js": "^2.3.2", "countup.js": "^2.3.2",
"dayjs": "^1.11.5", "dayjs": "^1.11.5",
"element-plus": "^2.2.16", "element-plus": "^2.2.17",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"pinia": "^2.0.21", "pinia": "^2.0.22",
"qs": "^6.11.0", "qs": "^6.11.0",
"ua-parser-js": "^1.0.2", "ua-parser-js": "^1.0.2",
"video.js": "^7.20.2", "video.js": "^7.20.2",
"vue": "^3.2.38", "vue": "^3.2.39",
"vue-router": "^4.1.5" "vue-router": "^4.1.5"
}, },
"devDependencies": { "devDependencies": {
...@@ -42,10 +42,10 @@ ...@@ -42,10 +42,10 @@
"chalk": "^5.0.1", "chalk": "^5.0.1",
"eslint": "^8.5.0", "eslint": "^8.5.0",
"eslint-plugin-vue": "^9.4.0", "eslint-plugin-vue": "^9.4.0",
"sass": "^1.54.8", "sass": "^1.54.9",
"typescript": "~4.7.4", "typescript": "~4.7.4",
"unplugin-auto-import": "^0.11.2", "unplugin-auto-import": "^0.11.2",
"vite": "^3.1.0", "vite": "^3.1.2",
"vite-plugin-checker": "^0.5.1", "vite-plugin-checker": "^0.5.1",
"vue-tsc": "^0.40.5" "vue-tsc": "^0.40.5"
} }
...@@ -705,36 +705,36 @@ ...@@ -705,36 +705,36 @@
} }
}, },
"node_modules/@vue/compiler-core": { "node_modules/@vue/compiler-core": {
"version": "3.2.38", "version": "3.2.39",
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.2.38.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.39.tgz",
"integrity": "sha512-/FsvnSu7Z+lkd/8KXMa4yYNUiqQrI22135gfsQYVGuh5tqEgOB0XqrUdb/KnCLa5+TmQLPwvyUnKMyCpu+SX3Q==", "integrity": "sha512-mf/36OWXqWn0wsC40nwRRGheR/qoID+lZXbIuLnr4/AngM0ov8Xvv8GHunC0rKRIkh60bTqydlqTeBo49rlbqw==",
"dependencies": { "dependencies": {
"@babel/parser": "^7.16.4", "@babel/parser": "^7.16.4",
"@vue/shared": "3.2.38", "@vue/shared": "3.2.39",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"source-map": "^0.6.1" "source-map": "^0.6.1"
} }
}, },
"node_modules/@vue/compiler-dom": { "node_modules/@vue/compiler-dom": {
"version": "3.2.38", "version": "3.2.39",
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.2.38.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.39.tgz",
"integrity": "sha512-zqX4FgUbw56kzHlgYuEEJR8mefFiiyR3u96498+zWPsLeh1WKvgIReoNE+U7gG8bCUdvsrJ0JRmev0Ky6n2O0g==", "integrity": "sha512-HMFI25Be1C8vLEEv1hgEO1dWwG9QQ8LTTPmCkblVJY/O3OvWx6r1+zsox5mKPMGvqYEZa6l8j+xgOfUspgo7hw==",
"dependencies": { "dependencies": {
"@vue/compiler-core": "3.2.38", "@vue/compiler-core": "3.2.39",
"@vue/shared": "3.2.38" "@vue/shared": "3.2.39"
} }
}, },
"node_modules/@vue/compiler-sfc": { "node_modules/@vue/compiler-sfc": {
"version": "3.2.38", "version": "3.2.39",
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.2.38.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.39.tgz",
"integrity": "sha512-KZjrW32KloMYtTcHAFuw3CqsyWc5X6seb8KbkANSWt3Cz9p2qA8c1GJpSkksFP9ABb6an0FLCFl46ZFXx3kKpg==", "integrity": "sha512-fqAQgFs1/BxTUZkd0Vakn3teKUt//J3c420BgnYgEOoVdTwYpBTSXCMJ88GOBCylmUBbtquGPli9tVs7LzsWIA==",
"dependencies": { "dependencies": {
"@babel/parser": "^7.16.4", "@babel/parser": "^7.16.4",
"@vue/compiler-core": "3.2.38", "@vue/compiler-core": "3.2.39",
"@vue/compiler-dom": "3.2.38", "@vue/compiler-dom": "3.2.39",
"@vue/compiler-ssr": "3.2.38", "@vue/compiler-ssr": "3.2.39",
"@vue/reactivity-transform": "3.2.38", "@vue/reactivity-transform": "3.2.39",
"@vue/shared": "3.2.38", "@vue/shared": "3.2.39",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"magic-string": "^0.25.7", "magic-string": "^0.25.7",
"postcss": "^8.1.10", "postcss": "^8.1.10",
...@@ -742,12 +742,12 @@ ...@@ -742,12 +742,12 @@
} }
}, },
"node_modules/@vue/compiler-ssr": { "node_modules/@vue/compiler-ssr": {
"version": "3.2.38", "version": "3.2.39",
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.2.38.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.39.tgz",
"integrity": "sha512-bm9jOeyv1H3UskNm4S6IfueKjUNFmi2kRweFIGnqaGkkRePjwEcfCVqyS3roe7HvF4ugsEkhf4+kIvDhip6XzQ==", "integrity": "sha512-EoGCJ6lincKOZGW+0Ky4WOKsSmqL7hp1ZYgen8M7u/mlvvEQUaO9tKKOy7K43M9U2aA3tPv0TuYYQFrEbK2eFQ==",
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.2.38", "@vue/compiler-dom": "3.2.39",
"@vue/shared": "3.2.38" "@vue/shared": "3.2.39"
} }
}, },
"node_modules/@vue/devtools-api": { "node_modules/@vue/devtools-api": {
...@@ -780,60 +780,60 @@ ...@@ -780,60 +780,60 @@
} }
}, },
"node_modules/@vue/reactivity": { "node_modules/@vue/reactivity": {
"version": "3.2.38", "version": "3.2.39",
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.2.38.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.39.tgz",
"integrity": "sha512-6L4myYcH9HG2M25co7/BSo0skKFHpAN8PhkNPM4xRVkyGl1K5M3Jx4rp5bsYhvYze2K4+l+pioN4e6ZwFLUVtw==", "integrity": "sha512-vlaYX2a3qMhIZfrw3Mtfd+BuU+TZmvDrPMa+6lpfzS9k/LnGxkSuf0fhkP0rMGfiOHPtyKoU9OJJJFGm92beVQ==",
"dependencies": { "dependencies": {
"@vue/shared": "3.2.38" "@vue/shared": "3.2.39"
} }
}, },
"node_modules/@vue/reactivity-transform": { "node_modules/@vue/reactivity-transform": {
"version": "3.2.38", "version": "3.2.39",
"resolved": "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.2.38.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.39.tgz",
"integrity": "sha512-3SD3Jmi1yXrDwiNJqQ6fs1x61WsDLqVk4NyKVz78mkaIRh6d3IqtRnptgRfXn+Fzf+m6B1KxBYWq1APj6h4qeA==", "integrity": "sha512-HGuWu864zStiWs9wBC6JYOP1E00UjMdDWIG5W+FpUx28hV3uz9ODOKVNm/vdOy/Pvzg8+OcANxAVC85WFBbl3A==",
"dependencies": { "dependencies": {
"@babel/parser": "^7.16.4", "@babel/parser": "^7.16.4",
"@vue/compiler-core": "3.2.38", "@vue/compiler-core": "3.2.39",
"@vue/shared": "3.2.38", "@vue/shared": "3.2.39",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"magic-string": "^0.25.7" "magic-string": "^0.25.7"
} }
}, },
"node_modules/@vue/runtime-core": { "node_modules/@vue/runtime-core": {
"version": "3.2.38", "version": "3.2.39",
"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.2.38.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.39.tgz",
"integrity": "sha512-kk0qiSiXUU/IKxZw31824rxmFzrLr3TL6ZcbrxWTKivadoKupdlzbQM4SlGo4MU6Zzrqv4fzyUasTU1jDoEnzg==", "integrity": "sha512-xKH5XP57JW5JW+8ZG1khBbuLakINTgPuINKL01hStWLTTGFOrM49UfCFXBcFvWmSbci3gmJyLl2EAzCaZWsx8g==",
"dependencies": { "dependencies": {
"@vue/reactivity": "3.2.38", "@vue/reactivity": "3.2.39",
"@vue/shared": "3.2.38" "@vue/shared": "3.2.39"
} }
}, },
"node_modules/@vue/runtime-dom": { "node_modules/@vue/runtime-dom": {
"version": "3.2.38", "version": "3.2.39",
"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.2.38.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.39.tgz",
"integrity": "sha512-4PKAb/ck2TjxdMSzMsnHViOrrwpudk4/A56uZjhzvusoEU9xqa5dygksbzYepdZeB5NqtRw5fRhWIiQlRVK45A==", "integrity": "sha512-4G9AEJP+sLhsqf5wXcyKVWQKUhI+iWfy0hWQgea+CpaTD7BR0KdQzvoQdZhwCY6B3oleSyNLkLAQwm0ya/wNoA==",
"dependencies": { "dependencies": {
"@vue/runtime-core": "3.2.38", "@vue/runtime-core": "3.2.39",
"@vue/shared": "3.2.38", "@vue/shared": "3.2.39",
"csstype": "^2.6.8" "csstype": "^2.6.8"
} }
}, },
"node_modules/@vue/server-renderer": { "node_modules/@vue/server-renderer": {
"version": "3.2.38", "version": "3.2.39",
"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.2.38.tgz", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.39.tgz",
"integrity": "sha512-pg+JanpbOZ5kEfOZzO2bt02YHd+ELhYP8zPeLU1H0e7lg079NtuuSB8fjLdn58c4Ou8UQ6C1/P+528nXnLPAhA==", "integrity": "sha512-1yn9u2YBQWIgytFMjz4f/t0j43awKytTGVptfd3FtBk76t1pd8mxbek0G/DrnjJhd2V7mSTb5qgnxMYt8Z5iSQ==",
"dependencies": { "dependencies": {
"@vue/compiler-ssr": "3.2.38", "@vue/compiler-ssr": "3.2.39",
"@vue/shared": "3.2.38" "@vue/shared": "3.2.39"
}, },
"peerDependencies": { "peerDependencies": {
"vue": "3.2.38" "vue": "3.2.39"
} }
}, },
"node_modules/@vue/shared": { "node_modules/@vue/shared": {
"version": "3.2.38", "version": "3.2.39",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.2.38.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.39.tgz",
"integrity": "sha512-dTyhTIRmGXBjxJE+skC8tTWCGLCVc4wQgRRLt8+O9p5ewBAjoBwtCAkLPrtToSr1xltoe3st21Pv953aOZ7alg==" "integrity": "sha512-D3dl2ZB9qE6mTuWPk9RlhDeP1dgNRUKC3NJxji74A4yL8M2MwlhLKUC/49WHjrNzSPug58fWx/yFbaTzGAQSBw=="
}, },
"node_modules/@vue/tsconfig": { "node_modules/@vue/tsconfig": {
"version": "0.1.3", "version": "0.1.3",
...@@ -1392,9 +1392,9 @@ ...@@ -1392,9 +1392,9 @@
} }
}, },
"node_modules/csstype": { "node_modules/csstype": {
"version": "2.6.20", "version": "2.6.21",
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-2.6.20.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz",
"integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==" "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w=="
}, },
"node_modules/data-uri-to-buffer": { "node_modules/data-uri-to-buffer": {
"version": "3.0.1", "version": "3.0.1",
...@@ -1545,9 +1545,9 @@ ...@@ -1545,9 +1545,9 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
}, },
"node_modules/element-plus": { "node_modules/element-plus": {
"version": "2.2.16", "version": "2.2.17",
"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.2.16.tgz", "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.2.17.tgz",
"integrity": "sha512-rvaTMFIujec9YDC5lyaiQv2XVUCHuhVDq2k+9vQxP78N8Wd07iEOGa9pvEVOO2uYc75l4rSl2RE/IWPH/6Mdzw==", "integrity": "sha512-MGwMIE/q+FFD3kgS23x8HIe5043tmD1cTRwjhIX9o6fim1avFnUkrsfYRvybbz4CkyqSb185EheZS5AUPpXh2g==",
"dependencies": { "dependencies": {
"@ctrl/tinycolor": "^3.4.1", "@ctrl/tinycolor": "^3.4.1",
"@element-plus/icons-vue": "^2.0.6", "@element-plus/icons-vue": "^2.0.6",
...@@ -3035,7 +3035,7 @@ ...@@ -3035,7 +3035,7 @@
}, },
"node_modules/magic-string": { "node_modules/magic-string": {
"version": "0.25.9", "version": "0.25.9",
"resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.25.9.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
"integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
"dependencies": { "dependencies": {
"sourcemap-codec": "^1.4.8" "sourcemap-codec": "^1.4.8"
...@@ -3486,13 +3486,16 @@ ...@@ -3486,13 +3486,16 @@
} }
}, },
"node_modules/pinia": { "node_modules/pinia": {
"version": "2.0.21", "version": "2.0.22",
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.0.21.tgz", "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.22.tgz",
"integrity": "sha512-6ol04PtL29O0Z6JHI47O3JUSoyOJ7Og0rstXrHVMZSP4zAldsQBXJCNF0i/H7m8vp/Hjd/CSmuPl7C5QAwpeWQ==", "integrity": "sha512-u+b8/BC+tmvo3ACbYO2w5NfxHWFOjvvw9DQnyT0dW8aUMCPRQT5QnfZ5R5W2MzZBMTeZRMQI7V/QFbafmM9QHw==",
"dependencies": { "dependencies": {
"@vue/devtools-api": "^6.2.1", "@vue/devtools-api": "^6.2.1",
"vue-demi": "*" "vue-demi": "*"
}, },
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": { "peerDependencies": {
"@vue/composition-api": "^1.4.0", "@vue/composition-api": "^1.4.0",
"typescript": ">=4.4.4", "typescript": ">=4.4.4",
...@@ -3825,9 +3828,9 @@ ...@@ -3825,9 +3828,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
}, },
"node_modules/sass": { "node_modules/sass": {
"version": "1.54.8", "version": "1.54.9",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.54.8.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.9.tgz",
"integrity": "sha512-ib4JhLRRgbg6QVy6bsv5uJxnJMTS2soVcCp9Y88Extyy13A8vV0G1fAwujOzmNkFQbR3LvedudAMbtuNRPbQww==", "integrity": "sha512-xb1hjASzEH+0L0WI9oFjqhRi51t/gagWnxLiwUNMltA0Ab6jIDkAacgKiGYKM9Jhy109osM7woEEai6SXeJo5Q==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"chokidar": ">=3.0.0 <4.0.0", "chokidar": ">=3.0.0 <4.0.0",
...@@ -4460,9 +4463,9 @@ ...@@ -4460,9 +4463,9 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "3.1.0", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.1.0.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-3.1.2.tgz",
"integrity": "sha512-YBg3dUicDpDWFCGttmvMbVyS9ydjntwEjwXRj2KBFwSB8SxmGcudo1yb8FW5+M/G86aS8x828ujnzUVdsLjs9g==", "integrity": "sha512-wTDKPkiVbeT+drTPdkuvjVIC/2vKKUc1w3qNOuwgpyvPCZF6fvdxB5v5WEcCsqaYea0zrwA4+XialJKCHM3oVQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"esbuild": "^0.15.6", "esbuild": "^0.15.6",
...@@ -4639,15 +4642,15 @@ ...@@ -4639,15 +4642,15 @@
"dev": true "dev": true
}, },
"node_modules/vue": { "node_modules/vue": {
"version": "3.2.38", "version": "3.2.39",
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.2.38.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.39.tgz",
"integrity": "sha512-hHrScEFSmDAWL0cwO4B6WO7D3sALZPbfuThDsGBebthrNlDxdJZpGR3WB87VbjpPh96mep1+KzukYEhpHDFa8Q==", "integrity": "sha512-tRkguhRTw9NmIPXhzk21YFBqXHT2t+6C6wPOgQ50fcFVWnPdetmRqbmySRHznrYjX2E47u0cGlKGcxKZJ38R/g==",
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.2.38", "@vue/compiler-dom": "3.2.39",
"@vue/compiler-sfc": "3.2.38", "@vue/compiler-sfc": "3.2.39",
"@vue/runtime-dom": "3.2.38", "@vue/runtime-dom": "3.2.39",
"@vue/server-renderer": "3.2.38", "@vue/server-renderer": "3.2.39",
"@vue/shared": "3.2.38" "@vue/shared": "3.2.39"
} }
}, },
"node_modules/vue-eslint-parser": { "node_modules/vue-eslint-parser": {
...@@ -5357,36 +5360,36 @@ ...@@ -5357,36 +5360,36 @@
} }
}, },
"@vue/compiler-core": { "@vue/compiler-core": {
"version": "3.2.38", "version": "3.2.39",
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.2.38.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.39.tgz",
"integrity": "sha512-/FsvnSu7Z+lkd/8KXMa4yYNUiqQrI22135gfsQYVGuh5tqEgOB0XqrUdb/KnCLa5+TmQLPwvyUnKMyCpu+SX3Q==", "integrity": "sha512-mf/36OWXqWn0wsC40nwRRGheR/qoID+lZXbIuLnr4/AngM0ov8Xvv8GHunC0rKRIkh60bTqydlqTeBo49rlbqw==",
"requires": { "requires": {
"@babel/parser": "^7.16.4", "@babel/parser": "^7.16.4",
"@vue/shared": "3.2.38", "@vue/shared": "3.2.39",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"source-map": "^0.6.1" "source-map": "^0.6.1"
} }
}, },
"@vue/compiler-dom": { "@vue/compiler-dom": {
"version": "3.2.38", "version": "3.2.39",
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.2.38.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.39.tgz",
"integrity": "sha512-zqX4FgUbw56kzHlgYuEEJR8mefFiiyR3u96498+zWPsLeh1WKvgIReoNE+U7gG8bCUdvsrJ0JRmev0Ky6n2O0g==", "integrity": "sha512-HMFI25Be1C8vLEEv1hgEO1dWwG9QQ8LTTPmCkblVJY/O3OvWx6r1+zsox5mKPMGvqYEZa6l8j+xgOfUspgo7hw==",
"requires": { "requires": {
"@vue/compiler-core": "3.2.38", "@vue/compiler-core": "3.2.39",
"@vue/shared": "3.2.38" "@vue/shared": "3.2.39"
} }
}, },
"@vue/compiler-sfc": { "@vue/compiler-sfc": {
"version": "3.2.38", "version": "3.2.39",
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.2.38.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.39.tgz",
"integrity": "sha512-KZjrW32KloMYtTcHAFuw3CqsyWc5X6seb8KbkANSWt3Cz9p2qA8c1GJpSkksFP9ABb6an0FLCFl46ZFXx3kKpg==", "integrity": "sha512-fqAQgFs1/BxTUZkd0Vakn3teKUt//J3c420BgnYgEOoVdTwYpBTSXCMJ88GOBCylmUBbtquGPli9tVs7LzsWIA==",
"requires": { "requires": {
"@babel/parser": "^7.16.4", "@babel/parser": "^7.16.4",
"@vue/compiler-core": "3.2.38", "@vue/compiler-core": "3.2.39",
"@vue/compiler-dom": "3.2.38", "@vue/compiler-dom": "3.2.39",
"@vue/compiler-ssr": "3.2.38", "@vue/compiler-ssr": "3.2.39",
"@vue/reactivity-transform": "3.2.38", "@vue/reactivity-transform": "3.2.39",
"@vue/shared": "3.2.38", "@vue/shared": "3.2.39",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"magic-string": "^0.25.7", "magic-string": "^0.25.7",
"postcss": "^8.1.10", "postcss": "^8.1.10",
...@@ -5394,12 +5397,12 @@ ...@@ -5394,12 +5397,12 @@
} }
}, },
"@vue/compiler-ssr": { "@vue/compiler-ssr": {
"version": "3.2.38", "version": "3.2.39",
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.2.38.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.39.tgz",
"integrity": "sha512-bm9jOeyv1H3UskNm4S6IfueKjUNFmi2kRweFIGnqaGkkRePjwEcfCVqyS3roe7HvF4ugsEkhf4+kIvDhip6XzQ==", "integrity": "sha512-EoGCJ6lincKOZGW+0Ky4WOKsSmqL7hp1ZYgen8M7u/mlvvEQUaO9tKKOy7K43M9U2aA3tPv0TuYYQFrEbK2eFQ==",
"requires": { "requires": {
"@vue/compiler-dom": "3.2.38", "@vue/compiler-dom": "3.2.39",
"@vue/shared": "3.2.38" "@vue/shared": "3.2.39"
} }
}, },
"@vue/devtools-api": { "@vue/devtools-api": {
...@@ -5419,57 +5422,57 @@ ...@@ -5419,57 +5422,57 @@
} }
}, },
"@vue/reactivity": { "@vue/reactivity": {
"version": "3.2.38", "version": "3.2.39",
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.2.38.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.39.tgz",
"integrity": "sha512-6L4myYcH9HG2M25co7/BSo0skKFHpAN8PhkNPM4xRVkyGl1K5M3Jx4rp5bsYhvYze2K4+l+pioN4e6ZwFLUVtw==", "integrity": "sha512-vlaYX2a3qMhIZfrw3Mtfd+BuU+TZmvDrPMa+6lpfzS9k/LnGxkSuf0fhkP0rMGfiOHPtyKoU9OJJJFGm92beVQ==",
"requires": { "requires": {
"@vue/shared": "3.2.38" "@vue/shared": "3.2.39"
} }
}, },
"@vue/reactivity-transform": { "@vue/reactivity-transform": {
"version": "3.2.38", "version": "3.2.39",
"resolved": "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.2.38.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.39.tgz",
"integrity": "sha512-3SD3Jmi1yXrDwiNJqQ6fs1x61WsDLqVk4NyKVz78mkaIRh6d3IqtRnptgRfXn+Fzf+m6B1KxBYWq1APj6h4qeA==", "integrity": "sha512-HGuWu864zStiWs9wBC6JYOP1E00UjMdDWIG5W+FpUx28hV3uz9ODOKVNm/vdOy/Pvzg8+OcANxAVC85WFBbl3A==",
"requires": { "requires": {
"@babel/parser": "^7.16.4", "@babel/parser": "^7.16.4",
"@vue/compiler-core": "3.2.38", "@vue/compiler-core": "3.2.39",
"@vue/shared": "3.2.38", "@vue/shared": "3.2.39",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"magic-string": "^0.25.7" "magic-string": "^0.25.7"
} }
}, },
"@vue/runtime-core": { "@vue/runtime-core": {
"version": "3.2.38", "version": "3.2.39",
"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.2.38.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.39.tgz",
"integrity": "sha512-kk0qiSiXUU/IKxZw31824rxmFzrLr3TL6ZcbrxWTKivadoKupdlzbQM4SlGo4MU6Zzrqv4fzyUasTU1jDoEnzg==", "integrity": "sha512-xKH5XP57JW5JW+8ZG1khBbuLakINTgPuINKL01hStWLTTGFOrM49UfCFXBcFvWmSbci3gmJyLl2EAzCaZWsx8g==",
"requires": { "requires": {
"@vue/reactivity": "3.2.38", "@vue/reactivity": "3.2.39",
"@vue/shared": "3.2.38" "@vue/shared": "3.2.39"
} }
}, },
"@vue/runtime-dom": { "@vue/runtime-dom": {
"version": "3.2.38", "version": "3.2.39",
"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.2.38.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.39.tgz",
"integrity": "sha512-4PKAb/ck2TjxdMSzMsnHViOrrwpudk4/A56uZjhzvusoEU9xqa5dygksbzYepdZeB5NqtRw5fRhWIiQlRVK45A==", "integrity": "sha512-4G9AEJP+sLhsqf5wXcyKVWQKUhI+iWfy0hWQgea+CpaTD7BR0KdQzvoQdZhwCY6B3oleSyNLkLAQwm0ya/wNoA==",
"requires": { "requires": {
"@vue/runtime-core": "3.2.38", "@vue/runtime-core": "3.2.39",
"@vue/shared": "3.2.38", "@vue/shared": "3.2.39",
"csstype": "^2.6.8" "csstype": "^2.6.8"
} }
}, },
"@vue/server-renderer": { "@vue/server-renderer": {
"version": "3.2.38", "version": "3.2.39",
"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.2.38.tgz", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.39.tgz",
"integrity": "sha512-pg+JanpbOZ5kEfOZzO2bt02YHd+ELhYP8zPeLU1H0e7lg079NtuuSB8fjLdn58c4Ou8UQ6C1/P+528nXnLPAhA==", "integrity": "sha512-1yn9u2YBQWIgytFMjz4f/t0j43awKytTGVptfd3FtBk76t1pd8mxbek0G/DrnjJhd2V7mSTb5qgnxMYt8Z5iSQ==",
"requires": { "requires": {
"@vue/compiler-ssr": "3.2.38", "@vue/compiler-ssr": "3.2.39",
"@vue/shared": "3.2.38" "@vue/shared": "3.2.39"
} }
}, },
"@vue/shared": { "@vue/shared": {
"version": "3.2.38", "version": "3.2.39",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.2.38.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.39.tgz",
"integrity": "sha512-dTyhTIRmGXBjxJE+skC8tTWCGLCVc4wQgRRLt8+O9p5ewBAjoBwtCAkLPrtToSr1xltoe3st21Pv953aOZ7alg==" "integrity": "sha512-D3dl2ZB9qE6mTuWPk9RlhDeP1dgNRUKC3NJxji74A4yL8M2MwlhLKUC/49WHjrNzSPug58fWx/yFbaTzGAQSBw=="
}, },
"@vue/tsconfig": { "@vue/tsconfig": {
"version": "0.1.3", "version": "0.1.3",
...@@ -5898,9 +5901,9 @@ ...@@ -5898,9 +5901,9 @@
"dev": true "dev": true
}, },
"csstype": { "csstype": {
"version": "2.6.20", "version": "2.6.21",
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-2.6.20.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz",
"integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==" "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w=="
}, },
"data-uri-to-buffer": { "data-uri-to-buffer": {
"version": "3.0.1", "version": "3.0.1",
...@@ -6011,9 +6014,9 @@ ...@@ -6011,9 +6014,9 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
}, },
"element-plus": { "element-plus": {
"version": "2.2.16", "version": "2.2.17",
"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.2.16.tgz", "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.2.17.tgz",
"integrity": "sha512-rvaTMFIujec9YDC5lyaiQv2XVUCHuhVDq2k+9vQxP78N8Wd07iEOGa9pvEVOO2uYc75l4rSl2RE/IWPH/6Mdzw==", "integrity": "sha512-MGwMIE/q+FFD3kgS23x8HIe5043tmD1cTRwjhIX9o6fim1avFnUkrsfYRvybbz4CkyqSb185EheZS5AUPpXh2g==",
"requires": { "requires": {
"@ctrl/tinycolor": "^3.4.1", "@ctrl/tinycolor": "^3.4.1",
"@element-plus/icons-vue": "^2.0.6", "@element-plus/icons-vue": "^2.0.6",
...@@ -7106,7 +7109,7 @@ ...@@ -7106,7 +7109,7 @@
}, },
"magic-string": { "magic-string": {
"version": "0.25.9", "version": "0.25.9",
"resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.25.9.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
"integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
"requires": { "requires": {
"sourcemap-codec": "^1.4.8" "sourcemap-codec": "^1.4.8"
...@@ -7456,9 +7459,9 @@ ...@@ -7456,9 +7459,9 @@
"dev": true "dev": true
}, },
"pinia": { "pinia": {
"version": "2.0.21", "version": "2.0.22",
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.0.21.tgz", "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.22.tgz",
"integrity": "sha512-6ol04PtL29O0Z6JHI47O3JUSoyOJ7Og0rstXrHVMZSP4zAldsQBXJCNF0i/H7m8vp/Hjd/CSmuPl7C5QAwpeWQ==", "integrity": "sha512-u+b8/BC+tmvo3ACbYO2w5NfxHWFOjvvw9DQnyT0dW8aUMCPRQT5QnfZ5R5W2MzZBMTeZRMQI7V/QFbafmM9QHw==",
"requires": { "requires": {
"@vue/devtools-api": "^6.2.1", "@vue/devtools-api": "^6.2.1",
"vue-demi": "*" "vue-demi": "*"
...@@ -7718,9 +7721,9 @@ ...@@ -7718,9 +7721,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
}, },
"sass": { "sass": {
"version": "1.54.8", "version": "1.54.9",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.54.8.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.9.tgz",
"integrity": "sha512-ib4JhLRRgbg6QVy6bsv5uJxnJMTS2soVcCp9Y88Extyy13A8vV0G1fAwujOzmNkFQbR3LvedudAMbtuNRPbQww==", "integrity": "sha512-xb1hjASzEH+0L0WI9oFjqhRi51t/gagWnxLiwUNMltA0Ab6jIDkAacgKiGYKM9Jhy109osM7woEEai6SXeJo5Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"chokidar": ">=3.0.0 <4.0.0", "chokidar": ">=3.0.0 <4.0.0",
...@@ -8209,9 +8212,9 @@ ...@@ -8209,9 +8212,9 @@
} }
}, },
"vite": { "vite": {
"version": "3.1.0", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.1.0.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-3.1.2.tgz",
"integrity": "sha512-YBg3dUicDpDWFCGttmvMbVyS9ydjntwEjwXRj2KBFwSB8SxmGcudo1yb8FW5+M/G86aS8x828ujnzUVdsLjs9g==", "integrity": "sha512-wTDKPkiVbeT+drTPdkuvjVIC/2vKKUc1w3qNOuwgpyvPCZF6fvdxB5v5WEcCsqaYea0zrwA4+XialJKCHM3oVQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"esbuild": "^0.15.6", "esbuild": "^0.15.6",
...@@ -8320,15 +8323,15 @@ ...@@ -8320,15 +8323,15 @@
"dev": true "dev": true
}, },
"vue": { "vue": {
"version": "3.2.38", "version": "3.2.39",
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.2.38.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.39.tgz",
"integrity": "sha512-hHrScEFSmDAWL0cwO4B6WO7D3sALZPbfuThDsGBebthrNlDxdJZpGR3WB87VbjpPh96mep1+KzukYEhpHDFa8Q==", "integrity": "sha512-tRkguhRTw9NmIPXhzk21YFBqXHT2t+6C6wPOgQ50fcFVWnPdetmRqbmySRHznrYjX2E47u0cGlKGcxKZJ38R/g==",
"requires": { "requires": {
"@vue/compiler-dom": "3.2.38", "@vue/compiler-dom": "3.2.39",
"@vue/compiler-sfc": "3.2.38", "@vue/compiler-sfc": "3.2.39",
"@vue/runtime-dom": "3.2.38", "@vue/runtime-dom": "3.2.39",
"@vue/server-renderer": "3.2.38", "@vue/server-renderer": "3.2.39",
"@vue/shared": "3.2.38" "@vue/shared": "3.2.39"
} }
}, },
"vue-eslint-parser": { "vue-eslint-parser": {
......
...@@ -23,14 +23,14 @@ ...@@ -23,14 +23,14 @@
"blueimp-md5": "^2.19.0", "blueimp-md5": "^2.19.0",
"countup.js": "^2.3.2", "countup.js": "^2.3.2",
"dayjs": "^1.11.5", "dayjs": "^1.11.5",
"element-plus": "^2.2.16", "element-plus": "^2.2.17",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"pinia": "^2.0.21", "pinia": "^2.0.22",
"qs": "^6.11.0", "qs": "^6.11.0",
"ua-parser-js": "^1.0.2", "ua-parser-js": "^1.0.2",
"video.js": "^7.20.2", "video.js": "^7.20.2",
"vue": "^3.2.38", "vue": "^3.2.39",
"vue-router": "^4.1.5" "vue-router": "^4.1.5"
}, },
"devDependencies": { "devDependencies": {
...@@ -48,10 +48,10 @@ ...@@ -48,10 +48,10 @@
"chalk": "^5.0.1", "chalk": "^5.0.1",
"eslint": "^8.5.0", "eslint": "^8.5.0",
"eslint-plugin-vue": "^9.4.0", "eslint-plugin-vue": "^9.4.0",
"sass": "^1.54.8", "sass": "^1.54.9",
"typescript": "~4.7.4", "typescript": "~4.7.4",
"unplugin-auto-import": "^0.11.2", "unplugin-auto-import": "^0.11.2",
"vite": "^3.1.0", "vite": "^3.1.2",
"vite-plugin-checker": "^0.5.1", "vite-plugin-checker": "^0.5.1",
"vue-tsc": "^0.40.5" "vue-tsc": "^0.40.5"
} }
......
...@@ -4,10 +4,10 @@ const json2Array = function (data: Record<string, string>) { ...@@ -4,10 +4,10 @@ const json2Array = function (data: Record<string, string>) {
return Object.keys(data).map(code => ({ code, label: data[code], value: data[code] })) return Object.keys(data).map(code => ({ code, label: data[code], value: data[code] }))
} }
export function useArea() { export function useArea(province: string, city: string, county: string) {
const provinceValue = ref('') const provinceValue = ref(province || '')
const cityValue = ref('') const cityValue = ref(city || '')
const countyValue = ref('') const countyValue = ref(county || '')
watch(provinceValue, () => { watch(provinceValue, () => {
cityValue.value = '' cityValue.value = ''
......
import httpRequest from '@/utils/axios' import httpRequest from '@/utils/axios'
import type { ContestantCreateParams, ContestantUpdateParams } from './types' import type { ContestantCreateParams, ContestantUpdateParams } from './types'
// 获取参赛选手列表 // 获取参赛选手列表
export function getContestantList(params?: { name?: string; page?: number; 'per-page'?: number }) { export function getContestantList(params?: { student_name?: string; page?: number; 'per-page'?: number }) {
return httpRequest.get('/api/resource/v1/backend/experiment/list', { params }) return httpRequest.get('/api/resource/v1/backend/competition-competitor/list', { params })
} }
// 创建参赛选手 // 创建参赛选手
......
export interface Contestant { export interface Contestant {
id: string id: string
competition_id: string
student_id: string
mode: string
picture: string
login_id: string
init_pass: string
grade: string
teacher_name: string
train_count: string
commit_time: string
commit_status: string
score: string
score_status: string
created_time: string
updated_time: string
student: Student
competition: {
id: string
name: string
}
}
export interface Student {
id: string
sso_id: string
id_type: string
id_number: string
organ_id: string
sno_number: string
name: string name: string
company: string mobile: string
phone: string
gender: string gender: string
birthday: string
province: string province: string
city: string city: string
county: string county: string
specialty_id: string
status: string
created_operator: string
updated_operator: string
created_time: string
updated_time: string
delete_time: string
info: {
id: string
province_id: string
city_id: string
county_id: string
province_name: string
city_name: string
county_name: string
status: string
birthday: string
}
class: {
id: string
name: string
student_id: string
}
org: Org
specialty: {
name: string
id: string
}
}
export interface Org {
department_name: string
project_id: string
project_name: string
} }
export type ContestantCreateParams = Omit<Contestant, 'id'> export type ContestantCreateParams = Contestant
export type ContestantUpdateParams = Contestant export type ContestantUpdateParams = Contestant
<script setup lang="ts"> <script setup lang="ts">
import type { Contestant } from '../types' import type { Contestant } from '../types'
import { Upload } from '@element-plus/icons-vue' import { CirclePlus, Upload } from '@element-plus/icons-vue'
import AppList from '@/components/base/AppList.vue' import AppList from '@/components/base/AppList.vue'
import { getContestantList } from '../api' import { getContestantList } from '../api'
import { useMapStore } from '@/stores/map'
const FormDialog = defineAsyncComponent(() => import('../components/FormDialog.vue')) const FormDialog = defineAsyncComponent(() => import('../components/FormDialog.vue'))
const ImportDialog = defineAsyncComponent(() => import('../components/ImportDialog.vue')) const ImportDialog = defineAsyncComponent(() => import('../components/ImportDialog.vue'))
const appList = $ref<InstanceType<typeof AppList> | null>(null) const appList = $ref<InstanceType<typeof AppList> | null>(null)
// 性别
const genderList = useMapStore().getMapValuesByKey('system_gender')
// 列表配置 // 列表配置
const listOptions = { const listOptions = {
remote: { remote: {
httpRequest: getContestantList, httpRequest: getContestantList,
params: { contest_id: '', name: '' } params: { competition_id: '', student_name: '' }
}, },
filters: [ filters: [
{ {
type: 'select', type: 'select',
prop: 'course_id', prop: 'competition_id',
label: '赛项', label: '赛项',
placeholder: '请选择赛项' placeholder: '请选择赛项'
}, },
...@@ -26,15 +28,22 @@ const listOptions = { ...@@ -26,15 +28,22 @@ const listOptions = {
], ],
columns: [ columns: [
{ label: '序号', type: 'index', width: 60 }, { label: '序号', type: 'index', width: 60 },
{ label: '赛项名称', prop: 'name' }, { label: '赛项名称', prop: 'competition.name' },
{ label: '选手姓名', prop: 'length' }, { label: '选手姓名', prop: 'student.name' },
{ label: '参赛ID', prop: 'teacher_names' }, { label: '参赛ID', prop: 'login_id' },
{ label: '性别', prop: 'type_name' }, {
{ label: '学校', prop: 'score' }, label: '性别',
{ label: '所在专业', prop: 'score' }, prop: 'student.gender',
{ label: '所在年级', prop: 'score' }, computed({ row }: { row: Contestant }) {
{ label: '所在班级', prop: 'score' }, const found = genderList.find(item => item.value === row.student.gender)
{ label: '训练次数', prop: 'score' }, return found?.label || row.student.gender
}
},
{ label: '学校', prop: 'student.org.department_name' },
{ label: '所在专业', prop: 'student.specialty.name' },
{ label: '所在年级', prop: 'grade' },
{ label: '所在班级', prop: 'student.class.name' },
{ label: '训练次数', prop: 'train_count' },
{ label: '更新时间', prop: 'updated_time' }, { label: '更新时间', prop: 'updated_time' },
{ label: '操作', slots: 'table-x', width: 200 } { label: '操作', slots: 'table-x', width: 200 }
] ]
...@@ -44,6 +53,12 @@ const importVisible = $ref(false) ...@@ -44,6 +53,12 @@ const importVisible = $ref(false)
let dialogVisible = $ref(false) let dialogVisible = $ref(false)
const rowData = ref<Contestant | undefined | null>(null) const rowData = ref<Contestant | undefined | null>(null)
// 新增
function handleAdd() {
rowData.value = null
dialogVisible = true
}
// 编辑 // 编辑
function handleUpdate(row: Contestant) { function handleUpdate(row: Contestant) {
rowData.value = row rowData.value = row
...@@ -59,6 +74,7 @@ function onUpdateSuccess() { ...@@ -59,6 +74,7 @@ function onUpdateSuccess() {
<AppCard title="参赛选手管理"> <AppCard title="参赛选手管理">
<AppList v-bind="listOptions" ref="appList"> <AppList v-bind="listOptions" ref="appList">
<template #header-buttons> <template #header-buttons>
<el-button type="primary" round :icon="CirclePlus" @click="handleAdd">新增参赛选手</el-button>
<el-button type="primary" round :icon="Upload" @click="importVisible = true">批量导入</el-button> <el-button type="primary" round :icon="Upload" @click="importVisible = true">批量导入</el-button>
</template> </template>
......
import httpRequest from '@/utils/axios' import httpRequest from '@/utils/axios'
import type { ContestCreateParams, ContestUpdateParams } from './types' import type {
ContestCreateParams,
ContestUpdateParams,
ContestBookUpdateParams,
ContestVideoCreateParams,
ContestVideoUpdateParams
} from './types'
// 获取赛项列表 // 获取赛项列表
export function getContestItemList(params?: { page?: number; 'per-page'?: number }) { export function getContestItemList(params?: { page?: number; 'per-page'?: number }) {
...@@ -21,58 +27,54 @@ export function updateContest(data: ContestUpdateParams) { ...@@ -21,58 +27,54 @@ export function updateContest(data: ContestUpdateParams) {
return httpRequest.post('/api/resource/v1/backend/competition/update', data) return httpRequest.post('/api/resource/v1/backend/competition/update', data)
} }
// 获取指导老师列表
export function getTeacherList(params?: { name?: string }) {
return httpRequest.get('/api/resource/v1/backend/teacher/faculty-advisers', { params })
}
// 更新评分规则 // 更新评分规则
export function updateContestRules(data: ContestUpdateParams) { export function updateContestRules(data: ContestUpdateParams) {
return httpRequest.post('/api/resource/v1/backend/competition-rule/save', data) return httpRequest.post('/api/resource/v1/backend/competition-rule/save', data)
} }
// 获取赛项课程列表
export function getContestCourseList(params: { organ_id: string }) { // 获取赛项指导书详情
return httpRequest.get('/api/resource/v1/backend/competition/courses', { params }) export function getContestBook(params: { competition_id: string }) {
} return httpRequest.get('/api/resource/v1/backend/competition-book/detail', { params })
// 获取赛项指导老师列表
export function getContestTeacherList(params: { organ_id: string }) {
return httpRequest.get('/api/resource/v1/backend/competition/teachers', { params })
} }
// 获取赛项关联班级列表 // 添加赛项指导书
export function getContestClassList(params: { experiment_id: string; page?: number; 'per-page'?: number }) { export function createContestBook(data: ContestBookUpdateParams) {
return httpRequest.get('/api/resource/v1/backend/competition/class-add', { params }) return httpRequest.post('/api/resource/v1/backend/competition-book/create', data)
} }
// 赛项关联班级 // 更新赛项指导书
export function experimentAddClass(data: { experiment_id: string; classes_id: string; type: 'add' | 'delete' }) { export function updateContestBook(data: ContestBookUpdateParams) {
return httpRequest.post('/api/resource/v1/backend/competition/class-add', data) return httpRequest.post('/api/resource/v1/backend/competition-book/update', data)
} }
// 删除赛项指导书
// 获取班级学生列表 export function deleteContestBook(data: { competition_id: string }) {
export function getClassStudentList(params: { class_id: string; page?: number; 'per-page'?: number }) { return httpRequest.post('/api/resource/v1/backend/competition-book/delete', data)
return httpRequest.get('/api/resource/v1/backend/competition/class-students', { params })
} }
// 获取班级小组列表 // 获取赛项操作视频列表
export function getContestClassGroupsList(params: { experiment_id: string; class_id: string }) { export function getContestVideoList(params: { competition_id: string }) {
return httpRequest.get('/api/resource/v1/backend/competition/class-teams', { params }) return httpRequest.get('/api/resource/v1/backend/competition-video/list', { params })
} }
// 获取赛项操作视频详情
// 获取赛项小组 export function getContestVideo(params: { competition_id: string }) {
export function getContestGroup(params: { team_id: string; page?: number; 'per-page'?: number }) { return httpRequest.get('/api/resource/v1/backend/competition-video/detail', { params })
return httpRequest.get('/api/resource/v1/backend/competition/team-view', { params })
} }
// 获取赛项操作视频详情
// 新增赛项小组 export function getContestVideoPalyInfo(params: { source_id: string }) {
export function experimentAddClassGroup(data: { experiment_id: string; class_id: string; name: string }) { return httpRequest.get('/api/resource/v1/backend/competition-video/replay-list', { params })
return httpRequest.post('/api/resource/v1/backend/competition/team-add', data)
} }
// 添加赛项操作视频
// 赛项小组添加学生 export function createContestVideo(data: ContestVideoCreateParams) {
export function experimentGroupAddStudent(data: { team_id: string; students_id: string; type: 'add' | 'delete' }) { return httpRequest.post('/api/resource/v1/backend/competition-video/create', data)
return httpRequest.post('/api/resource/v1/backend/competition/team-add-student', data)
} }
// 更新赛项操作视频
// 获取赛项关联班级列表 export function updateContestVideo(data: ContestVideoUpdateParams) {
export function getContestGroupStudentList(params: { team_id: string; page?: number; 'per-page'?: number }) { return httpRequest.post('/api/resource/v1/backend/competition-video/update', data)
return httpRequest.get('/api/resource/v1/backend/competition/team-add-student', { params })
} }
// 删除赛项操作视频
// 获取指导老师列表 export function deleteContestVideo(data: { id: string }) {
export function getTeacherList(params?: { name?: string }) { return httpRequest.post('/api/resource/v1/backend/competition-video/delete', data)
return httpRequest.get('/api/resource/v1/backend/teacher/faculty-advisers', { params })
} }
...@@ -4,6 +4,9 @@ import type { ContestItem, ContestCreateParams, ContestUpdateParams } from '../t ...@@ -4,6 +4,9 @@ import type { ContestItem, ContestCreateParams, ContestUpdateParams } from '../t
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { pick } from 'lodash-es' import { pick } from 'lodash-es'
import dayjs from 'dayjs'
import isBetween from 'dayjs/plugin/isBetween'
import { createContest, updateContest } from '../api' import { createContest, updateContest } from '../api'
import { useMapStore } from '@/stores/map' import { useMapStore } from '@/stores/map'
import { useGetTeacherList } from '../composables/useGetTeacherList' import { useGetTeacherList } from '../composables/useGetTeacherList'
...@@ -19,6 +22,8 @@ const emit = defineEmits<{ ...@@ -19,6 +22,8 @@ const emit = defineEmits<{
(e: 'update:modelValue', visible: boolean): void (e: 'update:modelValue', visible: boolean): void
}>() }>()
dayjs.extend(isBetween)
// 赛项类型 // 赛项类型
const types = useMapStore().getMapValuesByKey('competition_type') const types = useMapStore().getMapValuesByKey('competition_type')
// 主办单位 // 主办单位
...@@ -48,6 +53,7 @@ const form = reactive({ ...@@ -48,6 +53,7 @@ const form = reactive({
train_platform_uri: '', train_platform_uri: '',
competition_uri: '', competition_uri: '',
dateRange: undefined, dateRange: undefined,
date: undefined,
datetimeRange: undefined datetimeRange: undefined
}) })
watchEffect(() => { watchEffect(() => {
...@@ -56,20 +62,35 @@ watchEffect(() => { ...@@ -56,20 +62,35 @@ watchEffect(() => {
const organizer_ids = props.data.organizers.map(item => item.id) const organizer_ids = props.data.organizers.map(item => item.id)
const technical_support_unit_id = props.data.technical_support_unit.id const technical_support_unit_id = props.data.technical_support_unit.id
const teacher_ids = props.data.teachers.map(item => item.id) const teacher_ids = props.data.teachers.map(item => item.id)
const dateRange = [new Date(parseInt(props.data.start_range) * 1000), new Date(parseInt(props.data.end_range) * 1000)] const dateRange = [new Date(props.data.start_range * 1000), new Date(props.data.end_range * 1000)]
const datetimeRange = [new Date(parseInt(props.data.start_at) * 1000), new Date(parseInt(props.data.end_at) * 1000)] const date = new Date(props.data.start_at * 1000)
const apply_expiration_date = parseInt(props.data.apply_expiration_date) * 1000 const datetimeRange = [new Date(props.data.start_at * 1000), new Date(props.data.end_at * 1000)]
const apply_expiration_date = props.data.apply_expiration_date * 1000
Object.assign(form, props.data, { Object.assign(form, props.data, {
host_unit_id, host_unit_id,
organizer_ids, organizer_ids,
technical_support_unit_id, technical_support_unit_id,
teacher_ids, teacher_ids,
dateRange, dateRange,
date,
datetimeRange, datetimeRange,
apply_expiration_date apply_expiration_date
}) })
}) })
const checkApplyExpirationDate = (rule: any, value: any, callback: any) => {
if (!value) {
callback(new Error('请选择报名截止日期'))
} else {
const [firstDate, secondDate] = form.dateRange || []
if (
!dayjs(value).isBetween(firstDate, secondDate, 'date', '[]') ||
!dayjs(value).isBefore(dayjs(form.date), 'date')
) {
callback(new Error('请选择赛项周期内的日期,且必须早于正式比赛日期'))
}
callback()
}
}
const rules = ref<FormRules>({ const rules = ref<FormRules>({
name: [{ required: true, message: '请输入赛项名称' }], name: [{ required: true, message: '请输入赛项名称' }],
host_unit_id: [{ required: true, message: '请选择主办单位' }], host_unit_id: [{ required: true, message: '请选择主办单位' }],
...@@ -78,8 +99,15 @@ const rules = ref<FormRules>({ ...@@ -78,8 +99,15 @@ const rules = ref<FormRules>({
type: [{ required: true, message: '请选择赛项类型' }], type: [{ required: true, message: '请选择赛项类型' }],
teacher_ids: [{ type: 'array', required: true, message: '请选择指导教师', trigger: 'change' }], teacher_ids: [{ type: 'array', required: true, message: '请选择指导教师', trigger: 'change' }],
dateRange: [{ type: 'array', required: true, message: '请选择赛项周期', trigger: 'change' }], dateRange: [{ type: 'array', required: true, message: '请选择赛项周期', trigger: 'change' }],
date: [{ required: true, message: '请选择正式比赛日期', trigger: 'change' }],
datetimeRange: [{ type: 'array', required: true, message: '请选择正式比赛时间', trigger: 'change' }], datetimeRange: [{ type: 'array', required: true, message: '请选择正式比赛时间', trigger: 'change' }],
apply_expiration_date: [{ required: true, message: '请选择报名截止日期' }], apply_expiration_date: [
{ required: true, message: '请选择报名截止日期' },
{
validator: checkApplyExpirationDate,
message: '请选择赛项周期内的日期,且必须早于正式比赛日期'
}
],
train_platform_uri: [{ required: true, message: '请输入训练平台地址' }], train_platform_uri: [{ required: true, message: '请输入训练平台地址' }],
competition_uri: [{ required: true, message: '请输入正式比赛地址' }], competition_uri: [{ required: true, message: '请输入正式比赛地址' }],
status: [{ required: true, message: '请选择有效状态' }], status: [{ required: true, message: '请选择有效状态' }],
...@@ -98,14 +126,17 @@ function handleSubmit() { ...@@ -98,14 +126,17 @@ function handleSubmit() {
formRef?.validate().then(() => { formRef?.validate().then(() => {
const [firstDate, secondDate] = form.dateRange || [] const [firstDate, secondDate] = form.dateRange || []
const [firstDatetime, secondDatetime] = form.datetimeRange || [] const [firstDatetime, secondDatetime] = form.datetimeRange || []
const year = dayjs(form.date).year()
const month = dayjs(form.date).month()
const date = dayjs(form.date).date()
const mergedForm = { const mergedForm = {
...form, ...form,
organizer_ids: JSON.stringify(form.organizer_ids), organizer_ids: JSON.stringify(form.organizer_ids),
// teacher_ids: form.teacher_ids.join(','), // teacher_ids: form.teacher_ids.join(','),
start_range: new Date(firstDate).getTime() / 1000, start_range: new Date(firstDate).getTime() / 1000,
end_range: new Date(secondDate).getTime() / 1000, end_range: new Date(secondDate).getTime() / 1000,
start_at: new Date(firstDatetime).getTime() / 1000, start_at: dayjs(firstDatetime).year(year).month(month).date(date).unix(),
end_at: new Date(secondDatetime).getTime() / 1000, end_at: dayjs(secondDatetime).year(year).month(month).date(date).unix(),
apply_expiration_date: new Date(form.apply_expiration_date).getTime() / 1000 apply_expiration_date: new Date(form.apply_expiration_date).getTime() / 1000
} }
const params: ContestUpdateParams = pick(mergedForm, [ const params: ContestUpdateParams = pick(mergedForm, [
...@@ -146,6 +177,14 @@ function handleUpdate(params: ContestUpdateParams) { ...@@ -146,6 +177,14 @@ function handleUpdate(params: ContestUpdateParams) {
emit('update:modelValue', false) emit('update:modelValue', false)
}) })
} }
// 赛项周期改变
function handleDateRangeChange(value: any) {
if (value) {
const [, secondDate] = form.dateRange || []
form.date = secondDate
}
}
</script> </script>
<template> <template>
...@@ -191,10 +230,26 @@ function handleUpdate(params: ContestUpdateParams) { ...@@ -191,10 +230,26 @@ function handleUpdate(params: ContestUpdateParams) {
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="赛项周期" prop="dateRange"> <el-form-item label="赛项周期" prop="dateRange">
<el-date-picker type="daterange" v-model="form.dateRange" style="width: 100%" /> <el-date-picker
type="daterange"
range-separator="至"
v-model="form.dateRange"
style="width: 100%"
@change="handleDateRangeChange"
/>
</el-form-item>
<el-form-item label="正式比赛日期" prop="date">
<el-date-picker v-model="form.date" style="width: 100%" />
</el-form-item> </el-form-item>
<el-form-item label="正式比赛时间" prop="datetimeRange"> <el-form-item label="正式比赛时间" prop="datetimeRange">
<el-date-picker type="datetimerange" v-model="form.datetimeRange" style="width: 100%" /> <el-time-picker
is-range
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
v-model="form.datetimeRange"
style="width: 100%"
/>
</el-form-item> </el-form-item>
<el-form-item label="报名截止日期" prop="apply_expiration_date"> <el-form-item label="报名截止日期" prop="apply_expiration_date">
<el-date-picker v-model="form.apply_expiration_date" style="width: 100%" /> <el-date-picker v-model="form.apply_expiration_date" style="width: 100%" />
......
<script setup lang="ts"> <script setup lang="ts">
import type { ContestBookItem } from '../types'
import { CirclePlus } from '@element-plus/icons-vue' import { CirclePlus } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import AppList from '@/components/base/AppList.vue' import AppList from '@/components/base/AppList.vue'
import { getContestBook, deleteContestBook } from '../api'
const FormDialog = defineAsyncComponent(() => import('./ViewBookFormDialog.vue'))
interface Props {
id: string
}
const props = defineProps<Props>()
const appList = $ref<InstanceType<typeof AppList> | null>(null) const appList = $ref<InstanceType<typeof AppList> | null>(null)
// 列表配置 // 列表配置
const listOptions = { const listOptions = {
hasPagination: false,
remote: {
httpRequest: getContestBook,
params: { competition_id: props.id },
callback(res: any) {
return res.detail?.id ? { list: [res.detail] } : { list: [] }
}
},
columns: [ columns: [
{ label: '序号', type: 'index', width: 60 }, { label: '序号', type: 'index', width: 60 },
{ label: '训练指导书名称', prop: 'specialty_id_name' }, { label: '训练指导书名称', prop: 'name' },
{ label: '文件类型', prop: 'name' }, { label: '文件类型', prop: 'type' },
{ label: '创建人', prop: 'student_nums' }, { label: '创建人', prop: 'create_user.real_name' },
{ label: '创建时间', prop: 'updated_time' }, { label: '创建时间', prop: 'updated_time' },
{ label: '更新时间', prop: 'updated_time' }, { label: '更新时间', prop: 'updated_time' },
{ label: '操作', slots: 'table-x', width: 200 } { label: '操作', slots: 'table-x', width: 180 }
] ]
} }
// 刷新
function handleRefetch() {
appList?.refetch()
}
let rowData = $ref<ClassItem | null>(null)
let dialogVisible = $ref(false)
const rowData = ref<ContestBookItem>()
// 新增
function handleAdd() {
rowData.value = undefined
dialogVisible = true
}
// 编辑
function handleUpdate(row: ContestBookItem) {
rowData.value = row
dialogVisible = true
}
// 删除 // 删除
function handleRemoveClass(row: ClassItem) { function handleRemoveClass(row: ContestBookItem) {
ElMessageBox.confirm('确定要删除吗?', '提示').then(() => { ElMessageBox.confirm('确定要删除吗?', '提示').then(() => {
experimentAddClass({ experiment_id: props.id, classes_id: row.id, type: 'delete' }).then(() => { deleteContestBook({ competition_id: row.competition_id }).then(() => {
ElMessage({ message: '删除成功', type: 'success' }) ElMessage({ message: '删除成功', type: 'success' })
appList?.refetch() onUpdateSuccess()
}) })
}) })
} }
function onUpdateSuccess() {
appList?.refetch()
}
</script> </script>
<template> <template>
<AppList v-bind="listOptions" ref="appList"> <AppList v-bind="listOptions" ref="appList">
<template #header-buttons> <template #header-buttons>
<el-button type="primary" :icon="CirclePlus">新增</el-button> <el-button type="primary" :icon="CirclePlus" @click="handleAdd">新增</el-button>
</template> </template>
<template #table-x="{ row }"> <template #table-x="{ row }">
<el-button type="primary" round>查阅</el-button> <el-button link round type="success">查阅</el-button>
<el-button type="primary" round @click="handleRemoveClass(row)" v-permission="'v1-backend-experiment-class-add'" <el-button link round type="primary" @click="handleUpdate(row)">编辑</el-button>
<el-button
link
round
type="danger"
@click="handleRemoveClass(row)"
v-permission="'v1-backend-experiment-class-add'"
>删除</el-button >删除</el-button
> >
</template> </template>
</AppList> </AppList>
<FormDialog v-model="dialogVisible" :data="rowData" @update="onUpdateSuccess" v-if="dialogVisible"></FormDialog>
</template> </template>
<script setup lang="ts">
import type { FormInstance, FormRules } from 'element-plus'
import type { ContestItem, ContestBookItem, ContestBookUpdateParams } from '../types'
import { ElMessageBox, ElMessage } from 'element-plus'
import { pick } from 'lodash-es'
import AppUpload from '@/components/base/AppUpload.vue'
import { createContestBook, updateContestBook } from '../api'
import { useMapStore } from '@/stores/map'
interface Props {
data?: ContestBookItem
}
const props = defineProps<Props>()
const emit = defineEmits<{
(e: 'update'): void
(e: 'update:modelValue', visible: boolean): void
}>()
const detail = $ref<ContestItem>(inject('detail'))
// 数据状态
const status = useMapStore().getMapValuesByKey('system_status')
const formRef = $ref<FormInstance>()
const form = reactive<any>({
competition_id: detail.id,
files: [],
url: '',
type: '',
name: '',
status: '1',
protocol: false
})
watchEffect(() => {
if (!props.data) return
form.files = [JSON.parse(props.data.url)]
Object.assign(form, { protocol: true }, props.data)
})
const checkProtocol = (rule: any, value: any, callback: any) => {
if (!value) {
return callback(new Error('请阅读并同意'))
} else {
callback()
}
}
const rules = ref<FormRules>({
files: [{ type: 'array', required: true, message: '请上传训练指导书' }],
name: [{ required: true, message: '请输入训练指导书名称', trigger: 'blur' }],
course_id: [{ required: true, message: '请选择关联训练课程', trigger: 'change' }],
experiment_id: [{ required: true, message: '请选择关联训练', trigger: 'change' }],
protocol: [{ validator: checkProtocol, message: '请阅读并同意', trigger: 'change' }]
})
const isUpdate = $computed(() => {
return !!props.data?.id
})
const title = $computed(() => {
return isUpdate ? '编辑训练指导书' : '新增训练指导书'
})
function handleBeforeUpload() {
if (form.files.length) {
return ElMessageBox.confirm('系统仅支持1个训练指导书,此操作将覆盖原有训练指导书文件,确认上传新文件吗?', '提示')
}
return true
}
function handleUploadSuccess(file: any) {
form.name = file.name.split('.').shift()
form.type = file.raw.type || 'unknown'
}
// 提交
function handleSubmit() {
formRef?.validate().then(() => {
const [file] = form.files
const params = Object.assign({}, pick(form, ['competition_id', 'name', 'type', 'status']), {
url: JSON.stringify({ name: file.name, url: file.url, size: file.size })
})
isUpdate ? handleUpdate(params) : handleCreate(params)
})
}
// 新增
function handleCreate(params: ContestBookUpdateParams) {
createContestBook(params).then(() => {
ElMessage({ message: '创建成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
})
}
// 修改
function handleUpdate(params: ContestBookUpdateParams) {
updateContestBook(params).then(() => {
ElMessage({ message: '修改成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
})
}
</script>
<template>
<el-dialog :title="title" :close-on-click-modal="false" width="600px" @update:modelValue="$emit('update:modelValue')">
<el-form ref="formRef" :model="form" :rules="rules" label-width="124px">
<el-form-item label="训练指导书文件" prop="files">
<AppUpload
v-model="form.files"
:limit="1"
:beforeUpload="handleBeforeUpload"
accept=".doc,.docx,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,.pdf,application/pdf,.ppt,.pptx,application/vnd.ms-powerpoint,.csv,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
@success="handleUploadSuccess"
>
<template #tip>训练指导书文件支持格式包含:doc docx xls xlsx pdf ppt pptx,大小不超过50M</template>
</AppUpload>
</el-form-item>
<el-form-item label="训练指导书名称" prop="name">
<el-input v-model="form.name"></el-input>
<p class="form-tips">训练指导书名称自动取值于文件名称,可以进行二次修改。</p>
</el-form-item>
<el-form-item label="关联赛项">
<el-input :value="detail.name" disabled></el-input>
</el-form-item>
<el-form-item label="有效状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="item in status" :key="item.id" :label="item.value">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item prop="protocol">
<el-checkbox label="我已阅读并同意" v-model="form.protocol" />
<a
href="https://view.officeapps.live.com/op/view.aspx?src=https://webapp-pub.oss-cn-beijing.aliyuncs.com/center_resource/%E7%B4%AB%E8%8D%86%E6%95%99%E8%82%B2%E7%94%A8%E6%88%B7%E5%85%A5%E9%A9%BB%E5%8F%8A%E7%BD%91%E7%BB%9C%E6%95%99%E5%AD%A6%E8%B5%84%E6%BA%90%E5%8D%8F%E8%AE%AE(1).docx"
target="_blank"
>《紫荆教育用户入驻及网络教学资源协议》</a
>
</el-form-item>
<el-row justify="center">
<el-button type="primary" round auto-insert-space @click="handleSubmit">保存</el-button>
<el-button round auto-insert-space @click="$emit('update:modelValue', false)">取消</el-button>
</el-row>
</el-form>
</el-dialog>
</template>
<style lang="scss" scoped>
.form-tips {
font-size: 12px;
color: #999;
}
</style>
<script setup lang="ts"> <script setup lang="ts">
import type { ContestVideoItem } from '../types'
import { CirclePlus } from '@element-plus/icons-vue' import { CirclePlus } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import AppList from '@/components/base/AppList.vue' import AppList from '@/components/base/AppList.vue'
import { getContestVideoList, deleteContestVideo } from '../api'
const FormDialog = defineAsyncComponent(() => import('./ViewVideoFormDialog.vue'))
interface Props {
id: string
}
const props = defineProps<Props>()
const appList = $ref<InstanceType<typeof AppList> | null>(null) const appList = $ref<InstanceType<typeof AppList> | null>(null)
// 列表配置 // 列表配置
const listOptions = { const listOptions = {
hasPagination: false,
remote: {
httpRequest: getContestVideoList,
params: { competition_id: props.id }
},
columns: [ columns: [
{ label: '序号', type: 'index', width: 60 }, { label: '序号', type: 'index', width: 60 },
{ label: '操作视频名称', prop: 'specialty_id_name' }, { label: '操作视频名称', prop: 'name' },
{ label: '视频时长(分钟)', prop: 'name' }, { label: '视频时长(分钟)', prop: 'duration' },
{ label: '创建人', prop: 'student_nums' }, { label: '创建人', prop: 'create_user.real_name' },
{ label: '创建时间', prop: 'created_time' }, { label: '创建时间', prop: 'created_time' },
{ label: '更新时间', prop: 'updated_time' }, { label: '更新时间', prop: 'updated_time' },
{ label: '操作', slots: 'table-x', width: 200 } { label: '操作', slots: 'table-x', width: 180 }
] ]
} }
// 刷新
function handleRefetch() {
appList?.refetch()
}
let rowData = $ref<ClassItem | null>(null)
let dialogVisible = $ref(false)
const rowData = ref<ContestVideoItem>()
// 新增
function handleAdd() {
rowData.value = undefined
dialogVisible = true
}
// 编辑
function handleUpdate(row: ContestVideoItem) {
rowData.value = row
dialogVisible = true
}
// 删除 // 删除
function handleRemoveClass(row: ClassItem) { function handleRemoveClass(row: ContestVideoItem) {
ElMessageBox.confirm('确定要删除吗?', '提示').then(() => { ElMessageBox.confirm('确定要删除吗?', '提示').then(() => {
experimentAddClass({ experiment_id: props.id, classes_id: row.id, type: 'delete' }).then(() => { deleteContestVideo({ id: row.id }).then(() => {
ElMessage({ message: '删除成功', type: 'success' }) ElMessage({ message: '删除成功', type: 'success' })
appList?.refetch() onUpdateSuccess()
}) })
}) })
} }
function onUpdateSuccess() {
appList?.refetch()
}
</script> </script>
<template> <template>
<AppList v-bind="listOptions" ref="appList"> <AppList v-bind="listOptions" ref="appList">
<template #header-buttons> <template #header-buttons>
<el-button type="primary" :icon="CirclePlus">新增</el-button> <el-button type="primary" :icon="CirclePlus" @click="handleAdd">新增</el-button>
</template> </template>
<template #table-x="{ row }"> <template #table-x="{ row }">
<el-button type="primary" round>查阅</el-button> <el-button link round type="success">查阅</el-button>
<el-button type="primary" round @click="handleRemoveClass(row)" v-permission="'v1-backend-experiment-class-add'" <el-button link round type="primary" @click="handleUpdate(row)">编辑</el-button>
<el-button
link
round
type="danger"
@click="handleRemoveClass(row)"
v-permission="'v1-backend-experiment-class-add'"
>删除</el-button >删除</el-button
> >
</template> </template>
</AppList> </AppList>
<FormDialog v-model="dialogVisible" :data="rowData" @update="onUpdateSuccess" v-if="dialogVisible"></FormDialog>
</template> </template>
<script setup lang="ts">
import type { FormInstance, FormRules } from 'element-plus'
import type { ContestItem, ContestVideoItem, ContestVideoCreateParams, ContestVideoUpdateParams } from '../types'
import { ElMessageBox, ElMessage } from 'element-plus'
import UploadVideo from '@/components/UploadVideo.vue'
import { createContestVideo, updateContestVideo } from '../api'
import { useMapStore } from '@/stores/map'
interface Props {
data?: ContestVideoItem
}
const props = defineProps<Props>()
const emit = defineEmits<{
(e: 'update'): void
(e: 'update:modelValue', visible: boolean): void
}>()
const detail = $ref<ContestItem>(inject('detail'))
// 数据状态
const status = useMapStore().getMapValuesByKey('system_status')
const formRef = $ref<FormInstance>()
const form = reactive<any>({
competition_id: detail.id,
name: '',
status: '1',
source_id: '',
duration: 0,
type: '',
protocol: false
})
watchEffect(() => {
if (!props.data) return
Object.assign(form, { protocol: true }, props.data)
})
const checkProtocol = (rule: any, value: any, callback: any) => {
if (!value) {
return callback(new Error('请阅读并同意'))
} else {
callback()
}
}
const rules = ref<FormRules>({
source_id: [{ required: true, message: '请上传训练操作视频' }],
name: [{ required: true, message: '请输入训练操作视频名称', trigger: 'blur' }],
course_id: [{ required: true, message: '请选择关联训练课程', trigger: 'change' }],
experiment_id: [{ required: true, message: '请选择关联训练', trigger: 'change' }],
protocol: [{ validator: checkProtocol, message: '请阅读并同意', trigger: 'change' }]
})
const isUpdate = $computed(() => {
return !!props.data?.id
})
const title = $computed(() => {
return isUpdate ? '编辑训练操作视频' : '新增训练操作视频'
})
function handleBeforeUpload() {
if (form.source_id) {
return ElMessageBox.confirm(
'系统仅支持1个训练操作视频,此操作将覆盖原有训练操作视频文件,确认上传新文件吗?',
'提示'
)
}
return true
}
function handleUploadSuccess(uploadInfo: any) {
form.name = uploadInfo.file.name.split('.').shift()
form.source_id = uploadInfo.videoId
}
// 提交
function handleSubmit() {
formRef?.validate().then(() => {
isUpdate ? handleUpdate(form) : handleCreate(form)
})
}
// 新增
function handleCreate(params: ContestVideoCreateParams) {
createContestVideo(params).then(() => {
ElMessage({ message: '创建成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
})
}
// 修改
function handleUpdate(params: ContestVideoUpdateParams) {
updateContestVideo(params).then(() => {
ElMessage({ message: '修改成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
})
}
</script>
<template>
<el-dialog :title="title" :close-on-click-modal="false" width="600px" @update:modelValue="$emit('update:modelValue')">
<el-form ref="formRef" :model="form" :rules="rules" label-width="140px">
<el-form-item label="训练操作视频文件" prop="source_id">
<UploadVideo :beforeUpload="handleBeforeUpload" @success="handleUploadSuccess">
<template #tip>训练操作视频文件支持格式包含:doc docx xls xlsx pdf ppt pptx,大小不超过50M</template>
</UploadVideo>
</el-form-item>
<el-form-item label="训练操作视频名称" prop="name">
<el-input v-model="form.name"></el-input>
<p class="form-tips">操作视频名称自动取值于文件名称,可以进行二次修改。</p>
</el-form-item>
<el-form-item label="关联赛项">
<el-input :value="detail.name" disabled></el-input>
</el-form-item>
<el-form-item label="有效状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="item in status" :key="item.id" :label="item.value">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item prop="protocol">
<el-checkbox label="我已阅读并同意" v-model="form.protocol" />
<a
href="https://view.officeapps.live.com/op/view.aspx?src=https://webapp-pub.oss-cn-beijing.aliyuncs.com/center_resource/%E7%B4%AB%E8%8D%86%E6%95%99%E8%82%B2%E7%94%A8%E6%88%B7%E5%85%A5%E9%A9%BB%E5%8F%8A%E7%BD%91%E7%BB%9C%E6%95%99%E5%AD%A6%E8%B5%84%E6%BA%90%E5%8D%8F%E8%AE%AE(1).docx"
target="_blank"
>《紫荆教育用户入驻及网络教学资源协议》</a
>
</el-form-item>
<el-row justify="center">
<el-button type="primary" round auto-insert-space @click="handleSubmit">保存</el-button>
<el-button round auto-insert-space @click="$emit('update:modelValue', false)">取消</el-button>
</el-row>
</el-form>
</el-dialog>
</template>
<style lang="scss" scoped>
.form-tips {
font-size: 12px;
color: #999;
}
</style>
...@@ -9,11 +9,11 @@ export interface ContestItem { ...@@ -9,11 +9,11 @@ export interface ContestItem {
id: string id: string
name: string name: string
type: string type: string
start_range: string start_range: number
end_range: string end_range: number
start_at: string start_at: number
end_at: string end_at: number
apply_expiration_date: string apply_expiration_date: number
status: string status: string
logo: string logo: string
cover: string cover: string
...@@ -52,3 +52,58 @@ export interface ContestCreateParams { ...@@ -52,3 +52,58 @@ export interface ContestCreateParams {
} }
export type ContestUpdateParams = ContestCreateParams & { id: string } export type ContestUpdateParams = ContestCreateParams & { id: string }
export interface ContestBookItem {
id: string
competition_id: string
name: string
type: string
url: string
status: string
created_operator: string
created_time: string
updated_time: string
create_user: User
}
export interface ContestBookUpdateParams {
competition_id: string
name: string
url: string
type: string
status: string
}
export interface User {
id: string
username: string
nickname: string
real_name: string
}
export interface ContestVideoItem {
id: string
competition_id: string
name: string
duration: string
size: string
cover: string
type: string
status: string
source_id: string
created_operator: string
created_time: string
updated_time: string
create_user: User
}
export interface ContestVideoCreateParams {
competition_id: string
name: string
type: string
status: string
duration: number
source_id: string
}
export type ContestVideoUpdateParams = ContestVideoCreateParams & { id: string }
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import type { ContestItem } from '../types' import type { ContestItem } from '../types'
import { getContestItem } from '../api' import { getContestItem } from '../api'
import { useMapStore } from '@/stores/map' import { useMapStore } from '@/stores/map'
import dayjs from 'dayjs'
const ViewBook = defineAsyncComponent(() => import('../components/ViewBook.vue')) const ViewBook = defineAsyncComponent(() => import('../components/ViewBook.vue'))
const ViewVideo = defineAsyncComponent(() => import('../components/ViewVideo.vue')) const ViewVideo = defineAsyncComponent(() => import('../components/ViewVideo.vue'))
...@@ -44,7 +45,14 @@ function fetchInfo() { ...@@ -44,7 +45,14 @@ function fetchInfo() {
} }
onMounted(fetchInfo) onMounted(fetchInfo)
const judgingRulesVisible = $ref(true) function formatDate(timestamp: number): string {
return dayjs(timestamp * 1000).format('YYYY-MM-DD')
}
function formatDateTime(timestamp: number): string {
return dayjs(timestamp * 1000).format('YYYY-MM-DD HH:mm:ss')
}
const judgingRulesVisible = $ref(false)
</script> </script>
<template> <template>
...@@ -67,19 +75,21 @@ const judgingRulesVisible = $ref(true) ...@@ -67,19 +75,21 @@ const judgingRulesVisible = $ref(true)
<el-descriptions-item label="指导教师:">{{ teacherText }}</el-descriptions-item> <el-descriptions-item label="指导教师:">{{ teacherText }}</el-descriptions-item>
<el-descriptions-item label="承办单位:">{{ orgText }}</el-descriptions-item> <el-descriptions-item label="承办单位:">{{ orgText }}</el-descriptions-item>
<el-descriptions-item label="赛项周期:" <el-descriptions-item label="赛项周期:"
>{{ detail.start_range }} ~ {{ detail.end_range }}</el-descriptions-item >{{ formatDate(detail.start_range) }} ~ {{ formatDate(detail.end_range) }}</el-descriptions-item
> >
<el-descriptions-item label="技术支持单位:">{{ detail.technical_support_unit.label }}</el-descriptions-item> <el-descriptions-item label="技术支持单位:">{{ detail.technical_support_unit.label }}</el-descriptions-item>
<el-descriptions-item label="正式比赛日期:">{{ detail.start_at }} ~ {{ detail.end_at }}</el-descriptions-item> <el-descriptions-item label="正式比赛日期:"
>{{ formatDateTime(detail.start_at) }} ~ {{ formatDateTime(detail.end_at) }}</el-descriptions-item
>
<el-descriptions-item label="生效状态:">{{ statusText }}</el-descriptions-item> <el-descriptions-item label="生效状态:">{{ statusText }}</el-descriptions-item>
</el-descriptions> </el-descriptions>
</div> </div>
</AppCard> </AppCard>
<AppCard title="训练指导书"> <AppCard title="训练指导书">
<ViewBook></ViewBook> <ViewBook :id="id"></ViewBook>
</AppCard> </AppCard>
<AppCard title="操作视频"> <AppCard title="操作视频">
<ViewVideo></ViewVideo> <ViewVideo :id="id"></ViewVideo>
</AppCard> </AppCard>
<JudgingRulesDialog v-model="judgingRulesVisible" v-if="judgingRulesVisible && detail"></JudgingRulesDialog> <JudgingRulesDialog v-model="judgingRulesVisible" v-if="judgingRulesVisible && detail"></JudgingRulesDialog>
</template> </template>
......
...@@ -3,20 +3,32 @@ import type { JudgeCreateParams, JudgeUpdateParams } from './types' ...@@ -3,20 +3,32 @@ import type { JudgeCreateParams, JudgeUpdateParams } from './types'
// 获取评分专家列表 // 获取评分专家列表
export function getJudgeList(params?: { name?: string; page?: number; 'per-page'?: number }) { export function getJudgeList(params?: { name?: string; page?: number; 'per-page'?: number }) {
return httpRequest.get('/api/resource/v1/backend/experiment/list', { params }) return httpRequest.get('/api/resource/v1/backend/expert/list', { params })
}
// 获取评分专家详情
export function getJudge(params: { id: string }) {
return httpRequest.get('/api/resource/v1/backend/expert/detail', { params })
} }
// 创建评分专家 // 创建评分专家
export function createJudge(data: JudgeCreateParams) { export function createJudge(data: JudgeCreateParams) {
return httpRequest.post('/api/resource/v1/backend/experiment/list', data) return httpRequest.post('/api/resource/v1/backend/expert/create', data)
} }
// 修改评分专家 // 修改评分专家
export function updateJudge(data: JudgeUpdateParams) { export function updateJudge(data: JudgeUpdateParams) {
return httpRequest.post('/api/resource/v1/backend/experiment/list', data) return httpRequest.post('/api/resource/v1/backend/expert/update', data)
} }
// 获取评分专家列表 // 修改评分专家
export function uploadJudge(data: { file: File }) { export function importJudge(data: { file: File }) {
return httpRequest.post('/api/resource/v1/backend/experiment/list', data) return httpRequest.post('/api/resource/v1/backend/expert/import-exports', data, {
headers: { 'Content-Type': 'multipart/form-data' }
})
}
// 获取评分专家赛项列表
export function getJudgeContestList(params: { id: string }) {
return httpRequest.get('/api/resource/v1/backend/expert/competitions', { params })
} }
...@@ -7,7 +7,7 @@ import { useMapStore } from '@/stores/map' ...@@ -7,7 +7,7 @@ import { useMapStore } from '@/stores/map'
import { useArea } from '@/composables/useArea' import { useArea } from '@/composables/useArea'
interface Props { interface Props {
data?: Judge | null data?: Judge
} }
const props = defineProps<Props>() const props = defineProps<Props>()
...@@ -19,31 +19,45 @@ const emit = defineEmits<{ ...@@ -19,31 +19,45 @@ const emit = defineEmits<{
// 性别 // 性别
const genderList = useMapStore().getMapValuesByKey('system_gender') const genderList = useMapStore().getMapValuesByKey('system_gender')
// 职务
const positionList = useMapStore().getMapValuesByKey('expert_position')
const formRef = $ref<FormInstance>() const formRef = $ref<FormInstance>()
const form = reactive<JudgeCreateParams>({ const form = reactive<JudgeCreateParams>({
name: '', name: '',
phone: '', mobile: '',
company: '', company: '',
gender: '1', sex: '1',
position: '', position_id: '',
province: '', province: '',
city: '', city: '',
county: '' area: ''
}) })
const { provinceList, cityList, countyList, provinceValue, cityValue, countyValue } = useArea(
props.data?.province || '',
props.data?.city || '',
props.data?.area || ''
)
watchEffect(() => {
form.province = provinceValue.value
form.city = cityValue.value
form.area = countyValue.value
})
watchEffect(() => { watchEffect(() => {
if (!props.data) return if (!props.data) return
Object.assign(form, props.data) Object.assign(form, props.data, { position_id: props.data.position.id })
}) })
const rules = ref<FormRules>({ const rules = ref<FormRules>({
name: [{ required: true, message: '请选择赛项所属部门/学校' }], name: [{ required: true, message: '请输入姓名' }],
phone: [{ required: true, message: '请选择赛项课程' }], mobile: [{ required: true, message: '请输入联系电话' }],
company: [{ required: true, message: '请输入赛项名称' }], company: [{ required: true, message: '请输入所在单位' }],
position: [{ required: true, message: '请输入赛项学时' }], position_id: [{ required: true, message: '请选择职务' }],
province: [{ required: true, message: '请选择赛项类型' }], province: [{ required: true, message: '请选择' }],
city: [{ required: true, message: '请选择指导教师', trigger: 'change' }], city: [{ required: true, message: '请选择' }],
county: [{ required: true, message: '请输入赛项总成绩' }] area: [{ required: true, message: '请选择区/县' }]
}) })
const isUpdate = $computed(() => { const isUpdate = $computed(() => {
return !!props.data?.id return !!props.data?.id
...@@ -52,13 +66,6 @@ const title = $computed(() => { ...@@ -52,13 +66,6 @@ const title = $computed(() => {
return isUpdate ? '编辑专家信息' : '新增评分专家' return isUpdate ? '编辑专家信息' : '新增评分专家'
}) })
const { provinceList, cityList, countyList, provinceValue, cityValue, countyValue } = useArea()
watchEffect(() => {
form.province = provinceValue.value
form.city = cityValue.value
form.county = countyValue.value
})
// 提交 // 提交
function handleSubmit() { function handleSubmit() {
formRef?.validate().then(() => { formRef?.validate().then(() => {
...@@ -90,20 +97,20 @@ function handleUpdate() { ...@@ -90,20 +97,20 @@ function handleUpdate() {
<el-form-item label="姓名" prop="name"> <el-form-item label="姓名" prop="name">
<el-input v-model="form.name" :disabled="isUpdate" /> <el-input v-model="form.name" :disabled="isUpdate" />
</el-form-item> </el-form-item>
<el-form-item label="电话" prop="phone"> <el-form-item label="联系电话" prop="mobile">
<el-input v-model="form.phone" :disabled="isUpdate" /> <el-input v-model="form.mobile" :disabled="isUpdate" />
</el-form-item> </el-form-item>
<el-form-item label="所在单位" prop="position"> <el-form-item label="所在单位" prop="company">
<el-input v-model="form.position" /> <el-input v-model="form.company" />
</el-form-item> </el-form-item>
<el-form-item label="性别" prop="gender"> <el-form-item label="性别" prop="sex">
<el-radio-group v-model="form.gender"> <el-radio-group v-model="form.sex">
<el-radio v-for="item in genderList" :key="item.id" :label="item.value">{{ item.label }}</el-radio> <el-radio v-for="item in genderList" :key="item.id" :label="item.value">{{ item.label }}</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label="职务" prop="position"> <el-form-item label="职务" prop="position_id">
<el-select v-model="form.position" style="width: 100%" :disabled="isUpdate"> <el-select v-model="form.position_id" style="width: 100%">
<el-option v-for="item in genderList" :key="item.id" :label="item.label" :value="item.id"></el-option> <el-option v-for="item in positionList" :key="item.id" :label="item.label" :value="item.id"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="省" prop="province"> <el-form-item label="省" prop="province">
...@@ -116,7 +123,7 @@ function handleUpdate() { ...@@ -116,7 +123,7 @@ function handleUpdate() {
<el-option v-for="item in cityList" :value="item.label" :key="item.code"></el-option> <el-option v-for="item in cityList" :value="item.label" :key="item.code"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="区/县" prop="county"> <el-form-item label="区/县" prop="area">
<el-select v-model="countyValue" style="width: 100%"> <el-select v-model="countyValue" style="width: 100%">
<el-option v-for="item in countyList" :value="item.label" :key="item.code"></el-option> <el-option v-for="item in countyList" :value="item.label" :key="item.code"></el-option>
</el-select> </el-select>
......
...@@ -3,7 +3,7 @@ import { Upload } from '@element-plus/icons-vue' ...@@ -3,7 +3,7 @@ import { Upload } from '@element-plus/icons-vue'
import { useFileDialog } from '@vueuse/core' import { useFileDialog } from '@vueuse/core'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { uploadJudge } from '../api' import { importJudge } from '../api'
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update'): void (e: 'update'): void
...@@ -20,7 +20,7 @@ function handleImport() { ...@@ -20,7 +20,7 @@ function handleImport() {
watchEffect(() => { watchEffect(() => {
if (!files.value?.length) return if (!files.value?.length) return
const [file] = files.value const [file] = files.value
uploadJudge({ file }).then(() => { importJudge({ file }).then(() => {
ElMessage({ message: '导入成功', type: 'success' }) ElMessage({ message: '导入成功', type: 'success' })
emit('update') emit('update')
}) })
...@@ -32,9 +32,7 @@ watchEffect(() => { ...@@ -32,9 +32,7 @@ watchEffect(() => {
<div class="box"> <div class="box">
<el-button type="primary" round :icon="Upload" @click="handleImport">本地上传</el-button> <el-button type="primary" round :icon="Upload" @click="handleImport">本地上传</el-button>
<p> <p>
<a <a href="https://webapp-pub.ezijing.com/project/saas-lab/%E4%B8%93%E5%AE%B6%E6%A8%A1%E6%9D%BF.xlsx" download
href="https://webapp-pub.ezijing.com/project/saas-lab/%E5%AE%9E%E9%AA%8C%E6%88%90%E7%BB%A9%E5%AF%BC%E5%85%A5%E6%A8%A1%E6%9D%BF.xlsx"
download
>下载模板</a >下载模板</a
> >
</p> </p>
......
<script setup lang="ts"> <script setup lang="ts">
import type { Judge } from '../types' import type { Judge } from '../types'
// import { getExperimentClassGroupsList } from '../api' import { getJudgeContestList } from '../api'
interface Props { interface Props {
data: Judge data: Judge
} }
defineProps<Props>() const props = defineProps<Props>()
// 列表配置 // 列表配置
const listOptions = { const listOptions = {
// remote: { hasPagination: false,
// httpRequest: getExperimentClassGroupsList, remote: {
// params: { experiment_id: data?.id, class_id: props.data?.id } httpRequest: getJudgeContestList,
// }, params: { id: props.data.id },
callback(res: any) {
return { list: res.detail.competitions }
}
},
columns: [ columns: [
{ label: '序号', type: 'index', width: 60 }, { label: '序号', type: 'index', width: 60 },
{ label: '赛项名称', prop: 'name' }, { label: '赛项名称', prop: 'name' },
{ label: '角色', prop: 'student_num' } { label: '角色', prop: 'role' }
] ]
} }
</script> </script>
...@@ -24,9 +28,9 @@ const listOptions = { ...@@ -24,9 +28,9 @@ const listOptions = {
<el-dialog title="查看专家信息"> <el-dialog title="查看专家信息">
<el-descriptions :column="2"> <el-descriptions :column="2">
<el-descriptions-item label="姓名:">{{ data.name }}</el-descriptions-item> <el-descriptions-item label="姓名:">{{ data.name }}</el-descriptions-item>
<el-descriptions-item label="联系电话:">{{ data.phone }}</el-descriptions-item> <el-descriptions-item label="联系电话:">{{ data.mobile }}</el-descriptions-item>
<el-descriptions-item label="所在单位:">{{ data.company }}</el-descriptions-item> <el-descriptions-item label="所在单位:">{{ data.company }}</el-descriptions-item>
<el-descriptions-item label="职务:">{{ data.position }}</el-descriptions-item> <el-descriptions-item label="职务:">{{ data.position.label }}</el-descriptions-item>
</el-descriptions> </el-descriptions>
<AppList v-bind="listOptions"></AppList> <AppList v-bind="listOptions"></AppList>
<el-row justify="center"> <el-row justify="center">
......
export interface Judge { export interface Judge {
id: string id: string
sso_id: string
name: string name: string
phone: string mobile: string
company: string company: string
gender: string sex: string
position: string
province: string province: string
city: string city: string
county: string area: string
created_operator: string
updated_operator: string
created_time: string
updated_time: string
position: JudgePosition
competition: JudgeCompetition
check_count: 1
}
export interface JudgePosition {
id: string
label: string
value: string
}
export interface JudgeCompetition {
id: string
name: string
}
export interface JudgeCreateParams {
name: string
mobile: string
company: string
sex: string
position_id: string
province: string
city: string
area: string
} }
export type JudgeCreateParams = Omit<Judge, 'id'> export type JudgeUpdateParams = JudgeCreateParams & { id: string }
export type JudgeUpdateParams = Judge
<script setup lang="ts"> <script setup lang="ts">
import type { Judge } from '../types' import type { Judge } from '../types'
import { Upload } from '@element-plus/icons-vue' import { CirclePlus, Upload } from '@element-plus/icons-vue'
import AppList from '@/components/base/AppList.vue' import AppList from '@/components/base/AppList.vue'
import { getJudgeList } from '../api' import { getJudgeList } from '../api'
import { useMapStore } from '@/stores/map'
const ViewDialog = defineAsyncComponent(() => import('../components/ViewDialog.vue')) const ViewDialog = defineAsyncComponent(() => import('../components/ViewDialog.vue'))
const FormDialog = defineAsyncComponent(() => import('../components/FormDialog.vue')) const FormDialog = defineAsyncComponent(() => import('../components/FormDialog.vue'))
...@@ -10,32 +11,40 @@ const ImportDialog = defineAsyncComponent(() => import('../components/ImportDial ...@@ -10,32 +11,40 @@ const ImportDialog = defineAsyncComponent(() => import('../components/ImportDial
const appList = $ref<InstanceType<typeof AppList> | null>(null) const appList = $ref<InstanceType<typeof AppList> | null>(null)
// 性别
const genderList = useMapStore().getMapValuesByKey('system_gender')
// 列表配置 // 列表配置
const listOptions = { const listOptions = {
remote: { remote: {
httpRequest: getJudgeList, httpRequest: getJudgeList,
params: { contest_id: '', name: '' } params: { competition_id: '', name: '' }
}, },
filters: [ filters: [
{ {
type: 'select', type: 'select',
prop: 'course_id', prop: 'competition_id',
label: '赛项', label: '赛项',
placeholder: '请选择赛项' placeholder: '请选择赛项'
}, },
{ type: 'input', prop: 'student_name', label: '专家姓名', placeholder: '请输入专家姓名' } { type: 'input', prop: 'name', label: '专家姓名', placeholder: '请输入专家姓名' }
], ],
columns: [ columns: [
{ label: '序号', type: 'index', width: 60 }, { label: '序号', type: 'index', width: 60 },
{ label: '赛项名称', prop: 'name' }, { label: '专家姓名', prop: 'name' },
{ label: '专家姓名', prop: 'length' }, { label: '所在单位', prop: 'company' },
{ label: '参赛ID', prop: 'teacher_names' }, {
{ label: '性别', prop: 'type_name' }, label: '性别',
{ label: '学校', prop: 'score' }, prop: 'sex',
{ label: '所在专业', prop: 'score' }, computed({ row }: { row: Judge }) {
{ label: '所在年级', prop: 'score' }, const found = genderList.find(item => item.value === row.sex)
{ label: '所在班级', prop: 'score' }, return found?.label || row.sex
{ label: '训练次数', prop: 'score' }, }
},
{ label: '职务', prop: 'position.label' },
{ label: '省', prop: 'province' },
{ label: '市', prop: 'city' },
{ label: '区/县', prop: 'area' },
{ label: '批改次数', prop: 'check_count' },
{ label: '更新时间', prop: 'updated_time' }, { label: '更新时间', prop: 'updated_time' },
{ label: '操作', slots: 'table-x', width: 200 } { label: '操作', slots: 'table-x', width: 200 }
] ]
...@@ -51,6 +60,12 @@ function handleView(row: Judge) { ...@@ -51,6 +60,12 @@ function handleView(row: Judge) {
viewDialogVisible = true viewDialogVisible = true
} }
// 新增
function handleAdd() {
rowData.value = null
dialogVisible = true
}
// 编辑 // 编辑
let dialogVisible = $ref(false) let dialogVisible = $ref(false)
function handleUpdate(row: Judge) { function handleUpdate(row: Judge) {
...@@ -67,6 +82,7 @@ function onUpdateSuccess() { ...@@ -67,6 +82,7 @@ function onUpdateSuccess() {
<AppCard title="评分专家管理"> <AppCard title="评分专家管理">
<AppList v-bind="listOptions" ref="appList"> <AppList v-bind="listOptions" ref="appList">
<template #header-buttons> <template #header-buttons>
<el-button type="primary" round :icon="CirclePlus" @click="handleAdd">新增评分专家</el-button>
<el-button type="primary" round :icon="Upload" @click="importVisible = true">批量导入</el-button> <el-button type="primary" round :icon="Upload" @click="importVisible = true">批量导入</el-button>
</template> </template>
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论