提交 f476067c authored 作者: lihuihui's avatar lihuihui
...@@ -202,6 +202,7 @@ ...@@ -202,6 +202,7 @@
"usePreferredDark": true, "usePreferredDark": true,
"usePreferredLanguages": true, "usePreferredLanguages": true,
"usePreferredReducedMotion": true, "usePreferredReducedMotion": true,
"usePrevious": true,
"useRafFn": true, "useRafFn": true,
"useRefHistory": true, "useRefHistory": true,
"useResizeObserver": true, "useResizeObserver": true,
......
...@@ -203,6 +203,7 @@ declare global { ...@@ -203,6 +203,7 @@ declare global {
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark'] const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages'] const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion'] const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
const usePrevious: typeof import('@vueuse/core')['usePrevious']
const useRafFn: typeof import('@vueuse/core')['useRafFn'] const useRafFn: typeof import('@vueuse/core')['useRafFn']
const useRefHistory: typeof import('@vueuse/core')['useRefHistory'] const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver'] const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
......
...@@ -10,12 +10,13 @@ ...@@ -10,12 +10,13 @@
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.0.10", "@element-plus/icons-vue": "^2.0.10",
"@tinymce/tinymce-vue": "^5.0.0", "@tinymce/tinymce-vue": "^5.0.0",
"@vueuse/core": "^9.11.1", "@vueuse/core": "^9.12.0",
"axios": "^1.2.5", "axios": "^1.3.1",
"blueimp-md5": "^2.19.0", "blueimp-md5": "^2.19.0",
"element-plus": "^2.2.28", "element-plus": "^2.2.28",
"pinia": "^2.0.29", "lodash-es": "^4.17.21",
"vue": "^3.2.45", "pinia": "^2.0.30",
"vue": "^3.2.47",
"vue-router": "^4.1.6" "vue-router": "^4.1.6"
}, },
"devDependencies": { "devDependencies": {
...@@ -27,13 +28,13 @@ ...@@ -27,13 +28,13 @@
"@vue/tsconfig": "^0.1.3", "@vue/tsconfig": "^0.1.3",
"ali-oss": "^6.17.1", "ali-oss": "^6.17.1",
"chalk": "^5.2.0", "chalk": "^5.2.0",
"eslint": "^8.32.0", "eslint": "^8.33.0",
"eslint-plugin-vue": "^9.9.0", "eslint-plugin-vue": "^9.9.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"sass": "^1.57.1", "sass": "^1.58.0",
"typescript": "~4.9.4", "typescript": "~4.9.5",
"unplugin-auto-import": "^0.12.2", "unplugin-auto-import": "^0.13.0",
"vite": "^4.0.4", "vite": "^4.1.1",
"vue-tsc": "^1.0.24" "vue-tsc": "^1.0.24"
} }
}, },
...@@ -71,9 +72,9 @@ ...@@ -71,9 +72,9 @@
} }
}, },
"node_modules/@esbuild/android-arm": { "node_modules/@esbuild/android-arm": {
"version": "0.16.12", "version": "0.16.17",
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.16.12.tgz", "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.16.17.tgz",
"integrity": "sha512-CTWgMJtpCyCltrvipZrrcjjRu+rzm6pf9V8muCsJqtKujR3kPmU4ffbckvugNNaRmhxAF1ZI3J+0FUIFLFg8KA==", "integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
...@@ -87,9 +88,9 @@ ...@@ -87,9 +88,9 @@
} }
}, },
"node_modules/@esbuild/android-arm64": { "node_modules/@esbuild/android-arm64": {
"version": "0.16.12", "version": "0.16.17",
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.16.12.tgz", "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz",
"integrity": "sha512-0LacmiIW+X0/LOLMZqYtZ7d4uY9fxYABAYhSSOu+OGQVBqH4N5eIYgkT7bBFnR4Nm3qo6qS3RpHKVrDASqj/uQ==", "integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
...@@ -103,9 +104,9 @@ ...@@ -103,9 +104,9 @@
} }
}, },
"node_modules/@esbuild/android-x64": { "node_modules/@esbuild/android-x64": {
"version": "0.16.12", "version": "0.16.17",
"resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.16.12.tgz", "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.16.17.tgz",
"integrity": "sha512-sS5CR3XBKQXYpSGMM28VuiUnbX83Z+aWPZzClW+OB2JquKqxoiwdqucJ5qvXS8pM6Up3RtJfDnRQZkz3en2z5g==", "integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
...@@ -119,9 +120,9 @@ ...@@ -119,9 +120,9 @@
} }
}, },
"node_modules/@esbuild/darwin-arm64": { "node_modules/@esbuild/darwin-arm64": {
"version": "0.16.12", "version": "0.16.17",
"resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.16.12.tgz", "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz",
"integrity": "sha512-Dpe5hOAQiQRH20YkFAg+wOpcd4PEuXud+aGgKBQa/VriPJA8zuVlgCOSTwna1CgYl05lf6o5els4dtuyk1qJxQ==", "integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
...@@ -135,9 +136,9 @@ ...@@ -135,9 +136,9 @@
} }
}, },
"node_modules/@esbuild/darwin-x64": { "node_modules/@esbuild/darwin-x64": {
"version": "0.16.12", "version": "0.16.17",
"resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.16.12.tgz", "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz",
"integrity": "sha512-ApGRA6X5txIcxV0095X4e4KKv87HAEXfuDRcGTniDWUUN+qPia8sl/BqG/0IomytQWajnUn4C7TOwHduk/FXBQ==", "integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
...@@ -151,9 +152,9 @@ ...@@ -151,9 +152,9 @@
} }
}, },
"node_modules/@esbuild/freebsd-arm64": { "node_modules/@esbuild/freebsd-arm64": {
"version": "0.16.12", "version": "0.16.17",
"resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.12.tgz", "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz",
"integrity": "sha512-AMdK2gA9EU83ccXCWS1B/KcWYZCj4P3vDofZZkl/F/sBv/fphi2oUqUTox/g5GMcIxk8CF1CVYTC82+iBSyiUg==", "integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
...@@ -167,9 +168,9 @@ ...@@ -167,9 +168,9 @@
} }
}, },
"node_modules/@esbuild/freebsd-x64": { "node_modules/@esbuild/freebsd-x64": {
"version": "0.16.12", "version": "0.16.17",
"resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.16.12.tgz", "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz",
"integrity": "sha512-KUKB9w8G/xaAbD39t6gnRBuhQ8vIYYlxGT2I+mT6UGRnCGRr1+ePFIGBQmf5V16nxylgUuuWVW1zU2ktKkf6WQ==", "integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
...@@ -183,9 +184,9 @@ ...@@ -183,9 +184,9 @@
} }
}, },
"node_modules/@esbuild/linux-arm": { "node_modules/@esbuild/linux-arm": {
"version": "0.16.12", "version": "0.16.17",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.16.12.tgz", "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz",
"integrity": "sha512-vhDdIv6z4eL0FJyNVfdr3C/vdd/Wc6h1683GJsFoJzfKb92dU/v88FhWdigg0i6+3TsbSDeWbsPUXb4dif2abg==", "integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
...@@ -199,9 +200,9 @@ ...@@ -199,9 +200,9 @@
} }
}, },
"node_modules/@esbuild/linux-arm64": { "node_modules/@esbuild/linux-arm64": {
"version": "0.16.12", "version": "0.16.17",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.16.12.tgz", "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz",
"integrity": "sha512-29HXMLpLklDfmw7T2buGqq3HImSUaZ1ArmrPOMaNiZZQptOSZs32SQtOHEl8xWX5vfdwZqrBfNf8Te4nArVzKQ==", "integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
...@@ -215,9 +216,9 @@ ...@@ -215,9 +216,9 @@
} }
}, },
"node_modules/@esbuild/linux-ia32": { "node_modules/@esbuild/linux-ia32": {
"version": "0.16.12", "version": "0.16.17",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.16.12.tgz", "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz",
"integrity": "sha512-JFDuNDTTfgD1LJg7wHA42o2uAO/9VzHYK0leAVnCQE/FdMB599YMH73ux+nS0xGr79pv/BK+hrmdRin3iLgQjg==", "integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
...@@ -231,9 +232,9 @@ ...@@ -231,9 +232,9 @@
} }
}, },
"node_modules/@esbuild/linux-loong64": { "node_modules/@esbuild/linux-loong64": {
"version": "0.16.12", "version": "0.16.17",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.16.12.tgz", "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz",
"integrity": "sha512-xTGzVPqm6WKfCC0iuj1fryIWr1NWEM8DMhAIo+4rFgUtwy/lfHl+Obvus4oddzRDbBetLLmojfVZGmt/g/g+Rw==", "integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==",
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
...@@ -247,9 +248,9 @@ ...@@ -247,9 +248,9 @@
} }
}, },
"node_modules/@esbuild/linux-mips64el": { "node_modules/@esbuild/linux-mips64el": {
"version": "0.16.12", "version": "0.16.17",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.16.12.tgz", "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz",
"integrity": "sha512-zI1cNgHa3Gol+vPYjIYHzKhU6qMyOQrvZ82REr5Fv7rlh5PG6SkkuCoH7IryPqR+BK2c/7oISGsvPJPGnO2bHQ==", "integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==",
"cpu": [ "cpu": [
"mips64el" "mips64el"
], ],
...@@ -263,9 +264,9 @@ ...@@ -263,9 +264,9 @@
} }
}, },
"node_modules/@esbuild/linux-ppc64": { "node_modules/@esbuild/linux-ppc64": {
"version": "0.16.12", "version": "0.16.17",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.16.12.tgz", "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz",
"integrity": "sha512-/C8OFXExoMmvTDIOAM54AhtmmuDHKoedUd0Otpfw3+AuuVGemA1nQK99oN909uZbLEU6Bi+7JheFMG3xGfZluQ==", "integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
...@@ -279,9 +280,9 @@ ...@@ -279,9 +280,9 @@
} }
}, },
"node_modules/@esbuild/linux-riscv64": { "node_modules/@esbuild/linux-riscv64": {
"version": "0.16.12", "version": "0.16.17",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.16.12.tgz", "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz",
"integrity": "sha512-qeouyyc8kAGV6Ni6Isz8hUsKMr00EHgVwUKWNp1r4l88fHEoNTDB8mmestvykW6MrstoGI7g2EAsgr0nxmuGYg==", "integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
...@@ -295,9 +296,9 @@ ...@@ -295,9 +296,9 @@
} }
}, },
"node_modules/@esbuild/linux-s390x": { "node_modules/@esbuild/linux-s390x": {
"version": "0.16.12", "version": "0.16.17",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.16.12.tgz", "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz",
"integrity": "sha512-s9AyI/5vz1U4NNqnacEGFElqwnHusWa81pskAf8JNDM2eb6b2E6PpBmT8RzeZv6/TxE6/TADn2g9bb0jOUmXwQ==", "integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
...@@ -311,9 +312,9 @@ ...@@ -311,9 +312,9 @@
} }
}, },
"node_modules/@esbuild/linux-x64": { "node_modules/@esbuild/linux-x64": {
"version": "0.16.12", "version": "0.16.17",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.16.12.tgz", "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz",
"integrity": "sha512-e8YA7GQGLWhvakBecLptUiKxOk4E/EPtSckS1i0MGYctW8ouvNUoh7xnU15PGO2jz7BYl8q1R6g0gE5HFtzpqQ==", "integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
...@@ -327,9 +328,9 @@ ...@@ -327,9 +328,9 @@
} }
}, },
"node_modules/@esbuild/netbsd-x64": { "node_modules/@esbuild/netbsd-x64": {
"version": "0.16.12", "version": "0.16.17",
"resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.16.12.tgz", "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz",
"integrity": "sha512-z2+kUxmOqBS+6SRVd57iOLIHE8oGOoEnGVAmwjm2aENSP35HPS+5cK+FL1l+rhrsJOFIPrNHqDUNechpuG96Sg==", "integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
...@@ -343,9 +344,9 @@ ...@@ -343,9 +344,9 @@
} }
}, },
"node_modules/@esbuild/openbsd-x64": { "node_modules/@esbuild/openbsd-x64": {
"version": "0.16.12", "version": "0.16.17",
"resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.16.12.tgz", "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz",
"integrity": "sha512-PAonw4LqIybwn2/vJujhbg1N9W2W8lw9RtXIvvZoyzoA/4rA4CpiuahVbASmQohiytRsixbNoIOUSjRygKXpyA==", "integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
...@@ -359,9 +360,9 @@ ...@@ -359,9 +360,9 @@
} }
}, },
"node_modules/@esbuild/sunos-x64": { "node_modules/@esbuild/sunos-x64": {
"version": "0.16.12", "version": "0.16.17",
"resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.16.12.tgz", "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz",
"integrity": "sha512-+wr1tkt1RERi+Zi/iQtkzmMH4nS8+7UIRxjcyRz7lur84wCkAITT50Olq/HiT4JN2X2bjtlOV6vt7ptW5Gw60Q==", "integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
...@@ -375,9 +376,9 @@ ...@@ -375,9 +376,9 @@
} }
}, },
"node_modules/@esbuild/win32-arm64": { "node_modules/@esbuild/win32-arm64": {
"version": "0.16.12", "version": "0.16.17",
"resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.16.12.tgz", "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz",
"integrity": "sha512-XEjeUSHmjsAOJk8+pXJu9pFY2O5KKQbHXZWQylJzQuIBeiGrpMeq9sTVrHefHxMOyxUgoKQTcaTS+VK/K5SviA==", "integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
...@@ -391,9 +392,9 @@ ...@@ -391,9 +392,9 @@
} }
}, },
"node_modules/@esbuild/win32-ia32": { "node_modules/@esbuild/win32-ia32": {
"version": "0.16.12", "version": "0.16.17",
"resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.16.12.tgz", "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz",
"integrity": "sha512-eRKPM7e0IecUAUYr2alW7JGDejrFJXmpjt4MlfonmQ5Rz9HWpKFGCjuuIRgKO7W9C/CWVFXdJ2GjddsBXqQI4A==", "integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
...@@ -407,9 +408,9 @@ ...@@ -407,9 +408,9 @@
} }
}, },
"node_modules/@esbuild/win32-x64": { "node_modules/@esbuild/win32-x64": {
"version": "0.16.12", "version": "0.16.17",
"resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.16.12.tgz", "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz",
"integrity": "sha512-iPYKN78t3op2+erv2frW568j1q0RpqX6JOLZ7oPPaAV1VaF7dDstOrNw37PVOYoTWE11pV4A1XUitpdEFNIsPg==", "integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
...@@ -871,36 +872,36 @@ ...@@ -871,36 +872,36 @@
} }
}, },
"node_modules/@vue/compiler-core": { "node_modules/@vue/compiler-core": {
"version": "3.2.45", "version": "3.2.47",
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.2.45.tgz", "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.2.47.tgz",
"integrity": "sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==", "integrity": "sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==",
"dependencies": { "dependencies": {
"@babel/parser": "^7.16.4", "@babel/parser": "^7.16.4",
"@vue/shared": "3.2.45", "@vue/shared": "3.2.47",
"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.45", "version": "3.2.47",
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz", "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz",
"integrity": "sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==", "integrity": "sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==",
"dependencies": { "dependencies": {
"@vue/compiler-core": "3.2.45", "@vue/compiler-core": "3.2.47",
"@vue/shared": "3.2.45" "@vue/shared": "3.2.47"
} }
}, },
"node_modules/@vue/compiler-sfc": { "node_modules/@vue/compiler-sfc": {
"version": "3.2.45", "version": "3.2.47",
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.2.45.tgz", "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.2.47.tgz",
"integrity": "sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q==", "integrity": "sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==",
"dependencies": { "dependencies": {
"@babel/parser": "^7.16.4", "@babel/parser": "^7.16.4",
"@vue/compiler-core": "3.2.45", "@vue/compiler-core": "3.2.47",
"@vue/compiler-dom": "3.2.45", "@vue/compiler-dom": "3.2.47",
"@vue/compiler-ssr": "3.2.45", "@vue/compiler-ssr": "3.2.47",
"@vue/reactivity-transform": "3.2.45", "@vue/reactivity-transform": "3.2.47",
"@vue/shared": "3.2.45", "@vue/shared": "3.2.47",
"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",
...@@ -908,12 +909,12 @@ ...@@ -908,12 +909,12 @@
} }
}, },
"node_modules/@vue/compiler-ssr": { "node_modules/@vue/compiler-ssr": {
"version": "3.2.45", "version": "3.2.47",
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.2.45.tgz", "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.2.47.tgz",
"integrity": "sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==", "integrity": "sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==",
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.2.45", "@vue/compiler-dom": "3.2.47",
"@vue/shared": "3.2.45" "@vue/shared": "3.2.47"
} }
}, },
"node_modules/@vue/devtools-api": { "node_modules/@vue/devtools-api": {
...@@ -946,60 +947,60 @@ ...@@ -946,60 +947,60 @@
} }
}, },
"node_modules/@vue/reactivity": { "node_modules/@vue/reactivity": {
"version": "3.2.45", "version": "3.2.47",
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.2.45.tgz", "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.2.47.tgz",
"integrity": "sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==", "integrity": "sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==",
"dependencies": { "dependencies": {
"@vue/shared": "3.2.45" "@vue/shared": "3.2.47"
} }
}, },
"node_modules/@vue/reactivity-transform": { "node_modules/@vue/reactivity-transform": {
"version": "3.2.45", "version": "3.2.47",
"resolved": "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.2.45.tgz", "resolved": "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.2.47.tgz",
"integrity": "sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==", "integrity": "sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==",
"dependencies": { "dependencies": {
"@babel/parser": "^7.16.4", "@babel/parser": "^7.16.4",
"@vue/compiler-core": "3.2.45", "@vue/compiler-core": "3.2.47",
"@vue/shared": "3.2.45", "@vue/shared": "3.2.47",
"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.45", "version": "3.2.47",
"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.2.45.tgz", "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.2.47.tgz",
"integrity": "sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==", "integrity": "sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==",
"dependencies": { "dependencies": {
"@vue/reactivity": "3.2.45", "@vue/reactivity": "3.2.47",
"@vue/shared": "3.2.45" "@vue/shared": "3.2.47"
} }
}, },
"node_modules/@vue/runtime-dom": { "node_modules/@vue/runtime-dom": {
"version": "3.2.45", "version": "3.2.47",
"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.2.45.tgz", "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.2.47.tgz",
"integrity": "sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==", "integrity": "sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA==",
"dependencies": { "dependencies": {
"@vue/runtime-core": "3.2.45", "@vue/runtime-core": "3.2.47",
"@vue/shared": "3.2.45", "@vue/shared": "3.2.47",
"csstype": "^2.6.8" "csstype": "^2.6.8"
} }
}, },
"node_modules/@vue/server-renderer": { "node_modules/@vue/server-renderer": {
"version": "3.2.45", "version": "3.2.47",
"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.2.45.tgz", "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.2.47.tgz",
"integrity": "sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==", "integrity": "sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==",
"dependencies": { "dependencies": {
"@vue/compiler-ssr": "3.2.45", "@vue/compiler-ssr": "3.2.47",
"@vue/shared": "3.2.45" "@vue/shared": "3.2.47"
}, },
"peerDependencies": { "peerDependencies": {
"vue": "3.2.45" "vue": "3.2.47"
} }
}, },
"node_modules/@vue/shared": { "node_modules/@vue/shared": {
"version": "3.2.45", "version": "3.2.47",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.2.45.tgz", "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.2.47.tgz",
"integrity": "sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==" "integrity": "sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ=="
}, },
"node_modules/@vue/tsconfig": { "node_modules/@vue/tsconfig": {
"version": "0.1.3", "version": "0.1.3",
...@@ -1016,13 +1017,13 @@ ...@@ -1016,13 +1017,13 @@
} }
}, },
"node_modules/@vueuse/core": { "node_modules/@vueuse/core": {
"version": "9.11.1", "version": "9.12.0",
"resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.11.1.tgz", "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.12.0.tgz",
"integrity": "sha512-E/cizD1w9ILkq4axYjZrXLkKaBfzloaby2n3NMjUfd6yI/jkfTVgc6iwy/Cw2e++Ld4LphGbO+3MhzizvwUslQ==", "integrity": "sha512-h/Di8Bvf6xRcvS/PvUVheiMYYz3U0tH3X25YxONSaAUBa841ayMwxkuzx/DGUMCW/wHWzD8tRy2zYmOC36r4sg==",
"dependencies": { "dependencies": {
"@types/web-bluetooth": "^0.0.16", "@types/web-bluetooth": "^0.0.16",
"@vueuse/metadata": "9.11.1", "@vueuse/metadata": "9.12.0",
"@vueuse/shared": "9.11.1", "@vueuse/shared": "9.12.0",
"vue-demi": "*" "vue-demi": "*"
} }
}, },
...@@ -1049,14 +1050,14 @@ ...@@ -1049,14 +1050,14 @@
} }
}, },
"node_modules/@vueuse/metadata": { "node_modules/@vueuse/metadata": {
"version": "9.11.1", "version": "9.12.0",
"resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.11.1.tgz", "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.12.0.tgz",
"integrity": "sha512-ABjkrG+VXggNhjfGyw5e/sekxTZfXTwjrYXkkWQmQ7Biyv+Gq9UD6IDNfeGvQZEINI0Qzw6nfuO2UFCd3hlrxQ==" "integrity": "sha512-9oJ9MM9lFLlmvxXUqsR1wLt1uF7EVbP5iYaHJYqk+G2PbMjY6EXvZeTjbdO89HgoF5cI6z49o2zT/jD9SVoNpQ=="
}, },
"node_modules/@vueuse/shared": { "node_modules/@vueuse/shared": {
"version": "9.11.1", "version": "9.12.0",
"resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.11.1.tgz", "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.12.0.tgz",
"integrity": "sha512-UTZYGAjT96hWn4buf4wstZbeheBVNcKPQuej6qpoSkjF1atdaeCD6kqm9uGL2waHfisSgH9mq0qCRiBOk5C/2w==", "integrity": "sha512-TWuJLACQ0BVithVTRbex4Wf1a1VaRuSpVeyEd4vMUWl54PzlE0ciFUshKCXnlLuD0lxIaLK4Ypj3NXYzZh4+SQ==",
"dependencies": { "dependencies": {
"vue-demi": "*" "vue-demi": "*"
} }
...@@ -1292,9 +1293,9 @@ ...@@ -1292,9 +1293,9 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.2.5", "version": "1.3.1",
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.2.5.tgz", "resolved": "https://registry.npmmirror.com/axios/-/axios-1.3.1.tgz",
"integrity": "sha512-9pU/8mmjSSOb4CXVsvGIevN+MlO/t9OWtKadTaLuN85Gge3HGorUckgp8A/2FH4V4hJ7JuQ3LIeI7KAV9ITZrQ==", "integrity": "sha512-78pWJsQTceInlyaeBQeYZ/QgZeWS8hGeKiIJiDKQe3hEyBb7sEMq0K4gjx+Va6WHTYO4zI/RRl8qGRzn0YMadA==",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.0", "follow-redirects": "^1.15.0",
"form-data": "^4.0.0", "form-data": "^4.0.0",
...@@ -1782,9 +1783,9 @@ ...@@ -1782,9 +1783,9 @@
} }
}, },
"node_modules/esbuild": { "node_modules/esbuild": {
"version": "0.16.12", "version": "0.16.17",
"resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.16.12.tgz", "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.16.17.tgz",
"integrity": "sha512-eq5KcuXajf2OmivCl4e89AD3j8fbV+UTE9vczEzq5haA07U9oOTzBWlh3+6ZdjJR7Rz2QfWZ2uxZyhZxBgJ4+g==", "integrity": "sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"bin": { "bin": {
...@@ -1794,28 +1795,28 @@ ...@@ -1794,28 +1795,28 @@
"node": ">=12" "node": ">=12"
}, },
"optionalDependencies": { "optionalDependencies": {
"@esbuild/android-arm": "0.16.12", "@esbuild/android-arm": "0.16.17",
"@esbuild/android-arm64": "0.16.12", "@esbuild/android-arm64": "0.16.17",
"@esbuild/android-x64": "0.16.12", "@esbuild/android-x64": "0.16.17",
"@esbuild/darwin-arm64": "0.16.12", "@esbuild/darwin-arm64": "0.16.17",
"@esbuild/darwin-x64": "0.16.12", "@esbuild/darwin-x64": "0.16.17",
"@esbuild/freebsd-arm64": "0.16.12", "@esbuild/freebsd-arm64": "0.16.17",
"@esbuild/freebsd-x64": "0.16.12", "@esbuild/freebsd-x64": "0.16.17",
"@esbuild/linux-arm": "0.16.12", "@esbuild/linux-arm": "0.16.17",
"@esbuild/linux-arm64": "0.16.12", "@esbuild/linux-arm64": "0.16.17",
"@esbuild/linux-ia32": "0.16.12", "@esbuild/linux-ia32": "0.16.17",
"@esbuild/linux-loong64": "0.16.12", "@esbuild/linux-loong64": "0.16.17",
"@esbuild/linux-mips64el": "0.16.12", "@esbuild/linux-mips64el": "0.16.17",
"@esbuild/linux-ppc64": "0.16.12", "@esbuild/linux-ppc64": "0.16.17",
"@esbuild/linux-riscv64": "0.16.12", "@esbuild/linux-riscv64": "0.16.17",
"@esbuild/linux-s390x": "0.16.12", "@esbuild/linux-s390x": "0.16.17",
"@esbuild/linux-x64": "0.16.12", "@esbuild/linux-x64": "0.16.17",
"@esbuild/netbsd-x64": "0.16.12", "@esbuild/netbsd-x64": "0.16.17",
"@esbuild/openbsd-x64": "0.16.12", "@esbuild/openbsd-x64": "0.16.17",
"@esbuild/sunos-x64": "0.16.12", "@esbuild/sunos-x64": "0.16.17",
"@esbuild/win32-arm64": "0.16.12", "@esbuild/win32-arm64": "0.16.17",
"@esbuild/win32-ia32": "0.16.12", "@esbuild/win32-ia32": "0.16.17",
"@esbuild/win32-x64": "0.16.12" "@esbuild/win32-x64": "0.16.17"
} }
}, },
"node_modules/escape-html": { "node_modules/escape-html": {
...@@ -1906,9 +1907,9 @@ ...@@ -1906,9 +1907,9 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "8.32.0", "version": "8.33.0",
"resolved": "https://registry.npmmirror.com/eslint/-/eslint-8.32.0.tgz", "resolved": "https://registry.npmmirror.com/eslint/-/eslint-8.33.0.tgz",
"integrity": "sha512-nETVXpnthqKPFyuY2FNjz/bEd6nbosRgKbkgS/y1C7LJop96gYHWpiguLecMHQ2XCPxn77DS0P+68WzG6vkZSQ==", "integrity": "sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint/eslintrc": "^1.4.1", "@eslint/eslintrc": "^1.4.1",
...@@ -3842,9 +3843,9 @@ ...@@ -3842,9 +3843,9 @@
} }
}, },
"node_modules/pinia": { "node_modules/pinia": {
"version": "2.0.29", "version": "2.0.30",
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.0.29.tgz", "resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.0.30.tgz",
"integrity": "sha512-5z/KpFecq/cIgfeTnulJXldiLcTITRkTe3N58RKYSj0Pc1EdR6oyCdnf5A9jLoVwBqX5LtHhd0kGlpzWvk9oiQ==", "integrity": "sha512-q6DUmxWwe/mQgg+55QQjykpKC+aGeGdaJV3niminl19V08dE+LRTvSEuqi6/NLSGCKHI49KGL6tMNEOssFiMyA==",
"dependencies": { "dependencies": {
"@vue/devtools-api": "^6.4.5", "@vue/devtools-api": "^6.4.5",
"vue-demi": "*" "vue-demi": "*"
...@@ -3903,9 +3904,9 @@ ...@@ -3903,9 +3904,9 @@
"dev": true "dev": true
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.20", "version": "8.4.21",
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.20.tgz", "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.21.tgz",
"integrity": "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==", "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
"dependencies": { "dependencies": {
"nanoid": "^3.3.4", "nanoid": "^3.3.4",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
...@@ -4156,9 +4157,9 @@ ...@@ -4156,9 +4157,9 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "3.9.0", "version": "3.12.1",
"resolved": "https://registry.npmmirror.com/rollup/-/rollup-3.9.0.tgz", "resolved": "https://registry.npmmirror.com/rollup/-/rollup-3.12.1.tgz",
"integrity": "sha512-nGGylpmblyjTpF4lEUPgmOw6OVxRvnI6Iuuh6Lz4O/X66cVOX1XJSsqP1YamxQ+mPuFE7qJxLFDSCk8rNv5dDw==", "integrity": "sha512-t9elERrz2i4UU9z7AwISj3CQcXP39cWxgRWLdf4Tm6aKm1eYrqHIgjzXBgb67GNY1sZckTFFi0oMozh3/S++Ig==",
"dev": true, "dev": true,
"bin": { "bin": {
"rollup": "dist/bin/rollup" "rollup": "dist/bin/rollup"
...@@ -4204,9 +4205,9 @@ ...@@ -4204,9 +4205,9 @@
"dev": true "dev": true
}, },
"node_modules/sass": { "node_modules/sass": {
"version": "1.57.1", "version": "1.58.0",
"resolved": "https://registry.npmmirror.com/sass/-/sass-1.57.1.tgz", "resolved": "https://registry.npmmirror.com/sass/-/sass-1.58.0.tgz",
"integrity": "sha512-O2+LwLS79op7GI0xZ8fqzF7X2m/m8WFfI02dHOdsK5R2ECeS5F62zrwg/relM1rjSLy7Vd/DiMNIvPrQGsA0jw==", "integrity": "sha512-PiMJcP33DdKtZ/1jSjjqVIKihoDc6yWmYr9K/4r3fVVIEDAluD0q7XZiRKrNJcPK3qkLRF/79DND1H5q1LBjgg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"chokidar": ">=3.0.0 <4.0.0", "chokidar": ">=3.0.0 <4.0.0",
...@@ -4651,9 +4652,9 @@ ...@@ -4651,9 +4652,9 @@
} }
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "4.9.4", "version": "4.9.5",
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-4.9.4.tgz", "resolved": "https://registry.npmmirror.com/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"devOptional": true, "devOptional": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
...@@ -4694,9 +4695,9 @@ ...@@ -4694,9 +4695,9 @@
} }
}, },
"node_modules/unimport": { "node_modules/unimport": {
"version": "2.0.1", "version": "2.1.0",
"resolved": "https://registry.npmmirror.com/unimport/-/unimport-2.0.1.tgz", "resolved": "https://registry.npmmirror.com/unimport/-/unimport-2.1.0.tgz",
"integrity": "sha512-hMeDspGrEcocahicTr0AQYUGes24FvJtOxk9QEjeEOGv+n1EdpsDiT6z8t209PWhemPg0T5w/ooTVhup2GdrFA==", "integrity": "sha512-GDVIxATluUquX8EqelT6DtnmnZaXGID1jsO9IXwlnxb0OIEqKAxTOnTlnGmHbseoGTh+ZC9kcNDaO18HYQj9KA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@rollup/pluginutils": "^5.0.2", "@rollup/pluginutils": "^5.0.2",
...@@ -4764,16 +4765,16 @@ ...@@ -4764,16 +4765,16 @@
} }
}, },
"node_modules/unplugin-auto-import": { "node_modules/unplugin-auto-import": {
"version": "0.12.2", "version": "0.13.0",
"resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-0.12.2.tgz", "resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-0.13.0.tgz",
"integrity": "sha512-hC4w0GZjPjmLtrxV0u10XO350V9eCtQyEyifXr7B9UGD7SvbbIvKuOcHt58Zd4FAqZJXKWoXkpr9mdhBp85Usw==", "integrity": "sha512-nKMxDbkjM4FRPInFfm7sWrJOKgxfKKwb5yLPP+DEGl/SG0/FtBoW1LnZL4PQfx0FXjertoHO1P/5nDf+RSip2Q==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@antfu/utils": "^0.7.2", "@antfu/utils": "^0.7.2",
"@rollup/pluginutils": "^5.0.2", "@rollup/pluginutils": "^5.0.2",
"local-pkg": "^0.4.3", "local-pkg": "^0.4.3",
"magic-string": "^0.27.0", "magic-string": "^0.27.0",
"unimport": "^2.0.1", "unimport": "^2.1.0",
"unplugin": "^1.0.1" "unplugin": "^1.0.1"
}, },
"engines": { "engines": {
...@@ -4883,15 +4884,15 @@ ...@@ -4883,15 +4884,15 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "4.0.4", "version": "4.1.1",
"resolved": "https://registry.npmmirror.com/vite/-/vite-4.0.4.tgz", "resolved": "https://registry.npmmirror.com/vite/-/vite-4.1.1.tgz",
"integrity": "sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==", "integrity": "sha512-LM9WWea8vsxhr782r9ntg+bhSFS06FJgCvvB0+8hf8UWtvaiDagKYWXndjfX6kGl74keHJUcpzrQliDXZlF5yg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"esbuild": "^0.16.3", "esbuild": "^0.16.14",
"postcss": "^8.4.20", "postcss": "^8.4.21",
"resolve": "^1.22.1", "resolve": "^1.22.1",
"rollup": "^3.7.0" "rollup": "^3.10.0"
}, },
"bin": { "bin": {
"vite": "bin/vite.js" "vite": "bin/vite.js"
...@@ -4948,15 +4949,15 @@ ...@@ -4948,15 +4949,15 @@
} }
}, },
"node_modules/vue": { "node_modules/vue": {
"version": "3.2.45", "version": "3.2.47",
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.2.45.tgz", "resolved": "https://registry.npmmirror.com/vue/-/vue-3.2.47.tgz",
"integrity": "sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==", "integrity": "sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==",
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.2.45", "@vue/compiler-dom": "3.2.47",
"@vue/compiler-sfc": "3.2.45", "@vue/compiler-sfc": "3.2.47",
"@vue/runtime-dom": "3.2.45", "@vue/runtime-dom": "3.2.47",
"@vue/server-renderer": "3.2.45", "@vue/server-renderer": "3.2.47",
"@vue/shared": "3.2.45" "@vue/shared": "3.2.47"
} }
}, },
"node_modules/vue-eslint-parser": { "node_modules/vue-eslint-parser": {
......
...@@ -17,12 +17,13 @@ ...@@ -17,12 +17,13 @@
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.0.10", "@element-plus/icons-vue": "^2.0.10",
"@tinymce/tinymce-vue": "^5.0.0", "@tinymce/tinymce-vue": "^5.0.0",
"@vueuse/core": "^9.11.1", "@vueuse/core": "^9.12.0",
"axios": "^1.2.5", "axios": "^1.3.1",
"blueimp-md5": "^2.19.0", "blueimp-md5": "^2.19.0",
"element-plus": "^2.2.28", "element-plus": "^2.2.28",
"pinia": "^2.0.29", "lodash-es": "^4.17.21",
"vue": "^3.2.45", "pinia": "^2.0.30",
"vue": "^3.2.47",
"vue-router": "^4.1.6" "vue-router": "^4.1.6"
}, },
"devDependencies": { "devDependencies": {
...@@ -34,13 +35,13 @@ ...@@ -34,13 +35,13 @@
"@vue/tsconfig": "^0.1.3", "@vue/tsconfig": "^0.1.3",
"ali-oss": "^6.17.1", "ali-oss": "^6.17.1",
"chalk": "^5.2.0", "chalk": "^5.2.0",
"eslint": "^8.32.0", "eslint": "^8.33.0",
"eslint-plugin-vue": "^9.9.0", "eslint-plugin-vue": "^9.9.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"sass": "^1.57.1", "sass": "^1.58.0",
"typescript": "~4.9.4", "typescript": "~4.9.5",
"unplugin-auto-import": "^0.12.2", "unplugin-auto-import": "^0.13.0",
"vite": "^4.0.4", "vite": "^4.1.1",
"vue-tsc": "^1.0.24" "vue-tsc": "^1.0.24"
} }
} }
...@@ -43,3 +43,17 @@ export function getUploadVideoAuth(data: { title: string; file_name: string }) { ...@@ -43,3 +43,17 @@ export function getUploadVideoAuth(data: { title: string; file_name: string }) {
export function updateUploadVideoAuth(data: { source_id: string }) { export function updateUploadVideoAuth(data: { source_id: string }) {
return httpRequest.post('/api/lab/v1/teacher/video/create-auth', data) return httpRequest.post('/api/lab/v1/teacher/video/create-auth', data)
} }
// 获取实验下的所有用户属性
export function getMetaUserAttrList() {
return httpRequest.get('/api/lab/v1/experiment/meta-member/all')
}
// 获取实验下的所有事件
export function getMetaEventList() {
return httpRequest.get('/api/lab/v1/experiment/meta-event/all')
}
// 获取实验下的所有标签
export function getTagList(params?: { check_role?: boolean }) {
return httpRequest.get('/api/lab/v1/experiment/tag/all', { params })
}
.rule {
display: flex;
.el-form-item {
margin-right: 10px;
margin-bottom: 0;
}
.el-select,
.el-input {
width: 120px;
}
}
.rule-operator {
margin-right: 10px;
position: relative;
min-width: 40px;
min-height: 40px;
&::before {
content: ' ';
position: absolute;
left: 50%;
top: 0;
bottom: 0;
width: 2px;
background-color: #d4dbe1;
transform: translateX(-50%);
}
span {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 24px;
height: 24px;
line-height: 24px;
color: var(--main-color);
background: #fff;
text-align: center;
border: 1px solid #d4dbe1;
border-radius: 50%;
cursor: pointer;
}
}
.rule-list {
flex: 1;
}
.rule-item {
margin: 10px 0;
}
...@@ -148,9 +148,9 @@ defineExpose({ refetch, tableRef }) ...@@ -148,9 +148,9 @@ defineExpose({ refetch, tableRef })
@change="search" @change="search"
v-if="item.type === 'select'"> v-if="item.type === 'select'">
<el-option <el-option
:label="option[item.labelKey] || option.label"
:value="option[item.valueKey] || option.value"
v-for="(option, index) in item.options" v-for="(option, index) in item.options"
:label="option[item.labelKey] || option.label || option"
:value="option[item.valueKey] || option.value || option"
:key="index" /> :key="index" />
</el-select> </el-select>
</template> </template>
......
<script setup lang="ts">
import type { EventRule, EventRuleItem, RuleAttr } from '@/types'
import { Operation, Plus, CloseBold } from '@element-plus/icons-vue'
import { useMetaEvent } from '@/composables/useAllData'
import {
stringOperatorList,
numberOperatorList,
dateOperatorList,
happenInfoList,
triggerInfoList
} from '@/utils/dictionary'
const eventAttrRule = ref(inject('eventAttrRule') as EventRule)
const { metaEventList } = useMetaEvent()
// 获取逻辑运算符名称
function getLogicalName(value: 'and' | 'or') {
return value === 'or' ? '或' : '且'
}
// 获取运算符列表
function getOperatorList(type: string) {
if (type === '1') return stringOperatorList
if (type === '2' || type === '3') return numberOperatorList
if (type === '4' || type === '5') return dateOperatorList
return stringOperatorList
}
// 获取事件属性列表
function getEventAttrList(eventId: string) {
return metaEventList.value.find(item => item.id === eventId)?.event_attrs || []
}
// 切换逻辑运算符
function toggleOperate(rule: EventRule) {
rule.current_logic_operate = rule.current_logic_operate === 'or' ? 'and' : 'or'
}
// 添加条件
function handleAdd() {
eventAttrRule.value.items.push({
happen_info: { is_happened: true, event_id: '', event_name: '', attr_list: [] },
trigger_info: { operate: '', operate_name: '', value: '' }
})
}
// 删除条件
function handleRemove(index: number) {
eventAttrRule.value.items.splice(index, 1)
}
// 事件改变
function handleEventChange(value: string, rule: EventRuleItem) {
const currentEvent = metaEventList.value.find(item => item.id === value)
rule.happen_info.event_name = currentEvent?.name || ''
}
// 添加属性条件
function handleAttrAdd(attrs: RuleAttr[]) {
attrs.push({ attr_id: '', attr: '', attr_name: '', attr_type: '', operate: '', operate_name: '', value: '' })
}
// 删除属性条件
function handleAttrRemove(attrs: RuleAttr[], index: number) {
attrs.splice(index, 1)
}
// 属性改变
function handleAttrChange(value: string, attr: RuleAttr, rule: EventRuleItem) {
const found = getEventAttrList(rule.happen_info.event_id).find(item => item.id === value)
attr.attr = found?.english_name || ''
attr.attr_name = found?.name || ''
attr.attr_type = found?.type || ''
// 清空条件数据
attr.operate = ''
attr.operate_name = ''
attr.value = ''
}
// 条件改变
function handleOperateChange(value: string, item: RuleAttr) {
const found = getOperatorList(item.attr_type).find(item => item.value === value)
item.operate_name = found?.label || ''
item.value = ''
}
// 条件改变
function handleTriggerOperateChange(value: string, rule: EventRuleItem) {
const found = numberOperatorList.find(item => item.value === value)
rule.trigger_info.operate_name = found?.label || ''
}
</script>
<template>
<el-card shadow="never">
<template #header>
<el-button circle color="#f90" :icon="Operation"></el-button>
事件属性满足以下条件
</template>
<div class="rule" v-if="eventAttrRule.items.length">
<div class="rule-operator">
<span @click="toggleOperate(eventAttrRule)">{{ getLogicalName(eventAttrRule.current_logic_operate) }}</span>
</div>
<div class="rule-list">
<section class="rule-item" v-for="(rule, index) in eventAttrRule.items" :key="index">
<el-row>
<!-- 发生 -->
<el-form-item>
<el-select v-model="rule.happen_info.is_happened">
<el-option v-for="option in happenInfoList" v-bind="option"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="rule.happen_info.event_id" @change="value => handleEventChange(value, rule)">
<el-option
v-for="option in metaEventList"
:key="option.id"
:label="option.name"
:value="option.id"></el-option>
</el-select>
</el-form-item>
<el-button text :icon="Plus" @click="handleAttrAdd(rule.happen_info.attr_list)">添加条件</el-button>
<el-button text :icon="CloseBold" @click="handleRemove(index)"></el-button>
</el-row>
<!-- 属性条件 -->
<el-row class="rule-item" justify="space-between" v-for="(attr, index) in rule.happen_info.attr_list">
<div>
<el-form-item>
<el-select v-model="attr.attr_id" @change="value => handleAttrChange(value, attr, rule)">
<el-option
v-for="option in getEventAttrList(rule.happen_info.event_id)"
:key="option.id"
:label="option.name"
:value="option.id"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="attr.operate" @change="value => handleOperateChange(value, attr)">
<el-option
v-for="option in getOperatorList(attr.attr_type)"
:label="option.alias || option.label"
:value="option.value"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-input v-model="attr.value" />
</el-form-item>
</div>
<el-button text :icon="CloseBold" @click="handleAttrRemove(rule.happen_info.attr_list, index)"></el-button>
</el-row>
<!-- 触发 -->
<el-row style="margin-top: 10px">
<el-form-item>
<el-select model-value="触发次数">
<el-option v-for="option in triggerInfoList" v-bind="option"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="rule.trigger_info.operate" @change="value => handleTriggerOperateChange(value, rule)">
<el-option
v-for="option in numberOperatorList"
:label="option.alias || option.label"
:value="option.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-input v-model="rule.trigger_info.value" />
</el-form-item>
</el-row>
</section>
</div>
</div>
<el-button text :icon="Plus" @click="handleAdd">添加条件</el-button>
</el-card>
</template>
<style src="@/assets/styles/rule.scss"></style>
<script setup lang="ts"></script>
<template></template>
<script setup lang="ts">
import type { UserAttrRule, RuleAttr } from '@/types'
import { UserFilled, Plus, CloseBold } from '@element-plus/icons-vue'
import { useUserAttr } from '@/composables/useAllData'
import { stringOperatorList, numberOperatorList, dateOperatorList } from '@/utils/dictionary'
const userAttrRule = ref(inject('userAttrRule') as UserAttrRule)
const { userAttrList } = useUserAttr()
// 获取逻辑运算符名称
function getLogicalName(value: 'and' | 'or') {
return value === 'or' ? '或' : '且'
}
// 获取运算符列表
function getOperatorList(type: string) {
if (type === '1') return stringOperatorList
if (type === '2' || type === '3') return numberOperatorList
if (type === '4' || type === '5') return dateOperatorList
return stringOperatorList
}
// 切换逻辑运算符
function toggleOperate(rule: UserAttrRule) {
rule.current_logic_operate = rule.current_logic_operate === 'or' ? 'and' : 'or'
}
// 添加条件
function handleAdd() {
userAttrRule.value.items.push({
attr_id: '',
attr: '',
attr_name: '',
attr_type: '',
operate: '',
operate_name: '',
value: ''
})
}
// 删除
function handleRemove(rule: UserAttrRule, index: number) {
rule.items.splice(index, 1)
}
// 属性改变
function handleAttrChange(value: string, item: RuleAttr) {
const found = userAttrList.value.find(item => item.id === value)
item.attr = found?.english_name || ''
item.attr_name = found?.name || ''
item.attr_type = found?.type || ''
// 清空条件数据
item.operate = ''
item.operate_name = ''
item.value = ''
}
// 条件改变
function handleOperateChange(value: string, item: RuleAttr) {
const found = getOperatorList(item.attr_type).find(item => item.value === value)
item.operate_name = found?.label || ''
item.value = ''
}
</script>
<template>
<el-card shadow="never">
<template #header>
<el-button circle color="#006df1" :icon="UserFilled"></el-button>
用户属性满足以下条件
</template>
<div class="rule" v-if="userAttrRule.items.length">
<div class="rule-operator">
<span @click="toggleOperate(userAttrRule)">{{ getLogicalName(userAttrRule.current_logic_operate) }}</span>
</div>
<div class="rule-list">
<el-row class="rule-item" v-for="(item, index) in userAttrRule.items" justify="space-between">
<div>
<el-form-item>
<el-select v-model="item.attr_id" @change="value => handleAttrChange(value, item)">
<el-option
v-for="option in userAttrList"
:key="option.id"
:label="option.name"
:value="option.id"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="item.operate" @change="value => handleOperateChange(value, item)">
<el-option
v-for="option in getOperatorList(item.attr_type)"
:label="option.alias || option.label"
:value="option.value"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-input v-model="item.value" />
</el-form-item>
</div>
<el-button text :icon="CloseBold" @click="handleRemove(userAttrRule, index)"></el-button>
</el-row>
</div>
</div>
<el-button text :icon="Plus" @click="handleAdd">添加条件</el-button>
</el-card>
</template>
<style src="@/assets/styles/rule.scss"></style>
import { getMetaUserAttrList, getMetaEventList, getTagList } from '@/api/base'
interface AttrType {
id: string
name: string
type: string
format: string
english_name: string
pinyin: string
}
const userAttrList = ref<AttrType[]>([])
export function useUserAttr() {
function fetchUserAttrList() {
getMetaUserAttrList().then((res: any) => {
userAttrList.value = res.data.items
})
}
onMounted(() => {
if (!userAttrList.value?.length) fetchUserAttrList()
})
return { fetchUserAttrList, userAttrList }
}
interface MetaEvent {
id: string
name: string
english_name: string
pinyin: string
event_attrs: AttrType[]
}
const metaEventList = ref<MetaEvent[]>([])
export function useMetaEvent() {
function fetchMetaEventList() {
getMetaEventList().then((res: any) => {
metaEventList.value = res.data.items
})
}
onMounted(() => {
if (!metaEventList.value?.length) fetchMetaEventList()
})
return { fetchMetaEventList, metaEventList }
}
const tagList = ref([])
export function useTag() {
function fetchTagList() {
getTagList({ check_role: true }).then((res: any) => {
tagList.value = res.data.items
})
}
onMounted(() => {
if (!tagList.value?.length) fetchTagList()
})
return { fetchTagList, tagList }
}
import httpRequest from '@/utils/axios'
import type {
GroupListRequest,
StaticGroupCreateRequest,
StaticGroupUpdateRequest,
DynamicGroupCreateRequest,
DynamicGroupUpdateRequest
} from './types'
// 获取群组列表
export function getGroupList(params?: GroupListRequest) {
return httpRequest.get('/api/lab/v1/experiment/group/list', { params })
}
// 创建静态群组
export function createStaticGroup(data: StaticGroupCreateRequest) {
return httpRequest.post('/api/lab/v1/experiment/group/create-static-group', data)
}
// 创建动态群组
export function createDynamicGroup(data: DynamicGroupCreateRequest) {
return httpRequest.post('/api/lab/v1/experiment/group/create-dynamic-group', data)
}
// 更新静态群组
export function updateStaticGroup(data: StaticGroupUpdateRequest) {
return httpRequest.post('/api/lab/v1/experiment/group/update-static-group', data)
}
// 更新动态群组
export function updateDynamicGroup(data: DynamicGroupUpdateRequest) {
return httpRequest.post('/api/lab/v1/experiment/group/update-dynamic-group', data)
}
// 删除群组
export function deleteGroup(data: { ids: string[] }) {
return httpRequest.post('/api/lab/v1/experiment/group/delete', data)
}
// 获取群组数据信息
export function getGroupStatistics(params: { group_id: string }) {
return httpRequest.get('/api/lab/v1/experiment/group/statistics-detail', { params })
}
// 获取群组成员
export function getGroupMembers(params: { group_id: string; name?: string; id?: string }) {
return httpRequest.get('/api/lab/v1/experiment/group/members', { params })
}
// 清空静态群组成员
export function clearGroupMembers(data: { group_id: string }) {
return httpRequest.post('/api/lab/v1/experiment/group/clear-members', data)
}
// 搜索群组成员
export function searchGroupMembers(params: { group_id: string; name?: string; id?: string }) {
return httpRequest.get('/api/lab/v1/experiment/group/search-members', { params })
}
// 手动添加静态群组
export function addGroupMembers(data: { group_id: string; user_ids: string[] }) {
return httpRequest.post('/api/lab/v1/experiment/group/add-members', data)
}
<script setup lang="ts">
import type { Group, GroupMember } from '../types'
import { ElMessage } from 'element-plus'
import AppList from '@/components/base/AppList.vue'
import { getNameByValue, groupTypeList } from '@/utils/dictionary'
import { useMapStore } from '@/stores/map'
import { searchGroupMembers, addGroupMembers } from '../api'
const props = defineProps<{
data: Group
}>()
const emit = defineEmits<{
(e: 'update'): void
(e: 'update:modelValue', visible: boolean): void
}>()
const statusList = useMapStore().getMapValuesByKey('system_status')
const appList = $ref<InstanceType<typeof AppList> | null>(null)
// 列表配置
const listOptions = computed(() => {
return {
remote: {
httpRequest: searchGroupMembers,
params: { group_id: props.data.id }
},
columns: [
{ type: 'selection' },
{ label: '序号', type: 'index', width: 60 },
{ label: '用户ID', prop: 'id' },
{ label: '姓名', prop: 'name' },
{ label: '性别', prop: 'gender' },
{ label: '手机号码', prop: 'mobile' },
{ label: '来源连接', prop: 'experiment_connection_id' },
{ label: '状态', prop: 'status' },
{ label: '更新人', prop: 'updated_operator.real_name' },
{ label: '更新时间', prop: 'updated_time' }
]
}
})
let multipleSelection = $ref<GroupMember[]>([])
function handleSelectionChange(selection: GroupMember[]) {
multipleSelection = selection
}
// 添加
function handleAdd() {
const ids = multipleSelection.map(item => item.id)
addGroupMembers({ group_id: props.data.id, user_ids: ids }).then(() => {
ElMessage({ message: '添加成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
})
}
</script>
<template>
<el-dialog title="添加群组用户" width="800px" append-to-body @update:modelValue="$emit('update:modelValue')">
<el-form label-suffix=":" label-width="82px">
<el-row>
<el-col :span="8">
<el-form-item label="群组名称">{{ data.name }}</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="群组类型">{{ getNameByValue(data.type, groupTypeList) }}</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="状态">
<el-tag :type="data.status === '1' ? 'success' : 'danger'">
{{ getNameByValue(data.status, statusList) }}
</el-tag>
</el-form-item>
</el-col>
</el-row>
</el-form>
<AppList v-bind="listOptions" ref="appList" @selection-change="handleSelectionChange">
<template #table-status="{ row }: { row: Group }">
<el-tag :type="row.status === '1' ? 'success' : 'danger'">
{{ getNameByValue(row.status, statusList) }}
</el-tag>
</template>
</AppList>
<template #footer>
<el-row justify="center">
<el-button plain auto-insert-space @click="$emit('update:modelValue', false)">关闭</el-button>
<el-button type="primary" plain auto-insert-space @click="handleAdd" :disabled="!multipleSelection.length"
>添加</el-button
>
</el-row>
</template>
</el-dialog>
</template>
<style lang="scss">
.group-box {
display: flex;
justify-content: space-between;
dt {
margin-bottom: 10px;
text-align: center;
}
dd {
display: flex;
align-items: center;
justify-content: center;
span {
padding: 0 5px;
}
}
}
.group-box__count {
font-weight: bold;
color: var(--main-color);
}
</style>
<script setup lang="ts">
import type { Group } from '../types'
import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage } from 'element-plus'
import { updateStatusRuleList, dateUnitList, weekList } from '@/utils/dictionary'
import { createStaticGroup, updateStaticGroup, createDynamicGroup, updateDynamicGroup } from '../api'
import { pick } from 'lodash-es'
interface Props {
data: Partial<Group>
}
const props = defineProps<Props>()
const emit = defineEmits<{
(e: 'update'): void
(e: 'update:modelValue', visible: boolean): void
}>()
const isUpdate = $computed(() => !!props.data?.id)
const title = $computed(() => {
if (isUpdate) {
return props.data.type === '1' ? '修改静态群组' : '修改动态群组'
} else {
return props.data.type === '1' ? '新建静态群组' : '新建动态群组'
}
})
const formRef = $ref<FormInstance>()
const form = reactive({
id: '',
name: '',
type: '',
status: '1',
update_status: '1',
update_rule: { type: 1, info: 1 },
user_attr_rule: [],
event_attr_rule: [],
tag_rule: []
})
watchEffect(() => {
if (props.data?.id) {
let updateRule = { type: 1, info: 1 }
try {
updateRule = JSON.parse(props.data.update_rule as string)
} catch (error) {
console.log(error)
}
Object.assign(form, props.data, { update_rule: updateRule })
}
})
const rules = ref<FormRules>({
name: [{ required: true, message: '请输入群组名称' }],
url: [{ required: true, message: '请选择标签类型图标' }]
})
// 提交
function handleSubmit() {
formRef?.validate().then(() => (isUpdate ? handleUpdate() : handleCreate()))
}
// 新建
async function handleCreate() {
if (props.data.type === '1') {
// 静态群组
const params = pick(form, ['name', 'status'])
await createStaticGroup(params)
} else {
// 动态群组
const params = pick(
{
...form,
update_rule: JSON.stringify(form.update_rule),
user_attr_rule: JSON.stringify(form.user_attr_rule),
event_attr_rule: JSON.stringify(form.event_attr_rule),
tag_rule: JSON.stringify(form.tag_rule)
},
['name', 'update_status', 'update_rule', 'user_attr_rule', 'event_attr_rule', 'tag_rule', 'status']
)
await createDynamicGroup(params)
}
ElMessage({ message: '创建成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
}
// 修改
async function handleUpdate() {
if (props.data.type === '1') {
// 静态群组
const params = pick(form, ['id', 'name', 'status'])
await updateStaticGroup(params)
} else {
// 动态群组
const params = pick(
{
...form,
update_rule: JSON.stringify(form.update_rule),
user_attr_rule: JSON.stringify(form.user_attr_rule),
event_attr_rule: JSON.stringify(form.event_attr_rule),
tag_rule: JSON.stringify(form.tag_rule)
},
['id', 'name', 'update_status', 'update_rule', 'user_attr_rule', 'event_attr_rule', 'tag_rule', 'status']
)
await updateDynamicGroup(params)
}
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-suffix=":" label-width="100px">
<el-form-item label="群组名称" prop="name">
<el-input v-model="form.name" placeholder="请输入" />
</el-form-item>
<template v-if="data.type === '2'">
<el-form-item label="更新频率" prop="update_status">
<el-radio-group v-model="form.update_status">
<el-radio v-for="item in updateStatusRuleList" :key="item.value" :label="item.value">
{{ item.label }}
</el-radio>
</el-radio-group>
<div class="update-rule-wrap" v-if="form.update_status === '1'">
<span></span>
<el-select v-model="form.update_rule.type" placeholder=" " style="width: 60px">
<el-option v-for="item in dateUnitList" :label="item.label" :value="item.value"></el-option>
</el-select>
<template v-if="form.update_rule.type === 1">
<span>的凌晨更新</span>
</template>
<template v-if="form.update_rule.type === 2">
<span></span>
<el-select v-model="form.update_rule.info" placeholder=" " style="width: 80px">
<el-option v-for="item in weekList" :label="item.label" :value="item.value"></el-option>
</el-select>
<span>的凌晨更新</span>
</template>
<template v-if="form.update_rule.type === 3">
<span></span>
<el-select v-model="form.update_rule.info" placeholder=" " style="width: 60px">
<el-option v-for="item in 6" :label="item" :value="item"></el-option>
</el-select>
<span>天的凌晨更新</span>
</template>
</div>
</el-form-item>
</template>
<el-form-item label="状态" prop="status">
<el-switch v-model="form.status" active-text="生效" active-value="1" inactive-text="失效" inactive-value="0" />
</el-form-item>
</el-form>
<template #footer>
<el-row justify="center">
<el-button plain auto-insert-space @click="$emit('update:modelValue', false)">关闭</el-button>
<el-button type="primary" auto-insert-space @click="handleSubmit">保存</el-button>
</el-row>
</template>
</el-dialog>
</template>
<style lang="scss">
.update-rule-wrap {
width: 100%;
.el-select {
margin: 0 10px;
}
}
</style>
<script setup lang="ts">
import type { Group } from '../types'
import { UserFilled, Plus, Delete } from '@element-plus/icons-vue'
import { ElMessageBox, ElMessage } from 'element-plus'
import AppList from '@/components/base/AppList.vue'
import { getNameByValue, groupTypeList, updateStatusRuleList } from '@/utils/dictionary'
import { useMapStore } from '@/stores/map'
import { getGroupStatistics, getGroupMembers, clearGroupMembers } from '../api'
import BindMembers from './BindMembers.vue'
const props = defineProps<{
data: Group
}>()
const emit = defineEmits<{
(e: 'update'): void
(e: 'update:modelValue', visible: boolean): void
}>()
const statusList = useMapStore().getMapValuesByKey('system_status')
const detail = reactive({ count: 0, status: 1, updated_time: '-' })
function fetchInfo() {
getGroupStatistics({ group_id: props.data.id }).then(res => {
Object.assign(detail, res.data.detail)
})
}
watchEffect(() => fetchInfo())
// 清空群组成员
function handleClearMembers() {
ElMessageBox.confirm('确定要删清空该群组成员吗?', '提示').then(() => {
clearGroupMembers({ group_id: props.data.id }).then(() => {
ElMessage({ message: '清空成功', type: 'success' })
handleRefresh()
})
})
}
const appList = $ref<InstanceType<typeof AppList> | null>(null)
// 列表配置
const listOptions = computed(() => {
return {
remote: {
httpRequest: getGroupMembers,
params: { group_id: props.data.id }
},
columns: [
{ label: '序号', type: 'index', width: 60 },
{ label: '用户ID', prop: 'id' },
{ label: '姓名', prop: 'name' },
{ label: '性别', prop: 'gender' },
{ label: '手机号码', prop: 'mobile' },
{ label: '来源连接', prop: 'experiment_connection_id' },
{ label: '状态', prop: 'status' },
{ label: '更新人', prop: 'updated_operator.real_name' },
{ label: '更新时间', prop: 'updated_time' }
]
}
})
// 刷新
function handleRefresh() {
fetchInfo()
appList?.refetch()
}
// 添加群组成员
let selectMembersVisible = $ref(false)
function handleAdd() {
selectMembersVisible = true
}
</script>
<template>
<el-dialog title="查看群组信息" width="800px" @update:modelValue="$emit('update:modelValue')">
<el-form label-suffix=":" label-width="82px">
<el-row>
<el-col :span="12">
<el-form-item label="群组名称">{{ data.name }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="群组类型">{{ getNameByValue(data.type, groupTypeList) }}</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="更新频率">{{ getNameByValue(data.update_status, updateStatusRuleList) }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态">
<el-tag :type="data.status === '1' ? 'success' : 'danger'">
{{ getNameByValue(data.status, statusList) }}
</el-tag>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-card>
<div class="group-box">
<dl>
<dt>群组人数</dt>
<dd class="label-box__count">
<el-icon><UserFilled></UserFilled></el-icon>
<span>{{ detail.count }}</span>
</dd>
</dl>
<template v-if="data.type === '1'">
<dl>
<dt></dt>
<dd>
<el-button type="danger" :icon="Delete" @click="handleClearMembers">清空用户群组</el-button>
<el-button type="primary" :icon="Plus" @click="handleAdd">添加用户</el-button>
</dd>
</dl>
</template>
<template v-else>
<dl>
<dt>最后更新时间</dt>
<dd>{{ detail.updated_time }}</dd>
</dl>
<dl>
<dt>更新状态</dt>
<dd>
<span>完成</span>
<el-button type="primary" plain size="small">立即更新</el-button>
</dd>
</dl>
</template>
</div>
</el-card>
<el-card style="margin-top: 20px">
<template #header>群组用户</template>
<AppList v-bind="listOptions" ref="appList"></AppList>
</el-card>
<template #footer>
<el-row justify="center">
<el-button plain auto-insert-space @click="$emit('update:modelValue', false)">关闭</el-button>
</el-row>
</template>
<BindMembers
:data="data"
v-model="selectMembersVisible"
@update="handleRefresh"
v-if="selectMembersVisible"></BindMembers>
</el-dialog>
</template>
<style lang="scss">
.group-box {
display: flex;
justify-content: space-between;
dt {
margin-bottom: 10px;
text-align: center;
}
dd {
display: flex;
align-items: center;
justify-content: center;
span {
padding: 0 5px;
}
}
}
.group-box__count {
font-weight: bold;
color: var(--main-color);
}
</style>
import type { Operator } from '@/types'
export interface Group {
id: string
name: string
type_id: string
status: string
type: '1' | '2'
update_status: string // '1' | '2'
update_rule: string
created_time: string
created_operator: Operator
updated_time: string
updated_operator: Operator
user_attr_rule?: string
event_attr_rule?: string
tag_rule?: string
}
export type GroupListRequest = Pick<Group, 'id' | 'name'> & { experiment_id?: string }
// 静态群组
export type StaticGroupUpdateRequest = Pick<Group, 'id' | 'name' | 'status'> & {
experiment_id?: string
}
export type StaticGroupCreateRequest = Omit<StaticGroupUpdateRequest, 'id'>
// 动态群组
export type DynamicGroupUpdateRequest = Pick<
Group,
'id' | 'name' | 'update_status' | 'update_rule' | 'user_attr_rule' | 'event_attr_rule' | 'tag_rule' | 'status'
> & {
experiment_id?: string
}
export type DynamicGroupCreateRequest = Omit<DynamicGroupUpdateRequest, 'id'>
export interface GroupMember {
id: string
name: string
gender: string
mobile: string
status: string
created_time: string
created_operator: Operator
updated_time: string
updated_operator: Operator
}
<script setup lang="ts"> <script setup lang="ts">
import type { Group } from '../types'
import { Plus, Delete } from '@element-plus/icons-vue' import { Plus, Delete } from '@element-plus/icons-vue'
import AppList from '@/components/base/AppList.vue' import AppList from '@/components/base/AppList.vue'
import { ElMessageBox, ElMessage } from 'element-plus'
import { getNameByValue, groupTypeList, updateStatusRuleList } from '@/utils/dictionary'
import { getGroupList, deleteGroup } from '../api'
import { useMapStore } from '@/stores/map'
const FormDialog = defineAsyncComponent(() => import('../components/FormDialog.vue'))
const ViewDialog = defineAsyncComponent(() => import('../components/ViewDialog.vue'))
const statusList = useMapStore().getMapValuesByKey('system_status')
const appList = $ref<InstanceType<typeof AppList> | null>(null) const appList = $ref<InstanceType<typeof AppList> | null>(null)
// 列表配置 // 列表配置
const listOptions = computed(() => { const listOptions = computed(() => {
return { return {
filters: [{ type: 'input', prop: 'name', placeholder: '请输入群组名称' }], remote: {
httpRequest: getGroupList,
params: { name: '', id: '', status: '', updated_operator: '' }
},
filters: [
{ type: 'input', prop: 'name', placeholder: '请输入群组名称' },
{ type: 'input', prop: 'id', placeholder: '请输入群组ID' },
{ type: 'select', prop: 'status', placeholder: '请选择群组状态', options: statusList },
{ type: 'input', prop: 'updated_operator', placeholder: '更新人' }
],
columns: [ columns: [
{ type: 'selection' }, { type: 'selection' },
{ label: '序号', type: 'index', width: 60 }, { label: '序号', type: 'index', width: 60 },
{ label: '群主ID', prop: 'id' }, { label: '群组ID', prop: 'id' },
{ label: '群主名称', prop: 'name' }, { label: '群组名称', prop: 'name' },
{ label: '群主类型', prop: 'name' }, {
{ label: '更新方式', prop: 'name' }, label: '群组类型',
{ label: '最近计算完成时间', prop: 'name' }, prop: 'type',
{ label: '状态', prop: 'name' }, computed({ row }: { row: Group }) {
{ label: '更新人', prop: 'name' }, return getNameByValue(row.type, groupTypeList)
{ label: '更新时间', prop: 'name' }, }
},
{
label: '更新方式',
prop: 'update_status',
computed({ row }: { row: Group }) {
return getNameByValue(row.update_status, updateStatusRuleList)
}
},
{ label: '最近计算完成时间', prop: 'record.updated_time' },
{ label: '状态', prop: 'status', slots: 'table-status' },
{ label: '更新人', prop: 'created_operator.real_name' },
{ label: '更新时间', prop: 'updated_time' },
{ label: '操作', slots: 'table-x', width: 240 } { label: '操作', slots: 'table-x', width: 240 }
], ]
data: [{}, {}]
} }
}) })
...@@ -28,23 +58,88 @@ const listOptions = computed(() => { ...@@ -28,23 +58,88 @@ const listOptions = computed(() => {
function handleRefresh() { function handleRefresh() {
appList?.refetch() appList?.refetch()
} }
let multipleSelection = $ref<Group[]>([])
function handleSelectionChange(selection: Group[]) {
multipleSelection = selection
}
let formVisible = $ref(false)
let currentRow = $ref<Partial<Group>>()
// 新建
function handleAdd(type: '1' | '2') {
currentRow = { type }
formVisible = true
}
// 修改
function handleUpdate(row: Group) {
currentRow = row
formVisible = true
}
// 删除
function handleRemove(rows: Group[]) {
ElMessageBox.confirm(rows.length > 1 ? '确定要删除勾选群组吗?' : '确定要删除该群组吗?', '提示').then(() => {
const ids = rows.map(item => item.id)
deleteGroup({ ids }).then(() => {
ElMessage({ message: '删除成功', type: 'success' })
handleRefresh()
})
})
}
let viewVisible = $ref(false)
// 查看
function handleView(row: Group) {
currentRow = row
viewVisible = true
}
</script> </script>
<template> <template>
<AppCard> <AppCard>
<AppList v-bind="listOptions" ref="appList"> <AppList v-bind="listOptions" ref="appList" @selection-change="handleSelectionChange">
<template #header-buttons> <template #header-buttons>
<el-space> <el-space>
<el-button type="primary" :icon="Plus">新建</el-button> <el-dropdown>
<el-button type="danger" plain :icon="Delete">删除</el-button> <el-button type="primary" :icon="Plus">新建</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="handleAdd('2')">新建动态群组</el-dropdown-item>
<el-dropdown-item @click="handleAdd('1')">新建静态群组</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-button
type="danger"
plain
:icon="Delete"
:disabled="!multipleSelection.length"
@click="handleRemove(multipleSelection)"
>删除</el-button
>
</el-space> </el-space>
</template> </template>
<template #table-x> <template #table-status="{ row }: { row: Group }">
<el-button type="primary" plain>查看</el-button> <el-tag :type="row.status === '1' ? 'success' : 'danger'">
<el-button type="primary" plain>编辑</el-button> {{ getNameByValue(row.status, statusList) }}
<el-button type="primary" plain>删除</el-button> </el-tag>
</template>
<template #table-x="{ row }">
<el-button type="primary" plain @click="handleView(row)">查看</el-button>
<el-button type="primary" plain @click="handleUpdate(row)">编辑</el-button>
<el-button type="primary" plain @click="handleRemove([row])">删除</el-button>
</template> </template>
</AppList> </AppList>
</AppCard> </AppCard>
<!-- 新建/修改群组 -->
<FormDialog
v-model="formVisible"
:data="currentRow"
@update="handleRefresh"
v-if="formVisible && currentRow"></FormDialog>
<!-- 查看 -->
<ViewDialog v-model="viewVisible" :data="(currentRow as Group)" v-if="viewVisible && currentRow"></ViewDialog>
</template> </template>
import httpRequest from '@/utils/axios'
import type {
LabelTypeListRequest,
LabelTypeCreateRequest,
LabelTypeUpdateRequest,
LabelListRequest,
LabelCreateRequest,
LabelUpdateRequest
} from './types'
// 获取标签类型列表
export function getLabelTypeList(params?: LabelTypeListRequest) {
return httpRequest.get('/api/lab/v1/experiment/tag-type/list', { params })
}
// 创建标签类型
export function createLabelType(data: LabelTypeCreateRequest) {
return httpRequest.post('/api/lab/v1/experiment/tag-type/create', data)
}
// 更新标签类型
export function updateLabelType(data: LabelTypeUpdateRequest) {
return httpRequest.post('/api/lab/v1/experiment/tag-type/update', data)
}
// 更新标签类型
export function deleteLabelType(data: { id: string }) {
return httpRequest.post('/api/lab/v1/experiment/tag-type/delete', data)
}
// 获取标签列表
export function getLabelList(params?: LabelListRequest) {
return httpRequest.get('/api/lab/v1/experiment/tag/list', { params })
}
// 创建标签
export function createLabel(data: LabelCreateRequest) {
return httpRequest.post('/api/lab/v1/experiment/tag/create', data)
}
// 更新标签
export function updateLabel(data: LabelUpdateRequest) {
return httpRequest.post('/api/lab/v1/experiment/tag/update', data)
}
// 更新标签
export function deleteLabel(data: { id: string }) {
return httpRequest.post('/api/lab/v1/experiment/tag/delete', data)
}
// 获取标签数据信息
export function getLabelStatistics(params: { id: string }) {
return httpRequest.get('/api/lab/v1/experiment/tag/statistics', { params })
}
// 获取标签规则
export function getLabelRule(params: { id: string }) {
return httpRequest.get('/api/lab/v1/experiment/tag/rule', { params })
}
// 更新标签规则
export function updateLabelRule(data: { id: string; user_attr_rule: string; event_attr_rule: string }) {
return httpRequest.post('/api/lab/v1/experiment/tag/save-rule', data)
}
<script setup lang="ts">
import type { Label } from '../types'
import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage } from 'element-plus'
import { updateStatusRuleList, dateUnitList, weekList } from '@/utils/dictionary'
import { createLabel, updateLabel } from '../api'
import { useLabelType } from '../composables/useLabelType'
import { pick } from 'lodash-es'
const props = defineProps<{
data?: Label
}>()
const emit = defineEmits<{
(e: 'update'): void
(e: 'update:modelValue', visible: boolean): void
}>()
const isUpdate = $computed(() => !!props.data?.id)
const title = $computed(() => (isUpdate ? '修改标签' : '新建标签'))
const { typeList } = useLabelType()
const formRef = $ref<FormInstance>()
const form = reactive({
id: '',
name: '',
type_id: '',
update_status: '1',
update_rule: { type: 1, info: 1 },
status: '1'
})
watchEffect(() => {
if (props.data) {
let updateRule = { type: 1, info: 1 }
try {
updateRule = JSON.parse(props.data.update_rule)
} catch (error) {
console.log(error)
}
Object.assign(form, props.data, { update_rule: updateRule })
}
})
const rules = ref<FormRules>({
name: [{ required: true, message: '请输入标签名称' }],
type_id: [{ required: true, message: '请选择标签类型' }],
update_status: [{ required: true, message: '请选择更新评率' }]
})
// 提交
function handleSubmit() {
formRef?.validate().then(() => (isUpdate ? handleUpdate() : handleCreate()))
}
// 新建
function handleCreate() {
const params = pick({ ...form, update_rule: JSON.stringify(form.update_rule) }, [
'name',
'type_id',
'update_status',
'update_rule',
'status'
])
createLabel(params).then(() => {
ElMessage({ message: '创建成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
})
}
// 修改
function handleUpdate() {
const params = pick({ ...form, update_rule: JSON.stringify(form.update_rule) }, [
'id',
'name',
'type_id',
'update_status',
'update_rule',
'status'
])
updateLabel(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-suffix=":" label-width="100px">
<el-form-item label="标签名称" prop="name">
<el-input v-model="form.name" placeholder="请输入" />
</el-form-item>
<el-form-item label="标签类型" prop="type_id">
<el-select v-model="form.type_id" style="width: 100%">
<el-option v-for="item in typeList" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="更新频率" prop="update_status">
<el-radio-group v-model="form.update_status">
<el-radio v-for="item in updateStatusRuleList" :key="item.value" :label="item.value">
{{ item.label }}
</el-radio>
</el-radio-group>
<div class="update-rule-wrap" v-if="form.update_status === '1'">
<span></span>
<el-select v-model="form.update_rule.type" placeholder=" " style="width: 60px">
<el-option v-for="item in dateUnitList" :label="item.label" :value="item.value"></el-option>
</el-select>
<template v-if="form.update_rule.type === 1">
<span>的凌晨更新</span>
</template>
<template v-if="form.update_rule.type === 2">
<span></span>
<el-select v-model="form.update_rule.info" placeholder=" " style="width: 80px">
<el-option v-for="item in weekList" :label="item.label" :value="item.value"></el-option>
</el-select>
<span>的凌晨更新</span>
</template>
<template v-if="form.update_rule.type === 3">
<span></span>
<el-select v-model="form.update_rule.info" placeholder=" " style="width: 60px">
<el-option v-for="item in 6" :label="item" :value="item"></el-option>
</el-select>
<span>天的凌晨更新</span>
</template>
</div>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-switch v-model="form.status" active-text="生效" active-value="1" inactive-text="失效" inactive-value="0" />
</el-form-item>
</el-form>
<template #footer>
<el-row justify="center">
<el-button plain auto-insert-space @click="$emit('update:modelValue', false)">关闭</el-button>
<el-button type="primary" auto-insert-space @click="handleSubmit">保存</el-button>
</el-row>
</template>
</el-dialog>
</template>
<style lang="scss">
.update-rule-wrap {
width: 100%;
.el-select {
margin: 0 10px;
}
}
</style>
<script setup lang="ts">
import type { Label } from '../types'
import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage } from 'element-plus'
import { useMapStore } from '@/stores/map'
import { getNameByValue, updateStatusRuleList } from '@/utils/dictionary'
import { getLabelRule, updateLabelRule } from '../api'
import UserRule from '@/components/rule/UserRule.vue'
import EventRule from '@/components/rule/EventRule.vue'
const props = defineProps<{
data: Label
}>()
const emit = defineEmits<{
(e: 'update'): void
(e: 'update:modelValue', visible: boolean): void
}>()
const statusList = useMapStore().getMapValuesByKey('system_status')
const formRef = $ref<FormInstance>()
const form = reactive({
id: props.data.id,
user_attr_rule: { current_logic_operate: 'and', items: [] },
event_attr_rule: { current_logic_operate: 'and', items: [] }
})
provide('userAttrRule', toRef(form, 'user_attr_rule'))
provide('eventAttrRule', toRef(form, 'event_attr_rule'))
function fetchInfo() {
getLabelRule({ id: props.data.id }).then(res => {
const { detail } = res.data
const [user_attr_rule = { current_logic_operate: 'and', items: [] }] = detail.user_attr_rule
const [event_attr_rule = { current_logic_operate: 'and', items: [] }] = detail.event_attr_rule
Object.assign(form, detail, { user_attr_rule, event_attr_rule })
})
}
watchEffect(() => fetchInfo())
const rules = ref<FormRules>({
name: [{ required: true, message: '请输入标签名称' }],
type: [{ required: true, message: '请选择标签类型' }]
})
// 提交
function handleSubmit() {
formRef?.validate().then(handleUpdate)
}
// 保存
function handleUpdate() {
const params = {
id: form.id,
user_attr_rule: JSON.stringify([form.user_attr_rule]),
event_attr_rule: JSON.stringify([form.event_attr_rule])
}
updateLabelRule(params).then(() => {
ElMessage({ message: '保存成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
})
}
</script>
<template>
<el-dialog
title="标签规则管理"
:close-on-click-modal="false"
width="800px"
@update:modelValue="$emit('update:modelValue')">
<el-form label-suffix=":" label-width="82px">
<el-row>
<el-col :span="12">
<el-form-item label="标签名称">{{ data.name }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="标签类型">{{ data.tag_type.name }}</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="更新频率">{{ getNameByValue(data.update_status, updateStatusRuleList) }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态">
<el-tag :type="data.status === '1' ? 'success' : 'danger'">
{{ getNameByValue(data.status, statusList) }}
</el-tag>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-form :model="form" :rules="rules" inline ref="formRef">
<!-- 用户属性规则 -->
<UserRule></UserRule>
<!-- 事件属性规则 -->
<EventRule style="margin-top: 20px"></EventRule>
</el-form>
<template #footer>
<el-row justify="center">
<el-button plain auto-insert-space @click="$emit('update:modelValue', false)">关闭</el-button>
<el-button type="primary" auto-insert-space @click="handleSubmit">保存</el-button>
</el-row>
</template>
</el-dialog>
</template>
<script setup lang="ts"> <script setup lang="ts">
import { Delete } from '@element-plus/icons-vue' import type { LabelType } from '../types'
import { Edit, Delete } from '@element-plus/icons-vue'
import { ElMessageBox, ElMessage } from 'element-plus'
import { deleteLabelType } from '../api'
import { useLabelType } from '../composables/useLabelType'
const typeList = $ref([ const LabelTypeFormDialog = defineAsyncComponent(() => import('../components/LabelTypeFormDialog.vue'))
{ name: '基础标签' },
{ name: '渠道标签' }, const { labelCount, typeList, fetchTypeList } = useLabelType()
{ name: '基础标签' },
{ name: '基础标签' }, let formVisible = $ref(false)
{ name: '基础标签' }, let currentRow = $ref<LabelType>()
{ name: '基础标签' }
]) // 新建
function handleAdd() {
currentRow = undefined
formVisible = true
}
// 修改
function handleUpdate(row: LabelType) {
currentRow = row
formVisible = true
}
// 删除
function handleRemove(row: LabelType) {
ElMessageBox.confirm('确定要删除该类型吗?', '提示').then(() => {
deleteLabelType({ id: row.id }).then(() => {
ElMessage({ message: '删除成功', type: 'success' })
fetchTypeList()
})
})
}
</script> </script>
<template> <template>
<el-button type="primary" style="width: 100%">添加标签类型</el-button> <el-button type="primary" style="width: 100%" @click="handleAdd">添加标签类型</el-button>
<div class="label-type-total"> <div class="label-type-total">
<h4>全部标签</h4> <h4>全部标签</h4>
<p>127个</p> <p>{{ labelCount }}</p>
</div> </div>
<ul> <ul>
<li class="label-type-item" v-for="(item, index) in typeList" :key="index"> <li class="label-type-item" v-for="item in typeList" :key="item.id">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" width="16" height="16"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" width="16" height="16" :fill="item.url">
<g> <g>
<path <path
d="M497.941 225.941L286.059 14.059A48 48 0 0 0 252.118 0H48C21.49 0 0 21.49 0 48v204.118a48 48 0 0 0 14.059 33.941l211.882 211.882c18.744 18.745 49.136 18.746 67.882 0l204.118-204.118c18.745-18.745 18.745-49.137 0-67.882zM112 160c-26.51 0-48-21.49-48-48s21.49-48 48-48 48 21.49 48 48-21.49 48-48 48zm513.941 133.823L421.823 497.941c-18.745 18.745-49.137 18.745-67.882 0l-.36-.36L527.64 323.522c16.999-16.999 26.36-39.6 26.36-63.64s-9.362-46.641-26.36-63.64L331.397 0h48.721a48 48 0 0 1 33.941 14.059l211.882 211.882c18.745 18.745 18.745 49.137 0 67.882z"></path> d="M497.941 225.941L286.059 14.059A48 48 0 0 0 252.118 0H48C21.49 0 0 21.49 0 48v204.118a48 48 0 0 0 14.059 33.941l211.882 211.882c18.744 18.745 49.136 18.746 67.882 0l204.118-204.118c18.745-18.745 18.745-49.137 0-67.882zM112 160c-26.51 0-48-21.49-48-48s21.49-48 48-48 48 21.49 48 48-21.49 48-48 48zm513.941 133.823L421.823 497.941c-18.745 18.745-49.137 18.745-67.882 0l-.36-.36L527.64 323.522c16.999-16.999 26.36-39.6 26.36-63.64s-9.362-46.641-26.36-63.64L331.397 0h48.721a48 48 0 0 1 33.941 14.059l211.882 211.882c18.745 18.745 18.745 49.137 0 67.882z"></path>
</g> </g>
</svg> </svg>
<p>{{ item.name }}</p> <p>{{ item.name }}</p>
<el-icon class="label-type-item__remove"><Delete /></el-icon> <div class="label-type-actions">
<el-icon class="label-type-item__edit" @click="handleUpdate(item)"><Edit /></el-icon>
<el-icon class="label-type-item__remove" @click="handleRemove(item)"><Delete /></el-icon>
</div>
</li> </li>
</ul> </ul>
<LabelTypeFormDialog
v-model="formVisible"
:data="currentRow"
@update="fetchTypeList"
v-if="formVisible"></LabelTypeFormDialog>
</template> </template>
<style lang="scss"> <style lang="scss">
...@@ -51,14 +82,24 @@ const typeList = $ref([ ...@@ -51,14 +82,24 @@ const typeList = $ref([
flex: 1; flex: 1;
} }
&:hover { &:hover {
background-color: #efefef; background: #efefef;
.label-type-item__remove { .label-type-actions {
display: block; display: flex;
} }
} }
} }
.label-type-item__remove { .label-type-actions {
display: none; display: none;
width: 40px;
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer; cursor: pointer;
.el-icon {
font-size: 14px;
&:hover {
color: var(--main-color);
}
}
} }
</style> </style>
<script setup lang="ts">
import type { LabelType } from '../types'
import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage } from 'element-plus'
import { createLabelType, updateLabelType } from '../api'
const props = defineProps<{
data?: LabelType
}>()
const emit = defineEmits<{
(e: 'update'): void
(e: 'update:modelValue', visible: boolean): void
}>()
const isUpdate = $computed(() => !!props.data?.id)
const title = $computed(() => (isUpdate ? '修改标签类型' : '新建标签类型'))
const formRef = $ref<FormInstance>()
const form = reactive({ id: '', name: '', url: '' })
watchEffect(() => {
if (props.data) Object.assign(form, props.data)
})
const rules = ref<FormRules>({
name: [{ required: true, message: '请输入标签类型名称' }],
url: [{ required: true, message: '请选择标签类型图标' }]
})
const iconColorList = $ref([
'#FF0000',
'#FF6600',
'#FFBF00',
'#FFD700',
'#66FF00',
'#00FFFF',
'#30D5C8',
'#6495ED',
'#003399',
'#003153',
'#003366',
'#2A52BE',
'#0047AB',
'#1E90FF',
'#002FA7',
'#000080',
'#5E86C1',
'#8000FF',
'#E32636',
'#FF00FF'
])
function handleChangeIcon(color: string) {
form.url = color
}
// 提交
function handleSubmit() {
formRef?.validate().then(() => (isUpdate ? handleUpdate() : handleCreate()))
}
// 新建
function handleCreate() {
createLabelType(form).then(() => {
ElMessage({ message: '创建成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
})
}
// 修改
function handleUpdate() {
updateLabelType(form).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-suffix=":" label-width="100px">
<el-form-item label="类型名称" prop="name">
<el-input v-model="form.name" placeholder="请输入" />
</el-form-item>
<el-form-item label="类型图标" prop="url">
<div class="label-type-icon-list">
<div
class="label-type-icon-item"
v-for="color in iconColorList"
:class="{ 'is-active': color === form.url }"
:key="color"
@click="handleChangeIcon(color)">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" width="16" height="16" :fill="color">
<g>
<path
d="M497.941 225.941L286.059 14.059A48 48 0 0 0 252.118 0H48C21.49 0 0 21.49 0 48v204.118a48 48 0 0 0 14.059 33.941l211.882 211.882c18.744 18.745 49.136 18.746 67.882 0l204.118-204.118c18.745-18.745 18.745-49.137 0-67.882zM112 160c-26.51 0-48-21.49-48-48s21.49-48 48-48 48 21.49 48 48-21.49 48-48 48zm513.941 133.823L421.823 497.941c-18.745 18.745-49.137 18.745-67.882 0l-.36-.36L527.64 323.522c16.999-16.999 26.36-39.6 26.36-63.64s-9.362-46.641-26.36-63.64L331.397 0h48.721a48 48 0 0 1 33.941 14.059l211.882 211.882c18.745 18.745 18.745 49.137 0 67.882z"></path>
</g>
</svg>
</div>
</div>
</el-form-item>
</el-form>
<template #footer>
<el-row justify="center">
<el-button plain auto-insert-space @click="$emit('update:modelValue', false)">关闭</el-button>
<el-button type="primary" auto-insert-space @click="handleSubmit">保存</el-button>
</el-row>
</template>
</el-dialog>
</template>
<style lang="scss">
.label-type-icon-list {
width: 100%;
display: grid;
justify-content: space-between;
grid-template-columns: repeat(5, 50px);
}
.label-type-icon-item {
width: 16px;
height: 16px;
padding: 16px;
line-height: 0;
cursor: pointer;
&:hover,
&.is-active {
border-radius: 50%;
box-shadow: rgb(0 0 0 / 40%) 0px 2px 6px 0px;
}
}
</style>
<script setup lang="ts">
import type { Label } from '../types'
import { UserFilled } from '@element-plus/icons-vue'
import { useMapStore } from '@/stores/map'
import { getNameByValue, updateStatusRuleList } from '@/utils/dictionary'
import { getLabelStatistics } from '../api'
// import { ElMessage } from 'element-plus'
const props = defineProps<{
data: Label
}>()
const statusList = useMapStore().getMapValuesByKey('system_status')
const detail = reactive({ count: 0, status: 1, updated_time: '-' })
function fetchInfo() {
getLabelStatistics({ id: props.data.id }).then(res => {
console.log(res)
Object.assign(detail, res.data.detail)
})
}
watchEffect(() => fetchInfo())
</script>
<template>
<el-dialog title="查看标签信息" width="600px">
<el-form label-suffix=":" label-width="82px">
<el-row>
<el-col :span="12">
<el-form-item label="标签名称">{{ data.name }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="标签类型">{{ data.tag_type.name }}</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="更新频率">{{ getNameByValue(data.update_status, updateStatusRuleList) }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态">
<el-tag :type="data.status === '1' ? 'success' : 'danger'">
{{ getNameByValue(data.status, statusList) }}
</el-tag>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-card>
<div class="label-box">
<dl>
<dt>符合标签人数</dt>
<dd class="label-box__count">
<el-icon><UserFilled></UserFilled></el-icon>
<span>{{ detail.count }}</span>
</dd>
</dl>
<dl>
<dt>最后更新时间</dt>
<dd>{{ detail.updated_time }}</dd>
</dl>
<dl>
<dt>更新状态</dt>
<dd>
<span>完成</span>
<el-button type="primary" plain size="small">立即更新</el-button>
</dd>
</dl>
</div>
</el-card>
<template #footer>
<el-row justify="center">
<el-button plain auto-insert-space @click="$emit('update:modelValue', false)">关闭</el-button>
</el-row>
</template>
</el-dialog>
</template>
<style lang="scss">
.label-box {
display: flex;
justify-content: space-between;
dt {
margin-bottom: 10px;
text-align: center;
}
dd {
display: flex;
align-items: center;
justify-content: center;
span {
padding: 0 5px;
}
}
}
.label-box__count {
font-weight: bold;
color: var(--main-color);
}
</style>
import type { LabelType } from '../types'
import { getLabelTypeList } from '../api'
const labelCount = ref(0)
const typeList = ref<LabelType[]>([])
export function useLabelType() {
function fetchTypeList() {
getLabelTypeList().then(res => {
labelCount.value = res.data.tag_count
typeList.value = res.data.items
})
}
onMounted(() => {
if (!typeList.value?.length) fetchTypeList()
})
return { fetchTypeList, typeList, labelCount }
}
import type { Operator } from '@/types'
// 标签类型
export interface LabelType {
id: string
name: string
url: string
}
export type LabelTypeListRequest = Pick<LabelType, 'id' | 'name'> & { experiment_id?: string }
export type LabelTypeUpdateRequest = Pick<LabelType, 'id' | 'name' | 'url'> & { experiment_id?: string }
export type LabelTypeCreateRequest = Omit<LabelTypeUpdateRequest, 'id'>
// 标签
export interface Label {
id: string
name: string
type_id: string
status: string
update_status: string // '1' | '2'
update_rule: string
tag_type: LabelType
created_time: string
created_operator: Operator
updated_time: string
updated_operator: Operator
}
// 标签更新规则
export interface LabelUpdateRule {
type: 1 | 2 | 3
info: number
}
export type LabelListRequest = Pick<Label, 'id' | 'name'> & { experiment_id?: string }
export type LabelUpdateRequest = Pick<Label, 'id' | 'name' | 'type_id' | 'update_status' | 'update_rule' | 'status'> & {
experiment_id?: string
}
export type LabelCreateRequest = Omit<LabelUpdateRequest, 'id'>
<script setup lang="ts"> <script setup lang="ts">
import type { Label } from '../types'
import { Plus } from '@element-plus/icons-vue' import { Plus } from '@element-plus/icons-vue'
import AppList from '@/components/base/AppList.vue' import AppList from '@/components/base/AppList.vue'
import LabelType from '../components/LabelType.vue' import LabelType from '../components/LabelType.vue'
import { ElMessageBox, ElMessage } from 'element-plus'
import { getLabelList, deleteLabel } from '../api'
import { useMapStore } from '@/stores/map'
import { getNameByValue, updateStatusRuleList } from '@/utils/dictionary'
import { useLabelType } from '../composables/useLabelType'
const LabelFormDialog = defineAsyncComponent(() => import('../components/LabelFormDialog.vue'))
const LabelViewDialog = defineAsyncComponent(() => import('../components/LabelViewDialog.vue'))
const LabelRuleDialog = defineAsyncComponent(() => import('../components/LabelRuleDialog.vue'))
const statusList = useMapStore().getMapValuesByKey('system_status')
const { typeList } = useLabelType()
const appList = $ref<InstanceType<typeof AppList> | null>(null) const appList = $ref<InstanceType<typeof AppList> | null>(null)
// 列表配置 // 列表配置
const listOptions = computed(() => { const listOptions = computed(() => {
return { return {
remote: {
httpRequest: getLabelList,
params: { name: '', type_id: '', status: '', updated_operator: '' }
},
filters: [ filters: [
{ type: 'input', prop: 'name', placeholder: '请输入标签名称' }, { type: 'input', prop: 'name', placeholder: '请输入标签名称' },
{ type: 'select', prop: 'type', placeholder: '请选择标签类型' } {
type: 'select',
prop: 'type_id',
placeholder: '请选择标签类型',
options: typeList.value,
labelKey: 'name',
valueKey: 'id'
},
{ type: 'select', prop: 'status', placeholder: '请选择标签状态', options: statusList },
{ type: 'input', prop: 'updated_operator', placeholder: '更新人' }
], ],
columns: [ columns: [
{ label: '序号', type: 'index', width: 60 }, { label: '序号', type: 'index', width: 60 },
{ label: '标签ID', prop: 'id' }, { label: '标签ID', prop: 'id' },
{ label: '标签名称', prop: 'name' }, { label: '标签名称', prop: 'name' },
{ label: '标签类型', prop: 'name' }, { label: '标签类型', prop: 'tag_type.name' },
{ label: '更新频率', prop: 'name' }, {
{ label: '状态', prop: 'name' }, label: '更新频率',
{ label: '更新人', prop: 'name' }, prop: 'update_status',
{ label: '更新时间', prop: 'name' }, computed({ row }: { row: Label }) {
{ label: '操作', slots: 'table-x', width: 240 } return getNameByValue(row.update_status, updateStatusRuleList)
], }
data: [{}, {}] },
{ label: '状态', prop: 'status', slots: 'table-status' },
{ label: '更新人', prop: 'updated_operator.real_name' },
{ label: '更新时间', prop: 'updated_time' },
{ label: '操作', slots: 'table-x', width: 320 }
]
} }
}) })
// 刷新 // 刷新
function handleRefresh() { function handleRefresh() {
appList?.refetch() appList?.refetch()
} }
let formVisible = $ref(false)
let currentRow = $ref<Label>()
// 新建
function handleAdd() {
currentRow = undefined
formVisible = true
}
// 修改
function handleUpdate(row: Label) {
currentRow = row
formVisible = true
}
// 删除
function handleRemove(row: Label) {
ElMessageBox.confirm('确定要删除该标签吗?', '提示').then(() => {
deleteLabel({ id: row.id }).then(() => {
ElMessage({ message: '删除成功', type: 'success' })
handleRefresh()
})
})
}
// 查看
let viewVisible = $ref(false)
function handleView(row: Label) {
currentRow = row
viewVisible = true
}
// 规则
let ruleVisible = $ref(false)
function handleRule(row: Label) {
currentRow = row
ruleVisible = true
}
</script> </script>
<template> <template>
...@@ -39,18 +104,34 @@ function handleRefresh() { ...@@ -39,18 +104,34 @@ function handleRefresh() {
<AppList v-bind="listOptions" ref="appList" class="label-right"> <AppList v-bind="listOptions" ref="appList" class="label-right">
<template #header-buttons> <template #header-buttons>
<el-space> <el-space>
<el-button type="primary" :icon="Plus">新建</el-button> <el-button type="primary" :icon="Plus" @click="handleAdd">新建</el-button>
</el-space> </el-space>
</template> </template>
<template #table-status="{ row }: { row: Label }">
<el-tag :type="row.status === '1' ? 'success' : 'danger'">
{{ getNameByValue(row.status, statusList) }}
</el-tag>
</template>
<template #table-x> <template #table-x="{ row }">
<el-button type="primary" plain>查看</el-button> <el-button type="primary" plain @click="handleRule(row)">规则</el-button>
<el-button type="primary" plain>编辑</el-button> <el-button type="primary" plain @click="handleView(row)">查看</el-button>
<el-button type="primary" plain>删除</el-button> <el-button type="primary" plain @click="handleUpdate(row)">编辑</el-button>
<el-button type="primary" plain @click="handleRemove(row)">删除</el-button>
</template> </template>
</AppList> </AppList>
</div> </div>
</AppCard> </AppCard>
<!-- 新建/修改标签 -->
<LabelFormDialog
v-model="formVisible"
:data="currentRow"
@update="handleRefresh"
v-if="formVisible"></LabelFormDialog>
<!-- 查看标签 -->
<LabelViewDialog v-model="viewVisible" :data="currentRow" v-if="viewVisible && currentRow"></LabelViewDialog>
<!-- 规则 -->
<LabelRuleDialog v-model="ruleVisible" :data="currentRow" v-if="ruleVisible && currentRow"></LabelRuleDialog>
</template> </template>
<style lang="scss"> <style lang="scss">
......
import httpRequest from '@/utils/axios'
import type { TripTemplateListRequest, TripTemplateCreateRequest, TripTemplateUpdateRequest } from './types'
// 获取旅程模板列表
export function getTripTemplateList(params: TripTemplateListRequest) {
return httpRequest.get('/api/lab/v1/experiment/itinerary/list', { params })
}
// 创建旅程模板
export function createTripTemplate(data: TripTemplateCreateRequest) {
return httpRequest.post('/api/lab/v1/experiment/itinerary/create', data)
}
// 更新旅程模板
export function updateTripTemplate(data: TripTemplateUpdateRequest) {
return httpRequest.post('/api/lab/v1/experiment/itinerary/update', data)
}
<script setup lang="ts">
import type { TripTemplate } from '../types'
import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage } from 'element-plus'
import { createTripTemplate, updateTripTemplate } from '../api'
import { tripTemplateTypeList } from '@/utils/dictionary'
import { pick } from 'lodash-es'
const props = defineProps<{ data?: TripTemplate }>()
const emit = defineEmits<{
(e: 'update'): void
(e: 'update:modelValue', visible: boolean): void
}>()
const isUpdate = $computed(() => !!props.data?.id)
const title = $computed(() => (isUpdate ? '修改旅程模板' : '新建旅程模板'))
const formRef = $ref<FormInstance>()
const form = reactive({
id: '',
name: '',
type: '1',
score: 100,
is_view_answer: '1',
status: '1'
})
watchEffect(() => {
if (props.data) Object.assign(form, props.data, { score: parseInt(props.data.score) })
})
const rules = ref<FormRules>({
name: [{ required: true, message: '请输入模板名称' }],
type: [{ required: true, message: '请选择模板类型' }],
score: [{ required: true, message: '请输入不超过100的整数数值' }]
})
// 提交
function handleSubmit() {
formRef?.validate().then(() => (isUpdate ? handleUpdate() : handleCreate()))
}
// 新建
function handleCreate() {
const params = pick(form, ['name', 'type', 'score', 'is_view_answer', 'status'])
createTripTemplate(params).then(() => {
ElMessage({ message: '创建成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
})
}
// 修改
function handleUpdate() {
const params = pick(form, ['id', 'name', 'type', 'score', 'is_view_answer', 'status'])
updateTripTemplate(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-suffix=":" label-width="170px">
<el-form-item label="模板名称" prop="name">
<el-input v-model="form.name" placeholder="请输入" />
</el-form-item>
<el-form-item label="模板类型" prop="type">
<el-select v-model="form.type" style="width: 100%">
<el-option v-for="item in tripTemplateTypeList" :key="item.value" v-bind="item"></el-option>
</el-select>
</el-form-item>
<el-form-item label="旅程分值" prop="score">
<el-input-number
v-model="form.score"
step-strictly
:min="0"
:max="100"
placeholder="请输入不超过100的整数数值"
style="width: 100%"></el-input-number>
</el-form-item>
<el-form-item label="是否允许学生查看解析" prop="is_view_answer">
<el-switch
v-model="form.is_view_answer"
active-text="允许"
active-value="1"
inactive-text="不允许"
inactive-value="0" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-switch v-model="form.status" active-text="生效" active-value="1" inactive-text="失效" inactive-value="0" />
</el-form-item>
</el-form>
<template #footer>
<el-row justify="center">
<el-button plain auto-insert-space @click="$emit('update:modelValue', false)">关闭</el-button>
<el-button type="primary" auto-insert-space @click="handleSubmit">保存</el-button>
</el-row>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import type { TripTemplate } from '../types'
import { useMapStore } from '@/stores/map'
import { getNameByValue, tripTemplateTypeList } from '@/utils/dictionary'
defineProps<{ data: TripTemplate }>()
const statusList = useMapStore().getMapValuesByKey('system_status')
</script>
<template>
<el-dialog title="查看旅程模板" width="600px">
<el-form ref="formRef" label-suffix=":">
<el-row justify="space-between">
<el-form-item label="模板名称" prop="name"> {{ data.name }} </el-form-item>
<el-form-item label="模板类型" prop="type">
{{ getNameByValue(data.type, tripTemplateTypeList) }}
</el-form-item>
<el-form-item label="旅程分值" prop="score"> {{ data.score }} </el-form-item>
</el-row>
<el-row justify="space-between">
<el-form-item label="是否允许学生查看解析" prop="is_view_answer">
<el-tag :type="data.is_view_answer === '1' ? 'success' : 'danger'">
{{ data.is_view_answer === '1' ? '允许' : '不允许' }}
</el-tag>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-tag :type="data.status === '1' ? 'success' : 'danger'">
{{ getNameByValue(data.status, statusList) }}
</el-tag>
</el-form-item>
</el-row>
</el-form>
<template #footer>
<el-row justify="center">
<el-button plain auto-insert-space @click="$emit('update:modelValue', false)">关闭</el-button>
</el-row>
</template>
</el-dialog>
</template>
import type { Operator } from '@/types'
export interface TripTemplate {
id: string
name: string
type: string
status: string
score: string
is_view_answer: string
created_time: string
created_operator: Operator
updated_time: string
updated_operator: Operator
}
export type TripTemplateListRequest = Pick<TripTemplate, 'name' | 'type' | 'status'> & { experiment_id?: string }
export type TripTemplateUpdateRequest = Pick<TripTemplate, 'id' | 'name' | 'type' | 'is_view_answer' | 'status'> & {
experiment_id?: string
score: number
}
export type TripTemplateCreateRequest = Omit<TripTemplateUpdateRequest, 'id'>
<script setup lang="ts"> <script setup lang="ts">
import type { TripTemplate } from '../types'
import { Plus } from '@element-plus/icons-vue' import { Plus } from '@element-plus/icons-vue'
import AppList from '@/components/base/AppList.vue' import AppList from '@/components/base/AppList.vue'
import { ElMessageBox } from 'element-plus'
import { getTripTemplateList } from '../api'
import { useMapStore } from '@/stores/map'
import { getNameByValue, tripTemplateTypeList } from '@/utils/dictionary'
const FormDialog = defineAsyncComponent(() => import('../components/FormDialog.vue'))
const ViewDialog = defineAsyncComponent(() => import('../components/ViewDialog.vue'))
const statusList = useMapStore().getMapValuesByKey('system_status')
const appList = $ref<InstanceType<typeof AppList> | null>(null) const appList = $ref<InstanceType<typeof AppList> | null>(null)
// 列表配置 // 列表配置
const listOptions = computed(() => { const listOptions = computed(() => {
return { return {
filters: [{ type: 'input', prop: 'name', placeholder: '请输入旅程模板名称' }], remote: {
httpRequest: getTripTemplateList,
params: { name: '', status: '', type: '' }
},
filters: [
{ type: 'input', prop: 'name', placeholder: '请输入旅程模板名称' },
{ type: 'select', prop: 'type', placeholder: '请选择旅程模板类型', options: tripTemplateTypeList },
{ type: 'select', prop: 'status', placeholder: '请选择旅程模板状态', options: statusList }
],
columns: [ columns: [
{ label: '序号', type: 'index', width: 60 }, { label: '序号', type: 'index', width: 60 },
{ label: '模板名称', prop: 'id' }, { label: '模板名称', prop: 'name' },
{ label: '模板类型', prop: 'name' }, { label: '所属实验', prop: 'experiment.name' },
{ label: '用户旅程分值', prop: 'name' }, {
{ label: '状态', prop: 'name' }, label: '模板类型',
{ label: '更新人', prop: 'name' }, prop: 'type',
{ label: '更新时间', prop: 'name' }, computed({ row }: { row: TripTemplate }) {
return getNameByValue(row.type, tripTemplateTypeList)
}
},
{
label: '用户旅程分值',
prop: 'score',
computed({ row }: { row: TripTemplate }) {
return parseInt(row.score)
}
},
{ label: '状态', prop: 'status', slots: 'table-status' },
{ label: '更新人', prop: 'updated_operator.real_name' },
{ label: '更新时间', prop: 'updated_time' },
{ label: '操作', slots: 'table-x', width: 300 } { label: '操作', slots: 'table-x', width: 300 }
], ]
data: [{}, {}]
} }
}) })
...@@ -25,6 +55,32 @@ const listOptions = computed(() => { ...@@ -25,6 +55,32 @@ const listOptions = computed(() => {
function handleRefresh() { function handleRefresh() {
appList?.refetch() appList?.refetch()
} }
let formVisible = $ref(false)
let currentRow = $ref<TripTemplate>()
// 新建
function handleAdd() {
currentRow = undefined
formVisible = true
}
// 修改
function handleUpdate(row: TripTemplate) {
currentRow = row
formVisible = true
}
// 删除
function handleRemove(row: TripTemplate) {
ElMessageBox.confirm('确定要删除该旅程模板吗?', '提示').then(() => {
handleRefresh()
})
}
// 查看
let viewVisible = $ref(false)
function handleView(row: TripTemplate) {
currentRow = row
viewVisible = true
}
</script> </script>
<template> <template>
...@@ -32,16 +88,22 @@ function handleRefresh() { ...@@ -32,16 +88,22 @@ function handleRefresh() {
<AppList v-bind="listOptions" ref="appList"> <AppList v-bind="listOptions" ref="appList">
<template #header-buttons> <template #header-buttons>
<el-space> <el-space>
<el-button type="primary" :icon="Plus">新建</el-button> <el-button type="primary" :icon="Plus" @click="handleAdd">新建</el-button>
</el-space> </el-space>
</template> </template>
<template #table-status="{ row }: { row: TripTemplate }">
<template #table-x> <el-tag :type="row.status === '1' ? 'success' : 'danger'">{{ getNameByValue(row.status, statusList) }}</el-tag>
</template>
<template #table-x="{ row }: { row: TripTemplate }">
<el-button type="primary" plain>配置</el-button> <el-button type="primary" plain>配置</el-button>
<el-button type="primary" plain>查看</el-button> <el-button type="primary" plain @click="handleView(row)">查看</el-button>
<el-button type="primary" plain>编辑</el-button> <el-button type="primary" plain @click="handleUpdate(row)">编辑</el-button>
<el-button type="primary" plain>删除</el-button> <el-button type="primary" plain @click="handleRemove(row)">删除</el-button>
</template> </template>
</AppList> </AppList>
</AppCard> </AppCard>
<!-- 新建/修改 -->
<FormDialog v-model="formVisible" :data="currentRow" @update="handleRefresh" v-if="formVisible"></FormDialog>
<!-- 查看 -->
<ViewDialog v-model="viewVisible" :data="currentRow" v-if="viewVisible && currentRow"></ViewDialog>
</template> </template>
...@@ -18,7 +18,12 @@ router.beforeEach(async (to, from, next) => { ...@@ -18,7 +18,12 @@ router.beforeEach(async (to, from, next) => {
user.isLogin ? next() : next('/401') user.isLogin ? next() : next('/401')
return return
} }
next() if (!to.query.experiment_id) {
to.query.experiment_id = '7025368348925886464'
next({ path: to.path, query: to.query })
} else {
next()
}
}) })
export default router export default router
...@@ -54,33 +54,53 @@ export interface PermissionType { ...@@ -54,33 +54,53 @@ export interface PermissionType {
tag: string tag: string
} }
export interface MessageType { export interface SystemDictionary {
cancel_time: string
channel: 0 | 1 | 2 | 3 | 4 | 5
created_at: string
fail_detail: string
from: string
from_source: number
id: string id: string
is_cancel: 0 | 1 label: string
is_platform: number value: string
is_read: 0 | 1
is_send: 0 | 1
messagebus_id: string
parent_id: number
payload: any
read_time: string
send_time: string
source: number
template_id: number
title: string
to: string
type: 1 | 2
updated_at: string
} }
export interface SystemDictionary { // 操作人
export interface Operator {
avatar: string
id: string id: string
label: string nickname: string
real_name: string
username: string
}
// 用户规则
export interface UserAttrRule {
current_logic_operate: 'and' | 'or'
items: RuleAttr[]
}
export interface RuleAttr {
attr_id: string
attr: string
attr_name: string
attr_type: string
operate: string
operate_name: string
value: string value: string
} }
// 事件规则
export interface EventRule {
current_logic_operate: 'and' | 'or'
items: EventRuleItem[]
}
export interface EventRuleItem {
happen_info: {
is_happened: boolean
event_id: string
event_name: string
attr_list: RuleAttr[]
}
trigger_info: {
operate: string
operate_name: string
value: string
}
}
...@@ -6,13 +6,18 @@ const httpRequest = axios.create({ ...@@ -6,13 +6,18 @@ const httpRequest = axios.create({
timeout: 60000, timeout: 60000,
withCredentials: true, withCredentials: true,
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded' // 'Content-Type': 'application/x-www-form-urlencoded'
} }
}) })
// 请求拦截 // 请求拦截
httpRequest.interceptors.request.use( httpRequest.interceptors.request.use(
function (config) { function (config) {
if (config.method === 'get')
config.params = Object.assign({}, config.params, { experiment_id: '7025368348925886464' })
if (config.method === 'post') config.data = Object.assign({}, config.data, { experiment_id: '7025368348925886464' })
return config return config
}, },
function (error) { function (error) {
......
// json to array export interface Dictionary {
export const json2Array = function (data: any, isValueToNumber = true) { label: string
return Object.keys(data).map(value => ({ label: data[value], value: isValueToNumber ? parseInt(value) : value })) value: string | number
} }
// 参赛模式 export function getNameByValue(value: string | number, list: Dictionary[]) {
export const contestMode: Record<string, any> = { return list.find(item => item.value === value)?.label || value
'1': '个人赛'
} }
// 参赛模式列表
export const contestModeList = json2Array(contestMode, false)
// 评分规则
export const scoreRule: Record<number, any> = {
1: '平均法',
2: '加权平均法',
3: '综合得分法',
4: '最高分'
}
// 参赛模式列表
export const scoreRuleList = json2Array(scoreRule, false)
// 实验成绩规则
export const gradeRule: Record<number, any> = {
1: '实验报告',
2: '实验准备',
3: '实验结果',
4: '课堂活跃度',
5: '自定义'
}
// 参赛模式列表
export const gradeRuleList = json2Array(gradeRule)
// 实验报告评分规则 // 旅程模板类型
export const reportScoreRule: Record<number, any> = { export const tripTemplateTypeList = [
1: '人工评分', { label: '自由旅程', value: '1' },
2: '自动评分' { label: '固定旅程', value: '2' }
]
// 群组类型
export const groupTypeList = [
{ label: '静态群组', value: '1' },
{ label: '动态群组', value: '2' }
]
// 更新方式
export const updateStatusRuleList = [
{ label: '自动更新', value: '1' },
{ label: '手动更新', value: '2' }
]
export const dateUnitList = [
{ label: '天', value: 1 },
{ label: '周', value: 2 },
{ label: '月', value: 3 }
]
export const weekList = [
{ label: '周一', value: 1 },
{ label: '周二', value: 2 },
{ label: '周三', value: 3 },
{ label: '周四', value: 4 },
{ label: '周五', value: 5 },
{ label: '周六', value: 6 },
{ label: '周日', value: 7 }
]
export interface OperatorType {
label: string
value: string
alias?: string
} }
export const reportScoreRuleList = json2Array(reportScoreRule)
// 字符串
export const stringOperatorList: OperatorType[] = [
{ label: '等于', value: '=' },
{ label: '不等于', value: '!=' },
{ label: '包含', value: 'in' },
{ label: '不包含', value: 'not in' },
{ label: '空值', value: 'null' },
{ label: '非空', value: 'not null' }
]
// 整数|数字
export const numberOperatorList: OperatorType[] = [
{ label: '=', value: '=' },
{ label: '!=', value: '!=', alias: '≠' },
{ label: '>', value: '>' },
{ label: '>=', value: '>=', alias: '≥' },
{ label: '<', value: '<' },
{ label: '<=', value: '<=', alias: '≤' },
{ label: '区间', value: 'range' },
{ label: '空值', value: 'null' },
{ label: '非空', value: 'not null' }
]
// 日期
export const dateOperatorList: OperatorType[] = [
{ label: '绝对时间前', value: 'before' },
{ label: '绝对时间后', value: 'after' },
{ label: '绝对时间区间', value: 'range' },
{ label: '相对时间点', value: '3' },
{ label: '在...天内', value: 'in_day' }
]
export const happenInfoList = [
{ label: '发生过', value: true },
{ label: '未发生过', value: false }
]
export const triggerInfoList = [{ label: '触发次数', value: '触发次数' }]
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论