修复一些已知的BUG
This commit is contained in:
parent
9572d9000f
commit
d0eca2f2ca
477
package-lock.json
generated
477
package-lock.json
generated
@ -11,6 +11,8 @@
|
||||
"@dataview/datav-vue3": "^0.0.0-test.1672506674342",
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@kjgl77/datav-vue3": "^1.7.4",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
"axios": "^1.7.9",
|
||||
"datav-vue3": "^1.0.0",
|
||||
"echarts": "^5.6.0",
|
||||
@ -756,11 +758,21 @@
|
||||
"node": ">=12.20"
|
||||
}
|
||||
},
|
||||
"node_modules/@transloadit/prettier-bytes": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmmirror.com/@transloadit/prettier-bytes/-/prettier-bytes-0.0.7.tgz",
|
||||
"integrity": "sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA=="
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.6",
|
||||
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/event-emitter": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmmirror.com/@types/event-emitter/-/event-emitter-0.3.5.tgz",
|
||||
"integrity": "sha512-zx2/Gg0Eg7gwEiOIIh5w9TrhKKTeQh7CPCOPNc0el4pLSwzebA8SmnHwZs2dWlLONvyulykSwGSQxQHLhjGLvQ=="
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.17.15",
|
||||
"integrity": "sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw=="
|
||||
@ -780,6 +792,56 @@
|
||||
"undici-types": "~6.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@uppy/companion-client": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmmirror.com/@uppy/companion-client/-/companion-client-2.2.2.tgz",
|
||||
"integrity": "sha512-5mTp2iq97/mYSisMaBtFRry6PTgZA6SIL7LePteOV5x0/DxKfrZW3DEiQERJmYpHzy7k8johpm2gHnEKto56Og==",
|
||||
"dependencies": {
|
||||
"@uppy/utils": "^4.1.2",
|
||||
"namespace-emitter": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@uppy/core": {
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmmirror.com/@uppy/core/-/core-2.3.4.tgz",
|
||||
"integrity": "sha512-iWAqppC8FD8mMVqewavCz+TNaet6HPXitmGXpGGREGrakZ4FeuWytVdrelydzTdXx6vVKkOmI2FLztGg73sENQ==",
|
||||
"dependencies": {
|
||||
"@transloadit/prettier-bytes": "0.0.7",
|
||||
"@uppy/store-default": "^2.1.1",
|
||||
"@uppy/utils": "^4.1.3",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"mime-match": "^1.0.2",
|
||||
"namespace-emitter": "^2.0.1",
|
||||
"nanoid": "^3.1.25",
|
||||
"preact": "^10.5.13"
|
||||
}
|
||||
},
|
||||
"node_modules/@uppy/store-default": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/@uppy/store-default/-/store-default-2.1.1.tgz",
|
||||
"integrity": "sha512-xnpTxvot2SeAwGwbvmJ899ASk5tYXhmZzD/aCFsXePh/v8rNvR2pKlcQUH7cF/y4baUGq3FHO/daKCok/mpKqQ=="
|
||||
},
|
||||
"node_modules/@uppy/utils": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/@uppy/utils/-/utils-4.1.3.tgz",
|
||||
"integrity": "sha512-nTuMvwWYobnJcytDO3t+D6IkVq/Qs4Xv3vyoEZ+Iaf8gegZP+rEyoaFT2CK5XLRMienPyqRqNbIfRuFaOWSIFw==",
|
||||
"dependencies": {
|
||||
"lodash.throttle": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@uppy/xhr-upload": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/@uppy/xhr-upload/-/xhr-upload-2.1.3.tgz",
|
||||
"integrity": "sha512-YWOQ6myBVPs+mhNjfdWsQyMRWUlrDLMoaG7nvf/G6Y3GKZf8AyjFDjvvJ49XWQ+DaZOftGkHmF1uh/DBeGivJQ==",
|
||||
"dependencies": {
|
||||
"@uppy/companion-client": "^2.2.2",
|
||||
"@uppy/utils": "^4.1.2",
|
||||
"nanoid": "^3.1.25"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@uppy/core": "^2.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
"version": "5.2.1",
|
||||
"integrity": "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==",
|
||||
@ -950,6 +1012,156 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/basic-modules": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/basic-modules/-/basic-modules-1.1.7.tgz",
|
||||
"integrity": "sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==",
|
||||
"dependencies": {
|
||||
"is-url": "^1.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@wangeditor/core": "1.x",
|
||||
"dom7": "^3.0.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"nanoid": "^3.2.0",
|
||||
"slate": "^0.72.0",
|
||||
"snabbdom": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/code-highlight": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/code-highlight/-/code-highlight-1.0.3.tgz",
|
||||
"integrity": "sha512-iazHwO14XpCuIWJNTQTikqUhGKyqj+dUNWJ9288Oym9M2xMVHvnsOmDU2sgUDWVy+pOLojReMPgXCsvvNlOOhw==",
|
||||
"dependencies": {
|
||||
"prismjs": "^1.23.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@wangeditor/core": "1.x",
|
||||
"dom7": "^3.0.0",
|
||||
"slate": "^0.72.0",
|
||||
"snabbdom": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/core": {
|
||||
"version": "1.1.19",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/core/-/core-1.1.19.tgz",
|
||||
"integrity": "sha512-KevkB47+7GhVszyYF2pKGKtCSj/YzmClsD03C3zTt+9SR2XWT5T0e3yQqg8baZpcMvkjs1D8Dv4fk8ok/UaS2Q==",
|
||||
"dependencies": {
|
||||
"@types/event-emitter": "^0.3.3",
|
||||
"event-emitter": "^0.3.5",
|
||||
"html-void-elements": "^2.0.0",
|
||||
"i18next": "^20.4.0",
|
||||
"scroll-into-view-if-needed": "^2.2.28",
|
||||
"slate-history": "^0.66.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@uppy/core": "^2.1.1",
|
||||
"@uppy/xhr-upload": "^2.0.3",
|
||||
"dom7": "^3.0.0",
|
||||
"is-hotkey": "^0.2.0",
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.foreach": "^4.5.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"lodash.toarray": "^4.4.0",
|
||||
"nanoid": "^3.2.0",
|
||||
"slate": "^0.72.0",
|
||||
"snabbdom": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/editor": {
|
||||
"version": "5.1.23",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/editor/-/editor-5.1.23.tgz",
|
||||
"integrity": "sha512-0RxfeVTuK1tktUaPROnCoFfaHVJpRAIE2zdS0mpP+vq1axVQpLjM8+fCvKzqYIkH0Pg+C+44hJpe3VVroSkEuQ==",
|
||||
"dependencies": {
|
||||
"@uppy/core": "^2.1.1",
|
||||
"@uppy/xhr-upload": "^2.0.3",
|
||||
"@wangeditor/basic-modules": "^1.1.7",
|
||||
"@wangeditor/code-highlight": "^1.0.3",
|
||||
"@wangeditor/core": "^1.1.19",
|
||||
"@wangeditor/list-module": "^1.0.5",
|
||||
"@wangeditor/table-module": "^1.1.4",
|
||||
"@wangeditor/upload-image-module": "^1.0.2",
|
||||
"@wangeditor/video-module": "^1.1.4",
|
||||
"dom7": "^3.0.0",
|
||||
"is-hotkey": "^0.2.0",
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.foreach": "^4.5.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"lodash.toarray": "^4.4.0",
|
||||
"nanoid": "^3.2.0",
|
||||
"slate": "^0.72.0",
|
||||
"snabbdom": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/editor-for-vue": {
|
||||
"version": "5.1.12",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/editor-for-vue/-/editor-for-vue-5.1.12.tgz",
|
||||
"integrity": "sha512-0Ds3D8I+xnpNWezAeO7HmPRgTfUxHLMd9JKcIw+QzvSmhC5xUHbpCcLU+KLmeBKTR/zffnS5GQo6qi3GhTMJWQ==",
|
||||
"peerDependencies": {
|
||||
"@wangeditor/editor": ">=5.1.0",
|
||||
"vue": "^3.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/list-module": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/list-module/-/list-module-1.0.5.tgz",
|
||||
"integrity": "sha512-uDuYTP6DVhcYf7mF1pTlmNn5jOb4QtcVhYwSSAkyg09zqxI1qBqsfUnveeDeDqIuptSJhkh81cyxi+MF8sEPOQ==",
|
||||
"peerDependencies": {
|
||||
"@wangeditor/core": "1.x",
|
||||
"dom7": "^3.0.0",
|
||||
"slate": "^0.72.0",
|
||||
"snabbdom": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/table-module": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/table-module/-/table-module-1.1.4.tgz",
|
||||
"integrity": "sha512-5saanU9xuEocxaemGdNi9t8MCDSucnykEC6jtuiT72kt+/Hhh4nERYx1J20OPsTCCdVr7hIyQenFD1iSRkIQ6w==",
|
||||
"peerDependencies": {
|
||||
"@wangeditor/core": "1.x",
|
||||
"dom7": "^3.0.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"nanoid": "^3.2.0",
|
||||
"slate": "^0.72.0",
|
||||
"snabbdom": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/upload-image-module": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/upload-image-module/-/upload-image-module-1.0.2.tgz",
|
||||
"integrity": "sha512-z81lk/v71OwPDYeQDxj6cVr81aDP90aFuywb8nPD6eQeECtOymrqRODjpO6VGvCVxVck8nUxBHtbxKtjgcwyiA==",
|
||||
"peerDependencies": {
|
||||
"@uppy/core": "^2.0.3",
|
||||
"@uppy/xhr-upload": "^2.0.3",
|
||||
"@wangeditor/basic-modules": "1.x",
|
||||
"@wangeditor/core": "1.x",
|
||||
"dom7": "^3.0.0",
|
||||
"lodash.foreach": "^4.5.0",
|
||||
"slate": "^0.72.0",
|
||||
"snabbdom": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/video-module": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/video-module/-/video-module-1.1.4.tgz",
|
||||
"integrity": "sha512-ZdodDPqKQrgx3IwWu4ZiQmXI8EXZ3hm2/fM6E3t5dB8tCaIGWQZhmqd6P5knfkRAd3z2+YRSRbxOGfoRSp/rLg==",
|
||||
"peerDependencies": {
|
||||
"@uppy/core": "^2.1.4",
|
||||
"@uppy/xhr-upload": "^2.0.7",
|
||||
"@wangeditor/core": "1.x",
|
||||
"dom7": "^3.0.0",
|
||||
"nanoid": "^3.2.0",
|
||||
"slate": "^0.72.0",
|
||||
"snabbdom": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/alien-signals": {
|
||||
"version": "0.4.14",
|
||||
"integrity": "sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==",
|
||||
@ -1038,10 +1250,27 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/compute-scroll-into-view": {
|
||||
"version": "1.0.20",
|
||||
"resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz",
|
||||
"integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg=="
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
||||
},
|
||||
"node_modules/d": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/d/-/d-1.0.2.tgz",
|
||||
"integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==",
|
||||
"dependencies": {
|
||||
"es5-ext": "^0.10.64",
|
||||
"type": "^2.7.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/datav-vue3": {
|
||||
"version": "1.0.0",
|
||||
"integrity": "sha512-ehQgoAxyZHZwLtZBJ8mlEe41bOjGH816bPH0XhtjR6saVYUgt/HUslcE2M18lm0FfmTlLOcvPtvoDBlitIyehg==",
|
||||
@ -1087,6 +1316,14 @@
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/dom7": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/dom7/-/dom7-3.0.0.tgz",
|
||||
"integrity": "sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==",
|
||||
"dependencies": {
|
||||
"ssr-window": "^3.0.0-alpha.1"
|
||||
}
|
||||
},
|
||||
"node_modules/dot-prop": {
|
||||
"version": "9.0.0",
|
||||
"integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==",
|
||||
@ -1195,10 +1432,47 @@
|
||||
"node": ">=12.x"
|
||||
}
|
||||
},
|
||||
"node_modules/es5-ext": {
|
||||
"version": "0.10.64",
|
||||
"resolved": "https://registry.npmmirror.com/es5-ext/-/es5-ext-0.10.64.tgz",
|
||||
"integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"es6-iterator": "^2.0.3",
|
||||
"es6-symbol": "^3.1.3",
|
||||
"esniff": "^2.0.1",
|
||||
"next-tick": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/es6-iterator": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/es6-iterator/-/es6-iterator-2.0.3.tgz",
|
||||
"integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
|
||||
"dependencies": {
|
||||
"d": "1",
|
||||
"es5-ext": "^0.10.35",
|
||||
"es6-symbol": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/es6-promise": {
|
||||
"version": "4.2.8",
|
||||
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
|
||||
},
|
||||
"node_modules/es6-symbol": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/es6-symbol/-/es6-symbol-3.1.4.tgz",
|
||||
"integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==",
|
||||
"dependencies": {
|
||||
"d": "^1.0.2",
|
||||
"ext": "^1.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.24.2",
|
||||
"integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==",
|
||||
@ -1626,6 +1900,20 @@
|
||||
"version": "1.0.3",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
|
||||
},
|
||||
"node_modules/esniff": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/esniff/-/esniff-2.0.1.tgz",
|
||||
"integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==",
|
||||
"dependencies": {
|
||||
"d": "^1.0.1",
|
||||
"es5-ext": "^0.10.62",
|
||||
"event-emitter": "^0.3.5",
|
||||
"type": "^2.7.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/estree-walker": {
|
||||
"version": "2.0.2",
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
|
||||
@ -1640,6 +1928,23 @@
|
||||
"url": "https://github.com/eta-dev/eta?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/event-emitter": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmmirror.com/event-emitter/-/event-emitter-0.3.5.tgz",
|
||||
"integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==",
|
||||
"dependencies": {
|
||||
"d": "1",
|
||||
"es5-ext": "~0.10.14"
|
||||
}
|
||||
},
|
||||
"node_modules/ext": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmmirror.com/ext/-/ext-1.7.0.tgz",
|
||||
"integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
|
||||
"dependencies": {
|
||||
"type": "^2.7.2"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
@ -1719,10 +2024,36 @@
|
||||
"node": ">=12.22.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-void-elements": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-2.0.1.tgz",
|
||||
"integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/http-status-emojis": {
|
||||
"version": "2.2.0",
|
||||
"integrity": "sha512-ompKtgwpx8ff0hsbpIB7oE4ax1LXoHmftsHHStMELX56ivG3GhofTX8ZHWlUaFKfGjcGjw6G3rPk7dJRXMmbbg=="
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "20.6.1",
|
||||
"resolved": "https://registry.npmmirror.com/i18next/-/i18next-20.6.1.tgz",
|
||||
"integrity": "sha512-yCMYTMEJ9ihCwEQQ3phLo7I/Pwycf8uAx+sRHwwk5U9Aui/IZYgQRyMqXafQOw5QQ7DM1Z+WyEXWIqSuJHhG2A==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/immer": {
|
||||
"version": "9.0.21",
|
||||
"resolved": "https://registry.npmmirror.com/immer/-/immer-9.0.21.tgz",
|
||||
"integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/immer"
|
||||
}
|
||||
},
|
||||
"node_modules/immutable": {
|
||||
"version": "5.0.3",
|
||||
"integrity": "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==",
|
||||
@ -1763,6 +2094,11 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-hotkey": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/is-hotkey/-/is-hotkey-0.2.0.tgz",
|
||||
"integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw=="
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
@ -1772,6 +2108,19 @@
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-plain-object": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-5.0.0.tgz",
|
||||
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-url": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmmirror.com/is-url/-/is-url-1.2.4.tgz",
|
||||
"integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww=="
|
||||
},
|
||||
"node_modules/json-server": {
|
||||
"version": "1.0.0-beta.3",
|
||||
"integrity": "sha512-DwE69Ep5ccwIJZBUIWEENC30Yj8bwr4Ax9W9VoIWAYnB8Sj4ReptscO8/DRHv/nXwVlmb3Bk73Ls86+VZdYkkA==",
|
||||
@ -1824,6 +2173,42 @@
|
||||
"lodash-es": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash.camelcase": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="
|
||||
},
|
||||
"node_modules/lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
|
||||
},
|
||||
"node_modules/lodash.debounce": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
|
||||
},
|
||||
"node_modules/lodash.foreach": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz",
|
||||
"integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ=="
|
||||
},
|
||||
"node_modules/lodash.isequal": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
|
||||
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead."
|
||||
},
|
||||
"node_modules/lodash.throttle": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
|
||||
"integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="
|
||||
},
|
||||
"node_modules/lodash.toarray": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz",
|
||||
"integrity": "sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw=="
|
||||
},
|
||||
"node_modules/lowdb": {
|
||||
"version": "7.0.1",
|
||||
"integrity": "sha512-neJAj8GwF0e8EpycYIDFqEPcx9Qz4GUho20jWFR7YiFeXzF1YMLdxB36PypcTSPMA+4+LvgyMacYhlr18Zlymw==",
|
||||
@ -1888,6 +2273,14 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/mime-match/-/mime-match-1.0.2.tgz",
|
||||
"integrity": "sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==",
|
||||
"dependencies": {
|
||||
"wildcard": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
@ -1924,6 +2317,11 @@
|
||||
"integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/namespace-emitter": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/namespace-emitter/-/namespace-emitter-2.0.1.tgz",
|
||||
"integrity": "sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g=="
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.8",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||
@ -1947,6 +2345,11 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/next-tick": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/next-tick/-/next-tick-1.1.0.tgz",
|
||||
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||
@ -2024,6 +2427,23 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/preact": {
|
||||
"version": "10.26.4",
|
||||
"resolved": "https://registry.npmmirror.com/preact/-/preact-10.26.4.tgz",
|
||||
"integrity": "sha512-KJhO7LBFTjP71d83trW+Ilnjbo+ySsaAgCfXOXUlmGzJ4ygYPWmysm77yg4emwfmoz3b22yvH5IsVFHbhUaH5w==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/preact"
|
||||
}
|
||||
},
|
||||
"node_modules/prismjs": {
|
||||
"version": "1.29.0",
|
||||
"resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.29.0.tgz",
|
||||
"integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
@ -2340,6 +2760,14 @@
|
||||
"@parcel/watcher": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/scroll-into-view-if-needed": {
|
||||
"version": "2.2.31",
|
||||
"resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz",
|
||||
"integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==",
|
||||
"dependencies": {
|
||||
"compute-scroll-into-view": "^1.0.20"
|
||||
}
|
||||
},
|
||||
"node_modules/sirv": {
|
||||
"version": "2.0.4",
|
||||
"integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==",
|
||||
@ -2352,6 +2780,35 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/slate": {
|
||||
"version": "0.72.8",
|
||||
"resolved": "https://registry.npmmirror.com/slate/-/slate-0.72.8.tgz",
|
||||
"integrity": "sha512-/nJwTswQgnRurpK+bGJFH1oM7naD5qDmHd89JyiKNT2oOKD8marW0QSBtuFnwEbL5aGCS8AmrhXQgNOsn4osAw==",
|
||||
"dependencies": {
|
||||
"immer": "^9.0.6",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"tiny-warning": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/slate-history": {
|
||||
"version": "0.66.0",
|
||||
"resolved": "https://registry.npmmirror.com/slate-history/-/slate-history-0.66.0.tgz",
|
||||
"integrity": "sha512-6MWpxGQZiMvSINlCbMW43E2YBSVMCMCIwQfBzGssjWw4kb0qfvj0pIdblWNRQZD0hR6WHP+dHHgGSeVdMWzfng==",
|
||||
"dependencies": {
|
||||
"is-plain-object": "^5.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"slate": ">=0.65.3"
|
||||
}
|
||||
},
|
||||
"node_modules/snabbdom": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmmirror.com/snabbdom/-/snabbdom-3.6.2.tgz",
|
||||
"integrity": "sha512-ig5qOnCDbugFntKi6c7Xlib8bA6xiJVk8O+WdFrV3wxbMqeHO0hXFQC4nAhPVWfZfi8255lcZkNhtIBINCc4+Q==",
|
||||
"engines": {
|
||||
"node": ">=12.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sort-on": {
|
||||
"version": "6.1.0",
|
||||
"integrity": "sha512-WTECP0nYNWO1n2g5bpsV0yZN9cBmZsF8ThHFbOqVN0HBFRoaQZLLEMvMmJlKHNPYQeVngeI5+jJzIfFqOIo1OA==",
|
||||
@ -2372,6 +2829,11 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ssr-window": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/ssr-window/-/ssr-window-3.0.0.tgz",
|
||||
"integrity": "sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA=="
|
||||
},
|
||||
"node_modules/steno": {
|
||||
"version": "4.0.2",
|
||||
"integrity": "sha512-yhPIQXjrlt1xv7dyPQg2P17URmXbuM5pdGkpiMB3RenprfiBlvK415Lctfe0eshk90oA7/tNq7WEiMK8RSP39A==",
|
||||
@ -2382,6 +2844,11 @@
|
||||
"url": "https://github.com/sponsors/typicode"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-warning": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/tiny-warning/-/tiny-warning-1.0.3.tgz",
|
||||
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
@ -2405,6 +2872,11 @@
|
||||
"version": "2.3.0",
|
||||
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
|
||||
},
|
||||
"node_modules/type": {
|
||||
"version": "2.7.3",
|
||||
"resolved": "https://registry.npmmirror.com/type/-/type-2.7.3.tgz",
|
||||
"integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "4.33.0",
|
||||
"integrity": "sha512-s6zVrxuyKbbAsSAD5ZPTB77q4YIdRctkTbJ2/Dqlinwz+8ooH2gd+YA7VA6Pa93KML9GockVvoxjZ2vHP+mu8g==",
|
||||
@ -2586,6 +3058,11 @@
|
||||
"resolved": "",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/wildcard": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/wildcard/-/wildcard-1.1.2.tgz",
|
||||
"integrity": "sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng=="
|
||||
},
|
||||
"node_modules/zrender": {
|
||||
"version": "5.6.1",
|
||||
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz",
|
||||
|
||||
@ -12,6 +12,8 @@
|
||||
"@dataview/datav-vue3": "^0.0.0-test.1672506674342",
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@kjgl77/datav-vue3": "^1.7.4",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
"axios": "^1.7.9",
|
||||
"datav-vue3": "^1.0.0",
|
||||
"echarts": "^5.6.0",
|
||||
|
||||
67
src/api/about/parkGuidelines.js
Normal file
67
src/api/about/parkGuidelines.js
Normal file
@ -0,0 +1,67 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 获取游园需知列表
|
||||
* @param {Object} params - 查询参数
|
||||
* @returns {Promise} 返回游园需知列表数据
|
||||
*/
|
||||
export function getParkGuidelinesList(params = {}) {
|
||||
return request.get('/api/admin/park-guidelines', { params })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取游园需知详情
|
||||
* @param {string|number} id - 游园需知ID
|
||||
* @returns {Promise} 返回游园需知详情
|
||||
*/
|
||||
export function getParkGuidelinesDetail(id) {
|
||||
return request.get(`/api/admin/park-guidelines/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加游园需知
|
||||
* @param {Object} data - 游园需知数据
|
||||
* @returns {Promise} 返回添加结果
|
||||
*/
|
||||
export function addParkGuidelines(data) {
|
||||
return request.post('/api/admin/park-guidelines', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新游园需知
|
||||
* @param {string|number} id - 游园需知ID
|
||||
* @param {Object} data - 更新数据
|
||||
* @returns {Promise} 返回更新结果
|
||||
*/
|
||||
export function updateParkGuidelines(id, data) {
|
||||
return request.put(`/api/admin/park-guidelines/${id}`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除游园需知
|
||||
* @param {string|number} id - 游园需知ID
|
||||
* @returns {Promise} 返回删除结果
|
||||
*/
|
||||
export function deleteParkGuidelines(id) {
|
||||
return request.delete(`/api/admin/park-guidelines/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新游园需知状态
|
||||
* @param {string|number} id - 游园需知ID
|
||||
* @param {Object} data - 状态数据 { status: number }
|
||||
* @returns {Promise} 返回更新结果
|
||||
*/
|
||||
export function updateParkGuidelinesStatus(id, data) {
|
||||
return request.put(`/api/admin/park-guidelines/${id}/status`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新游园需知排序
|
||||
* @param {string|number} id - 游园需知ID
|
||||
* @param {Object} data - 排序数据 { sort_order: number }
|
||||
* @returns {Promise} 返回更新结果
|
||||
*/
|
||||
export function updateParkGuidelinesSort(id, data) {
|
||||
return request.put(`/api/admin/park-guidelines/${id}/sort`, data)
|
||||
}
|
||||
@ -6,7 +6,11 @@ import request from '@/utils/request'
|
||||
* @returns {Promise} 返回创建结果
|
||||
*/
|
||||
export function createActivity(data) {
|
||||
return request.post('/api/education/activities', data)
|
||||
return request.post('/api/education/activities', data, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@ -51,7 +55,11 @@ export function batchCancelActivities(ids) {
|
||||
* @returns {Promise} 返回更新结果
|
||||
*/
|
||||
export function updateActivity(id, data) {
|
||||
return request.put(`/api/education/activities/${id}`, data)
|
||||
return request.put(`/api/education/activities/${id}`, data, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@ -88,4 +96,23 @@ export function getCategoryActivities(params = {}) {
|
||||
...params
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取活动报名记录
|
||||
* @param {string|number} activityId - 活动ID
|
||||
* @returns {Promise} 返回报名记录列表
|
||||
*/
|
||||
export function getActivityEnrollments(activityId) {
|
||||
return request.get(`/api/admin/activity-enrollments/activities/${activityId}/enrollments`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新报名状态
|
||||
* @param {string|number} enrollmentId - 报名ID
|
||||
* @param {number} status - 状态:0-已取消 1-已报名 2-已完成
|
||||
* @returns {Promise} 返回更新结果
|
||||
*/
|
||||
export function updateEnrollmentStatus(enrollmentId, status) {
|
||||
return request.put(`/api/admin/activity-enrollments/enrollments/${enrollmentId}/status`, { status })
|
||||
}
|
||||
@ -5,7 +5,7 @@ import request from '@/utils/request'
|
||||
* @param {Object} params - 查询参数
|
||||
* @param {number} [params.page=1] - 页码
|
||||
* @param {number} [params.page_size=10] - 每页条数
|
||||
* @param {string} [params.device_type] - 设备类型:10000-摄像头,10001-无人机
|
||||
* @param {string} [params.device_type] - 设备类型:10000-摄像头,10001-无人机,0-传感器
|
||||
* @returns {Promise} 返回设备列表数据
|
||||
*/
|
||||
export function getDeviceList(params = {}) {
|
||||
|
||||
@ -189,9 +189,10 @@ const handleLogout = () => {
|
||||
<el-sub-menu index="projects">
|
||||
<template #title>
|
||||
<el-icon><component :is="icons.DataLine" /></el-icon>
|
||||
<span>项目简介</span>
|
||||
<span>关于我们</span>
|
||||
</template>
|
||||
<el-menu-item index="/projects">项目简介</el-menu-item>
|
||||
<el-menu-item index="/about/projects">项目简介</el-menu-item>
|
||||
<el-menu-item index="/about/needToKnow">游园需知</el-menu-item>
|
||||
</el-sub-menu>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
|
||||
@ -188,10 +188,16 @@ const router = createRouter({
|
||||
meta: { title: '数据管理', icon: 'data' }
|
||||
},
|
||||
{
|
||||
path: 'projects',
|
||||
path: 'about/projects',
|
||||
name: 'Projects',
|
||||
component: () => import('@/views/projects/index.vue'),
|
||||
meta: { title: '项目简介管理', icon: 'data' }
|
||||
component: () => import('@/views/about/projects/index.vue'),
|
||||
meta: { title: '项目简介', icon: 'data' }
|
||||
},
|
||||
{
|
||||
path: 'about/needToKnow',
|
||||
name: 'NeedToKnow',
|
||||
component: () => import('@/views/about/needToKnow/index.vue'),
|
||||
meta: { title: '游园需知', icon: 'data' }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,226 +1,265 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { Plus, Monitor, Timer } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getDeviceList, addDevice, updateDevice, deleteDevice } from '@/api/device'
|
||||
|
||||
// 模拟传感器数据
|
||||
const sensorList = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: '水质传感器01',
|
||||
type: 'water',
|
||||
status: 'online',
|
||||
data: {
|
||||
temperature: '25.6°C',
|
||||
ph: '7.2',
|
||||
oxygen: '6.8mg/L'
|
||||
// 加载状态
|
||||
const loading = ref(false)
|
||||
|
||||
// 传感器列表数据
|
||||
const sensorList = ref([])
|
||||
|
||||
// 分页参数
|
||||
const pagination = ref({
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
total: 0
|
||||
})
|
||||
|
||||
// 获取传感器列表
|
||||
const getSensorList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getDeviceList({
|
||||
page: pagination.value.page,
|
||||
page_size: pagination.value.page_size,
|
||||
device_type: 0 // 传感器类型为0
|
||||
})
|
||||
if (res.success) {
|
||||
sensorList.value = res.data.list || []
|
||||
if (res.data.pagination) {
|
||||
pagination.value.total = res.data.pagination.total
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(res.message || '获取传感器列表失败')
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '空气传感器01',
|
||||
type: 'air',
|
||||
status: 'online',
|
||||
data: {
|
||||
temperature: '28.3°C',
|
||||
humidity: '65%',
|
||||
pm25: '35μg/m³'
|
||||
} catch (error) {
|
||||
console.error('获取传感器列表失败:', error)
|
||||
ElMessage.error('获取传感器列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 处理页码改变
|
||||
const handleCurrentChange = (page) => {
|
||||
pagination.value.page = page
|
||||
getSensorList()
|
||||
}
|
||||
|
||||
// 处理每页条数改变
|
||||
const handleSizeChange = (size) => {
|
||||
pagination.value.page_size = size
|
||||
pagination.value.page = 1
|
||||
getSensorList()
|
||||
}
|
||||
|
||||
// 删除传感器
|
||||
const handleDelete = async (id) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除该传感器吗?', '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
const res = await deleteDevice(id)
|
||||
if (res.success) {
|
||||
ElMessage.success('删除成功')
|
||||
getSensorList()
|
||||
} else {
|
||||
ElMessage.error(res.message || '删除失败')
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '土壤传感器01',
|
||||
type: 'soil',
|
||||
status: 'offline',
|
||||
data: {
|
||||
moisture: '42%',
|
||||
ph: '6.8',
|
||||
nutrients: '中等'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '水质传感器02',
|
||||
type: 'water',
|
||||
status: 'online',
|
||||
data: {
|
||||
temperature: '26.1°C',
|
||||
ph: '7.4',
|
||||
oxygen: '7.1mg/L'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: '空气传感器02',
|
||||
type: 'air',
|
||||
status: 'error',
|
||||
data: {
|
||||
temperature: '27.8°C',
|
||||
humidity: '58%',
|
||||
pm25: '42μg/m³'
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('删除传感器失败:', error)
|
||||
ElMessage.error('删除失败')
|
||||
}
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
// 获取传感器状态样式
|
||||
const getStatusStyle = (status) => {
|
||||
if (!status) return { color: '#909399', text: '未知' }
|
||||
return {
|
||||
online: {
|
||||
1: {
|
||||
color: '#67C23A',
|
||||
text: '在线'
|
||||
},
|
||||
offline: {
|
||||
0: {
|
||||
color: '#909399',
|
||||
text: '离线'
|
||||
},
|
||||
error: {
|
||||
2: {
|
||||
color: '#F56C6C',
|
||||
text: '异常'
|
||||
}
|
||||
}[status]
|
||||
}[status.code] || { color: '#909399', text: status.text || '未知' }
|
||||
}
|
||||
|
||||
// 获取传感器类型信息
|
||||
const getTypeInfo = (type) => {
|
||||
return {
|
||||
water: {
|
||||
icon: 'WaterMeter',
|
||||
text: '水质传感器',
|
||||
0: {
|
||||
icon: 'Monitor',
|
||||
text: '传感器',
|
||||
color: '#409EFF'
|
||||
},
|
||||
air: {
|
||||
icon: 'Sunny',
|
||||
text: '空气传感器',
|
||||
color: '#67C23A'
|
||||
},
|
||||
soil: {
|
||||
icon: 'Plant',
|
||||
text: '土壤传感器',
|
||||
color: '#E6A23C'
|
||||
}
|
||||
}[type]
|
||||
}[type] || {
|
||||
icon: 'Monitor',
|
||||
text: '传感器',
|
||||
color: '#409EFF'
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化数据显示
|
||||
const formatValue = (value, unit = '') => {
|
||||
if (value === null || value === undefined) return '暂无数据'
|
||||
return `${value}${unit}`
|
||||
}
|
||||
|
||||
// 页面加载时获取数据
|
||||
onMounted(() => {
|
||||
getSensorList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="sensor-management">
|
||||
<div class="page-header">
|
||||
<div class="header-title">传感器管理</div>
|
||||
<div class="header-title">
|
||||
<el-icon><Monitor /></el-icon>
|
||||
传感器管理
|
||||
</div>
|
||||
<el-button type="primary" :icon="Plus">添加传感器</el-button>
|
||||
</div>
|
||||
|
||||
<div class="sensor-container">
|
||||
<div class="sensor-container" v-loading="loading">
|
||||
<div
|
||||
v-for="sensor in sensorList"
|
||||
:key="sensor.id"
|
||||
class="sensor-card"
|
||||
:class="{ 'offline': sensor.status === 'offline' }"
|
||||
:class="{ 'offline': sensor.status?.code === 0 }"
|
||||
>
|
||||
<div class="card-header">
|
||||
<div class="sensor-info">
|
||||
<el-icon :class="sensor.type">
|
||||
<component :is="getTypeInfo(sensor.type).icon" />
|
||||
<el-icon :class="sensor.device_type">
|
||||
<component :is="getTypeInfo(sensor.device_type).icon" />
|
||||
</el-icon>
|
||||
<span class="sensor-name">{{ sensor.name }}</span>
|
||||
<el-tag
|
||||
size="small"
|
||||
:type="sensor.status === 'online' ? 'success' : sensor.status === 'error' ? 'danger' : 'info'"
|
||||
>
|
||||
{{ getStatusStyle(sensor.status).text }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="sensor-actions">
|
||||
<el-button type="primary" link>编辑</el-button>
|
||||
<el-button type="danger" link>删除</el-button>
|
||||
<span class="sensor-name">{{ sensor.device_name }}</span>
|
||||
</div>
|
||||
<el-tag
|
||||
size="small"
|
||||
:type="sensor.status?.code === 1 ? 'success' : sensor.status?.code === 2 ? 'danger' : 'info'"
|
||||
>
|
||||
{{ getStatusStyle(sensor.status).text }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<template v-if="sensor.type === 'water'">
|
||||
<div class="data-item">
|
||||
<span class="label">温度</span>
|
||||
<span class="value">{{ sensor.data.temperature }}</span>
|
||||
<div class="info-section">
|
||||
<div class="info-item">
|
||||
<span class="label">设备编号</span>
|
||||
<span class="value">{{ sensor.device_code }}</span>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<span class="label">pH值</span>
|
||||
<span class="value">{{ sensor.data.ph }}</span>
|
||||
<div class="info-item">
|
||||
<span class="label">安装位置</span>
|
||||
<span class="value">{{ sensor.install_location || '暂无数据' }}</span>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<span class="label">溶解氧</span>
|
||||
<span class="value">{{ sensor.data.oxygen }}</span>
|
||||
</div>
|
||||
<div class="data-section">
|
||||
<div class="data-grid">
|
||||
<div class="data-item">
|
||||
<div class="data-value">{{ formatValue(sensor.data?.temp, '°C') }}</div>
|
||||
<div class="data-label">温度</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-value">{{ formatValue(sensor.data?.humi, '%') }}</div>
|
||||
<div class="data-label">湿度</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-value">{{ formatValue(sensor.data?.light_adc) }}</div>
|
||||
<div class="data-label">光照强度</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-value">{{ formatValue(sensor.data?.soil_adc) }}</div>
|
||||
<div class="data-label">土壤湿度</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="sensor.type === 'air'">
|
||||
<div class="data-item">
|
||||
<span class="label">温度</span>
|
||||
<span class="value">{{ sensor.data.temperature }}</span>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<div class="update-time">
|
||||
<el-icon><Timer /></el-icon>
|
||||
{{ sensor.last_update_time ? new Date(sensor.last_update_time).toLocaleString() : '暂无数据' }}
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<span class="label">湿度</span>
|
||||
<span class="value">{{ sensor.data.humidity }}</span>
|
||||
<div class="actions">
|
||||
<el-button type="primary" link>编辑</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(sensor.id)">删除</el-button>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<span class="label">PM2.5</span>
|
||||
<span class="value">{{ sensor.data.pm25 }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="data-item">
|
||||
<span class="label">湿度</span>
|
||||
<span class="value">{{ sensor.data.moisture }}</span>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<span class="label">pH值</span>
|
||||
<span class="value">{{ sensor.data.ph }}</span>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<span class="label">养分</span>
|
||||
<span class="value">{{ sensor.data.nutrients }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container" v-if="pagination.total > 10">
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.page"
|
||||
v-model:page-size="pagination.page_size"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="pagination.total"
|
||||
:background="true"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sensor-management {
|
||||
padding: 16px;
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
background: #f5f7fa;
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
padding: 0 4px;
|
||||
|
||||
.header-title {
|
||||
font-size: 20px;
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.el-icon {
|
||||
font-size: 24px;
|
||||
color: #409EFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sensor-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
|
||||
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
|
||||
gap: 20px;
|
||||
padding: 20px 0;
|
||||
position: relative;
|
||||
min-height: 200px;
|
||||
|
||||
.sensor-card {
|
||||
background: #fff;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
&.offline {
|
||||
@ -229,35 +268,28 @@ const getTypeInfo = (type) => {
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #fafafa;
|
||||
|
||||
.sensor-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: 10px;
|
||||
|
||||
.el-icon {
|
||||
font-size: 20px;
|
||||
|
||||
&.water {
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
&.air {
|
||||
color: #67C23A;
|
||||
}
|
||||
|
||||
&.soil {
|
||||
color: #E6A23C;
|
||||
}
|
||||
color: #409EFF;
|
||||
background: rgba(64, 158, 255, 0.1);
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.sensor-name {
|
||||
font-size: 14px;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
@ -265,31 +297,99 @@ const getTypeInfo = (type) => {
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 16px;
|
||||
padding: 16px 20px;
|
||||
|
||||
.data-item {
|
||||
.info-section {
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px dashed #f0f2f5;
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #606266;
|
||||
font-size: 13px;
|
||||
font-family: monospace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.data-section {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.data-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16px;
|
||||
|
||||
.data-item {
|
||||
background: #f8f9fb;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
|
||||
.data-value {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #409EFF;
|
||||
margin-bottom: 4px;
|
||||
font-family: 'DIN Alternate', sans-serif;
|
||||
}
|
||||
|
||||
.data-label {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer-section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px dashed #f0f2f5;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
.update-time {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
.el-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
padding: 16px 0;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
515
src/views/about/needToKnow/index.vue
Normal file
515
src/views/about/needToKnow/index.vue
Normal file
@ -0,0 +1,515 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, computed, shallowRef, onBeforeUnmount, watch } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Edit, Delete } from '@element-plus/icons-vue'
|
||||
import { getParkGuidelinesList, addParkGuidelines, updateParkGuidelines, deleteParkGuidelines, updateParkGuidelinesStatus, updateParkGuidelinesSort } from '@/api/about/parkGuidelines'
|
||||
import { formatDateTime } from '@/utils/format'
|
||||
import '@wangeditor/editor/dist/css/style.css'
|
||||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
title: '',
|
||||
category: undefined,
|
||||
status: undefined
|
||||
})
|
||||
|
||||
// 数据列表
|
||||
const loading = ref(false)
|
||||
const guidelinesList = ref([])
|
||||
const pagination = reactive({
|
||||
total: 0,
|
||||
page: 1,
|
||||
pageSize: 10
|
||||
})
|
||||
|
||||
// 类别选项
|
||||
const categoryOptions = [
|
||||
{ label: '注意事项', value: 'notice' },
|
||||
{ label: '园区规则', value: 'rules' },
|
||||
{ label: '设施说明', value: 'facilities' }
|
||||
]
|
||||
|
||||
// 弹窗显示控制
|
||||
const dialogVisible = ref(false)
|
||||
const dialogType = ref('add')
|
||||
const dialogTitle = ref('添加游园需知')
|
||||
|
||||
// 编辑器实例,必须用 shallowRef
|
||||
const editorRef = shallowRef()
|
||||
|
||||
// 内容 HTML
|
||||
const valueHtml = ref('<p>请输入内容</p>')
|
||||
|
||||
// 工具栏配置
|
||||
const toolbarConfig = {
|
||||
excludeKeys: [
|
||||
'uploadImage',
|
||||
'uploadVideo',
|
||||
'insertTable',
|
||||
'group-video',
|
||||
'group-image',
|
||||
'insertTable'
|
||||
]
|
||||
}
|
||||
|
||||
// 编辑器配置
|
||||
const editorConfig = {
|
||||
placeholder: '请输入内容...',
|
||||
autoFocus: false,
|
||||
MENU_CONF: {}
|
||||
}
|
||||
|
||||
// 处理编辑器创建完成
|
||||
const handleCreated = (editor) => {
|
||||
editorRef.value = editor
|
||||
}
|
||||
|
||||
// 组件销毁时,也及时销毁编辑器
|
||||
onBeforeUnmount(() => {
|
||||
const editor = editorRef.value
|
||||
if (editor == null) return
|
||||
editor.destroy()
|
||||
})
|
||||
|
||||
// 表单数据
|
||||
const formRef = ref()
|
||||
const formData = ref({
|
||||
title: '',
|
||||
content: '',
|
||||
category: '',
|
||||
sort_order: 0,
|
||||
status: 1
|
||||
})
|
||||
|
||||
// 监听富文本内容变化
|
||||
watch(valueHtml, (html) => {
|
||||
formData.value.content = html
|
||||
})
|
||||
|
||||
// 表单校验规则
|
||||
const rules = {
|
||||
title: [
|
||||
{ required: true, message: '请输入标题', trigger: 'blur' },
|
||||
{ max: 200, message: '长度不能超过200个字符', trigger: 'blur' }
|
||||
],
|
||||
content: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入内容',
|
||||
trigger: 'change',
|
||||
validator: (rule, value, callback) => {
|
||||
if (!valueHtml.value || valueHtml.value === '<p>请输入内容</p>' || valueHtml.value === '<p><br></p>') {
|
||||
callback(new Error('请输入内容'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
category: [
|
||||
{ required: true, message: '请选择类别', trigger: 'change' }
|
||||
],
|
||||
sort_order: [
|
||||
{ required: true, message: '请输入排序顺序', trigger: 'blur' },
|
||||
{ type: 'number', message: '排序顺序必须为数字', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 获取列表数据
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 构建查询参数
|
||||
const params = {
|
||||
...queryParams,
|
||||
category: queryParams.category || undefined,
|
||||
status: queryParams.status === '' ? undefined : queryParams.status
|
||||
}
|
||||
|
||||
// 移除所有 undefined 的参数
|
||||
Object.keys(params).forEach(key =>
|
||||
params[key] === undefined && delete params[key]
|
||||
)
|
||||
|
||||
const res = await getParkGuidelinesList(params)
|
||||
if (res.success && res.data) {
|
||||
// 确保数据是数组
|
||||
let list = Array.isArray(res.data.list) ? [...res.data.list] : []
|
||||
|
||||
// 如果有标题搜索条件,在前端进行过滤
|
||||
const searchText = queryParams.title?.trim().toLowerCase()
|
||||
if (searchText) {
|
||||
list = list.filter(item =>
|
||||
item.title?.toLowerCase().includes(searchText)
|
||||
)
|
||||
}
|
||||
|
||||
guidelinesList.value = list
|
||||
|
||||
// 更新分页信息
|
||||
if (res.data.pagination) {
|
||||
pagination.total = searchText ? list.length : Number(res.data.pagination.total) || 0
|
||||
pagination.page = Number(res.data.pagination.current) || 1
|
||||
pagination.pageSize = Number(res.data.pagination.page_size) || 10
|
||||
}
|
||||
} else {
|
||||
guidelinesList.value = []
|
||||
ElMessage.error(res.message || '获取数据失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取游园需知列表失败:', error)
|
||||
guidelinesList.value = []
|
||||
ElMessage.error('获取游园需知列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 重置查询
|
||||
const resetQuery = () => {
|
||||
queryParams.title = ''
|
||||
queryParams.category = undefined
|
||||
queryParams.status = undefined
|
||||
getList()
|
||||
}
|
||||
|
||||
// 处理添加/编辑
|
||||
const handleAddOrEdit = (type, row) => {
|
||||
dialogType.value = type
|
||||
dialogTitle.value = type === 'add' ? '添加游园需知' : '编辑游园需知'
|
||||
dialogVisible.value = true
|
||||
|
||||
if (type === 'edit' && row) {
|
||||
formData.value = { ...row }
|
||||
valueHtml.value = row.content || '<p><br></p>'
|
||||
} else {
|
||||
formData.value = {
|
||||
title: '',
|
||||
content: '',
|
||||
category: '',
|
||||
sort_order: 0,
|
||||
status: 1
|
||||
}
|
||||
valueHtml.value = '<p><br></p>'
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const submitForm = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
const api = dialogType.value === 'add' ? addParkGuidelines : updateParkGuidelines
|
||||
// 构建提交的数据对象,包含富文本内容
|
||||
const submitData = {
|
||||
title: formData.value.title,
|
||||
content: valueHtml.value,
|
||||
category: formData.value.category,
|
||||
sort_order: formData.value.sort_order,
|
||||
status: formData.value.status
|
||||
}
|
||||
|
||||
const res = await api(
|
||||
dialogType.value === 'add' ? submitData : formData.value.id,
|
||||
submitData
|
||||
)
|
||||
|
||||
if (res.success) {
|
||||
ElMessage.success(dialogType.value === 'add' ? '添加成功' : '修改成功')
|
||||
dialogVisible.value = false
|
||||
getList()
|
||||
} else {
|
||||
ElMessage.error(res.message || '操作失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交失败:', error)
|
||||
ElMessage.error('提交失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 处理删除
|
||||
const handleDelete = async (row) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除该条游园需知吗?', '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
const res = await deleteParkGuidelines(row.id)
|
||||
if (res.success) {
|
||||
ElMessage.success('删除成功')
|
||||
getList()
|
||||
} else {
|
||||
ElMessage.error(res.message || '删除失败')
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('删除失败:', error)
|
||||
ElMessage.error('删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理页码改变
|
||||
const handleCurrentChange = (val) => {
|
||||
queryParams.page = val
|
||||
getList()
|
||||
}
|
||||
|
||||
// 处理每页条数改变
|
||||
const handleSizeChange = (val) => {
|
||||
queryParams.page_size = val
|
||||
queryParams.page = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
// 移除HTML标签,获取纯文本内容
|
||||
const getPlainText = (html) => {
|
||||
if (!html) return ''
|
||||
const temp = document.createElement('div')
|
||||
temp.innerHTML = html
|
||||
return temp.textContent || temp.innerText || ''
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 搜索区域 -->
|
||||
<el-card class="search-container">
|
||||
<el-form :model="queryParams" ref="queryForm" :inline="true">
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input
|
||||
v-model="queryParams.title"
|
||||
placeholder="请输入标题"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="类别" prop="category">
|
||||
<el-select
|
||||
v-model="queryParams.category"
|
||||
placeholder="请选择类别"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in categoryOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择状态"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
>
|
||||
<el-option label="启用" :value="1" />
|
||||
<el-option label="禁用" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="getList">查询</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 操作按钮区域 -->
|
||||
<el-card class="table-container">
|
||||
<template #header>
|
||||
<el-button type="primary" :icon="Plus" @click="handleAddOrEdit('add')">新增</el-button>
|
||||
</template>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="guidelinesList"
|
||||
border
|
||||
style="width: 100%"
|
||||
row-key="id"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="title" label="标题" min-width="200" show-overflow-tooltip align="center"/>
|
||||
<el-table-column prop="category" label="类别" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ categoryOptions.find(item => item.value === row.category)?.label || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="content" label="内容" min-width="300" show-overflow-tooltip align="center">
|
||||
<template #default="{ row }">
|
||||
{{ getPlainText(row.content) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sort_order" label="排序" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.sort_order }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'success' : 'info'" size="small">
|
||||
{{ row.status === 1 ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="created_at" label="创建时间" width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.created_at) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="creator_name" label="创建人" width="100" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="updated_at" label="更新时间" width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.updated_at) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="updater_name" label="更新人" width="100" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="150" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link :icon="Edit" @click="handleAddOrEdit('edit', row)">编辑</el-button>
|
||||
<el-button type="danger" link :icon="Delete" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.page"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="pagination.total"
|
||||
:background="true"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
v-if="pagination.total > 10"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 添加/编辑弹窗 -->
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
width="800px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input v-model="formData.title" placeholder="请输入标题" />
|
||||
</el-form-item>
|
||||
<el-form-item label="类别" prop="category">
|
||||
<el-select v-model="formData.category" placeholder="请选择类别" style="width: 100%">
|
||||
<el-option label="注意事项" value="notice" />
|
||||
<el-option label="园区规则" value="rules" />
|
||||
<el-option label="设施说明" value="facilities" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="内容" prop="content">
|
||||
<div class="editor-container">
|
||||
<Toolbar
|
||||
style="border-bottom: 1px solid #ccc"
|
||||
:editor="editorRef"
|
||||
:defaultConfig="toolbarConfig"
|
||||
mode="default"
|
||||
/>
|
||||
<Editor
|
||||
style="height: 300px"
|
||||
v-model="valueHtml"
|
||||
:defaultConfig="editorConfig"
|
||||
mode="default"
|
||||
@onCreated="handleCreated"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序顺序" prop="sort_order">
|
||||
<el-input-number
|
||||
v-model="formData.sort_order"
|
||||
:min="0"
|
||||
:max="9999"
|
||||
controls-position="right"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio :value="1">启用</el-radio>
|
||||
<el-radio :value="0">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
|
||||
.search-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
margin-bottom: 20px;
|
||||
|
||||
:deep(.el-card__header) {
|
||||
padding: 12px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
padding-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-container {
|
||||
border: 1px solid #ccc;
|
||||
z-index: 100;
|
||||
|
||||
:deep(.w-e-text-container) {
|
||||
min-height: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理编辑器工具栏的层级问题
|
||||
:deep(.w-e-toolbar) {
|
||||
z-index: 2 !important;
|
||||
}
|
||||
|
||||
:deep(.w-e-text-container) {
|
||||
z-index: 1 !important;
|
||||
}
|
||||
</style>
|
||||
@ -1,8 +1,9 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { ref, reactive, onMounted, watch } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Search, Refresh } from '@element-plus/icons-vue'
|
||||
import { Plus, Edit, Delete, Search, Refresh } from '@element-plus/icons-vue'
|
||||
import { formatDateTime } from '@/utils/format'
|
||||
import { reverseArray } from '@/utils/sort'
|
||||
import {
|
||||
getActivityList,
|
||||
createActivity,
|
||||
@ -10,32 +11,31 @@ import {
|
||||
cancelActivity,
|
||||
batchCancelActivities,
|
||||
updateActivityStatus,
|
||||
checkActivityCapacity
|
||||
checkActivityCapacity,
|
||||
getActivityEnrollments,
|
||||
updateEnrollmentStatus
|
||||
} from '@/api/activity/study'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
// 格式化日期时间
|
||||
const formatDate = (date) => {
|
||||
if (!date) return ''
|
||||
const d = new Date(date)
|
||||
const pad = (num) => (num < 10 ? `0${num}` : num)
|
||||
|
||||
const year = d.getFullYear()
|
||||
const month = pad(d.getMonth() + 1)
|
||||
const day = pad(d.getDate())
|
||||
const hours = pad(d.getHours())
|
||||
const minutes = pad(d.getMinutes())
|
||||
const seconds = pad(d.getSeconds())
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = ref({
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
title: '',
|
||||
category: '',
|
||||
status: ''
|
||||
category: undefined,
|
||||
status: undefined
|
||||
})
|
||||
|
||||
// 数据列表
|
||||
const loading = ref(false)
|
||||
const tableData = ref([])
|
||||
const selectedRows = ref([])
|
||||
|
||||
// 分页配置
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
|
||||
// 研学活动类型选项
|
||||
const categoryOptions = [
|
||||
{ label: '实地考察', value: 'field_study' },
|
||||
@ -51,25 +51,24 @@ const statusOptions = [
|
||||
{ label: '已结束', value: 0 }
|
||||
]
|
||||
|
||||
// 表格数据
|
||||
const tableData = ref([])
|
||||
const allData = ref([]) // 添加一个存储所有数据的数组
|
||||
const loading = ref(false)
|
||||
const selectedRows = ref([])
|
||||
// 搜索表单
|
||||
const searchForm = ref({
|
||||
title: '',
|
||||
category: '',
|
||||
status: ''
|
||||
})
|
||||
|
||||
// 分页配置
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
// 获取用户store
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 获取研学活动列表
|
||||
// 获取列表数据
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getActivityList()
|
||||
if (res.success) {
|
||||
// 保存所有数据
|
||||
allData.value = res.data.map(item => ({
|
||||
// 保存所有数据到临时变量
|
||||
let allData = res.data.map(item => ({
|
||||
id: item.id,
|
||||
title: item.title,
|
||||
category: item.category,
|
||||
@ -84,64 +83,67 @@ const getList = async () => {
|
||||
status: item.status,
|
||||
created_at: item.created_at,
|
||||
updated_at: item.updated_at,
|
||||
activity_code: item.activity_code
|
||||
activity_code: item.activity_code,
|
||||
image: item.image
|
||||
}))
|
||||
// 前端搜索和分页
|
||||
filterAndPaginateData()
|
||||
|
||||
// 将数据倒序排列
|
||||
allData = reverseArray(allData)
|
||||
|
||||
// 应用搜索过滤
|
||||
if (searchForm.value.title) {
|
||||
const keyword = searchForm.value.title.toLowerCase()
|
||||
allData = allData.filter(item =>
|
||||
item.title.toLowerCase().includes(keyword)
|
||||
)
|
||||
}
|
||||
|
||||
if (searchForm.value.category) {
|
||||
allData = allData.filter(item =>
|
||||
item.category === searchForm.value.category
|
||||
)
|
||||
}
|
||||
|
||||
if (searchForm.value.status !== '') {
|
||||
allData = allData.filter(item =>
|
||||
item.status === Number(searchForm.value.status)
|
||||
)
|
||||
}
|
||||
|
||||
// 更新总数据
|
||||
total.value = allData.length
|
||||
|
||||
// 前端分页处理
|
||||
const start = (currentPage.value - 1) * pageSize.value
|
||||
const end = start + pageSize.value
|
||||
tableData.value = allData.slice(start, end)
|
||||
} else {
|
||||
ElMessage.error(res.message || '获取研学活动列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取研学活动列表错误:', error)
|
||||
ElMessage.error('获取研学活动列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 前端搜索和分页
|
||||
const filterAndPaginateData = () => {
|
||||
// 1. 先进行搜索过滤
|
||||
let filteredData = [...allData.value]
|
||||
// 处理页码改变
|
||||
const handleCurrentChange = (val) => {
|
||||
currentPage.value = val
|
||||
getList()
|
||||
}
|
||||
|
||||
// 按活动名称搜索
|
||||
if (searchForm.value.title) {
|
||||
const keyword = searchForm.value.title.toLowerCase()
|
||||
filteredData = filteredData.filter(item =>
|
||||
item.title.toLowerCase().includes(keyword)
|
||||
)
|
||||
}
|
||||
|
||||
// 按活动类型过滤
|
||||
if (searchForm.value.category) {
|
||||
filteredData = filteredData.filter(item =>
|
||||
item.category === searchForm.value.category
|
||||
)
|
||||
}
|
||||
|
||||
// 按状态过滤 - 只在状态值不为空字符串时进行过滤
|
||||
if (searchForm.value.status !== '') {
|
||||
filteredData = filteredData.filter(item =>
|
||||
item.status === Number(searchForm.value.status)
|
||||
)
|
||||
}
|
||||
|
||||
// 按id正序排序
|
||||
filteredData.sort((a, b) => a.id - b.id)
|
||||
|
||||
// 2. 更新总数
|
||||
total.value = filteredData.length
|
||||
|
||||
// 3. 进行分页
|
||||
const start = (currentPage.value - 1) * pageSize.value
|
||||
const end = start + pageSize.value
|
||||
tableData.value = filteredData.slice(start, end)
|
||||
// 处理每页条数改变
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
currentPage.value = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
// 处理搜索
|
||||
const handleSearch = () => {
|
||||
currentPage.value = 1 // 重置到第一页
|
||||
filterAndPaginateData()
|
||||
getList()
|
||||
}
|
||||
|
||||
// 重置搜索
|
||||
@ -152,7 +154,7 @@ const resetSearch = () => {
|
||||
status: ''
|
||||
}
|
||||
currentPage.value = 1
|
||||
filterAndPaginateData()
|
||||
getList()
|
||||
}
|
||||
|
||||
// 处理清空操作
|
||||
@ -163,23 +165,16 @@ const handleClear = (field) => {
|
||||
|
||||
// 监听分页变化
|
||||
watch([currentPage, pageSize], () => {
|
||||
filterAndPaginateData()
|
||||
getList()
|
||||
})
|
||||
|
||||
// 监听搜索条件变化
|
||||
watch(searchForm, (newVal, oldVal) => {
|
||||
// 如果是状态字段发生变化且变为空字符串,说明是清空操作
|
||||
if (oldVal.status !== '' && newVal.status === '') {
|
||||
// 重置页码
|
||||
currentPage.value = 1
|
||||
// 直接更新数据,不进行状态过滤
|
||||
filterAndPaginateData()
|
||||
return
|
||||
}
|
||||
|
||||
// 其他情况下重置到第一页并更新数据
|
||||
currentPage.value = 1
|
||||
filterAndPaginateData()
|
||||
getList()
|
||||
}, { deep: true })
|
||||
|
||||
// 刷新列表
|
||||
@ -191,6 +186,7 @@ const handleRefresh = () => {
|
||||
const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('')
|
||||
const formMode = ref('create')
|
||||
const formRef = ref()
|
||||
const form = ref({
|
||||
title: '',
|
||||
category: 'field_study',
|
||||
@ -201,7 +197,9 @@ const form = ref({
|
||||
description: '',
|
||||
requirements: '',
|
||||
cost: 50.00,
|
||||
status: 1
|
||||
status: 1,
|
||||
image_url: null,
|
||||
imageUrl: ''
|
||||
})
|
||||
|
||||
// 表单规则
|
||||
@ -226,9 +224,48 @@ const formRules = {
|
||||
cost: [
|
||||
{ required: true, message: '请输入活动费用', trigger: 'blur' },
|
||||
{ type: 'number', min: 0, message: '费用不能小于0', trigger: 'blur' }
|
||||
],
|
||||
image_url: [
|
||||
{ required: true, message: '请上传活动图片', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
// 处理图片上传前的验证
|
||||
const beforeImageUpload = (file) => {
|
||||
const isImage = file.type.startsWith('image/')
|
||||
const isLt2M = file.size / 1024 / 1024 < 2
|
||||
|
||||
if (!isImage) {
|
||||
ElMessage.error('只能上传图片文件!')
|
||||
return false
|
||||
}
|
||||
if (!isLt2M) {
|
||||
ElMessage.error('图片大小不能超过 2MB!')
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 处理图片变更
|
||||
const handleImageChange = (uploadFile) => {
|
||||
const file = uploadFile.raw
|
||||
if (!file) return
|
||||
|
||||
form.value.image_url = file
|
||||
form.value.imageUrl = URL.createObjectURL(file)
|
||||
}
|
||||
|
||||
// 处理图片移除
|
||||
const handleImageRemove = () => {
|
||||
form.value.imageUrl = ''
|
||||
form.value.image_url = null
|
||||
// 重置表单的图片验证状态
|
||||
if (formRef.value) {
|
||||
formRef.value.validateField('image_url')
|
||||
}
|
||||
}
|
||||
|
||||
// 新增研学活动
|
||||
const handleAdd = () => {
|
||||
formMode.value = 'create'
|
||||
@ -243,7 +280,9 @@ const handleAdd = () => {
|
||||
description: '',
|
||||
requirements: '',
|
||||
cost: 50.00,
|
||||
status: 1
|
||||
status: 1,
|
||||
image_url: null,
|
||||
imageUrl: ''
|
||||
}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
@ -254,42 +293,68 @@ const handleEdit = (row) => {
|
||||
dialogTitle.value = '编辑研学活动'
|
||||
form.value = {
|
||||
...row,
|
||||
category: row.category || 'field_study'
|
||||
category: row.category || 'field_study',
|
||||
imageUrl: row.image || '',
|
||||
image_url: null // 重置图片文件
|
||||
}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const formRef = ref(null)
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
// 格式化提交数据
|
||||
const submitData = {
|
||||
title: form.value.title.trim(),
|
||||
category: form.value.category,
|
||||
start_time: form.value.start_time ? formatDate(new Date(form.value.start_time)) : '',
|
||||
end_time: form.value.end_time ? formatDate(new Date(form.value.end_time)) : '',
|
||||
location: form.value.location.trim(),
|
||||
capacity: Number(form.value.capacity),
|
||||
description: (form.value.description || '').trim(),
|
||||
requirements: (form.value.requirements || '').trim(),
|
||||
cost: Number(form.value.cost),
|
||||
status: Number(form.value.status)
|
||||
// 创建 FormData 对象
|
||||
const formData = new FormData()
|
||||
|
||||
// 添加基本字段
|
||||
formData.append('title', form.value.title.trim())
|
||||
formData.append('category', form.value.category)
|
||||
|
||||
// 处理日期 - 使用MySQL兼容的日期时间格式
|
||||
if (form.value.start_time) {
|
||||
const startDate = new Date(form.value.start_time)
|
||||
const formattedStartTime = formatDate(startDate)
|
||||
formData.append('start_time', formattedStartTime)
|
||||
}
|
||||
|
||||
if (form.value.end_time) {
|
||||
const endDate = new Date(form.value.end_time)
|
||||
const formattedEndTime = formatDate(endDate)
|
||||
formData.append('end_time', formattedEndTime)
|
||||
}
|
||||
|
||||
// 确保数字字段是数字类型
|
||||
formData.append('capacity', form.value.capacity.toString())
|
||||
formData.append('cost', form.value.cost.toString())
|
||||
formData.append('status', form.value.status.toString())
|
||||
|
||||
// 其他字段
|
||||
formData.append('location', form.value.location.trim())
|
||||
if (form.value.description) {
|
||||
formData.append('description', form.value.description.trim())
|
||||
}
|
||||
if (form.value.requirements) {
|
||||
formData.append('requirements', form.value.requirements.trim())
|
||||
}
|
||||
|
||||
// 添加图片文件
|
||||
if (form.value.image_url instanceof File) {
|
||||
formData.append('image_url', form.value.image_url)
|
||||
}
|
||||
|
||||
// 验证必填字段
|
||||
if (!submitData.title || !submitData.start_time || !submitData.end_time || !submitData.location) {
|
||||
if (!formData.get('title') || !formData.get('start_time') || !formData.get('end_time') || !formData.get('location')) {
|
||||
ElMessage.error('请填写必填字段')
|
||||
return
|
||||
}
|
||||
|
||||
// 验证时间
|
||||
const startTime = new Date(submitData.start_time)
|
||||
const endTime = new Date(submitData.end_time)
|
||||
const startTime = new Date(formData.get('start_time'))
|
||||
const endTime = new Date(formData.get('end_time'))
|
||||
const now = new Date()
|
||||
|
||||
// 验证结束时间必须大于开始时间
|
||||
@ -298,24 +363,12 @@ const handleSubmit = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
// 编辑模式下的时间验证
|
||||
if (formMode.value === 'edit') {
|
||||
// 计算距离结束时间还有多少小时
|
||||
const hoursUntilEnd = (endTime - now) / (1000 * 60 * 60)
|
||||
|
||||
// 如果结束时间早于当前时间或距离结束不足2小时,直接拦截
|
||||
if (endTime < now || hoursUntilEnd <= 2) {
|
||||
ElMessage.warning(endTime < now ? '活动已结束,无法更新活动信息' : '距离活动结束不足2小时,无法更新活动信息')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let res
|
||||
if (formMode.value === 'create') {
|
||||
res = await createActivity(submitData)
|
||||
res = await createActivity(formData)
|
||||
} else {
|
||||
const id = form.value.id
|
||||
res = await updateActivity(id, submitData)
|
||||
res = await updateActivity(id, formData)
|
||||
}
|
||||
|
||||
if (res.success) {
|
||||
@ -326,8 +379,11 @@ const handleSubmit = async () => {
|
||||
ElMessage.error(res.message || `${formMode.value === 'create' ? '新增' : '编辑'}失败`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`${formMode.value === 'create' ? '新增' : '编辑'}研学活动错误:`, error)
|
||||
ElMessage.error('操作失败,请检查输入是否正确')
|
||||
if (error.response) {
|
||||
ElMessage.error(error.response.data.message || '操作失败,请检查输入是否正确')
|
||||
} else {
|
||||
ElMessage.error('操作失败,请检查网络连接')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -419,26 +475,110 @@ const handleStatusChange = async (row) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 检查活动容量
|
||||
const checkCapacity = async (row) => {
|
||||
// 报名详情对话框
|
||||
const enrollmentDialogVisible = ref(false)
|
||||
const enrollmentLoading = ref(false)
|
||||
const enrollmentList = ref([])
|
||||
const currentActivity = ref(null)
|
||||
|
||||
// 获取报名详情
|
||||
const getEnrollmentDetails = async (row) => {
|
||||
if (!row || !row.id) {
|
||||
ElMessage.error('无效的活动ID')
|
||||
return
|
||||
}
|
||||
|
||||
enrollmentLoading.value = true
|
||||
currentActivity.value = row
|
||||
enrollmentDialogVisible.value = true
|
||||
|
||||
try {
|
||||
const res = await checkActivityCapacity(row.id)
|
||||
const activityId = row.id
|
||||
const res = await getActivityEnrollments(activityId)
|
||||
if (res.success) {
|
||||
ElMessage.success(`当前报名人数:${res.data.currentParticipants}/${res.data.maxParticipants}`)
|
||||
// 确保enrollmentList是一个数组
|
||||
enrollmentList.value = Array.isArray(res.data.list) ? res.data.list : []
|
||||
} else {
|
||||
ElMessage.error(res.message || '检查容量失败')
|
||||
ElMessage.error(res.message || '获取报名详情失败')
|
||||
enrollmentList.value = [] // 确保失败时也是空数组
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('检查研学活动容量错误:', error)
|
||||
ElMessage.error('检查容量失败')
|
||||
console.error('获取报名详情失败:', error)
|
||||
ElMessage.error('获取报名详情失败')
|
||||
enrollmentList.value = [] // 确保出错时也是空数组
|
||||
} finally {
|
||||
enrollmentLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取报名状态标签类型
|
||||
const getEnrollmentStatusType = (status) => {
|
||||
const statusMap = {
|
||||
0: 'info',
|
||||
1: 'success',
|
||||
2: 'warning'
|
||||
}
|
||||
return statusMap[status] || 'info'
|
||||
}
|
||||
|
||||
// 获取报名状态文本
|
||||
const getEnrollmentStatusText = (status) => {
|
||||
const statusMap = {
|
||||
0: '已取消',
|
||||
1: '已报名',
|
||||
2: '已完成'
|
||||
}
|
||||
return statusMap[status] || '未知'
|
||||
}
|
||||
|
||||
// 在 checkCapacity 函数中修改
|
||||
const checkCapacity = async (row) => {
|
||||
if (!row || !row.id) {
|
||||
ElMessage.error('无效的活动ID')
|
||||
return
|
||||
}
|
||||
// 直接传递当前行数据
|
||||
getEnrollmentDetails(row)
|
||||
}
|
||||
|
||||
// 表格选择
|
||||
const handleSelectionChange = (rows) => {
|
||||
selectedRows.value = rows
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (date) => {
|
||||
if (!date) return ''
|
||||
const d = new Date(date)
|
||||
const pad = (num) => (num < 10 ? `0${num}` : num)
|
||||
|
||||
const year = d.getFullYear()
|
||||
const month = pad(d.getMonth() + 1)
|
||||
const day = pad(d.getDate())
|
||||
const hours = pad(d.getHours())
|
||||
const minutes = pad(d.getMinutes())
|
||||
const seconds = pad(d.getSeconds())
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
// 更新报名状态
|
||||
const handleEnrollmentStatusChange = async (row) => {
|
||||
try {
|
||||
const res = await updateEnrollmentStatus(row.id, row.status)
|
||||
if (res.success) {
|
||||
ElMessage.success('状态更新成功')
|
||||
// 重新获取报名详情
|
||||
getEnrollmentDetails(currentActivity.value)
|
||||
} else {
|
||||
ElMessage.error(res.message || '状态更新失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新报名状态失败:', error)
|
||||
ElMessage.error('更新报名状态失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
@ -534,7 +674,7 @@ onMounted(() => {
|
||||
<el-table-column prop="enrolled" label="已报名" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="checkCapacity(row)">
|
||||
{{ row.enrolled }}/{{ row.capacity }}
|
||||
{{ row.enrolled ?? 0 }}/{{ row.capacity ?? 0 }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@ -563,6 +703,11 @@ onMounted(() => {
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="user_id" label="用户昵称" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ userStore.userInfo?.nickname || userStore.userInfo?.username || row.user_id }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="250" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
@ -688,6 +833,24 @@ onMounted(() => {
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="活动图片" prop="image_url" required>
|
||||
<el-upload
|
||||
class="activity-image-uploader"
|
||||
action="#"
|
||||
:show-file-list="false"
|
||||
:on-change="handleImageChange"
|
||||
:before-upload="beforeImageUpload"
|
||||
:auto-upload="false"
|
||||
accept="image/*"
|
||||
>
|
||||
<img v-if="form.imageUrl" :src="form.imageUrl" class="activity-image" />
|
||||
<el-icon v-else class="activity-image-uploader-icon"><Plus /></el-icon>
|
||||
</el-upload>
|
||||
<div v-if="form.imageUrl" class="image-actions">
|
||||
<el-button type="danger" link @click="handleImageRemove">移除图片</el-button>
|
||||
</div>
|
||||
<div class="image-tip">建议尺寸:750x422px,格式:JPG、PNG,大小:不超过2MB</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
@ -696,6 +859,87 @@ onMounted(() => {
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 报名详情对话框 -->
|
||||
<el-dialog
|
||||
v-model="enrollmentDialogVisible"
|
||||
:title="currentActivity?.title ? `报名详情 - ${currentActivity.title}` : '报名详情'"
|
||||
width="900px"
|
||||
destroy-on-close
|
||||
>
|
||||
<div class="enrollment-dialog-content" v-loading="enrollmentLoading">
|
||||
<div class="enrollment-summary">
|
||||
<div class="summary-item">
|
||||
<span class="label">总人数上限:</span>
|
||||
<span class="value">{{ currentActivity?.capacity ?? 0 }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="label">已报名人数:</span>
|
||||
<span class="value">{{ currentActivity?.enrolled ?? 0 }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="label">剩余名额:</span>
|
||||
<span class="value">{{ (currentActivity?.capacity ?? 0) - (currentActivity?.enrolled ?? 0) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table :data="enrollmentList" style="width: 100%" border>
|
||||
<el-table-column type="index" label="序号" width="80" align="center" />
|
||||
<el-table-column prop="user_id" label="用户昵称" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ userStore.userInfo?.nickname || userStore.userInfo?.username || row.user_id }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="enrollment_time" label="报名时间" min-width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.enrollment_time) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-model="row.status"
|
||||
placeholder="请选择状态"
|
||||
size="small"
|
||||
style="width: 100px"
|
||||
@change="() => handleEnrollmentStatusChange(row)"
|
||||
>
|
||||
<el-option label="已取消" :value="0" />
|
||||
<el-option label="已报名" :value="1" />
|
||||
<el-option label="已完成" :value="2" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="feedback" label="活动反馈" min-width="200" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<el-popover
|
||||
placement="top-start"
|
||||
trigger="hover"
|
||||
:width="300"
|
||||
v-if="row.feedback"
|
||||
>
|
||||
<template #default>
|
||||
<div style="max-height: 200px; overflow-y: auto;">{{ row.feedback }}</div>
|
||||
</template>
|
||||
<template #reference>
|
||||
<span>{{ row.feedback }}</span>
|
||||
</template>
|
||||
</el-popover>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="feedback_time" label="反馈时间" min-width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.feedback_time ? formatDateTime(row.feedback_time) : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="enrollment-empty" v-if="!enrollmentLoading && (!enrollmentList || enrollmentList.length === 0)">
|
||||
<el-empty description="暂无报名数据" />
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -753,5 +997,81 @@ onMounted(() => {
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.activity-image-uploader {
|
||||
:deep(.el-upload) {
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: border-color 0.3s;
|
||||
|
||||
&:hover {
|
||||
border-color: #409EFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.activity-image-uploader-icon {
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
width: 178px;
|
||||
height: 178px;
|
||||
text-align: center;
|
||||
line-height: 178px;
|
||||
}
|
||||
|
||||
.activity-image {
|
||||
width: 178px;
|
||||
height: 178px;
|
||||
display: block;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.image-tip {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 8px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.image-actions {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.enrollment-dialog-content {
|
||||
.enrollment-summary {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
margin-bottom: 20px;
|
||||
padding: 15px 20px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
|
||||
.summary-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.label {
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #409EFF;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.enrollment-empty {
|
||||
padding: 40px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -31,9 +31,19 @@ const categoryOptions = [
|
||||
{ label: '植物', value: 'plant' }
|
||||
]
|
||||
|
||||
// 保护等级选项
|
||||
const protectionLevelOptions = [
|
||||
{ label: '国家一级', value: 'national_first' },
|
||||
{ label: '国家二级', value: 'national_second' },
|
||||
{ label: '省级', value: 'provincial' },
|
||||
{ label: '普通', value: 'normal' }
|
||||
]
|
||||
|
||||
// 图表实例
|
||||
const categoryChartRef = ref(null)
|
||||
let categoryChart = null
|
||||
const protectionChartRef = ref(null)
|
||||
let protectionChart = null
|
||||
|
||||
// 统计卡片数据
|
||||
const statsCards = ref([
|
||||
@ -267,6 +277,96 @@ const updateDistributionChart = (data) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化保护等级图表
|
||||
const initProtectionChart = () => {
|
||||
if (!protectionChartRef.value) return
|
||||
|
||||
protectionChart = echarts.init(protectionChartRef.value)
|
||||
const option = {
|
||||
title: {
|
||||
text: '保护等级统计',
|
||||
textStyle: {
|
||||
fontSize: 16,
|
||||
fontWeight: 500,
|
||||
color: '#303133'
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
},
|
||||
formatter: '{b}: {c}种'
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '10%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: [],
|
||||
axisLabel: {
|
||||
interval: 0,
|
||||
rotate: 30
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '物种数量',
|
||||
minInterval: 1
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '物种数量',
|
||||
type: 'bar',
|
||||
barWidth: '40%',
|
||||
data: [],
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
formatter: '{c}种'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
protectionChart.setOption(option)
|
||||
}
|
||||
|
||||
// 更新保护等级图表数据
|
||||
const updateProtectionChart = (data) => {
|
||||
if (!protectionChart) return
|
||||
|
||||
// 保护等级图表数据
|
||||
const protectionData = Object.entries(data.protection_levels)
|
||||
.filter(([_, count]) => count > 0)
|
||||
.map(([level, count]) => ({
|
||||
name: protectionLevelOptions.find(item => item.value === level)?.label || level,
|
||||
value: count
|
||||
}))
|
||||
.sort((a, b) => b.value - a.value)
|
||||
|
||||
protectionChart.setOption({
|
||||
xAxis: {
|
||||
data: protectionData.map(item => item.name)
|
||||
},
|
||||
series: [{
|
||||
data: protectionData.map(item => ({
|
||||
value: item.value,
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#83bff6' },
|
||||
{ offset: 0.5, color: '#409EFF' },
|
||||
{ offset: 1, color: '#2c76c5' }
|
||||
])
|
||||
}
|
||||
}))
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
const initData = async () => {
|
||||
try {
|
||||
@ -551,6 +651,9 @@ const fetchStatisticsData = async () => {
|
||||
|
||||
// 更新物种分布图表
|
||||
updateCategoryChart(res.data)
|
||||
|
||||
// 更新保护等级图表
|
||||
updateProtectionChart(res.data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取统计数据失败:', error)
|
||||
@ -560,11 +663,13 @@ const fetchStatisticsData = async () => {
|
||||
onMounted(() => {
|
||||
initData()
|
||||
startAutoRefresh()
|
||||
initTrendChart();
|
||||
initDistributionChart();
|
||||
initTrendChart()
|
||||
initDistributionChart()
|
||||
initCategoryChart()
|
||||
initProtectionChart()
|
||||
window.addEventListener('resize', () => {
|
||||
categoryChart?.resize()
|
||||
protectionChart?.resize()
|
||||
})
|
||||
});
|
||||
|
||||
@ -576,8 +681,13 @@ onUnmounted(() => {
|
||||
categoryChart.dispose()
|
||||
categoryChart = null
|
||||
}
|
||||
if (protectionChart) {
|
||||
protectionChart.dispose()
|
||||
protectionChart = null
|
||||
}
|
||||
window.removeEventListener('resize', () => {
|
||||
categoryChart?.resize()
|
||||
protectionChart?.resize()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
@ -629,6 +739,10 @@ onUnmounted(() => {
|
||||
<div class="chart-title">趋势统计</div>
|
||||
<div id="trendChart" class="chart-content"></div>
|
||||
</div>
|
||||
<div class="chart-item">
|
||||
<div class="chart-title">保护等级统计</div>
|
||||
<div ref="protectionChartRef" class="chart-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -791,18 +905,18 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.charts-container {
|
||||
display: flex;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 20px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.chart-item {
|
||||
flex: 1;
|
||||
|
||||
.chart-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: v.$text-primary;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.chart-content {
|
||||
|
||||
@ -93,115 +93,6 @@ const rules = {
|
||||
]
|
||||
}
|
||||
|
||||
// 添加统计信息的响应式数据
|
||||
const statistics = ref({
|
||||
categories: {},
|
||||
protection_levels: {}
|
||||
})
|
||||
|
||||
// 图表实例
|
||||
const protectionChartRef = ref(null)
|
||||
let protectionChart = null
|
||||
|
||||
// 基础URL
|
||||
const baseUrl = computed(() => import.meta.env.VITE_API_BASE_URL || '')
|
||||
|
||||
// 获取完整的图片URL
|
||||
const getFullImageUrl = (url) => {
|
||||
if (!url) return ''
|
||||
if (url.startsWith('http')) return url
|
||||
if (url.startsWith('data:')) return url
|
||||
if (url.startsWith('blob:')) return url
|
||||
|
||||
// 移除URL开头的斜杠,避免重复
|
||||
const cleanUrl = url.startsWith('/') ? url.slice(1) : url
|
||||
// 确保不会重复添加 uploads 路径
|
||||
if (cleanUrl.startsWith('uploads/')) {
|
||||
return `${baseUrl.value}/${cleanUrl}`
|
||||
}
|
||||
return `${baseUrl.value}/uploads/${cleanUrl}`
|
||||
}
|
||||
|
||||
// 初始化图表
|
||||
const initCharts = () => {
|
||||
if (protectionChartRef.value) {
|
||||
protectionChart = echarts.init(protectionChartRef.value)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新图表数据
|
||||
const updateCharts = () => {
|
||||
// 保护等级图表数据
|
||||
const protectionData = Object.entries(statistics.value.protection_levels)
|
||||
.filter(([_, count]) => count > 0)
|
||||
.map(([level, count]) => ({
|
||||
name: protectionLevelOptions.find(item => item.value === level)?.label || level,
|
||||
value: count
|
||||
}))
|
||||
.sort((a, b) => b.value - a.value)
|
||||
|
||||
// 设置保护等级图表
|
||||
protectionChart?.setOption({
|
||||
title: {
|
||||
text: '保护等级统计',
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
},
|
||||
formatter: '{b}: {c}种'
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '10%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: protectionData.map(item => item.name),
|
||||
axisLabel: {
|
||||
interval: 0,
|
||||
rotate: 30
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '物种数量',
|
||||
minInterval: 1
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '物种数量',
|
||||
type: 'bar',
|
||||
barWidth: '40%',
|
||||
data: protectionData.map(item => ({
|
||||
value: item.value,
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#83bff6' },
|
||||
{ offset: 0.5, color: '#409EFF' },
|
||||
{ offset: 1, color: '#2c76c5' }
|
||||
])
|
||||
}
|
||||
})),
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
formatter: '{c}种'
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 监听窗口大小变化
|
||||
const handleResize = () => {
|
||||
protectionChart?.resize()
|
||||
}
|
||||
|
||||
// 获取列表数据
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
@ -401,8 +292,6 @@ const getStatistics = async () => {
|
||||
try {
|
||||
const res = await getSpeciesStatistics()
|
||||
statistics.value = res.data
|
||||
// 更新图表数据
|
||||
updateCharts()
|
||||
} catch (error) {
|
||||
console.error('获取统计信息失败:', error)
|
||||
}
|
||||
@ -443,32 +332,37 @@ const handleSortChange = ({ prop, order }) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 基础URL
|
||||
const baseUrl = computed(() => import.meta.env.VITE_API_BASE_URL || '')
|
||||
|
||||
// 获取完整的图片URL
|
||||
const getFullImageUrl = (url) => {
|
||||
if (!url) return ''
|
||||
if (url.startsWith('http')) return url
|
||||
if (url.startsWith('data:')) return url
|
||||
if (url.startsWith('blob:')) return url
|
||||
|
||||
// 移除URL开头的斜杠,避免重复
|
||||
const cleanUrl = url.startsWith('/') ? url.slice(1) : url
|
||||
// 确保不会重复添加 uploads 路径
|
||||
if (cleanUrl.startsWith('uploads/')) {
|
||||
return `${baseUrl.value}/${cleanUrl}`
|
||||
}
|
||||
return `${baseUrl.value}/uploads/${cleanUrl}`
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
// 初始化图表
|
||||
initCharts()
|
||||
getStatistics()
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// 销毁图表实例
|
||||
protectionChart?.dispose()
|
||||
window.removeEventListener('resize', handleResize)
|
||||
// 不再需要清理图表实例
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 统计信息展示 -->
|
||||
<el-row :gutter="20" class="statistics-container">
|
||||
<el-col :span="24">
|
||||
<el-card>
|
||||
<div ref="protectionChartRef" style="height: 400px"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 搜索区域 -->
|
||||
<el-card class="search-container">
|
||||
<el-form :model="queryParams" ref="queryForm" :inline="true">
|
||||
@ -732,30 +626,6 @@ onUnmounted(() => {
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.statistics-container {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.card-header {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.statistics-content {
|
||||
.statistics-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
|
||||
span {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.species-image {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user