修复一些已知的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",
|
"@dataview/datav-vue3": "^0.0.0-test.1672506674342",
|
||||||
"@element-plus/icons-vue": "^2.3.1",
|
"@element-plus/icons-vue": "^2.3.1",
|
||||||
"@kjgl77/datav-vue3": "^1.7.4",
|
"@kjgl77/datav-vue3": "^1.7.4",
|
||||||
|
"@wangeditor/editor": "^5.1.23",
|
||||||
|
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"datav-vue3": "^1.0.0",
|
"datav-vue3": "^1.0.0",
|
||||||
"echarts": "^5.6.0",
|
"echarts": "^5.6.0",
|
||||||
@ -756,11 +758,21 @@
|
|||||||
"node": ">=12.20"
|
"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": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
|
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/@types/lodash": {
|
||||||
"version": "4.17.15",
|
"version": "4.17.15",
|
||||||
"integrity": "sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw=="
|
"integrity": "sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw=="
|
||||||
@ -780,6 +792,56 @@
|
|||||||
"undici-types": "~6.20.0"
|
"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": {
|
"node_modules/@vitejs/plugin-vue": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"integrity": "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==",
|
"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": {
|
"node_modules/alien-signals": {
|
||||||
"version": "0.4.14",
|
"version": "0.4.14",
|
||||||
"integrity": "sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==",
|
"integrity": "sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==",
|
||||||
@ -1038,10 +1250,27 @@
|
|||||||
"node": ">= 0.8"
|
"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": {
|
"node_modules/csstype": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
"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": {
|
"node_modules/datav-vue3": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"integrity": "sha512-ehQgoAxyZHZwLtZBJ8mlEe41bOjGH816bPH0XhtjR6saVYUgt/HUslcE2M18lm0FfmTlLOcvPtvoDBlitIyehg==",
|
"integrity": "sha512-ehQgoAxyZHZwLtZBJ8mlEe41bOjGH816bPH0XhtjR6saVYUgt/HUslcE2M18lm0FfmTlLOcvPtvoDBlitIyehg==",
|
||||||
@ -1087,6 +1316,14 @@
|
|||||||
"node": ">=0.10"
|
"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": {
|
"node_modules/dot-prop": {
|
||||||
"version": "9.0.0",
|
"version": "9.0.0",
|
||||||
"integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==",
|
"integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==",
|
||||||
@ -1195,10 +1432,47 @@
|
|||||||
"node": ">=12.x"
|
"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": {
|
"node_modules/es6-promise": {
|
||||||
"version": "4.2.8",
|
"version": "4.2.8",
|
||||||
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
|
"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": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.24.2",
|
"version": "0.24.2",
|
||||||
"integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==",
|
"integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==",
|
||||||
@ -1626,6 +1900,20 @@
|
|||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
|
"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": {
|
"node_modules/estree-walker": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
|
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
|
||||||
@ -1640,6 +1928,23 @@
|
|||||||
"url": "https://github.com/eta-dev/eta?sponsor=1"
|
"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": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||||
@ -1719,10 +2024,36 @@
|
|||||||
"node": ">=12.22.0"
|
"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": {
|
"node_modules/http-status-emojis": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"integrity": "sha512-ompKtgwpx8ff0hsbpIB7oE4ax1LXoHmftsHHStMELX56ivG3GhofTX8ZHWlUaFKfGjcGjw6G3rPk7dJRXMmbbg=="
|
"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": {
|
"node_modules/immutable": {
|
||||||
"version": "5.0.3",
|
"version": "5.0.3",
|
||||||
"integrity": "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==",
|
"integrity": "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==",
|
||||||
@ -1763,6 +2094,11 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/is-number": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||||
@ -1772,6 +2108,19 @@
|
|||||||
"node": ">=0.12.0"
|
"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": {
|
"node_modules/json-server": {
|
||||||
"version": "1.0.0-beta.3",
|
"version": "1.0.0-beta.3",
|
||||||
"integrity": "sha512-DwE69Ep5ccwIJZBUIWEENC30Yj8bwr4Ax9W9VoIWAYnB8Sj4ReptscO8/DRHv/nXwVlmb3Bk73Ls86+VZdYkkA==",
|
"integrity": "sha512-DwE69Ep5ccwIJZBUIWEENC30Yj8bwr4Ax9W9VoIWAYnB8Sj4ReptscO8/DRHv/nXwVlmb3Bk73Ls86+VZdYkkA==",
|
||||||
@ -1824,6 +2173,42 @@
|
|||||||
"lodash-es": "*"
|
"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": {
|
"node_modules/lowdb": {
|
||||||
"version": "7.0.1",
|
"version": "7.0.1",
|
||||||
"integrity": "sha512-neJAj8GwF0e8EpycYIDFqEPcx9Qz4GUho20jWFR7YiFeXzF1YMLdxB36PypcTSPMA+4+LvgyMacYhlr18Zlymw==",
|
"integrity": "sha512-neJAj8GwF0e8EpycYIDFqEPcx9Qz4GUho20jWFR7YiFeXzF1YMLdxB36PypcTSPMA+4+LvgyMacYhlr18Zlymw==",
|
||||||
@ -1888,6 +2273,14 @@
|
|||||||
"node": ">= 0.6"
|
"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": {
|
"node_modules/mime-types": {
|
||||||
"version": "2.1.35",
|
"version": "2.1.35",
|
||||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
@ -1924,6 +2317,11 @@
|
|||||||
"integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
|
"integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.8",
|
"version": "3.3.8",
|
||||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||||
@ -1947,6 +2345,11 @@
|
|||||||
"node": ">= 0.6"
|
"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": {
|
"node_modules/node-addon-api": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||||
@ -2024,6 +2427,23 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"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": {
|
"node_modules/proxy-from-env": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||||
@ -2340,6 +2760,14 @@
|
|||||||
"@parcel/watcher": "^2.4.1"
|
"@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": {
|
"node_modules/sirv": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==",
|
"integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==",
|
||||||
@ -2352,6 +2780,35 @@
|
|||||||
"node": ">= 10"
|
"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": {
|
"node_modules/sort-on": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"integrity": "sha512-WTECP0nYNWO1n2g5bpsV0yZN9cBmZsF8ThHFbOqVN0HBFRoaQZLLEMvMmJlKHNPYQeVngeI5+jJzIfFqOIo1OA==",
|
"integrity": "sha512-WTECP0nYNWO1n2g5bpsV0yZN9cBmZsF8ThHFbOqVN0HBFRoaQZLLEMvMmJlKHNPYQeVngeI5+jJzIfFqOIo1OA==",
|
||||||
@ -2372,6 +2829,11 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/steno": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"integrity": "sha512-yhPIQXjrlt1xv7dyPQg2P17URmXbuM5pdGkpiMB3RenprfiBlvK415Lctfe0eshk90oA7/tNq7WEiMK8RSP39A==",
|
"integrity": "sha512-yhPIQXjrlt1xv7dyPQg2P17URmXbuM5pdGkpiMB3RenprfiBlvK415Lctfe0eshk90oA7/tNq7WEiMK8RSP39A==",
|
||||||
@ -2382,6 +2844,11 @@
|
|||||||
"url": "https://github.com/sponsors/typicode"
|
"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": {
|
"node_modules/to-regex-range": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||||
@ -2405,6 +2872,11 @@
|
|||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
|
"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": {
|
"node_modules/type-fest": {
|
||||||
"version": "4.33.0",
|
"version": "4.33.0",
|
||||||
"integrity": "sha512-s6zVrxuyKbbAsSAD5ZPTB77q4YIdRctkTbJ2/Dqlinwz+8ooH2gd+YA7VA6Pa93KML9GockVvoxjZ2vHP+mu8g==",
|
"integrity": "sha512-s6zVrxuyKbbAsSAD5ZPTB77q4YIdRctkTbJ2/Dqlinwz+8ooH2gd+YA7VA6Pa93KML9GockVvoxjZ2vHP+mu8g==",
|
||||||
@ -2586,6 +3058,11 @@
|
|||||||
"resolved": "",
|
"resolved": "",
|
||||||
"link": true
|
"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": {
|
"node_modules/zrender": {
|
||||||
"version": "5.6.1",
|
"version": "5.6.1",
|
||||||
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz",
|
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz",
|
||||||
|
|||||||
@ -12,6 +12,8 @@
|
|||||||
"@dataview/datav-vue3": "^0.0.0-test.1672506674342",
|
"@dataview/datav-vue3": "^0.0.0-test.1672506674342",
|
||||||
"@element-plus/icons-vue": "^2.3.1",
|
"@element-plus/icons-vue": "^2.3.1",
|
||||||
"@kjgl77/datav-vue3": "^1.7.4",
|
"@kjgl77/datav-vue3": "^1.7.4",
|
||||||
|
"@wangeditor/editor": "^5.1.23",
|
||||||
|
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"datav-vue3": "^1.0.0",
|
"datav-vue3": "^1.0.0",
|
||||||
"echarts": "^5.6.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} 返回创建结果
|
* @returns {Promise} 返回创建结果
|
||||||
*/
|
*/
|
||||||
export function createActivity(data) {
|
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} 返回更新结果
|
* @returns {Promise} 返回更新结果
|
||||||
*/
|
*/
|
||||||
export function updateActivity(id, data) {
|
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'
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -89,3 +97,22 @@ export function getCategoryActivities(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 {Object} params - 查询参数
|
||||||
* @param {number} [params.page=1] - 页码
|
* @param {number} [params.page=1] - 页码
|
||||||
* @param {number} [params.page_size=10] - 每页条数
|
* @param {number} [params.page_size=10] - 每页条数
|
||||||
* @param {string} [params.device_type] - 设备类型:10000-摄像头,10001-无人机
|
* @param {string} [params.device_type] - 设备类型:10000-摄像头,10001-无人机,0-传感器
|
||||||
* @returns {Promise} 返回设备列表数据
|
* @returns {Promise} 返回设备列表数据
|
||||||
*/
|
*/
|
||||||
export function getDeviceList(params = {}) {
|
export function getDeviceList(params = {}) {
|
||||||
|
|||||||
@ -189,9 +189,10 @@ const handleLogout = () => {
|
|||||||
<el-sub-menu index="projects">
|
<el-sub-menu index="projects">
|
||||||
<template #title>
|
<template #title>
|
||||||
<el-icon><component :is="icons.DataLine" /></el-icon>
|
<el-icon><component :is="icons.DataLine" /></el-icon>
|
||||||
<span>项目简介</span>
|
<span>关于我们</span>
|
||||||
</template>
|
</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-sub-menu>
|
||||||
</el-menu>
|
</el-menu>
|
||||||
</el-aside>
|
</el-aside>
|
||||||
|
|||||||
@ -188,10 +188,16 @@ const router = createRouter({
|
|||||||
meta: { title: '数据管理', icon: 'data' }
|
meta: { title: '数据管理', icon: 'data' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'projects',
|
path: 'about/projects',
|
||||||
name: 'Projects',
|
name: 'Projects',
|
||||||
component: () => import('@/views/projects/index.vue'),
|
component: () => import('@/views/about/projects/index.vue'),
|
||||||
meta: { title: '项目简介管理', icon: 'data' }
|
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>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { Plus } from '@element-plus/icons-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([
|
const loading = ref(false)
|
||||||
{
|
|
||||||
id: 1,
|
// 传感器列表数据
|
||||||
name: '水质传感器01',
|
const sensorList = ref([])
|
||||||
type: 'water',
|
|
||||||
status: 'online',
|
// 分页参数
|
||||||
data: {
|
const pagination = ref({
|
||||||
temperature: '25.6°C',
|
page: 1,
|
||||||
ph: '7.2',
|
page_size: 10,
|
||||||
oxygen: '6.8mg/L'
|
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)
|
||||||
id: 3,
|
ElMessage.error('获取传感器列表失败')
|
||||||
name: '土壤传感器01',
|
} finally {
|
||||||
type: 'soil',
|
loading.value = false
|
||||||
status: 'offline',
|
|
||||||
data: {
|
|
||||||
moisture: '42%',
|
|
||||||
ph: '6.8',
|
|
||||||
nutrients: '中等'
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
{
|
|
||||||
id: 4,
|
// 处理页码改变
|
||||||
name: '水质传感器02',
|
const handleCurrentChange = (page) => {
|
||||||
type: 'water',
|
pagination.value.page = page
|
||||||
status: 'online',
|
getSensorList()
|
||||||
data: {
|
}
|
||||||
temperature: '26.1°C',
|
|
||||||
ph: '7.4',
|
// 处理每页条数改变
|
||||||
oxygen: '7.1mg/L'
|
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 || '删除失败')
|
||||||
}
|
}
|
||||||
},
|
} catch (error) {
|
||||||
{
|
if (error !== 'cancel') {
|
||||||
id: 5,
|
console.error('删除传感器失败:', error)
|
||||||
name: '空气传感器02',
|
ElMessage.error('删除失败')
|
||||||
type: 'air',
|
|
||||||
status: 'error',
|
|
||||||
data: {
|
|
||||||
temperature: '27.8°C',
|
|
||||||
humidity: '58%',
|
|
||||||
pm25: '42μg/m³'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
}
|
||||||
|
|
||||||
// 获取传感器状态样式
|
// 获取传感器状态样式
|
||||||
const getStatusStyle = (status) => {
|
const getStatusStyle = (status) => {
|
||||||
|
if (!status) return { color: '#909399', text: '未知' }
|
||||||
return {
|
return {
|
||||||
online: {
|
1: {
|
||||||
color: '#67C23A',
|
color: '#67C23A',
|
||||||
text: '在线'
|
text: '在线'
|
||||||
},
|
},
|
||||||
offline: {
|
0: {
|
||||||
color: '#909399',
|
color: '#909399',
|
||||||
text: '离线'
|
text: '离线'
|
||||||
},
|
},
|
||||||
error: {
|
2: {
|
||||||
color: '#F56C6C',
|
color: '#F56C6C',
|
||||||
text: '异常'
|
text: '异常'
|
||||||
}
|
}
|
||||||
}[status]
|
}[status.code] || { color: '#909399', text: status.text || '未知' }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取传感器类型信息
|
// 获取传感器类型信息
|
||||||
const getTypeInfo = (type) => {
|
const getTypeInfo = (type) => {
|
||||||
return {
|
return {
|
||||||
water: {
|
0: {
|
||||||
icon: 'WaterMeter',
|
icon: 'Monitor',
|
||||||
text: '水质传感器',
|
text: '传感器',
|
||||||
|
color: '#409EFF'
|
||||||
|
}
|
||||||
|
}[type] || {
|
||||||
|
icon: 'Monitor',
|
||||||
|
text: '传感器',
|
||||||
color: '#409EFF'
|
color: '#409EFF'
|
||||||
},
|
|
||||||
air: {
|
|
||||||
icon: 'Sunny',
|
|
||||||
text: '空气传感器',
|
|
||||||
color: '#67C23A'
|
|
||||||
},
|
|
||||||
soil: {
|
|
||||||
icon: 'Plant',
|
|
||||||
text: '土壤传感器',
|
|
||||||
color: '#E6A23C'
|
|
||||||
}
|
}
|
||||||
}[type]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 格式化数据显示
|
||||||
|
const formatValue = (value, unit = '') => {
|
||||||
|
if (value === null || value === undefined) return '暂无数据'
|
||||||
|
return `${value}${unit}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载时获取数据
|
||||||
|
onMounted(() => {
|
||||||
|
getSensorList()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="sensor-management">
|
<div class="sensor-management">
|
||||||
<div class="page-header">
|
<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>
|
<el-button type="primary" :icon="Plus">添加传感器</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sensor-container">
|
<div class="sensor-container" v-loading="loading">
|
||||||
<div
|
<div
|
||||||
v-for="sensor in sensorList"
|
v-for="sensor in sensorList"
|
||||||
:key="sensor.id"
|
:key="sensor.id"
|
||||||
class="sensor-card"
|
class="sensor-card"
|
||||||
:class="{ 'offline': sensor.status === 'offline' }"
|
:class="{ 'offline': sensor.status?.code === 0 }"
|
||||||
>
|
>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="sensor-info">
|
<div class="sensor-info">
|
||||||
<el-icon :class="sensor.type">
|
<el-icon :class="sensor.device_type">
|
||||||
<component :is="getTypeInfo(sensor.type).icon" />
|
<component :is="getTypeInfo(sensor.device_type).icon" />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
<span class="sensor-name">{{ sensor.name }}</span>
|
<span class="sensor-name">{{ sensor.device_name }}</span>
|
||||||
|
</div>
|
||||||
<el-tag
|
<el-tag
|
||||||
size="small"
|
size="small"
|
||||||
:type="sensor.status === 'online' ? 'success' : sensor.status === 'error' ? 'danger' : 'info'"
|
:type="sensor.status?.code === 1 ? 'success' : sensor.status?.code === 2 ? 'danger' : 'info'"
|
||||||
>
|
>
|
||||||
{{ getStatusStyle(sensor.status).text }}
|
{{ getStatusStyle(sensor.status).text }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<div class="sensor-actions">
|
|
||||||
<el-button type="primary" link>编辑</el-button>
|
|
||||||
<el-button type="danger" link>删除</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<template v-if="sensor.type === 'water'">
|
<div class="info-section">
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">设备编号</span>
|
||||||
|
<span class="value">{{ sensor.device_code }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">安装位置</span>
|
||||||
|
<span class="value">{{ sensor.install_location || '暂无数据' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-section">
|
||||||
|
<div class="data-grid">
|
||||||
<div class="data-item">
|
<div class="data-item">
|
||||||
<span class="label">温度</span>
|
<div class="data-value">{{ formatValue(sensor.data?.temp, '°C') }}</div>
|
||||||
<span class="value">{{ sensor.data.temperature }}</span>
|
<div class="data-label">温度</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="data-item">
|
<div class="data-item">
|
||||||
<span class="label">pH值</span>
|
<div class="data-value">{{ formatValue(sensor.data?.humi, '%') }}</div>
|
||||||
<span class="value">{{ sensor.data.ph }}</span>
|
<div class="data-label">湿度</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="data-item">
|
<div class="data-item">
|
||||||
<span class="label">溶解氧</span>
|
<div class="data-value">{{ formatValue(sensor.data?.light_adc) }}</div>
|
||||||
<span class="value">{{ sensor.data.oxygen }}</span>
|
<div class="data-label">光照强度</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>
|
||||||
<div class="data-item">
|
<div class="data-item">
|
||||||
<span class="label">湿度</span>
|
<div class="data-value">{{ formatValue(sensor.data?.soil_adc) }}</div>
|
||||||
<span class="value">{{ sensor.data.humidity }}</span>
|
<div class="data-label">土壤湿度</div>
|
||||||
</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>
|
</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="actions">
|
||||||
|
<el-button type="primary" link>编辑</el-button>
|
||||||
|
<el-button type="danger" link @click="handleDelete(sensor.id)">删除</el-button>
|
||||||
|
</div>
|
||||||
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.sensor-management {
|
.sensor-management {
|
||||||
padding: 16px;
|
padding: 20px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background: #f5f7fa;
|
|
||||||
|
|
||||||
.page-header {
|
.page-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
|
padding: 0 4px;
|
||||||
|
|
||||||
.header-title {
|
.header-title {
|
||||||
font-size: 20px;
|
font-size: 22px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333;
|
color: #333;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #409EFF;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sensor-container {
|
.sensor-container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
|
||||||
gap: 16px;
|
gap: 20px;
|
||||||
padding: 16px;
|
padding: 20px 0;
|
||||||
background: #fff;
|
position: relative;
|
||||||
border-radius: 8px;
|
min-height: 200px;
|
||||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
|
|
||||||
|
|
||||||
.sensor-card {
|
.sensor-card {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #e4e7ed;
|
border: 1px solid #e4e7ed;
|
||||||
border-radius: 8px;
|
border-radius: 12px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: translateY(-2px);
|
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 {
|
&.offline {
|
||||||
@ -229,35 +268,28 @@ const getTypeInfo = (type) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
padding: 16px;
|
padding: 16px 20px;
|
||||||
border-bottom: 1px solid #e4e7ed;
|
border-bottom: 1px solid #f0f2f5;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
background: #fafafa;
|
||||||
|
|
||||||
.sensor-info {
|
.sensor-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 10px;
|
||||||
|
|
||||||
.el-icon {
|
.el-icon {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
|
|
||||||
&.water {
|
|
||||||
color: #409EFF;
|
color: #409EFF;
|
||||||
}
|
background: rgba(64, 158, 255, 0.1);
|
||||||
|
padding: 8px;
|
||||||
&.air {
|
border-radius: 8px;
|
||||||
color: #67C23A;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.soil {
|
|
||||||
color: #E6A23C;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sensor-name {
|
.sensor-name {
|
||||||
font-size: 14px;
|
font-size: 15px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
@ -265,31 +297,99 @@ const getTypeInfo = (type) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card-content {
|
.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;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 8px;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
color: #666;
|
color: #909399;
|
||||||
font-size: 14px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.value {
|
.value {
|
||||||
color: #333;
|
color: #606266;
|
||||||
font-weight: 500;
|
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;
|
||||||
|
padding-top: 16px;
|
||||||
|
border-top: 1px dashed #f0f2f5;
|
||||||
|
|
||||||
|
.update-time {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-container {
|
||||||
|
padding: 16px 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</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>
|
<script setup>
|
||||||
import { ref, onMounted, watch } from 'vue'
|
import { ref, reactive, onMounted, watch } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
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 { formatDateTime } from '@/utils/format'
|
||||||
|
import { reverseArray } from '@/utils/sort'
|
||||||
import {
|
import {
|
||||||
getActivityList,
|
getActivityList,
|
||||||
createActivity,
|
createActivity,
|
||||||
@ -10,32 +11,31 @@ import {
|
|||||||
cancelActivity,
|
cancelActivity,
|
||||||
batchCancelActivities,
|
batchCancelActivities,
|
||||||
updateActivityStatus,
|
updateActivityStatus,
|
||||||
checkActivityCapacity
|
checkActivityCapacity,
|
||||||
|
getActivityEnrollments,
|
||||||
|
updateEnrollmentStatus
|
||||||
} from '@/api/activity/study'
|
} from '@/api/activity/study'
|
||||||
|
import { useUserStore } from '@/stores/user'
|
||||||
|
|
||||||
// 格式化日期时间
|
// 查询参数
|
||||||
const formatDate = (date) => {
|
const queryParams = reactive({
|
||||||
if (!date) return ''
|
page: 1,
|
||||||
const d = new Date(date)
|
page_size: 10,
|
||||||
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({
|
|
||||||
title: '',
|
title: '',
|
||||||
category: '',
|
category: undefined,
|
||||||
status: ''
|
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 = [
|
const categoryOptions = [
|
||||||
{ label: '实地考察', value: 'field_study' },
|
{ label: '实地考察', value: 'field_study' },
|
||||||
@ -51,25 +51,24 @@ const statusOptions = [
|
|||||||
{ label: '已结束', value: 0 }
|
{ label: '已结束', value: 0 }
|
||||||
]
|
]
|
||||||
|
|
||||||
// 表格数据
|
// 搜索表单
|
||||||
const tableData = ref([])
|
const searchForm = ref({
|
||||||
const allData = ref([]) // 添加一个存储所有数据的数组
|
title: '',
|
||||||
const loading = ref(false)
|
category: '',
|
||||||
const selectedRows = ref([])
|
status: ''
|
||||||
|
})
|
||||||
|
|
||||||
// 分页配置
|
// 获取用户store
|
||||||
const currentPage = ref(1)
|
const userStore = useUserStore()
|
||||||
const pageSize = ref(10)
|
|
||||||
const total = ref(0)
|
|
||||||
|
|
||||||
// 获取研学活动列表
|
// 获取列表数据
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await getActivityList()
|
const res = await getActivityList()
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
// 保存所有数据
|
// 保存所有数据到临时变量
|
||||||
allData.value = res.data.map(item => ({
|
let allData = res.data.map(item => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
title: item.title,
|
title: item.title,
|
||||||
category: item.category,
|
category: item.category,
|
||||||
@ -84,64 +83,67 @@ const getList = async () => {
|
|||||||
status: item.status,
|
status: item.status,
|
||||||
created_at: item.created_at,
|
created_at: item.created_at,
|
||||||
updated_at: item.updated_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 {
|
} else {
|
||||||
ElMessage.error(res.message || '获取研学活动列表失败')
|
ElMessage.error(res.message || '获取研学活动列表失败')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取研学活动列表错误:', error)
|
|
||||||
ElMessage.error('获取研学活动列表失败')
|
ElMessage.error('获取研学活动列表失败')
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 前端搜索和分页
|
// 处理页码改变
|
||||||
const filterAndPaginateData = () => {
|
const handleCurrentChange = (val) => {
|
||||||
// 1. 先进行搜索过滤
|
currentPage.value = val
|
||||||
let filteredData = [...allData.value]
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
// 按活动名称搜索
|
// 处理每页条数改变
|
||||||
if (searchForm.value.title) {
|
const handleSizeChange = (val) => {
|
||||||
const keyword = searchForm.value.title.toLowerCase()
|
pageSize.value = val
|
||||||
filteredData = filteredData.filter(item =>
|
currentPage.value = 1
|
||||||
item.title.toLowerCase().includes(keyword)
|
getList()
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 按活动类型过滤
|
|
||||||
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 handleSearch = () => {
|
const handleSearch = () => {
|
||||||
currentPage.value = 1 // 重置到第一页
|
currentPage.value = 1 // 重置到第一页
|
||||||
filterAndPaginateData()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置搜索
|
// 重置搜索
|
||||||
@ -152,7 +154,7 @@ const resetSearch = () => {
|
|||||||
status: ''
|
status: ''
|
||||||
}
|
}
|
||||||
currentPage.value = 1
|
currentPage.value = 1
|
||||||
filterAndPaginateData()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理清空操作
|
// 处理清空操作
|
||||||
@ -163,23 +165,16 @@ const handleClear = (field) => {
|
|||||||
|
|
||||||
// 监听分页变化
|
// 监听分页变化
|
||||||
watch([currentPage, pageSize], () => {
|
watch([currentPage, pageSize], () => {
|
||||||
filterAndPaginateData()
|
getList()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 监听搜索条件变化
|
// 监听搜索条件变化
|
||||||
watch(searchForm, (newVal, oldVal) => {
|
watch(searchForm, (newVal, oldVal) => {
|
||||||
// 如果是状态字段发生变化且变为空字符串,说明是清空操作
|
// 如果是状态字段发生变化且变为空字符串,说明是清空操作
|
||||||
if (oldVal.status !== '' && newVal.status === '') {
|
if (oldVal.status !== '' && newVal.status === '') {
|
||||||
// 重置页码
|
|
||||||
currentPage.value = 1
|
currentPage.value = 1
|
||||||
// 直接更新数据,不进行状态过滤
|
|
||||||
filterAndPaginateData()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
getList()
|
||||||
// 其他情况下重置到第一页并更新数据
|
|
||||||
currentPage.value = 1
|
|
||||||
filterAndPaginateData()
|
|
||||||
}, { deep: true })
|
}, { deep: true })
|
||||||
|
|
||||||
// 刷新列表
|
// 刷新列表
|
||||||
@ -191,6 +186,7 @@ const handleRefresh = () => {
|
|||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const dialogTitle = ref('')
|
const dialogTitle = ref('')
|
||||||
const formMode = ref('create')
|
const formMode = ref('create')
|
||||||
|
const formRef = ref()
|
||||||
const form = ref({
|
const form = ref({
|
||||||
title: '',
|
title: '',
|
||||||
category: 'field_study',
|
category: 'field_study',
|
||||||
@ -201,7 +197,9 @@ const form = ref({
|
|||||||
description: '',
|
description: '',
|
||||||
requirements: '',
|
requirements: '',
|
||||||
cost: 50.00,
|
cost: 50.00,
|
||||||
status: 1
|
status: 1,
|
||||||
|
image_url: null,
|
||||||
|
imageUrl: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
// 表单规则
|
// 表单规则
|
||||||
@ -226,9 +224,48 @@ const formRules = {
|
|||||||
cost: [
|
cost: [
|
||||||
{ required: true, message: '请输入活动费用', trigger: 'blur' },
|
{ required: true, message: '请输入活动费用', trigger: 'blur' },
|
||||||
{ type: 'number', min: 0, message: '费用不能小于0', 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 = () => {
|
const handleAdd = () => {
|
||||||
formMode.value = 'create'
|
formMode.value = 'create'
|
||||||
@ -243,7 +280,9 @@ const handleAdd = () => {
|
|||||||
description: '',
|
description: '',
|
||||||
requirements: '',
|
requirements: '',
|
||||||
cost: 50.00,
|
cost: 50.00,
|
||||||
status: 1
|
status: 1,
|
||||||
|
image_url: null,
|
||||||
|
imageUrl: ''
|
||||||
}
|
}
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
}
|
}
|
||||||
@ -254,42 +293,68 @@ const handleEdit = (row) => {
|
|||||||
dialogTitle.value = '编辑研学活动'
|
dialogTitle.value = '编辑研学活动'
|
||||||
form.value = {
|
form.value = {
|
||||||
...row,
|
...row,
|
||||||
category: row.category || 'field_study'
|
category: row.category || 'field_study',
|
||||||
|
imageUrl: row.image || '',
|
||||||
|
image_url: null // 重置图片文件
|
||||||
}
|
}
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交表单
|
// 提交表单
|
||||||
const formRef = ref(null)
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!formRef.value) return
|
if (!formRef.value) return
|
||||||
|
|
||||||
await formRef.value.validate(async (valid) => {
|
await formRef.value.validate(async (valid) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
try {
|
try {
|
||||||
// 格式化提交数据
|
// 创建 FormData 对象
|
||||||
const submitData = {
|
const formData = new FormData()
|
||||||
title: form.value.title.trim(),
|
|
||||||
category: form.value.category,
|
// 添加基本字段
|
||||||
start_time: form.value.start_time ? formatDate(new Date(form.value.start_time)) : '',
|
formData.append('title', form.value.title.trim())
|
||||||
end_time: form.value.end_time ? formatDate(new Date(form.value.end_time)) : '',
|
formData.append('category', form.value.category)
|
||||||
location: form.value.location.trim(),
|
|
||||||
capacity: Number(form.value.capacity),
|
// 处理日期 - 使用MySQL兼容的日期时间格式
|
||||||
description: (form.value.description || '').trim(),
|
if (form.value.start_time) {
|
||||||
requirements: (form.value.requirements || '').trim(),
|
const startDate = new Date(form.value.start_time)
|
||||||
cost: Number(form.value.cost),
|
const formattedStartTime = formatDate(startDate)
|
||||||
status: Number(form.value.status)
|
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('请填写必填字段')
|
ElMessage.error('请填写必填字段')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证时间
|
// 验证时间
|
||||||
const startTime = new Date(submitData.start_time)
|
const startTime = new Date(formData.get('start_time'))
|
||||||
const endTime = new Date(submitData.end_time)
|
const endTime = new Date(formData.get('end_time'))
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
|
|
||||||
// 验证结束时间必须大于开始时间
|
// 验证结束时间必须大于开始时间
|
||||||
@ -298,24 +363,12 @@ const handleSubmit = async () => {
|
|||||||
return
|
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
|
let res
|
||||||
if (formMode.value === 'create') {
|
if (formMode.value === 'create') {
|
||||||
res = await createActivity(submitData)
|
res = await createActivity(formData)
|
||||||
} else {
|
} else {
|
||||||
const id = form.value.id
|
const id = form.value.id
|
||||||
res = await updateActivity(id, submitData)
|
res = await updateActivity(id, formData)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
@ -326,8 +379,11 @@ const handleSubmit = async () => {
|
|||||||
ElMessage.error(res.message || `${formMode.value === 'create' ? '新增' : '编辑'}失败`)
|
ElMessage.error(res.message || `${formMode.value === 'create' ? '新增' : '编辑'}失败`)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`${formMode.value === 'create' ? '新增' : '编辑'}研学活动错误:`, error)
|
if (error.response) {
|
||||||
ElMessage.error('操作失败,请检查输入是否正确')
|
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 {
|
try {
|
||||||
const res = await checkActivityCapacity(row.id)
|
const activityId = row.id
|
||||||
|
const res = await getActivityEnrollments(activityId)
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
ElMessage.success(`当前报名人数:${res.data.currentParticipants}/${res.data.maxParticipants}`)
|
// 确保enrollmentList是一个数组
|
||||||
|
enrollmentList.value = Array.isArray(res.data.list) ? res.data.list : []
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(res.message || '检查容量失败')
|
ElMessage.error(res.message || '获取报名详情失败')
|
||||||
|
enrollmentList.value = [] // 确保失败时也是空数组
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('检查研学活动容量错误:', error)
|
console.error('获取报名详情失败:', error)
|
||||||
ElMessage.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) => {
|
const handleSelectionChange = (rows) => {
|
||||||
selectedRows.value = 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(() => {
|
onMounted(() => {
|
||||||
getList()
|
getList()
|
||||||
})
|
})
|
||||||
@ -534,7 +674,7 @@ onMounted(() => {
|
|||||||
<el-table-column prop="enrolled" label="已报名" width="100" align="center">
|
<el-table-column prop="enrolled" label="已报名" width="100" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button type="primary" link @click="checkCapacity(row)">
|
<el-button type="primary" link @click="checkCapacity(row)">
|
||||||
{{ row.enrolled }}/{{ row.capacity }}
|
{{ row.enrolled ?? 0 }}/{{ row.capacity ?? 0 }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@ -563,6 +703,11 @@ onMounted(() => {
|
|||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</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">
|
<el-table-column label="操作" width="250" fixed="right" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button
|
<el-button
|
||||||
@ -688,6 +833,24 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</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>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="dialog-footer">
|
<span class="dialog-footer">
|
||||||
@ -696,6 +859,87 @@ onMounted(() => {
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -753,5 +997,81 @@ onMounted(() => {
|
|||||||
.text-center {
|
.text-center {
|
||||||
text-align: 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>
|
</style>
|
||||||
@ -31,9 +31,19 @@ const categoryOptions = [
|
|||||||
{ label: '植物', value: 'plant' }
|
{ label: '植物', value: 'plant' }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// 保护等级选项
|
||||||
|
const protectionLevelOptions = [
|
||||||
|
{ label: '国家一级', value: 'national_first' },
|
||||||
|
{ label: '国家二级', value: 'national_second' },
|
||||||
|
{ label: '省级', value: 'provincial' },
|
||||||
|
{ label: '普通', value: 'normal' }
|
||||||
|
]
|
||||||
|
|
||||||
// 图表实例
|
// 图表实例
|
||||||
const categoryChartRef = ref(null)
|
const categoryChartRef = ref(null)
|
||||||
let categoryChart = null
|
let categoryChart = null
|
||||||
|
const protectionChartRef = ref(null)
|
||||||
|
let protectionChart = null
|
||||||
|
|
||||||
// 统计卡片数据
|
// 统计卡片数据
|
||||||
const statsCards = ref([
|
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 () => {
|
const initData = async () => {
|
||||||
try {
|
try {
|
||||||
@ -551,6 +651,9 @@ const fetchStatisticsData = async () => {
|
|||||||
|
|
||||||
// 更新物种分布图表
|
// 更新物种分布图表
|
||||||
updateCategoryChart(res.data)
|
updateCategoryChart(res.data)
|
||||||
|
|
||||||
|
// 更新保护等级图表
|
||||||
|
updateProtectionChart(res.data)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取统计数据失败:', error)
|
console.error('获取统计数据失败:', error)
|
||||||
@ -560,11 +663,13 @@ const fetchStatisticsData = async () => {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initData()
|
initData()
|
||||||
startAutoRefresh()
|
startAutoRefresh()
|
||||||
initTrendChart();
|
initTrendChart()
|
||||||
initDistributionChart();
|
initDistributionChart()
|
||||||
initCategoryChart()
|
initCategoryChart()
|
||||||
|
initProtectionChart()
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
categoryChart?.resize()
|
categoryChart?.resize()
|
||||||
|
protectionChart?.resize()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -576,8 +681,13 @@ onUnmounted(() => {
|
|||||||
categoryChart.dispose()
|
categoryChart.dispose()
|
||||||
categoryChart = null
|
categoryChart = null
|
||||||
}
|
}
|
||||||
|
if (protectionChart) {
|
||||||
|
protectionChart.dispose()
|
||||||
|
protectionChart = null
|
||||||
|
}
|
||||||
window.removeEventListener('resize', () => {
|
window.removeEventListener('resize', () => {
|
||||||
categoryChart?.resize()
|
categoryChart?.resize()
|
||||||
|
protectionChart?.resize()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@ -629,6 +739,10 @@ onUnmounted(() => {
|
|||||||
<div class="chart-title">趋势统计</div>
|
<div class="chart-title">趋势统计</div>
|
||||||
<div id="trendChart" class="chart-content"></div>
|
<div id="trendChart" class="chart-content"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="chart-item">
|
||||||
|
<div class="chart-title">保护等级统计</div>
|
||||||
|
<div ref="protectionChartRef" class="chart-content"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -791,18 +905,18 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.charts-container {
|
.charts-container {
|
||||||
display: flex;
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
|
||||||
.chart-item {
|
.chart-item {
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
.chart-title {
|
.chart-title {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: v.$text-primary;
|
color: v.$text-primary;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-content {
|
.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 () => {
|
const getList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@ -401,8 +292,6 @@ const getStatistics = async () => {
|
|||||||
try {
|
try {
|
||||||
const res = await getSpeciesStatistics()
|
const res = await getSpeciesStatistics()
|
||||||
statistics.value = res.data
|
statistics.value = res.data
|
||||||
// 更新图表数据
|
|
||||||
updateCharts()
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取统计信息失败:', 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(() => {
|
onMounted(() => {
|
||||||
getList()
|
getList()
|
||||||
// 初始化图表
|
|
||||||
initCharts()
|
|
||||||
getStatistics()
|
getStatistics()
|
||||||
window.addEventListener('resize', handleResize)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
// 销毁图表实例
|
// 不再需要清理图表实例
|
||||||
protectionChart?.dispose()
|
|
||||||
window.removeEventListener('resize', handleResize)
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<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-card class="search-container">
|
||||||
<el-form :model="queryParams" ref="queryForm" :inline="true">
|
<el-form :model="queryParams" ref="queryForm" :inline="true">
|
||||||
@ -732,30 +626,6 @@ onUnmounted(() => {
|
|||||||
padding: 10px 20px;
|
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 {
|
.species-image {
|
||||||
width: 60px;
|
width: 60px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user