冲突解决
This commit is contained in:
commit
f58982afc1
@ -207,7 +207,7 @@ public class EngEqDataServiceImpl implements EngEqDataService {
|
|||||||
"eng.INTRODUCE AS introduce, " +
|
"eng.INTRODUCE AS introduce, " +
|
||||||
"eng.LOGO AS logo, " +
|
"eng.LOGO AS logo, " +
|
||||||
"eng.INFFILE AS inffile, " +
|
"eng.INFFILE AS inffile, " +
|
||||||
"eng.INFFILE AS url, " +
|
"eng.url AS url, " +
|
||||||
"eng.BLDSTT AS bldstt, " +
|
"eng.BLDSTT AS bldstt, " +
|
||||||
"CASE eng.BLDSTT_CODE WHEN 0 THEN '0' WHEN 1 THEN '1' WHEN 2 THEN '2' ELSE NULL END AS bldsttCcode, " +
|
"CASE eng.BLDSTT_CODE WHEN 0 THEN '0' WHEN 1 THEN '1' WHEN 2 THEN '2' ELSE NULL END AS bldsttCcode, " +
|
||||||
"eng.BLDSTT_CODE AS bldsttCode, " +
|
"eng.BLDSTT_CODE AS bldsttCode, " +
|
||||||
|
|||||||
@ -410,7 +410,7 @@ public class VdMonitorServiceImpl implements VdMonitorService {
|
|||||||
"LEFT JOIN SD_FHBT_B_H fh ON fh.STCD = src.FHSTCD AND NVL(fh.IS_DELETED, 0) = 0 " +
|
"LEFT JOIN SD_FHBT_B_H fh ON fh.STCD = src.FHSTCD AND NVL(fh.IS_DELETED, 0) = 0 " +
|
||||||
"LEFT JOIN (" + buildStationBaseSql() + ") cst ON cst.STCD = src.CSTCD " +
|
"LEFT JOIN (" + buildStationBaseSql() + ") cst ON cst.STCD = src.CSTCD " +
|
||||||
"LEFT JOIN SD_STTP_B sttp ON sttp.STTP_CODE = src.STTP " +
|
"LEFT JOIN SD_STTP_B sttp ON sttp.STTP_CODE = src.STTP " +
|
||||||
" AND NVL(sttp.IS_DELETED, 0) = 0 AND NVL(sttp.ENABLED, 1) = 1 ";
|
" AND NVL(sttp.IS_DELETED, 0) = 0 AND NVL(sttp.ENABLE, 1) = 1 ";
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildVdDeviceBaseSql() {
|
private String buildVdDeviceBaseSql() {
|
||||||
@ -420,7 +420,8 @@ public class VdMonitorServiceImpl implements VdMonitorService {
|
|||||||
" CAST(NULL AS VARCHAR2(255)) AS LOGO, CAST(NULL AS VARCHAR2(1000)) AS INFFILE, " +
|
" CAST(NULL AS VARCHAR2(255)) AS LOGO, CAST(NULL AS VARCHAR2(1000)) AS INFFILE, " +
|
||||||
" vd.DTFRQCY, vd.REMARK, vd.VLSR, vd.VLSR_TM, vd.URL AS URL, CAST(NULL AS NUMBER(8,0)) AS ORDER_INDEX, " +
|
" vd.DTFRQCY, vd.REMARK, vd.VLSR, vd.VLSR_TM, vd.URL AS URL, CAST(NULL AS NUMBER(8,0)) AS ORDER_INDEX, " +
|
||||||
" 'SD_VDINFO_B' AS SOURCE_TABLE " +
|
" 'SD_VDINFO_B' AS SOURCE_TABLE " +
|
||||||
"FROM SD_VDINFO_B vd WHERE vd.STTP = 'VD_FP' " +
|
"FROM SD_VDINFO_B vd WHERE 1=1 " +
|
||||||
|
// " AND vd.STTP = 'VD_FP' "+
|
||||||
"UNION ALL " +
|
"UNION ALL " +
|
||||||
"SELECT sonar.STCD, sonar.STNM, sonar.MNTP, sonar.STTP, sonar.LGTD, sonar.LTTD, sonar.ELEV, sonar.STLC, " +
|
"SELECT sonar.STCD, sonar.STNM, sonar.MNTP, sonar.STTP, sonar.LGTD, sonar.LTTD, sonar.ELEV, sonar.STLC, " +
|
||||||
" sonar.JCDT, sonar.WDDT, sonar.BLDSTT_CODE, sonar.USFL, sonar.DTIN, sonar.STINDX, sonar.RSTCD, sonar.BASE_ID, " +
|
" sonar.JCDT, sonar.WDDT, sonar.BLDSTT_CODE, sonar.USFL, sonar.DTIN, sonar.STINDX, sonar.RSTCD, sonar.BASE_ID, " +
|
||||||
|
|||||||
@ -334,7 +334,7 @@ public class SdWtMonitorServiceImpl implements SdWtMonitorService {
|
|||||||
SUM(CASE WHEN NVL(t.DTIN, 0) = 1 THEN 1 ELSE 0 END) AS accessNum,
|
SUM(CASE WHEN NVL(t.DTIN, 0) = 1 THEN 1 ELSE 0 END) AS accessNum,
|
||||||
SUM(CASE WHEN NVL(t.BLDSTT_CODE, 0) = 2 THEN 1 ELSE 0 END) AS buildNum,
|
SUM(CASE WHEN NVL(t.BLDSTT_CODE, 0) = 2 THEN 1 ELSE 0 END) AS buildNum,
|
||||||
SUM(CASE WHEN NVL(t.BLDSTT_CODE, 0) = 1 THEN 1 ELSE 0 END) AS zjNum
|
SUM(CASE WHEN NVL(t.BLDSTT_CODE, 0) = 1 THEN 1 ELSE 0 END) AS zjNum
|
||||||
FROM SD_DFLTKW_B_H t
|
FROM SD_DW_B_H t
|
||||||
LEFT JOIN SD_ENGINFO_B_H eng ON eng.STCD = t.RSTCD
|
LEFT JOIN SD_ENGINFO_B_H eng ON eng.STCD = t.RSTCD
|
||||||
WHERE NVL(t.IS_DELETED, 0) = 0
|
WHERE NVL(t.IS_DELETED, 0) = 0
|
||||||
""" + (StrUtil.isNotBlank(baseId) ? """
|
""" + (StrUtil.isNotBlank(baseId) ? """
|
||||||
@ -704,7 +704,7 @@ public class SdWtMonitorServiceImpl implements SdWtMonitorService {
|
|||||||
.append("ELSE '未建/规划' END AS bldsttCcodeName, ")
|
.append("ELSE '未建/规划' END AS bldsttCcodeName, ")
|
||||||
.append("dw.BLDSTT_CODE AS bldsttCcode, ")
|
.append("dw.BLDSTT_CODE AS bldsttCcode, ")
|
||||||
.append("eng.HBRVCD AS hbrvcd ")
|
.append("eng.HBRVCD AS hbrvcd ")
|
||||||
.append("FROM SD_DFLTKW_B_H dw ")
|
.append("FROM SD_DW_B_H dw ")
|
||||||
.append("LEFT JOIN SD_ENGINFO_B_H eng ON eng.STCD = dw.RSTCD AND NVL(eng.IS_DELETED, 0) = 0 ")
|
.append("LEFT JOIN SD_ENGINFO_B_H eng ON eng.STCD = dw.RSTCD AND NVL(eng.IS_DELETED, 0) = 0 ")
|
||||||
.append("LEFT JOIN SD_HYDROBASE hb ON hb.BASEID = eng.BASE_ID AND NVL(hb.IS_DELETED, 0) = 0 ")
|
.append("LEFT JOIN SD_HYDROBASE hb ON hb.BASEID = eng.BASE_ID AND NVL(hb.IS_DELETED, 0) = 0 ")
|
||||||
.append("LEFT JOIN SD_HBRV_DIC hbrv ON hbrv.HBRVCD = eng.HBRVCD AND hbrv.BASEID = eng.BASE_ID AND NVL(hbrv.IS_DELETED, 0) = 0 ")
|
.append("LEFT JOIN SD_HBRV_DIC hbrv ON hbrv.HBRVCD = eng.HBRVCD AND hbrv.BASEID = eng.BASE_ID AND NVL(hbrv.IS_DELETED, 0) = 0 ")
|
||||||
|
|||||||
@ -7,12 +7,12 @@ VITE_APP_TITLE = '水电水利建设项目全过程环境管理信息平台'
|
|||||||
VITE_APP_PORT = 3000
|
VITE_APP_PORT = 3000
|
||||||
VITE_APP_BASE_API = '/dev-api'
|
VITE_APP_BASE_API = '/dev-api'
|
||||||
# 本地环境
|
# 本地环境
|
||||||
# VITE_APP_BASE_URL = 'http://10.84.121.122:8093'
|
VITE_APP_BASE_URL = 'http://localhost:8093'
|
||||||
# 测试环境
|
# 测试环境
|
||||||
# VITE_APP_BASE_URL = 'http://172.16.21.142:8093'
|
# VITE_APP_BASE_URL = 'http://172.16.21.142:8093'
|
||||||
# VITE_APP_BASE_URL = 'http://172.16.21.142:8096'
|
# VITE_APP_BASE_URL = 'http://172.16.21.142:8096'
|
||||||
# 汤伟
|
# 汤伟
|
||||||
VITE_APP_BASE_URL = 'http://10.84.121.21:8093'
|
# VITE_APP_BASE_URL = 'http://10.84.121.21:8093'
|
||||||
# VITE_APP_BASE_URL = 'http://192.168.1.162:8093'
|
# VITE_APP_BASE_URL = 'http://192.168.1.162:8093'
|
||||||
|
|
||||||
# 测试环境线上
|
# 测试环境线上
|
||||||
@ -23,8 +23,9 @@ VITE_APP_TEST_ONLINE_URL = 'https://211.99.26.225:12122'
|
|||||||
|
|
||||||
# 开发环境导入预览地址
|
# 开发环境导入预览地址
|
||||||
VITE_APP_BASE_API_URL = 'http://10.84.121.21:8093'
|
VITE_APP_BASE_API_URL = 'http://10.84.121.21:8093'
|
||||||
## 开发环境预览 图片视频地址
|
## 开发环境 附件服务地址
|
||||||
VITE_APP_PREVIEW_URL = 'https://211.99.26.225:12125'
|
VITE_APP_ATTACHMENT_URL = 'https://211.99.26.225:12125'
|
||||||
|
attachment
|
||||||
|
|
||||||
# 地图服务地址
|
# 地图服务地址
|
||||||
VITE_APP_MAP_URL = 'https://211.99.26.225:18085'
|
VITE_APP_MAP_URL = 'https://211.99.26.225:18085'
|
||||||
|
|||||||
@ -3,10 +3,17 @@ NODE_ENV='production'
|
|||||||
|
|
||||||
VITE_APP_TITLE = 'qgc-buji-web'
|
VITE_APP_TITLE = 'qgc-buji-web'
|
||||||
VITE_APP_PORT = 3000
|
VITE_APP_PORT = 3000
|
||||||
VITE_APP_BASE_API = '/prod-api'
|
VITE_APP_BASE_API = '/'
|
||||||
## 生产环境API地址
|
## 生产环境API地址
|
||||||
VITE_APP_BASE_URL = 'http://localhost:8093'
|
VITE_APP_BASE_URL = 'http://localhost:8093'
|
||||||
## 生产环境导入预览地址
|
## 生产环境导入预览地址
|
||||||
VITE_APP_BASE_API_URL = 'https://211.99.26.225:12130/prod-api'
|
VITE_APP_BASE_API_URL = 'https://211.99.26.225:12130/prod-api'
|
||||||
## 生产环境预览 图片视频地址
|
## 生产环境预览 图片视频地址
|
||||||
VITE_APP_PREVIEW_URL = 'https://211.99.26.225:12125'
|
VITE_APP_PREVIEW_URL = 'https://211.99.26.225:12125'
|
||||||
|
|
||||||
|
## 开发环境 附件服务地址
|
||||||
|
VITE_APP_ATTACHMENT_URL = 'https://211.99.26.225:12125'
|
||||||
|
attachment
|
||||||
|
|
||||||
|
# 地图服务地址
|
||||||
|
VITE_APP_MAP_URL = 'https://211.99.26.225:18085'
|
||||||
@ -37,6 +37,7 @@
|
|||||||
"ol": "^10.8.0",
|
"ol": "^10.8.0",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
"path-to-regexp": "^6.2.0",
|
"path-to-regexp": "^6.2.0",
|
||||||
|
"pdfjs-dist": "^6.0.227",
|
||||||
"pinia": "^2.0.12",
|
"pinia": "^2.0.12",
|
||||||
"screenfull": "^6.0.0",
|
"screenfull": "^6.0.0",
|
||||||
"sortablejs": "^1.14.0",
|
"sortablejs": "^1.14.0",
|
||||||
|
|||||||
@ -89,6 +89,9 @@ importers:
|
|||||||
path-to-regexp:
|
path-to-regexp:
|
||||||
specifier: ^6.2.0
|
specifier: ^6.2.0
|
||||||
version: 6.3.0
|
version: 6.3.0
|
||||||
|
pdfjs-dist:
|
||||||
|
specifier: ^6.0.227
|
||||||
|
version: 6.0.227
|
||||||
pinia:
|
pinia:
|
||||||
specifier: ^2.0.12
|
specifier: ^2.0.12
|
||||||
version: 2.3.1(typescript@6.0.3)(vue@3.5.33(typescript@6.0.3))
|
version: 2.3.1(typescript@6.0.3)(vue@3.5.33(typescript@6.0.3))
|
||||||
@ -595,6 +598,81 @@ packages:
|
|||||||
'@jridgewell/trace-mapping@0.3.9':
|
'@jridgewell/trace-mapping@0.3.9':
|
||||||
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
|
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
|
||||||
|
|
||||||
|
'@napi-rs/canvas-android-arm64@1.0.0':
|
||||||
|
resolution: {integrity: sha512-3hNKJObUK7JsCF9aJlVCs1J0/KE/gGfZNeK8MO1ge6bB3aicr5walGme9t9No1f/oyk9GgvdAT/rjSdsx3gbIw==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@napi-rs/canvas-darwin-arm64@1.0.0':
|
||||||
|
resolution: {integrity: sha512-ZIja19/BiGz2puhki+WUYSRriwFeFJ8Mi9eK3hZdSS85w4Y60cuEAJVhMCfKwswQkKkUtrnzdKMBuO7TupvexA==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@napi-rs/canvas-darwin-x64@1.0.0':
|
||||||
|
resolution: {integrity: sha512-hImggWc82jqZVpEsFR9S7PE9OQYjq/H/D7vwCGB6X1jRH+UVBP1+1niJTPBOat1B154T6GKK7/kcFtoWgjgFzQ==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@napi-rs/canvas-linux-arm-gnueabihf@1.0.0':
|
||||||
|
resolution: {integrity: sha512-hlJRy6d+kWLKVOG/+1rEvNQVURZ0DxxRPJsLmEWwhwiXZUJc0BF5o9esALHSEP4CoJK4wChRtj3hnyBgVx2oWA==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@napi-rs/canvas-linux-arm64-gnu@1.0.0':
|
||||||
|
resolution: {integrity: sha512-5Hru4T3RXkosRQafcjelv7AUzw9mXqmGYsxnzeDDOWveFCJyEPMSJltvGCM+jfH98seOCbfwm9KyFg6Jm5FhAA==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
|
'@napi-rs/canvas-linux-arm64-musl@1.0.0':
|
||||||
|
resolution: {integrity: sha512-LTUl9jS8WsLSUGaxQZKQkxfluOJRpgvBuxxdM4pYcjib+di8AU4OzQc6+L6SzGMLcKc9H0RAjojRatBhTMqYdg==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
|
'@napi-rs/canvas-linux-riscv64-gnu@1.0.0':
|
||||||
|
resolution: {integrity: sha512-Iz931SAZf+WVDzpjk52Q3ffW3zw0YflFwEZMgs036Wfu1kX/LrwT9wGjsuSqyduqefUkl91/vTdAjn8hQu5ezA==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
|
'@napi-rs/canvas-linux-x64-gnu@1.0.0':
|
||||||
|
resolution: {integrity: sha512-pFEQ5eFK4JusgN1K6KkO9DKP/Hi1WMJOkF8Ch03/khTc4bFbCKkCCsJG4YcOMOW9bI4XbT2/eMAWxhO0xaWgPA==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
|
'@napi-rs/canvas-linux-x64-musl@1.0.0':
|
||||||
|
resolution: {integrity: sha512-jnvr8NrLHiZ3NCiOKWqDbkI4Ah+QDrqtZ+sddPZBltEb1mQ2coSvCSJYfict+oAwcm0c970oTmVySpjKP/lnaA==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
|
'@napi-rs/canvas-win32-arm64-msvc@1.0.0':
|
||||||
|
resolution: {integrity: sha512-y2j9/Gfd5joqiqxdP/L1smqjQ+uAx3C4N0EC7bDHrnZEEH8ToM/OC5p3uHvtj4Lq591aHj+ArL01UDLNwT5HgQ==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@napi-rs/canvas-win32-x64-msvc@1.0.0':
|
||||||
|
resolution: {integrity: sha512-qwdhh9N6Gge/hC4pL9S1tQp0iKwhSl/dYjg7+RGp9k26iRGRi5MqqUyKGOXIWli0zOcuy5Y2wIH/jk2ry6i/jA==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@napi-rs/canvas@1.0.0':
|
||||||
|
resolution: {integrity: sha512-Jqxcy1XOIqj+lH9sl1GT+il6GR3uQv13vI2mrwubP3uT8Olak2ClDrK2RnxlQKjwv8BRr4b3ug0YR7c6hBX8wg==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
|
||||||
'@nodelib/fs.scandir@2.1.5':
|
'@nodelib/fs.scandir@2.1.5':
|
||||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@ -2864,6 +2942,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==}
|
resolution: {integrity: sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
pdfjs-dist@6.0.227:
|
||||||
|
resolution: {integrity: sha512-/P6M4SXw+70waMVLUM7rdRtvo+dEzqE1t6W/zQNvBETo2MaRa5rrvCcAYdfWGiUzadTgM0lJmRApUrW0d9zgKg==}
|
||||||
|
engines: {node: '>=22.13.0 || >=24'}
|
||||||
|
|
||||||
picocolors@1.1.1:
|
picocolors@1.1.1:
|
||||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||||
|
|
||||||
@ -4323,6 +4405,54 @@ snapshots:
|
|||||||
'@jridgewell/resolve-uri': 3.1.2
|
'@jridgewell/resolve-uri': 3.1.2
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
|
'@napi-rs/canvas-android-arm64@1.0.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@napi-rs/canvas-darwin-arm64@1.0.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@napi-rs/canvas-darwin-x64@1.0.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@napi-rs/canvas-linux-arm-gnueabihf@1.0.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@napi-rs/canvas-linux-arm64-gnu@1.0.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@napi-rs/canvas-linux-arm64-musl@1.0.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@napi-rs/canvas-linux-riscv64-gnu@1.0.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@napi-rs/canvas-linux-x64-gnu@1.0.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@napi-rs/canvas-linux-x64-musl@1.0.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@napi-rs/canvas-win32-arm64-msvc@1.0.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@napi-rs/canvas-win32-x64-msvc@1.0.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@napi-rs/canvas@1.0.0':
|
||||||
|
optionalDependencies:
|
||||||
|
'@napi-rs/canvas-android-arm64': 1.0.0
|
||||||
|
'@napi-rs/canvas-darwin-arm64': 1.0.0
|
||||||
|
'@napi-rs/canvas-darwin-x64': 1.0.0
|
||||||
|
'@napi-rs/canvas-linux-arm-gnueabihf': 1.0.0
|
||||||
|
'@napi-rs/canvas-linux-arm64-gnu': 1.0.0
|
||||||
|
'@napi-rs/canvas-linux-arm64-musl': 1.0.0
|
||||||
|
'@napi-rs/canvas-linux-riscv64-gnu': 1.0.0
|
||||||
|
'@napi-rs/canvas-linux-x64-gnu': 1.0.0
|
||||||
|
'@napi-rs/canvas-linux-x64-musl': 1.0.0
|
||||||
|
'@napi-rs/canvas-win32-arm64-msvc': 1.0.0
|
||||||
|
'@napi-rs/canvas-win32-x64-msvc': 1.0.0
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@nodelib/fs.scandir@2.1.5':
|
'@nodelib/fs.scandir@2.1.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nodelib/fs.stat': 2.0.5
|
'@nodelib/fs.stat': 2.0.5
|
||||||
@ -6817,6 +6947,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
resolve-protobuf-schema: 2.1.0
|
resolve-protobuf-schema: 2.1.0
|
||||||
|
|
||||||
|
pdfjs-dist@6.0.227:
|
||||||
|
optionalDependencies:
|
||||||
|
'@napi-rs/canvas': 1.0.0
|
||||||
|
|
||||||
picocolors@1.1.1: {}
|
picocolors@1.1.1: {}
|
||||||
|
|
||||||
picomatch@2.3.2: {}
|
picomatch@2.3.2: {}
|
||||||
|
|||||||
@ -6,3 +6,59 @@ export function getStcdDetail(url: string, stcd: string) {
|
|||||||
params: { stcd }
|
params: { stcd }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// 公共接口 - post
|
||||||
|
export function queryPostUrlList(url: string, data: any) {
|
||||||
|
return request({
|
||||||
|
url: url,
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 公共接口 - 获取预览地址
|
||||||
|
export function getIdUrl(params: any) {
|
||||||
|
return request({
|
||||||
|
url: import.meta.env.VITE_APP_ATTACHMENT_URL + '/get',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 全景影像
|
||||||
|
export function getPanoramaList(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/eq/msstbprpt/GetKendoList',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 实时视频
|
||||||
|
export function getVideoList(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/vd/vmsstbprpt/GetKendoList',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 实时视频单个查询
|
||||||
|
export function getVideoDetail(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/vd/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 实时视频 回放
|
||||||
|
export function getVideoPlayDetail(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/vd/runData/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 监测数据 - 电站
|
||||||
|
export function getMonitorData(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/eq/data/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
|
|
||||||
// 基地下拉列表
|
// 基地下拉列表
|
||||||
export function getBaseDropdown(data:any) {
|
export function getBaseDropdown(data: any) {
|
||||||
return request({
|
return request({
|
||||||
url: '/env/hydrobase/dropdown',
|
url: '/env/hydrobase/dropdown',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
@ -9,7 +9,7 @@ export function getBaseDropdown(data:any) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 流域下拉列表
|
// 流域下拉列表
|
||||||
export function getSelectForDropdown(data:any) {
|
export function getSelectForDropdown(data: any) {
|
||||||
return request({
|
return request({
|
||||||
url: '/env/rvcd/selectForDropdown',
|
url: '/env/rvcd/selectForDropdown',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
@ -17,7 +17,7 @@ export function getSelectForDropdown(data:any) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
//电站下拉列表
|
//电站下拉列表
|
||||||
export function getEngInfoDropdown(data:any) {
|
export function getEngInfoDropdown(data: any) {
|
||||||
return request({
|
return request({
|
||||||
url: '/env/engInfo/dropdown',
|
url: '/env/engInfo/dropdown',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
@ -25,7 +25,7 @@ export function getEngInfoDropdown(data:any) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 重新验证并更新行数据
|
// 重新验证并更新行数据
|
||||||
export function revalidateAndUpdateRow(data:any) {
|
export function revalidateAndUpdateRow(data: any) {
|
||||||
return request({
|
return request({
|
||||||
url: '/data/fishDraft/revalidateAndUpdateRow',
|
url: '/data/fishDraft/revalidateAndUpdateRow',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
@ -33,7 +33,7 @@ export function revalidateAndUpdateRow(data:any) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 删除 行数据
|
// 删除 行数据
|
||||||
export function deleteRowById(data:any) {
|
export function deleteRowById(data: any) {
|
||||||
return request({
|
return request({
|
||||||
url: '/data/fishDraft/deleteRowById',
|
url: '/data/fishDraft/deleteRowById',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
@ -42,7 +42,7 @@ export function deleteRowById(data:any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//过鱼设施下拉列表
|
//过鱼设施下拉列表
|
||||||
export function getFpssDropdown(params:any) {
|
export function getFpssDropdown(params: any) {
|
||||||
return request({
|
return request({
|
||||||
url: '/env/fpss/dropdown',
|
url: '/env/fpss/dropdown',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
@ -57,7 +57,7 @@ export function getFishDictoryDropdown() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 类似鱼类名称下拉列表
|
// 类似鱼类名称下拉列表
|
||||||
export function getSimilarFishDictoryDropdown(params:any) {
|
export function getSimilarFishDictoryDropdown(params: any) {
|
||||||
return request({
|
return request({
|
||||||
url: '/env/fishDictory/similar',
|
url: '/env/fishDictory/similar',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
@ -65,11 +65,11 @@ export function getSimilarFishDictoryDropdown(params:any) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 上传文件
|
// 上传文件
|
||||||
export function uploadFile(data:any) {
|
export function uploadFile(data: any) {
|
||||||
return request({
|
return request({
|
||||||
url: import.meta.env.VITE_APP_PREVIEW_URL + '/upload',
|
url: import.meta.env.VITE_APP_ATTACHMENT_URL + '/upload',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data,
|
data,
|
||||||
headers: { 'Content-Type': 'multipart/form-data' },
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="table-container" ref="tableContainerRef">
|
<div class="table-container" ref="tableContainerRef">
|
||||||
<a-table size="small" :loading="loading" :row-selection="enableRowSelection ? rowSelection : undefined"
|
<a-table
|
||||||
:data-source="tableData" :columns="enhancedColumns" :pagination="paginationConfig" :scroll="scrollConfig"
|
size="small"
|
||||||
:row-key="rowKey" @change="handleTableChange">
|
:loading="loading"
|
||||||
|
:row-selection="enableRowSelection ? rowSelection : undefined"
|
||||||
|
:data-source="tableData"
|
||||||
|
:columns="enhancedColumns"
|
||||||
|
:pagination="paginationConfig"
|
||||||
|
:scroll="scrollConfig"
|
||||||
|
:row-key="rowKey"
|
||||||
|
@change="handleTableChange"
|
||||||
|
>
|
||||||
<!-- 透传插槽,支持自定义列内容 -->
|
<!-- 透传插槽,支持自定义列内容 -->
|
||||||
<template v-for="slot in Object.keys($slots)" #[slot]="scope" :key="slot">
|
<template v-for="slot in Object.keys($slots)" #[slot]="scope" :key="slot">
|
||||||
<slot :name="slot" v-bind="scope"></slot>
|
<slot :name="slot" v-bind="scope"></slot>
|
||||||
@ -12,15 +20,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, watch, nextTick, h } from "vue";
|
import { ref, computed, onMounted, watch, nextTick, h } from 'vue';
|
||||||
import { Tooltip } from "ant-design-vue";
|
import { Tooltip } from 'ant-design-vue';
|
||||||
import { calcTableScrollY } from "@/utils/index";
|
import { calcTableScrollY } from '@/utils/index';
|
||||||
|
|
||||||
// --- Types ---
|
// --- Types ---
|
||||||
interface Props {
|
interface Props {
|
||||||
columns: any[];
|
columns: any[];
|
||||||
scrollY?: string | number; // 优先使用传入的固定值
|
scrollY?: string | number; // 优先使用传入的固定值
|
||||||
listUrl: (params: any) => Promise<any>;
|
scrollX?: string | number; // 横向滚动宽度
|
||||||
|
listUrl?: (params: any) => Promise<any>;
|
||||||
data?: any[];
|
data?: any[];
|
||||||
enableRowSelection?: boolean;
|
enableRowSelection?: boolean;
|
||||||
rowKey?: string;
|
rowKey?: string;
|
||||||
@ -37,7 +46,7 @@ interface Props {
|
|||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
enableRowSelection: false,
|
enableRowSelection: false,
|
||||||
rowKey: "id",
|
rowKey: 'id',
|
||||||
isPage: true,
|
isPage: true,
|
||||||
data: () => [],
|
data: () => [],
|
||||||
searchParams: () => ({}),
|
searchParams: () => ({}),
|
||||||
@ -45,16 +54,19 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
getCheckboxProps: undefined,
|
getCheckboxProps: undefined,
|
||||||
transformData: undefined,
|
transformData: undefined,
|
||||||
scrollY: undefined,
|
scrollY: undefined,
|
||||||
emptyPlaceholder: "-",
|
emptyPlaceholder: '-',
|
||||||
processEmptyValues: true, // 默认开启
|
processEmptyValues: true, // 默认开启
|
||||||
enableEllipsis: true, // 默认开启
|
enableEllipsis: true, // 默认开启
|
||||||
enableSort: false, // 默认关闭,确保旧代码不受影响
|
enableSort: false // 默认关闭,确保旧代码不受影响
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "data-loaded", params: any, data: any): void;
|
(e: 'data-loaded', params: any, data: any): void;
|
||||||
(e: "selection-change", selectedRowKeys: string[], selectedRows: any[]): void;
|
(e: 'selection-change', selectedRowKeys: string[], selectedRows: any[]): void;
|
||||||
(e: "sort-change", sortInfo: { field: string; order: 'ascend' | 'descend' | null }): void;
|
(
|
||||||
|
e: 'sort-change',
|
||||||
|
sortInfo: { field: string; order: 'ascend' | 'descend' | null }
|
||||||
|
): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// --- State ---
|
// --- State ---
|
||||||
@ -72,19 +84,23 @@ const tableScrollY = ref<number>(0); // ✅ 新增:存储容器高度
|
|||||||
// --- Computed Scroll Config ---
|
// --- Computed Scroll Config ---
|
||||||
const scrollConfig = computed(() => {
|
const scrollConfig = computed(() => {
|
||||||
const config: any = {
|
const config: any = {
|
||||||
x: "100%",
|
x: '100%',
|
||||||
y: 0,
|
y: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
// // 1. 如果父组件传入了固定的 scrollY,优先使用
|
// 1. 如果父组件传入了固定的 scrollX,优先使用
|
||||||
|
if (props.scrollX !== undefined && props.scrollX !== null) {
|
||||||
|
config.x = props.scrollX;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 如果父组件传入了固定的 scrollY,优先使用
|
||||||
if (props.scrollY !== undefined && props.scrollY !== null) {
|
if (props.scrollY !== undefined && props.scrollY !== null) {
|
||||||
config.y = props.scrollY;
|
config.y = props.scrollY;
|
||||||
}
|
}
|
||||||
// 2. 否则,如果检测到容器有高度,则使用容器高度作为滚动高度
|
// 3. 否则,如果检测到容器有高度,则使用容器高度作为滚动高度
|
||||||
else if (tableScrollY.value > 0) {
|
else if (tableScrollY.value > 0) {
|
||||||
config.y = tableScrollY.value;
|
config.y = tableScrollY.value;
|
||||||
}
|
}
|
||||||
console.log(config);
|
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
});
|
});
|
||||||
@ -95,11 +111,11 @@ const rowSelection = computed(() => ({
|
|||||||
onChange: (keys: string[], rows: any[]) => {
|
onChange: (keys: string[], rows: any[]) => {
|
||||||
selectedRowKeys.value = keys;
|
selectedRowKeys.value = keys;
|
||||||
selectedRows.value = rows;
|
selectedRows.value = rows;
|
||||||
emit("selection-change", keys, rows);
|
emit('selection-change', keys, rows);
|
||||||
},
|
},
|
||||||
getCheckboxProps: props.getCheckboxProps
|
getCheckboxProps: props.getCheckboxProps
|
||||||
? props.getCheckboxProps
|
? props.getCheckboxProps
|
||||||
: (record: any) => ({}),
|
: (record: any) => ({})
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- Pagination Config ---
|
// --- Pagination Config ---
|
||||||
@ -110,7 +126,7 @@ const paginationConfig = computed(() => ({
|
|||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showQuickJumper: true,
|
showQuickJumper: true,
|
||||||
showTotal: (total: number) => `共 ${total} 条`,
|
showTotal: (total: number) => `共 ${total} 条`,
|
||||||
pageSizeOptions: ["20", "50", "100"],
|
pageSizeOptions: ['20', '50', '100']
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- Methods ---
|
// --- Methods ---
|
||||||
@ -128,7 +144,7 @@ const getList = async (filter?: Record<string, any>) => {
|
|||||||
...props.searchParams,
|
...props.searchParams,
|
||||||
skip: page.value,
|
skip: page.value,
|
||||||
take: size.value,
|
take: size.value,
|
||||||
filter: lastFilter.value,
|
filter: lastFilter.value
|
||||||
};
|
};
|
||||||
if (!props.isPage) {
|
if (!props.isPage) {
|
||||||
delete params.take;
|
delete params.take;
|
||||||
@ -151,9 +167,9 @@ const getList = async (filter?: Record<string, any>) => {
|
|||||||
|
|
||||||
tableData.value = processData(finalRecords);
|
tableData.value = processData(finalRecords);
|
||||||
total.value = finalTotal;
|
total.value = finalTotal;
|
||||||
emit("data-loaded", params, { records: finalRecords, total: finalTotal });
|
emit('data-loaded', params, { records: finalRecords, total: finalTotal });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Fetch table data error:", error);
|
console.error('Fetch table data error:', error);
|
||||||
tableData.value = [];
|
tableData.value = [];
|
||||||
total.value = 0;
|
total.value = 0;
|
||||||
} finally {
|
} finally {
|
||||||
@ -166,14 +182,13 @@ const handleTableChange = (pag: any, filters: any, sorter: any) => {
|
|||||||
size.value = pag.pageSize;
|
size.value = pag.pageSize;
|
||||||
if (props.enableSort) {
|
if (props.enableSort) {
|
||||||
if (sorter && sorter.field) {
|
if (sorter && sorter.field) {
|
||||||
emit("sort-change", { field: sorter.field, order: sorter.order });
|
emit('sort-change', { field: sorter.field, order: sorter.order });
|
||||||
} else {
|
} else {
|
||||||
emit("sort-change", { field: '', order: null });
|
emit('sort-change', { field: '', order: null });
|
||||||
}
|
}
|
||||||
}else {
|
} else {
|
||||||
getList();
|
getList();
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
@ -201,8 +216,8 @@ defineExpose({
|
|||||||
clearSelection,
|
clearSelection,
|
||||||
getSelected: () => ({
|
getSelected: () => ({
|
||||||
keys: selectedRowKeys.value,
|
keys: selectedRowKeys.value,
|
||||||
rows: selectedRows.value,
|
rows: selectedRows.value
|
||||||
}),
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const observer = new ResizeObserver(() => {
|
const observer = new ResizeObserver(() => {
|
||||||
@ -218,7 +233,7 @@ const processData = (records: any[]) => {
|
|||||||
for (const key in processedRecord) {
|
for (const key in processedRecord) {
|
||||||
if (Object.prototype.hasOwnProperty.call(processedRecord, key)) {
|
if (Object.prototype.hasOwnProperty.call(processedRecord, key)) {
|
||||||
const val = processedRecord[key];
|
const val = processedRecord[key];
|
||||||
if (val === null || val === undefined || val === "") {
|
if (val === null || val === undefined || val === '') {
|
||||||
processedRecord[key] = props.emptyPlaceholder;
|
processedRecord[key] = props.emptyPlaceholder;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,15 +256,21 @@ const enhancedColumns = computed(() => {
|
|||||||
ellipsis: true, // 启用 Ant Design Vue 原生的 ellipsis 支持
|
ellipsis: true, // 启用 Ant Design Vue 原生的 ellipsis 支持
|
||||||
customRender: ({ text }: any) => {
|
customRender: ({ text }: any) => {
|
||||||
const content = String(text ?? props.emptyPlaceholder);
|
const content = String(text ?? props.emptyPlaceholder);
|
||||||
return h(
|
return h(Tooltip, { title: content }, () =>
|
||||||
Tooltip,
|
h(
|
||||||
{ title: content },
|
'div',
|
||||||
() => h('div', {
|
{
|
||||||
class: 'table-cell-ellipsis',
|
class: 'table-cell-ellipsis',
|
||||||
style: { overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }
|
style: {
|
||||||
}, content)
|
overflow: 'hidden',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
textOverflow: 'ellipsis'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
content
|
||||||
|
)
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -263,8 +284,24 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
if (props.data && props.data.length > 0) {
|
if (props.data && props.data.length > 0) {
|
||||||
tableData.value = processData(props.data);
|
tableData.value = processData(props.data);
|
||||||
|
total.value = props.data.length;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 监听 data prop 变化
|
||||||
|
watch(
|
||||||
|
() => props.data,
|
||||||
|
newData => {
|
||||||
|
if (newData && newData.length > 0) {
|
||||||
|
tableData.value = processData(newData);
|
||||||
|
total.value = newData.length;
|
||||||
|
} else {
|
||||||
|
tableData.value = [];
|
||||||
|
total.value = 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
|
|||||||
264
frontend/src/components/MapModal/components/Attachment.vue
Normal file
264
frontend/src/components/MapModal/components/Attachment.vue
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
<template>
|
||||||
|
<a-spin :spinning="loading">
|
||||||
|
<div class="attachment-container">
|
||||||
|
<!-- 左侧文件列表 -->
|
||||||
|
<div v-loading="loading" class="file-list" v-if="fileList.length > 0">
|
||||||
|
<div
|
||||||
|
v-for="file in fileList"
|
||||||
|
:key="file.id"
|
||||||
|
:class="['file-item', { active: selectedFile?.id === file.id }]"
|
||||||
|
@click="selectFile(file)"
|
||||||
|
>
|
||||||
|
<span class="file-name" :title="file.name">{{ file.name }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧PDF预览 -->
|
||||||
|
<div class="pdf-preview">
|
||||||
|
<div v-if="showDownload && selectedFile" class="download-bar">
|
||||||
|
<button class="download-btn" @click="downloadFile(selectedFile)">
|
||||||
|
<span class="download-icon">⬇️</span>
|
||||||
|
<span>下载文件</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<pdf-view :file-id="selectedFile?.id || ''" />
|
||||||
|
<a-empty
|
||||||
|
v-show="!loading && fileList.length === 0"
|
||||||
|
class="empty-state"
|
||||||
|
:description="
|
||||||
|
props.tabKey == 'attachment' ? '暂无数据' : '暂无批复文件'
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, reactive, watch, computed } from 'vue';
|
||||||
|
import PdfView from '@/components/pdf/pdfView.vue';
|
||||||
|
import { queryPostUrlList, getIdUrl } from '@/api/mapModal';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
const hasLoaded = ref(false); // 标记是否已经请求过
|
||||||
|
|
||||||
|
interface FileItem {
|
||||||
|
id: string | number;
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
isActive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
showDownload: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
tabKey: {
|
||||||
|
type: String,
|
||||||
|
default: 'attachment'
|
||||||
|
},
|
||||||
|
activeKey: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const fileList = reactive<FileItem[]>([]);
|
||||||
|
const selectedFile = ref<FileItem | null>(null);
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
const selectFile = (file: FileItem) => {
|
||||||
|
selectedFile.value = file;
|
||||||
|
};
|
||||||
|
|
||||||
|
const downloadFile = (file: FileItem) => {
|
||||||
|
window.open(file.url, '_blank');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载 查看报告
|
||||||
|
const loadReportData = () => {
|
||||||
|
if (hasLoaded.value) return;
|
||||||
|
const params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: modelStore.params.stcd
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loading.value = true;
|
||||||
|
queryPostUrlList(props.url, params).then(res => {
|
||||||
|
const ids = res.data?.data?.map((item: any) => item) || [];
|
||||||
|
if (ids.length === 0) {
|
||||||
|
hasLoaded.value = true;
|
||||||
|
loading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getIdUrl({
|
||||||
|
id: ids.join(',')
|
||||||
|
}).then(res1 => {
|
||||||
|
const data = res1?.data || {};
|
||||||
|
const fileArray = Object.keys(data).map(key => ({
|
||||||
|
id: key,
|
||||||
|
name: data[key].name,
|
||||||
|
url: data[key].fullpath
|
||||||
|
}));
|
||||||
|
loading.value = false;
|
||||||
|
hasLoaded.value = true;
|
||||||
|
updateFileList(fileArray);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载批复文件
|
||||||
|
const loadApprovalData = () => {
|
||||||
|
if (hasLoaded.value) return;
|
||||||
|
const params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: modelStore.params.stcd
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loading.value = true;
|
||||||
|
queryPostUrlList(props.url, params).then(res => {
|
||||||
|
const data = res.data?.data || [];
|
||||||
|
const fileArray = data.map((item: any) => ({
|
||||||
|
id: item.flpath.split('?')[1] || '',
|
||||||
|
name: item.approvalFileNo || item.approvalFileTitle,
|
||||||
|
url: item.flpath || ''
|
||||||
|
}));
|
||||||
|
hasLoaded.value = true;
|
||||||
|
updateFileList(fileArray);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateFileList = (files: FileItem[]) => {
|
||||||
|
fileList.length = 0;
|
||||||
|
files.forEach(file => {
|
||||||
|
fileList.push(file);
|
||||||
|
});
|
||||||
|
if (files.length > 0) {
|
||||||
|
selectedFile.value = files[0];
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initReportList = () => {
|
||||||
|
const type = props.tabKey;
|
||||||
|
if (type === 'approval') {
|
||||||
|
loadApprovalData();
|
||||||
|
} else if (type === 'attachment') {
|
||||||
|
loadReportData();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.isActive,
|
||||||
|
active => {
|
||||||
|
if (active) {
|
||||||
|
initReportList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true } // 立即执行一次
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.attachment-container {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 600px;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 左侧文件列表 */
|
||||||
|
.file-list {
|
||||||
|
width: 200px;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-right: 1px solid #e4e7ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
border-bottom: 1px solid #e4e7ed;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #e8f4fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #005293;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name {
|
||||||
|
font-size: 13px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 右侧PDF预览 */
|
||||||
|
.pdf-preview {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.download-bar {
|
||||||
|
position: absolute;
|
||||||
|
right: 60px;
|
||||||
|
top: 4px;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,16 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-spin :spinning="loading">
|
<a-spin :spinning="loading">
|
||||||
<div class="modal-baseic-container" >
|
<div class="modal-baseic-container">
|
||||||
<a-row class="w-[100%]" v-if="!loading">
|
<a-row class="w-[100%]" v-if="!loading">
|
||||||
<a-col :span="!item.visible ? 24 : (item.filed == '' ? 12 : 12)" v-for="(item, index) in tabledata"
|
<a-col
|
||||||
:key="item.key">
|
:span="!item.visible ? 24 : item.filed == '' ? 12 : 12"
|
||||||
|
v-for="(item, index) in tabledata"
|
||||||
|
:key="item.key"
|
||||||
|
>
|
||||||
<div class="col-title" v-if="!item.visible">
|
<div class="col-title" v-if="!item.visible">
|
||||||
{{ item.name }}:
|
{{ item.name }}:
|
||||||
<div v-if="modelStore.isBasicEdit && index == 0">
|
<div v-if="modelStore.isBasicEdit && index == 0">
|
||||||
<a-button v-if="!isEdit" type="primary" size="small" class="mr-2" @click="editBtn">
|
<a-button
|
||||||
|
v-if="!isEdit"
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
class="mr-2"
|
||||||
|
@click="editBtn"
|
||||||
|
>
|
||||||
编辑信息
|
编辑信息
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button v-else type="primary" size="small" class="mr-2" @click="saveBtn">
|
<a-button
|
||||||
|
v-else
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
class="mr-2"
|
||||||
|
@click="saveBtn"
|
||||||
|
>
|
||||||
保存信息
|
保存信息
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="primary" size="small" @click="recordBtn">
|
<a-button type="primary" size="small" @click="recordBtn">
|
||||||
@ -19,23 +34,46 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-left" v-if="item.visible && item.filed != ''">
|
<div class="col-left" v-if="item.visible && item.filed != ''">
|
||||||
<a-tooltip :title="item.name.length > 10 ? item.name : ''">{{ item.name }}:
|
<a-tooltip :title="item.name.length > 10 ? item.name : ''"
|
||||||
|
>{{ item.name }}:
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-right" v-if="item.visible">
|
<div class="col-right" v-if="item.visible">
|
||||||
<a-tooltip :title="!isEdit ? item.vlsr : ''">
|
<a-tooltip :title="!isEdit ? item.vlsr : ''">
|
||||||
<span v-if="!isEdit && item.filed != ''">{{ item.value || "-" }}</span>
|
<span v-if="!isEdit && item.filed != ''">{{
|
||||||
|
item.value || '-'
|
||||||
|
}}</span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
<!-- {{ item.value }} -->
|
<!-- {{ item.value }} -->
|
||||||
<a-input v-if="item.type == 'input'" v-model:value="item.value" :placeholder="item.name"
|
<a-input
|
||||||
size="small"></a-input>
|
v-if="item.type == 'input'"
|
||||||
<a-select v-else-if="item.type == 'select'" v-model:value="item.value" :placeholder="item.name"
|
v-model:value="item.value"
|
||||||
size="small" class="w-full"></a-select>
|
:placeholder="item.name"
|
||||||
|
size="small"
|
||||||
|
></a-input>
|
||||||
|
<a-select
|
||||||
|
v-else-if="item.type == 'select'"
|
||||||
|
v-model:value="item.value"
|
||||||
|
:placeholder="item.name"
|
||||||
|
size="small"
|
||||||
|
class="w-full"
|
||||||
|
></a-select>
|
||||||
<!-- <div v-else-if="item.type == 'date'">{{ item.value }}2222</div> -->
|
<!-- <div v-else-if="item.type == 'date'">{{ item.value }}2222</div> -->
|
||||||
<a-date-picker v-else-if="item.type == 'date'" v-model:value="item.value" :format="item.format"
|
<a-date-picker
|
||||||
:value-format="item.format" :picker="item.picker" class="w-full"></a-date-picker>
|
v-else-if="item.type == 'date'"
|
||||||
<a-input-number v-else-if="item.type == 'number'" v-model:value="item.value" :placeholder="item.name"
|
v-model:value="item.value"
|
||||||
size="small" class="w-full"></a-input-number>
|
:format="item.format"
|
||||||
|
:value-format="item.format"
|
||||||
|
:picker="item.picker"
|
||||||
|
class="w-full"
|
||||||
|
></a-date-picker>
|
||||||
|
<a-input-number
|
||||||
|
v-else-if="item.type == 'number'"
|
||||||
|
v-model:value="item.value"
|
||||||
|
:placeholder="item.name"
|
||||||
|
size="small"
|
||||||
|
class="w-full"
|
||||||
|
></a-input-number>
|
||||||
</span>
|
</span>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
@ -43,13 +81,22 @@
|
|||||||
</a-row>
|
</a-row>
|
||||||
|
|
||||||
<div v-if="isLogo" class="img_box_one">
|
<div v-if="isLogo" class="img_box_one">
|
||||||
<div class="img_box">
|
<div class="img_box">
|
||||||
<div class="img_box_img">
|
<div class="img_box_img">
|
||||||
<a-image style="width: 100%;" :src="baseUrl + '/?' + data2.msStbprpT.logo" />
|
<a-image
|
||||||
</div>
|
style="width: 100%"
|
||||||
|
:src="baseUrl + '/?' + data2.msStbprpT.logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a-modal v-model:open="visible" title="修改记录" :footer="null" width="900px" :zIndex="2000">
|
<a-modal
|
||||||
|
v-model:open="visible"
|
||||||
|
title="修改记录"
|
||||||
|
:footer="null"
|
||||||
|
width="900px"
|
||||||
|
:zIndex="2000"
|
||||||
|
>
|
||||||
123
|
123
|
||||||
</a-modal>
|
</a-modal>
|
||||||
</div>
|
</div>
|
||||||
@ -57,104 +104,121 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import dayjs from "dayjs";
|
import dayjs from 'dayjs';
|
||||||
import { ref, onMounted, nextTick,watch } from "vue";
|
import { ref, onMounted, nextTick, watch, computed } from 'vue';
|
||||||
import { BasicColumns, wtPointColumns, FhWpPointColumns, FhPointColumns, FhZQPointColumns, FPPointColumns, FBPointColumns, VaPointColumns, VPPointColumns, wePointColumns, waPointColumns, DwPointColumns, DwFivePointColumns, DwSixPointColumns, DwOnePointColumns, StinfoAiVidoPointColumns } from "../column.config";
|
import {
|
||||||
import { useModelStore } from "@/store/modules/model";
|
BasicColumns,
|
||||||
import { getStcdDetail } from "@/api/mapModal"
|
wtPointColumns,
|
||||||
|
FhWpPointColumns,
|
||||||
|
FhPointColumns,
|
||||||
|
FhZQPointColumns,
|
||||||
|
FPPointColumns,
|
||||||
|
FBPointColumns,
|
||||||
|
VaPointColumns,
|
||||||
|
VPPointColumns,
|
||||||
|
wePointColumns,
|
||||||
|
waPointColumns,
|
||||||
|
DwPointColumns,
|
||||||
|
DwFivePointColumns,
|
||||||
|
DwSixPointColumns,
|
||||||
|
DwOnePointColumns,
|
||||||
|
StinfoAiVidoPointColumns
|
||||||
|
} from '../column.config';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import { getStcdDetail } from '@/api/mapModal';
|
||||||
|
|
||||||
const baseUrl = import.meta.env.VITE_APP_PREVIEW_URL;
|
const baseUrl = import.meta.env.VITE_APP_ATTACHMENT_URL;
|
||||||
const modelStore = useModelStore();
|
const modelStore = useModelStore();
|
||||||
|
const hasLoaded = ref(false); // 标记是否已经请求过
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
url: {
|
url: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: ''
|
||||||
},
|
},
|
||||||
|
isActive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
});
|
});
|
||||||
const visible = ref(false);
|
const visible = ref(false);
|
||||||
const isLogo = ref(false)
|
const isLogo = ref(false);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const data2: any = ref({})
|
const data2: any = ref({});
|
||||||
const tabledata = ref([]);
|
const tabledata = ref([]);
|
||||||
console.log(BasicColumns);
|
|
||||||
const columnsConfig = ref([
|
const columnsConfig = ref([
|
||||||
{
|
{
|
||||||
type: "ENG",
|
type: 'ENG',
|
||||||
columns: BasicColumns,
|
columns: BasicColumns
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "wt_point",
|
type: 'wt_point',
|
||||||
columns: wtPointColumns,
|
columns: wtPointColumns
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "fh_wq_point",
|
type: 'fh_wq_point',
|
||||||
columns: FhWpPointColumns,
|
columns: FhWpPointColumns
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "fh_point",
|
type: 'fh_point',
|
||||||
columns: FhPointColumns,
|
columns: FhPointColumns
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "fh_zq_point",
|
type: 'fh_zq_point',
|
||||||
columns: FhZQPointColumns,
|
columns: FhZQPointColumns
|
||||||
},//
|
}, //
|
||||||
{
|
{
|
||||||
type: "fp_point",
|
type: 'fp_point',
|
||||||
columns: FPPointColumns,
|
columns: FPPointColumns
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "fb_point",
|
type: 'fb_point',
|
||||||
columns: FBPointColumns,
|
columns: FBPointColumns
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "va_point",
|
type: 'va_point',
|
||||||
columns: VaPointColumns,
|
columns: VaPointColumns
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "vp_point",
|
type: 'vp_point',
|
||||||
columns: VPPointColumns,
|
columns: VPPointColumns
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "we_point",
|
type: 'we_point',
|
||||||
columns: wePointColumns,
|
columns: wePointColumns
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "wa_point",
|
type: 'wa_point',
|
||||||
columns: waPointColumns,
|
columns: waPointColumns
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "stinfo_ai_video_point",
|
type: 'stinfo_ai_video_point',
|
||||||
columns: StinfoAiVidoPointColumns,
|
columns: StinfoAiVidoPointColumns
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "eng_alarm_point",
|
type: 'eng_alarm_point',
|
||||||
columns: BasicColumns,
|
columns: BasicColumns
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "fprd_point",
|
type: 'fprd_point',
|
||||||
columns: StinfoAiVidoPointColumns,
|
columns: StinfoAiVidoPointColumns
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "dw_point",
|
type: 'dw_point',
|
||||||
columns: DwPointColumns,
|
columns: DwPointColumns
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "DW_5",
|
type: 'DW_5',
|
||||||
columns: DwFivePointColumns,
|
columns: DwFivePointColumns
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "DW_6",
|
type: 'DW_6',
|
||||||
columns: DwSixPointColumns,
|
columns: DwSixPointColumns
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "DW_1",
|
type: 'DW_1',
|
||||||
columns: DwOnePointColumns,
|
columns: DwOnePointColumns
|
||||||
},
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//DW_1
|
//DW_1
|
||||||
]);
|
]);
|
||||||
@ -162,51 +226,58 @@ const isEdit = ref(false);
|
|||||||
const editBtn = () => {
|
const editBtn = () => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
isEdit.value = true;
|
isEdit.value = true;
|
||||||
tabledata.value.forEach((item) => {
|
tabledata.value.forEach(item => {
|
||||||
if (item.type == "date") item.value = dayjs(item.value).format(item.format);
|
if (item.type == 'date')
|
||||||
|
item.value = dayjs(item.value).format(item.format);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const saveBtn = () => {
|
const saveBtn = () => {
|
||||||
isEdit.value = false;
|
isEdit.value = false;
|
||||||
console.log(tabledata.value);
|
|
||||||
};
|
};
|
||||||
const recordBtn = () => {
|
const recordBtn = () => {
|
||||||
visible.value = true;
|
visible.value = true;
|
||||||
};
|
};
|
||||||
const getData = async () => {
|
const getData = async () => {
|
||||||
|
if (hasLoaded.value) return;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
console.log(props.url);
|
|
||||||
console.log(modelStore.params.stcd);
|
|
||||||
if (props.url && modelStore.params.stcd) {
|
if (props.url && modelStore.params.stcd) {
|
||||||
let res: any = await getStcdDetail(props.url, modelStore.params.stcd)
|
let res: any = await getStcdDetail(props.url, modelStore.params.stcd);
|
||||||
console.log(res);
|
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
let data = res.data
|
let data = res.data;
|
||||||
data2.value = data
|
data2.value = data;
|
||||||
|
|
||||||
let columns = [];
|
let columns = [];
|
||||||
columns = columnsConfig.value.find((item) => item.type == modelStore.params.sttp)
|
columns = columnsConfig.value.find(
|
||||||
.columns;
|
item => item.type == modelStore.params.sttp
|
||||||
|
).columns;
|
||||||
|
|
||||||
columns.forEach((item) => {
|
columns.forEach(item => {
|
||||||
if (item.visible) {
|
if (item.visible) {
|
||||||
if (item.type == "date") {
|
if (item.type == 'date') {
|
||||||
if (data.msStbprpT[item.filed] && data.msStbprpT[item.filed] !== "-") {
|
if (
|
||||||
item.value = dayjs(data.msStbprpT[item.filed] || "").format(item.format);
|
data.msStbprpT[item.filed] &&
|
||||||
|
data.msStbprpT[item.filed] !== '-'
|
||||||
|
) {
|
||||||
|
item.value = dayjs(data.msStbprpT[item.filed] || '').format(
|
||||||
|
item.format
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
item.value = null;
|
item.value = null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (item.type == "number") {
|
if (item.type == 'number') {
|
||||||
if (data.msStbprpT[item.filed]) {
|
if (data.msStbprpT[item.filed]) {
|
||||||
if (item.filed == "ttpwr") {
|
if (item.filed == 'ttpwr') {
|
||||||
item.value =
|
item.value =
|
||||||
(Number(data.msStbprpT[item.filed] || 0) / 10).toFixed(item.toFixed) ||
|
(Number(data.msStbprpT[item.filed] || 0) / 10).toFixed(
|
||||||
null;
|
item.toFixed
|
||||||
|
) || null;
|
||||||
} else {
|
} else {
|
||||||
item.value =
|
item.value =
|
||||||
Number(data.msStbprpT[item.filed] || 0).toFixed(item.toFixed) || null;
|
Number(data.msStbprpT[item.filed] || 0).toFixed(
|
||||||
|
item.toFixed
|
||||||
|
) || null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
item.value = null;
|
item.value = null;
|
||||||
@ -215,59 +286,54 @@ const getData = async () => {
|
|||||||
item.value = data.msStbprpT[item.filed] || null;
|
item.value = data.msStbprpT[item.filed] || null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let stcdItem = data.stcdValVoList.find((stcdItem) => {
|
let stcdItem = data.stcdValVoList.find(stcdItem => {
|
||||||
let str = "";
|
let str = '';
|
||||||
let arr = [
|
let arr = [
|
||||||
"baseName",
|
'baseName',
|
||||||
"addvcdName",
|
'addvcdName',
|
||||||
"hbrvcdName",
|
'hbrvcdName',
|
||||||
"bldsttCcodeName",
|
'bldsttCcodeName',
|
||||||
"dvtpName",
|
'dvtpName',
|
||||||
"rgcpName",
|
'rgcpName'
|
||||||
];
|
];
|
||||||
if (arr.includes(item.filed)) {
|
if (arr.includes(item.filed)) {
|
||||||
// 删除name后缀
|
// 删除name后缀
|
||||||
str = item.filed.replace("Name", "");
|
str = item.filed.replace('Name', '');
|
||||||
} else {
|
} else {
|
||||||
str = item.filed;
|
str = item.filed;
|
||||||
}
|
}
|
||||||
return stcdItem.attrKey == str;
|
return stcdItem.attrKey == str;
|
||||||
});
|
});
|
||||||
if (stcdItem && stcdItem.attrKey == "rgcp") {
|
if (stcdItem && stcdItem.attrKey == 'rgcp') {
|
||||||
console.log(stcdItem);
|
|
||||||
}
|
}
|
||||||
if (stcdItem && stcdItem.vlsr) {
|
if (stcdItem && stcdItem.vlsr) {
|
||||||
item.vlsr = stcdItem.vlsr;
|
item.vlsr = stcdItem.vlsr;
|
||||||
}
|
}
|
||||||
// console.log(stcdItem);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
tabledata.value = columns;
|
tabledata.value = columns;
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
hasLoaded.value = true;
|
||||||
} else {
|
} else {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
if (data2.value.msStbprpT.logo) {
|
if (data2.value.msStbprpT.logo) {
|
||||||
isLogo.value = true
|
isLogo.value = true;
|
||||||
} else {
|
} else {
|
||||||
isLogo.value = false
|
isLogo.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
watch(
|
||||||
|
() => props.isActive,
|
||||||
onMounted(() => {
|
active => {
|
||||||
getData()
|
if (active && !hasLoaded.value) {
|
||||||
|
getData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true } // 立即执行一次
|
||||||
|
);
|
||||||
setTimeout(() => {
|
|
||||||
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
//监听modelStore.params.stcd数据变化,刷新数据
|
//监听modelStore.params.stcd数据变化,刷新数据
|
||||||
watch(
|
watch(
|
||||||
() => modelStore.params.stcd,
|
() => modelStore.params.stcd,
|
||||||
@ -288,7 +354,7 @@ watch(
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
.img_box_one{
|
.img_box_one {
|
||||||
width: 40%;
|
width: 40%;
|
||||||
// height: 80%;
|
// height: 80%;
|
||||||
}
|
}
|
||||||
@ -306,20 +372,12 @@ watch(
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border: 1px solid rgba(0, 127, 204, 0.65);
|
border: 1px solid rgba(0, 127, 204, 0.65);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-row {
|
.ant-row {
|
||||||
border-left: 1px solid rgba(0, 127, 204, 0.65);
|
border-left: 1px solid rgba(0, 127, 204, 0.65);
|
||||||
border-top: 1px solid rgba(0, 127, 204, 0.65);
|
border-top: 1px solid rgba(0, 127, 204, 0.65);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
.ant-col:nth-last-child(-n + 2) {
|
|
||||||
|
|
||||||
.col-left,
|
|
||||||
.col-right {
|
|
||||||
// border-bottom: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-col {
|
.ant-col {
|
||||||
|
|||||||
@ -0,0 +1,201 @@
|
|||||||
|
<template>
|
||||||
|
<div class="early-warning-alert">
|
||||||
|
<a-tabs v-model:activeKey="activeTabKey" @change="handleTabChange">
|
||||||
|
<a-tab-pane v-for="tab in tabsItems" :key="tab.key" :tab="tab.name">
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
|
:columns="currentColumns"
|
||||||
|
:list-url="fetchTableData"
|
||||||
|
:search-params="searchParams"
|
||||||
|
:enable-sort="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, watch } from 'vue';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import { queryPostUrlList } from '@/api/mapModal';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
tabsItems: Array<{
|
||||||
|
name: string;
|
||||||
|
key: string;
|
||||||
|
type: string;
|
||||||
|
hiddenChart: boolean;
|
||||||
|
tableUrl: string;
|
||||||
|
}>;
|
||||||
|
isActive: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
const activeTabKey = ref(props.tabsItems?.[0]?.key || '');
|
||||||
|
const hasLoaded = ref(false);
|
||||||
|
const tableRef = ref();
|
||||||
|
|
||||||
|
// 搜索参数
|
||||||
|
const searchParams = computed(() => ({
|
||||||
|
...modelStore.params
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 不同 tab 的表头配置
|
||||||
|
const columnsMap: Record<string, any[]> = {
|
||||||
|
DesignParameterChangePrompt: [
|
||||||
|
{
|
||||||
|
title: '重大变动类型',
|
||||||
|
dataIndex: 'majorChangeType',
|
||||||
|
key: 'majorChangeType'
|
||||||
|
},
|
||||||
|
{ title: '依据阶段', dataIndex: 'basisStage', key: 'basisStage' },
|
||||||
|
{
|
||||||
|
title: '有重大变动的阶段',
|
||||||
|
dataIndex: 'changedStage',
|
||||||
|
key: 'changedStage'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '依据阶段数据',
|
||||||
|
dataIndex: 'basisStageData',
|
||||||
|
key: 'basisStageData'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '重大变动阶段数据',
|
||||||
|
dataIndex: 'changedStageData',
|
||||||
|
key: 'changedStageData'
|
||||||
|
},
|
||||||
|
{ title: '预警描述', dataIndex: 'warningDesc', key: 'warningDesc' }
|
||||||
|
],
|
||||||
|
ImplementEarlyWarning: [
|
||||||
|
{ title: '设施名称', dataIndex: 'stnm', key: 'stnm' },
|
||||||
|
{ title: '行政区', dataIndex: 'addvnm', key: 'addvnm' },
|
||||||
|
{ title: '流域', dataIndex: 'rvnm', key: 'rvnm' },
|
||||||
|
{
|
||||||
|
title: '可研(核准)',
|
||||||
|
dataIndex: 'feasibilityApproval',
|
||||||
|
key: 'feasibilityApproval'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '预可是否设计',
|
||||||
|
dataIndex: 'preFeasibilityDesign',
|
||||||
|
key: 'preFeasibilityDesign'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '环评是否有要求',
|
||||||
|
dataIndex: 'eiarqst',
|
||||||
|
key: 'eiarqst'
|
||||||
|
},
|
||||||
|
{ title: '专项设计', dataIndex: 'specialDesign', key: 'specialDesign' },
|
||||||
|
{ title: '建设', dataIndex: 'isbuild', key: 'isbuild' },
|
||||||
|
{ title: '证明材料', dataIndex: 'proofMaterial', key: 'proofMaterial' }
|
||||||
|
],
|
||||||
|
ConstructionEarlyWarning: [
|
||||||
|
{ title: '设施名称', dataIndex: 'facilityName', key: 'facilityName' },
|
||||||
|
{ title: '行政区', dataIndex: 'district', key: 'district' },
|
||||||
|
{ title: '流域', dataIndex: 'basin', key: 'basin' },
|
||||||
|
{
|
||||||
|
title: '可研(核准)',
|
||||||
|
dataIndex: 'feasibilityApproval',
|
||||||
|
key: 'feasibilityApproval'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '预可是否设计',
|
||||||
|
dataIndex: 'preFeasibilityDesign',
|
||||||
|
key: 'preFeasibilityDesign'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '环评是否有要求',
|
||||||
|
dataIndex: 'eiaRequirement',
|
||||||
|
key: 'eiaRequirement'
|
||||||
|
},
|
||||||
|
{ title: '专项设计', dataIndex: 'specialDesign', key: 'specialDesign' },
|
||||||
|
{ title: '建设', dataIndex: 'construction', key: 'construction' },
|
||||||
|
{ title: '证明材料', dataIndex: 'proofMaterial', key: 'proofMaterial' }
|
||||||
|
],
|
||||||
|
RunEarlyWarning: [
|
||||||
|
{ title: '设施名称', dataIndex: 'stnm', key: 'stnm' },
|
||||||
|
{ title: '所属行政区域', dataIndex: 'addvnm', key: 'addvnm' },
|
||||||
|
{ title: '运行状态', dataIndex: 'status', key: 'status' }
|
||||||
|
],
|
||||||
|
ReleaseEarlyWarning: [
|
||||||
|
{ title: '时间', dataIndex: 'releaseTime', key: 'releaseTime' },
|
||||||
|
{ title: '鱼类', dataIndex: 'fishType', key: 'fishType' },
|
||||||
|
{ title: '放鱼规格(cm)', dataIndex: 'fishSpec', key: 'fishSpec' },
|
||||||
|
{ title: '放鱼数量(尾)', dataIndex: 'fishCount', key: 'fishCount' },
|
||||||
|
{ title: '放流地点', dataIndex: 'releaseLocation', key: 'releaseLocation' },
|
||||||
|
{ title: '所在河流', dataIndex: 'river', key: 'river' },
|
||||||
|
{ title: '鱼龄(年)', dataIndex: 'fishAge', key: 'fishAge' },
|
||||||
|
{ title: '标记方式', dataIndex: 'markMethod', key: 'markMethod' },
|
||||||
|
{ title: '标记种类', dataIndex: 'markType', key: 'markType' },
|
||||||
|
{ title: '标记数量(尾)', dataIndex: 'markCount', key: 'markCount' },
|
||||||
|
{ title: '运行状态', dataIndex: 'runStatus', key: 'runStatus' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 当前 tab 的列配置
|
||||||
|
const currentColumns = computed(() => {
|
||||||
|
return columnsMap[activeTabKey.value] || [];
|
||||||
|
});
|
||||||
|
|
||||||
|
// 当前 tab 的接口地址
|
||||||
|
const currentTableUrl = computed(() => {
|
||||||
|
const tab = props.tabsItems.find(item => item.key === activeTabKey.value);
|
||||||
|
return tab?.tableUrl || '';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取表格数据
|
||||||
|
const fetchTableData = async (params: any) => {
|
||||||
|
if (!currentTableUrl.value) {
|
||||||
|
return { data: { records: [], total: 0 } };
|
||||||
|
}
|
||||||
|
return await queryPostUrlList(currentTableUrl.value, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// tab 切换
|
||||||
|
const handleTabChange = (key: string) => {
|
||||||
|
activeTabKey.value = key;
|
||||||
|
// 切换 tab 时刷新表格数据
|
||||||
|
if (tableRef.value) {
|
||||||
|
tableRef.value.refresh();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听 isActive 变化,首次激活时请求数据
|
||||||
|
watch(
|
||||||
|
() => props.isActive,
|
||||||
|
newVal => {
|
||||||
|
if (newVal && !hasLoaded.value) {
|
||||||
|
hasLoaded.value = true;
|
||||||
|
// 首次加载当前 tab 数据
|
||||||
|
setTimeout(() => {
|
||||||
|
if (tableRef.value) {
|
||||||
|
// tableRef.value.getList();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听 tabsItems 变化,初始化 activeTabKey
|
||||||
|
watch(
|
||||||
|
() => props.tabsItems,
|
||||||
|
newTabs => {
|
||||||
|
if (newTabs && newTabs.length > 0 && !activeTabKey.value) {
|
||||||
|
activeTabKey.value = newTabs[0].key;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.early-warning-alert {
|
||||||
|
.table-wrapper {
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
609
frontend/src/components/MapModal/components/MonitorInfo.vue
Normal file
609
frontend/src/components/MapModal/components/MonitorInfo.vue
Normal file
@ -0,0 +1,609 @@
|
|||||||
|
<template>
|
||||||
|
<div class="monitor-info">
|
||||||
|
<!-- Tabs 区域 -->
|
||||||
|
<a-tabs v-model:activeKey="activeTabKey">
|
||||||
|
<a-tab-pane v-for="tab in tabsList" :key="tab.key" :tab="tab.name" />
|
||||||
|
<template #rightExtra>
|
||||||
|
<div class="search-bar">
|
||||||
|
<a-range-picker
|
||||||
|
v-model:value="dateRange"
|
||||||
|
:format="dateFormat"
|
||||||
|
:show-time="showTime ? showTimeConfig : false"
|
||||||
|
:allowClear="false"
|
||||||
|
:disabled-date="disabledDate"
|
||||||
|
>
|
||||||
|
</a-range-picker>
|
||||||
|
<a-button type="primary" class="search-btn" @click="handleSearch">
|
||||||
|
查询
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-tabs>
|
||||||
|
|
||||||
|
<!-- 内容区域 -->
|
||||||
|
<div v-if="activeTabKey === 'dzxq.tabs.jcsj'" class="tab-content">
|
||||||
|
<a-spin :spinning="isLoading" tip="加载中...">
|
||||||
|
<!-- 左侧图表 + 右侧表格 -->
|
||||||
|
<div class="content-body">
|
||||||
|
<div class="chart-wrapper" ref="chartRef"></div>
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
|
:scrollY="480"
|
||||||
|
:scrollX="980"
|
||||||
|
:columns="currentColumns"
|
||||||
|
:data="tableData"
|
||||||
|
:isPage="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch, computed, onMounted, onUnmounted } from 'vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
|
import { getMonitorData } from '@/api/mapModal';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
isActive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasLoaded = ref(false);
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const activeTabKey = ref('dzxq.tabs.jcsj');
|
||||||
|
const chartRef = ref<HTMLDivElement | null>(null);
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
const tableRef = ref<any>(null);
|
||||||
|
const tableData = ref<any[]>([]);
|
||||||
|
|
||||||
|
// ==================== Tab 配置 ====================
|
||||||
|
const tabsConfig: Record<string, any[]> = {
|
||||||
|
'dzxq.tabs.jcsj': [{ name: '电站运行过程线', key: 'dzxq.tabs.jcsj' }]
|
||||||
|
};
|
||||||
|
|
||||||
|
const tabsList = computed(() => {
|
||||||
|
return tabsConfig[props.code] || tabsConfig['dzxq.tabs.jcsj'];
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 时间选择器配置 ====================
|
||||||
|
const timeConfig: Record<string, { showTime: boolean; format: string }> = {
|
||||||
|
'dzxq.tabs.jcsj': { showTime: true, format: 'YYYY-MM-DD HH:mm' }
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentTimeConfig = computed(() => {
|
||||||
|
return timeConfig[props.code] || timeConfig['dzxq.tabs.jcsj'];
|
||||||
|
});
|
||||||
|
|
||||||
|
const showTime = computed(() => currentTimeConfig.value.showTime);
|
||||||
|
const dateFormat = computed(() => currentTimeConfig.value.format);
|
||||||
|
|
||||||
|
// 时分秒配置(不选秒)
|
||||||
|
const showTimeConfig = {
|
||||||
|
format: 'HH:mm',
|
||||||
|
hourStep: 1,
|
||||||
|
minuteStep: 5,
|
||||||
|
secondStep: 60
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化日期范围:当前日期到前一个月
|
||||||
|
const initDateRange = (): [Dayjs, Dayjs] => {
|
||||||
|
const endDate = dayjs();
|
||||||
|
const startDate = dayjs().subtract(1, 'month');
|
||||||
|
return [startDate, endDate];
|
||||||
|
};
|
||||||
|
|
||||||
|
const dateRange = ref<[Dayjs, Dayjs] | undefined>(initDateRange());
|
||||||
|
|
||||||
|
// 禁用超过当前日期的选项
|
||||||
|
const disabledDate = (current: Dayjs) => {
|
||||||
|
return current && current.isAfter(dayjs(), 'day');
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 表格配置 ====================
|
||||||
|
const tableColumnsConfig: Record<string, any[]> = {
|
||||||
|
'dzxq.tabs.jcsj': [
|
||||||
|
{
|
||||||
|
title: '时间',
|
||||||
|
dataIndex: 'tm',
|
||||||
|
width: 180,
|
||||||
|
fixed: 'left',
|
||||||
|
customRender: ({ text }: any) =>
|
||||||
|
text ? dayjs(text).format('YYYY-MM-DD HH:mm:ss') : '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '坝上水位(m)',
|
||||||
|
dataIndex: 'rz',
|
||||||
|
width: 130,
|
||||||
|
customRender: ({ text }: any) =>
|
||||||
|
text !== undefined && text !== null ? Number(text).toFixed(2) : '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '坝下水位(m)',
|
||||||
|
dataIndex: 'dz',
|
||||||
|
width: 130,
|
||||||
|
customRender: ({ text }: any) =>
|
||||||
|
text !== undefined && text !== null ? Number(text).toFixed(2) : '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '入库流量(m³/s)',
|
||||||
|
dataIndex: 'qi',
|
||||||
|
width: 130,
|
||||||
|
customRender: ({ text }: any) =>
|
||||||
|
text !== undefined && text !== null ? Math.round(Number(text)) : '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '出库流量(m³/s)',
|
||||||
|
dataIndex: 'qo',
|
||||||
|
width: 130,
|
||||||
|
customRender: ({ text }: any) =>
|
||||||
|
text !== undefined && text !== null ? Number(text) : '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '生态流量(m³/s)',
|
||||||
|
dataIndex: 'qec',
|
||||||
|
width: 130,
|
||||||
|
customRender: ({ text }: any) =>
|
||||||
|
text !== undefined && text !== null ? Number(text).toFixed(1) : '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '生态流量限值(m³/s)',
|
||||||
|
dataIndex: 'qecLimit',
|
||||||
|
width: 150,
|
||||||
|
customRender: ({ text }: any) =>
|
||||||
|
text !== undefined && text !== null ? Number(text).toFixed(1) : '-'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentColumns = computed(() => {
|
||||||
|
return tableColumnsConfig[props.code] || tableColumnsConfig['dzxq.tabs.jcsj'];
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 图表图例配置 ====================
|
||||||
|
const chartLegendConfig: Record<string, string[]> = {
|
||||||
|
'dzxq.tabs.jcsj': [
|
||||||
|
'坝上水位',
|
||||||
|
'坝下水位',
|
||||||
|
'入库流量',
|
||||||
|
'出库流量',
|
||||||
|
'生态流量',
|
||||||
|
'生态流量限值'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const chartLegendData = computed(() => {
|
||||||
|
return chartLegendConfig[props.code] || chartLegendConfig['dzxq.tabs.jcsj'];
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 数据请求 ====================
|
||||||
|
const fetchData = async () => {
|
||||||
|
if (!dateRange.value) return;
|
||||||
|
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: modelStore.params.stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dateRange.value[0].format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dateRange.value[1].format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sort: [
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
dir: 'asc'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await getMonitorData(params);
|
||||||
|
const rawData = res?.data?.data || res?.data?.records || [];
|
||||||
|
|
||||||
|
// 数据按时间倒序显示(最新的在前,1月1号最后)
|
||||||
|
const reversedData = [...rawData].reverse();
|
||||||
|
tableData.value = reversedData;
|
||||||
|
updateChart(rawData); // 图表仍按升序渲染
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取数据失败:', error);
|
||||||
|
tableData.value = [];
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查询按钮点击事件
|
||||||
|
const handleSearch = () => {
|
||||||
|
fetchData();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 图表相关 ====================
|
||||||
|
const updateChart = (data: any[]) => {
|
||||||
|
if (!chartInstance) return;
|
||||||
|
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
chartInstance.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按时间升序
|
||||||
|
const sorted = [...data].sort(
|
||||||
|
(a, b) => new Date(a.tm).getTime() - new Date(b.tm).getTime()
|
||||||
|
);
|
||||||
|
|
||||||
|
const xAxisData = sorted.map(item => dayjs(item.tm).format('HH:mm'));
|
||||||
|
const rzData = sorted.map(item => item.rz);
|
||||||
|
const dzData = sorted.map(item => item.dz);
|
||||||
|
const qiData = sorted.map(item => item.qi);
|
||||||
|
const qoData = sorted.map(item => item.qo);
|
||||||
|
const qecData = sorted.map(item => item.qec);
|
||||||
|
const qecLimitData = sorted.map(item => item.qecLimit);
|
||||||
|
|
||||||
|
// 计算水位 Y 轴最大值(左侧)
|
||||||
|
const waterValues = [...rzData, ...dzData].filter(
|
||||||
|
v => v !== null && v !== undefined
|
||||||
|
);
|
||||||
|
const waterMax = waterValues.length > 0 ? Math.max(...waterValues) : 10;
|
||||||
|
const waterYMax = Math.ceil(waterMax * 1.1);
|
||||||
|
const waterYMin = Math.floor(Math.min(...waterValues) * 0.95);
|
||||||
|
|
||||||
|
// 计算流量 Y 轴最大值(右侧)
|
||||||
|
const flowValues = [...qiData, ...qoData, ...qecData, ...qecLimitData].filter(
|
||||||
|
v => v !== null && v !== undefined
|
||||||
|
);
|
||||||
|
const flowMax = flowValues.length > 0 ? Math.max(...flowValues) : 10;
|
||||||
|
const flowYMax = Math.ceil(flowMax * 1.1);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
backgroundColor: 'rgba(50, 50, 50, 0.9)',
|
||||||
|
textStyle: { color: '#fff', fontSize: 12 },
|
||||||
|
axisPointer: { type: 'cross' },
|
||||||
|
formatter: (params: any) => {
|
||||||
|
if (!params || params.length === 0) return '';
|
||||||
|
const dataIndex = params[0].dataIndex;
|
||||||
|
const fullTime = sorted[dataIndex]?.tm
|
||||||
|
? dayjs(sorted[dataIndex].tm).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
: '';
|
||||||
|
let html = `<div style="font-size:14px;margin-bottom:8px;">${fullTime}</div>`;
|
||||||
|
params.forEach((param: any) => {
|
||||||
|
const value = param.value != null ? param.value : '-';
|
||||||
|
html += `
|
||||||
|
<div style="display:flex;align-items:center;margin:4px 0;">
|
||||||
|
<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${param.color};margin-right:8px;"></span>
|
||||||
|
<span style="font-size:14px;">${param.seriesName}: ${value}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
type: 'scroll',
|
||||||
|
top: 10,
|
||||||
|
data: chartLegendData.value,
|
||||||
|
textStyle: { fontSize: 12 },
|
||||||
|
selected: {
|
||||||
|
坝上水位: false,
|
||||||
|
坝下水位: false,
|
||||||
|
入库流量: true,
|
||||||
|
出库流量: true,
|
||||||
|
生态流量: false,
|
||||||
|
生态流量限值: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 60,
|
||||||
|
right: 60,
|
||||||
|
top: 80,
|
||||||
|
bottom: 60
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: xAxisData,
|
||||||
|
axisLine: { lineStyle: { color: '#000000' } },
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
fontSize: 12
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: '#bfbfbf', type: 'solid' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: [
|
||||||
|
// 左侧 Y 轴:水位
|
||||||
|
{
|
||||||
|
name: '水位',
|
||||||
|
type: 'value',
|
||||||
|
position: 'left',
|
||||||
|
axisLine: { lineStyle: { color: '#000000' } },
|
||||||
|
alignTicks: true,
|
||||||
|
scale: true,
|
||||||
|
splitNumber: 9,
|
||||||
|
boundaryGap: ['10%', 0]
|
||||||
|
},
|
||||||
|
// 右侧 Y 轴:流量
|
||||||
|
{
|
||||||
|
name: '流量',
|
||||||
|
type: 'value',
|
||||||
|
position: 'right',
|
||||||
|
axisLine: { lineStyle: { color: '#000000' } },
|
||||||
|
alignTicks: true,
|
||||||
|
scale: true,
|
||||||
|
splitNumber: 9,
|
||||||
|
boundaryGap: ['10%', 0]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
// 水位系列(使用左侧 Y 轴)
|
||||||
|
{
|
||||||
|
name: '坝上水位',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
data: rzData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#56C2E3', width: 2 },
|
||||||
|
itemStyle: { color: '#56C2E3' },
|
||||||
|
markPoint: {
|
||||||
|
data: [
|
||||||
|
{ type: 'max', name: 'Max' },
|
||||||
|
{ type: 'min', name: 'Min' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '坝下水位',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
data: dzData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#7399C6', width: 2 },
|
||||||
|
itemStyle: { color: '#7399C6' },
|
||||||
|
markPoint: {
|
||||||
|
data: [
|
||||||
|
{ type: 'max', name: 'Max' },
|
||||||
|
{ type: 'min', name: 'Min' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 流量系列(使用右侧 Y 轴)
|
||||||
|
{
|
||||||
|
name: '入库流量',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
data: qiData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#4B79AB', width: 2 },
|
||||||
|
itemStyle: { color: '#4B79AB' },
|
||||||
|
markPoint: {
|
||||||
|
data: [
|
||||||
|
{ type: 'max', name: 'Max' },
|
||||||
|
{ type: 'min', name: 'Min' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '出库流量',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
data: qoData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#78C300', width: 2 },
|
||||||
|
itemStyle: { color: '#78C300' },
|
||||||
|
markPoint: {
|
||||||
|
data: [
|
||||||
|
{ type: 'max', name: 'Max' },
|
||||||
|
{ type: 'min', name: 'Min' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '生态流量',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
data: qecData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#00A050', width: 2 },
|
||||||
|
itemStyle: { color: '#00A050' },
|
||||||
|
markPoint: {
|
||||||
|
data: [
|
||||||
|
{ type: 'max', name: 'Max' },
|
||||||
|
{ type: 'min', name: 'Min' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '生态流量限值',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
data: qecLimitData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#F7A737', width: 2, type: 'dashed' },
|
||||||
|
itemStyle: { color: '#F7A737' },
|
||||||
|
markPoint: {
|
||||||
|
data: [
|
||||||
|
{ type: 'max', name: 'Max' },
|
||||||
|
{ type: 'min', name: 'Min' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
dataZoom: [
|
||||||
|
{
|
||||||
|
type: 'inside',
|
||||||
|
xAxisIndex: [0],
|
||||||
|
throttle: 50,
|
||||||
|
start: 0,
|
||||||
|
end: 100
|
||||||
|
}
|
||||||
|
],
|
||||||
|
toolbox: {
|
||||||
|
show: true,
|
||||||
|
feature: {
|
||||||
|
saveAsImage: { title: '保存为图片', type: 'png', pixelRatio: 2 }
|
||||||
|
},
|
||||||
|
right: 20,
|
||||||
|
top: 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
chartInstance.setOption(option, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartRef.value) return;
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
}
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.resize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const destroyChart = () => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
chartInstance = null;
|
||||||
|
}
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 初始化 ====================
|
||||||
|
const initData = () => {
|
||||||
|
if (hasLoaded.value) return;
|
||||||
|
fetchData();
|
||||||
|
setTimeout(() => {
|
||||||
|
initChart();
|
||||||
|
}, 200);
|
||||||
|
hasLoaded.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.isActive,
|
||||||
|
active => {
|
||||||
|
if (active && !hasLoaded.value) {
|
||||||
|
initData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => modelStore.params.stcd,
|
||||||
|
(newStcd, oldStcd) => {
|
||||||
|
if (newStcd && newStcd !== oldStcd) {
|
||||||
|
console.log('【MonitorInfo】stcd 变化,重新加载数据:', newStcd);
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
destroyChart();
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.monitor-info {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 600px;
|
||||||
|
:deep(.ant-tabs-nav) {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.search-btn {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.content-body {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
height: 600px;
|
||||||
|
|
||||||
|
.chart-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 500px;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 10px;
|
||||||
|
overflow-x: auto;
|
||||||
|
border-left: 1px solid #dcdfe6;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
140
frontend/src/components/MapModal/components/PanoramaInfo.vue
Normal file
140
frontend/src/components/MapModal/components/PanoramaInfo.vue
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
<template>
|
||||||
|
<div class="panorama-info-view-container">
|
||||||
|
<a-spin :spinning="loading">
|
||||||
|
<iframe
|
||||||
|
v-show="iframeUrl != ''"
|
||||||
|
:src="iframeUrl"
|
||||||
|
class="panorama-info-iframe"
|
||||||
|
frameborder="0"
|
||||||
|
></iframe>
|
||||||
|
<a-select
|
||||||
|
v-show="iframeUrl != ''"
|
||||||
|
class="panorama-info-select"
|
||||||
|
v-model:value="selectedOption"
|
||||||
|
:options="options"
|
||||||
|
:fieldNames="{
|
||||||
|
label: 'name',
|
||||||
|
value: 'id'
|
||||||
|
}"
|
||||||
|
placeholder="请选择文件"
|
||||||
|
@change="handleSelectChange"
|
||||||
|
/>
|
||||||
|
<a-empty
|
||||||
|
v-show="!loading && iframeUrl == ''"
|
||||||
|
class="empty-state"
|
||||||
|
description="暂无数据"
|
||||||
|
/>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import { getPanoramaList, getIdUrl } from '@/api/mapModal';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
|
||||||
|
const hasLoaded = ref(false); // 标记是否已经请求过
|
||||||
|
const loading = ref(true); // 标记是否正在加载中
|
||||||
|
const props = defineProps({
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
isActive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const selectedOption = ref('');
|
||||||
|
const options = ref([]);
|
||||||
|
const iframeUrl = ref('');
|
||||||
|
const handleSelectChange = value => {
|
||||||
|
options.value.forEach(item => {
|
||||||
|
if (item.id === value) {
|
||||||
|
iframeUrl.value = item.newUrl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const initPanoramaInfoList = () => {
|
||||||
|
if (hasLoaded.value) return;
|
||||||
|
const params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: modelStore.params.stcd
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
select: ['url', 'stnm']
|
||||||
|
};
|
||||||
|
loading.value = true;
|
||||||
|
getPanoramaList(params).then(res => {
|
||||||
|
if (res.data.data[0].url) {
|
||||||
|
getIdUrl({
|
||||||
|
id: res.data.data[0].url
|
||||||
|
}).then(res1 => {
|
||||||
|
hasLoaded.value = true; // 标记为已请求过
|
||||||
|
loading.value = false;
|
||||||
|
const data = res1?.data || {};
|
||||||
|
const fileArray = Object.keys(data).map(key => ({
|
||||||
|
id: key,
|
||||||
|
name: data[key].name,
|
||||||
|
oldUrl: data[key].fullpath,
|
||||||
|
newUrl:
|
||||||
|
import.meta.env.VITE_APP_ATTACHMENT_URL +
|
||||||
|
'/Pano/Show/' +
|
||||||
|
data[key].fullpath.split('?')[1]
|
||||||
|
}));
|
||||||
|
options.value = fileArray;
|
||||||
|
iframeUrl.value = options.value[0].newUrl;
|
||||||
|
selectedOption.value = options.value[0].id;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
hasLoaded.value = true; // 标记为已请求过
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
watch(
|
||||||
|
() => props.isActive,
|
||||||
|
active => {
|
||||||
|
if (active && !hasLoaded.value) {
|
||||||
|
initPanoramaInfoList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true } // 立即执行一次
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.panorama-info-view-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
.panorama-info-select {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panorama-info-iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
height: 600px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,12 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
监测数据
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { ref, watch } from "vue";
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
||||||
@ -1,682 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="body_one">
|
|
||||||
<div class="search_one">
|
|
||||||
<div class="search_left">
|
|
||||||
<div>鱼类选择:</div>
|
|
||||||
<a-select v-model:value="selectValue" mode="multiple" style="width: 260px;" placeholder=" "
|
|
||||||
:options="selectOption" @change="handleChange"></a-select>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a-range-picker v-model:value="rangePicker" format="YYYY-MM-DD" :disabled-date="disabledDate"
|
|
||||||
:allow-clear="false" @change="handleDateChange">
|
|
||||||
<template #renderExtraFooter>
|
|
||||||
<div class="quick-date-options">
|
|
||||||
<a-button size="small" @click="setQuickRange(7)">最近七天</a-button>
|
|
||||||
<a-button size="small" @click="setQuickRange(30)">最近一个月</a-button>
|
|
||||||
<a-button size="small" @click="setQuickRange(90)">最近三个月</a-button>
|
|
||||||
<a-button size="small" @click="setQuickRange(180)">最近六个月</a-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</a-range-picker>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="body_body">
|
|
||||||
<div class="echarts" ref="chartRef"></div>
|
|
||||||
<div class="table">
|
|
||||||
<BasicTable ref="tableRef" :scrollY="460" :columns="columns" :list-url="fishGetKendoListCust"
|
|
||||||
:search-params="{}" :transform-data="customTransform">
|
|
||||||
<!-- 同期对比列的自定义渲染 -->
|
|
||||||
<template #contrast="{ record }">
|
|
||||||
<span v-if="record.wt && record.beforeWt"
|
|
||||||
:style="{ color: (record.wt - record.beforeWt) > 0 ? 'rgb(255, 85, 0)' : 'rgb(135, 208, 104)' }">
|
|
||||||
{{ (record.wt - record.beforeWt) > 0 ? '+' : '' }}{{ (record.wt -
|
|
||||||
record.beforeWt).toFixed(2) }}
|
|
||||||
</span>
|
|
||||||
<span v-else>-</span>
|
|
||||||
</template>
|
|
||||||
</BasicTable>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { ref, watch, onMounted, onUnmounted, computed } from "vue";
|
|
||||||
import * as echarts from 'echarts';
|
|
||||||
import dayjs, { Dayjs } from 'dayjs';
|
|
||||||
import { useModelStore } from "@/store/modules/model";
|
|
||||||
import { infoGetKendoListCust, fishGetKendoListCust } from "@/api/sw";
|
|
||||||
import BasicTable from "@/components/BasicTable/index.vue";
|
|
||||||
const modelStore = useModelStore();
|
|
||||||
const selectOption = ref([])
|
|
||||||
const selectValue = ref([])
|
|
||||||
const tableRef = ref<any>(null);
|
|
||||||
const chartRef = ref<HTMLDivElement | null>(null);
|
|
||||||
let chartInstance: echarts.ECharts | null = null;
|
|
||||||
const chartData = ref<any[]>([]);
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
title: '时间',
|
|
||||||
dataIndex: 'dt',
|
|
||||||
width: 180,
|
|
||||||
customRender: ({ text }: any) => text ? dayjs(text).format('YYYY-MM-DD') : '-'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '水温°C',
|
|
||||||
dataIndex: 'wt',
|
|
||||||
customRender: ({ text }: any) => text !== undefined && text !== null ? text : '-'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '上一年水温',
|
|
||||||
dataIndex: 'beforeWt',
|
|
||||||
customRender: ({ text }: any) => text !== undefined && text !== null ? text : '-'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '同期对比',
|
|
||||||
dataIndex: 'contrast',
|
|
||||||
width: 120,
|
|
||||||
slots: { customRender: 'contrast' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '适宜产卵鱼',
|
|
||||||
dataIndex: 'spawnFish',
|
|
||||||
width: 200,
|
|
||||||
customRender: ({ record }: any) => {
|
|
||||||
if (record.fishList && Array.isArray(record.fishList) && record.fishList.length > 0) {
|
|
||||||
return record.fishList.map(fish => fish.name).join('、');
|
|
||||||
}
|
|
||||||
return '-';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
];
|
|
||||||
// 初始化日期范围:7天前到当天
|
|
||||||
const initDateRange = () => {
|
|
||||||
const endDate = dayjs();
|
|
||||||
const startDate = dayjs().subtract(7, 'day');
|
|
||||||
return [startDate, endDate] as [Dayjs, Dayjs];
|
|
||||||
};
|
|
||||||
|
|
||||||
const rangePicker = ref<[Dayjs, Dayjs] | undefined>(initDateRange());
|
|
||||||
// 禁用超过当前日期的选项
|
|
||||||
const disabledDate = (current: Dayjs) => {
|
|
||||||
// 不能选择今天之后的日期
|
|
||||||
return current && current.isAfter(dayjs(), 'day');
|
|
||||||
};
|
|
||||||
|
|
||||||
// 设置快捷日期范围
|
|
||||||
const setQuickRange = (days: number) => {
|
|
||||||
const endDate = dayjs();
|
|
||||||
const startDate = dayjs().subtract(days, 'day');
|
|
||||||
rangePicker.value = [startDate, endDate];
|
|
||||||
// 手动触发变更事件
|
|
||||||
handleDateChange(rangePicker.value, [
|
|
||||||
startDate.format('YYYY-MM-DD'),
|
|
||||||
endDate.format('YYYY-MM-DD')
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 日期改变时的处理方法(内容待定)
|
|
||||||
const handleDateChange = (dates: any, dateStrings: string[]) => {
|
|
||||||
console.log('日期改变:', dates, dateStrings);
|
|
||||||
getEchartsData()
|
|
||||||
gerTableData()
|
|
||||||
// TODO: 在此处添加日期改变后的业务逻辑
|
|
||||||
};
|
|
||||||
// 下拉框数据
|
|
||||||
const getSelectOption = async () => {
|
|
||||||
//
|
|
||||||
// modelStore.params.date
|
|
||||||
let params = {
|
|
||||||
"filter": {
|
|
||||||
"logic": "and",
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"field": "stcd",
|
|
||||||
"operator": "eq",
|
|
||||||
"dataType": "string",
|
|
||||||
"value": modelStore.params.stcd
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let res = await infoGetKendoListCust(params)
|
|
||||||
let data = res?.data?.data || res?.data
|
|
||||||
console.log(res)
|
|
||||||
selectOption.value = data.map(item => {
|
|
||||||
return {
|
|
||||||
label: item.name,
|
|
||||||
value: item.id,
|
|
||||||
pretemp: item.pretemp,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// 根据数据长度智能选择ID:2个及以上取前2个,少于2个取第1个
|
|
||||||
selectValue.value = data.length >= 2 ? data.slice(0, 2).map(item => item.id) : (data.length > 0 ? [data[0].id] : [])
|
|
||||||
}
|
|
||||||
//echarts 数据获取
|
|
||||||
const getEchartsData = async () => {
|
|
||||||
const params = {
|
|
||||||
"filter": {
|
|
||||||
"logic": "and",
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"field": "stcd",
|
|
||||||
"operator": "eq",
|
|
||||||
"dataType": "string",
|
|
||||||
"value": modelStore.params.stcd
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"field": "startTime",
|
|
||||||
"operator": "gte",
|
|
||||||
"dataType": "date",
|
|
||||||
"value": rangePicker.value[0].format('YYYY-MM-DD') + " 00:00:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"field": "endTime",
|
|
||||||
"operator": "lte",
|
|
||||||
"dataType": "date",
|
|
||||||
"value": rangePicker.value[1].format('YYYY-MM-DD') + " 23:59:59"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"sort": [
|
|
||||||
{
|
|
||||||
"field": "dt",
|
|
||||||
"dir": "desc"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
let res = await fishGetKendoListCust(params)
|
|
||||||
console.log('ECharts原始数据:', res)
|
|
||||||
|
|
||||||
// 存储原始数据到响应式变量
|
|
||||||
const rawData = res?.data?.data || res?.data?.records || []
|
|
||||||
chartData.value = rawData
|
|
||||||
|
|
||||||
// 延迟更新图表,确保DOM已渲染
|
|
||||||
setTimeout(() => {
|
|
||||||
updateChart()
|
|
||||||
}, 100)
|
|
||||||
}
|
|
||||||
//table 数据获取
|
|
||||||
const gerTableData = async () => {
|
|
||||||
const filter = {
|
|
||||||
logic: 'and',
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"field": "stcd",
|
|
||||||
"operator": "eq",
|
|
||||||
"dataType": "string",
|
|
||||||
"value": modelStore.params.stcd
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"field": "startTime",
|
|
||||||
"operator": "gte",
|
|
||||||
"dataType": "date",
|
|
||||||
"value": rangePicker.value[0].format('YYYY-MM-DD') + " 00:00:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"field": "endTime",
|
|
||||||
"operator": "lte",
|
|
||||||
"dataType": "date",
|
|
||||||
"value": rangePicker.value[1].format('YYYY-MM-DD') + " 23:59:59"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
tableRef.value?.getList(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleChange = (value: any) => {
|
|
||||||
console.log(value)
|
|
||||||
getEchartsData()
|
|
||||||
// gerTableData()
|
|
||||||
}
|
|
||||||
const customTransform = (res: any) => {
|
|
||||||
console.log('表格数据:', res);
|
|
||||||
return {
|
|
||||||
records: res?.data?.data || [],
|
|
||||||
total: res?.data?.total || 0
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// 过滤和处理图表数据
|
|
||||||
const processChartData = (rawData: any[]) => {
|
|
||||||
if (!rawData || !Array.isArray(rawData)) return [];
|
|
||||||
|
|
||||||
// 过滤掉 wt 或 beforeWt 为 null 的记录
|
|
||||||
const filtered = rawData.filter(item =>
|
|
||||||
item.wt !== null && item.wt !== undefined
|
|
||||||
);
|
|
||||||
// && item.beforeWt !== null && item.beforeWt !== undefined
|
|
||||||
|
|
||||||
// 按 dt 升序排序
|
|
||||||
return filtered.sort((a, b) =>
|
|
||||||
new Date(a.dt).getTime() - new Date(b.dt).getTime()
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 计算 Y 轴最大值(向上取整到个位)
|
|
||||||
const calculateYAxisMax = (data: any[]) => {
|
|
||||||
if (!data || data.length === 0) return 10;
|
|
||||||
|
|
||||||
const allValues = [
|
|
||||||
...data.map(item => item.wt),
|
|
||||||
...data.map(item => item.beforeWt)
|
|
||||||
].filter(v => v !== null && v !== undefined);
|
|
||||||
|
|
||||||
const max = Math.max(...allValues);
|
|
||||||
return Math.ceil(max);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 生成随机颜色(HSL转HEX)
|
|
||||||
const generateRandomColor = () => {
|
|
||||||
const hue = Math.random() * 360; // 色相:0-360°
|
|
||||||
const saturation = 40 + Math.random() * 30; // 饱和度:40%-70%
|
|
||||||
const lightness = 45 + Math.random() * 20; // 亮度:45%-65%
|
|
||||||
|
|
||||||
// HSL转RGB
|
|
||||||
const c = (1 - Math.abs(2 * lightness / 100 - 1)) * saturation / 100;
|
|
||||||
const x = c * (1 - Math.abs((hue / 60) % 2 - 1));
|
|
||||||
const m = lightness / 100 - c / 2;
|
|
||||||
|
|
||||||
let r = 0, g = 0, b = 0;
|
|
||||||
|
|
||||||
if (hue < 60) { r = c; g = x; b = 0; }
|
|
||||||
else if (hue < 120) { r = x; g = c; b = 0; }
|
|
||||||
else if (hue < 180) { r = 0; g = c; b = x; }
|
|
||||||
else if (hue < 240) { r = 0; g = x; b = c; }
|
|
||||||
else if (hue < 300) { r = x; g = 0; b = c; }
|
|
||||||
else { r = c; g = 0; b = x; }
|
|
||||||
|
|
||||||
// RGB转HEX
|
|
||||||
const toHex = (value: number) => {
|
|
||||||
const hex = Math.round((value + m) * 255).toString(16);
|
|
||||||
return hex.length === 1 ? '0' + hex : hex;
|
|
||||||
};
|
|
||||||
|
|
||||||
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 计算选中鱼类的温度范围数组
|
|
||||||
const calculateTempRanges = () => {
|
|
||||||
if (!selectValue.value || selectValue.value.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从 selectOption 中找到选中的鱼类
|
|
||||||
const selectedFish = selectOption.value.filter(fish =>
|
|
||||||
selectValue.value.includes(fish.value)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (selectedFish.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为每个鱼类生成温度范围和随机颜色
|
|
||||||
return selectedFish
|
|
||||||
.map(fish => {
|
|
||||||
if (!fish.pretemp || !Array.isArray(fish.pretemp) || fish.pretemp.length !== 2) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const min = parseFloat(fish.pretemp[0]);
|
|
||||||
const max = parseFloat(fish.pretemp[1]);
|
|
||||||
|
|
||||||
if (isNaN(min) || isNaN(max)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: fish.label,
|
|
||||||
min,
|
|
||||||
max,
|
|
||||||
color: generateRandomColor()
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter(item => item !== null);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 生成 ECharts 配置
|
|
||||||
const getChartOption = computed(() => {
|
|
||||||
const data = processChartData(chartData.value);
|
|
||||||
|
|
||||||
if (data.length === 0) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// const yAxisMax = calculateYAxisMax(data);
|
|
||||||
const tempRanges = calculateTempRanges();
|
|
||||||
const dataMax = calculateYAxisMax(data);
|
|
||||||
const tempMax = tempRanges.length > 0
|
|
||||||
? Math.max(...tempRanges.map(r => r.max))
|
|
||||||
: 0;
|
|
||||||
const yAxisMax = Math.max(dataMax, tempMax);
|
|
||||||
// 提取时间轴和数据
|
|
||||||
const xAxisData = data.map(item => dayjs(item.dt).format('YYYY-MM-DD'));
|
|
||||||
const wtData = data.map(item => item.wt);
|
|
||||||
const beforeWtData = data.map(item => item.beforeWt);
|
|
||||||
|
|
||||||
// 构建 markArea 配置(为每个鱼类生成独立的背景区域)
|
|
||||||
const markAreaConfig = tempRanges.length > 0 ? {
|
|
||||||
silent: true,
|
|
||||||
data: tempRanges.map(range => [
|
|
||||||
{
|
|
||||||
yAxis: range.min,
|
|
||||||
name: `${range.name}: ${range.min}-${range.max}°C`,
|
|
||||||
itemStyle: { color: `${range.color}26` }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
yAxis: range.max
|
|
||||||
}
|
|
||||||
])
|
|
||||||
} : undefined;
|
|
||||||
|
|
||||||
return {
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis',
|
|
||||||
backgroundColor: 'rgba(50, 50, 50, 0.9)',
|
|
||||||
textStyle: {
|
|
||||||
color: '#fff',
|
|
||||||
fontSize: 14
|
|
||||||
},
|
|
||||||
axisPointer: {
|
|
||||||
type: 'cross',
|
|
||||||
label: {
|
|
||||||
backgroundColor: '#6a7985'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
formatter: (params: any) => {
|
|
||||||
if (!params || params.length === 0) return '';
|
|
||||||
|
|
||||||
const date = params[0].axisValue;
|
|
||||||
let html = `<div style="font-weight:bold;margin-bottom:8px;">${date}</div>`;
|
|
||||||
|
|
||||||
params.forEach((param: any) => {
|
|
||||||
const dataIndex = param.dataIndex;
|
|
||||||
const record = data[dataIndex];
|
|
||||||
const value = param.value != null ? param.value : '-';
|
|
||||||
|
|
||||||
html += `
|
|
||||||
<div style="display:flex;align-items:center;margin:4px 0;">
|
|
||||||
<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${param.color};margin-right:8px;"></span>
|
|
||||||
<span>${param.seriesName}: ${value}°C</span>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
top: 10,
|
|
||||||
left: 'center',
|
|
||||||
data: ['现在水温', '上一年同期水温'],
|
|
||||||
textStyle: {
|
|
||||||
fontSize: 13
|
|
||||||
}
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
left: 60,
|
|
||||||
right: 40,
|
|
||||||
top: 60,
|
|
||||||
bottom: 50
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
type: 'category',
|
|
||||||
data: xAxisData,
|
|
||||||
boundaryGap: false,
|
|
||||||
axisLine: {
|
|
||||||
lineStyle: {
|
|
||||||
color: '#000000'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
axisTick: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
axisLabel: {
|
|
||||||
interval: (() => {
|
|
||||||
const dataLen = xAxisData.length;
|
|
||||||
if (dataLen <= 8) return 0; // 数据少时全部显示
|
|
||||||
|
|
||||||
// 计算最大允许的间隔,确保首尾都能显示
|
|
||||||
// 需要显示的首尾2个 + 中间最多6个 = 总共8个标签
|
|
||||||
const maxLabels = 8;
|
|
||||||
const minInterval = Math.ceil((dataLen - 1) / (maxLabels - 1));
|
|
||||||
|
|
||||||
// 如果原有计算的间隔更大,使用原有逻辑;否则使用最小间隔
|
|
||||||
const calculatedInterval = Math.floor((dataLen - 1) / 7);
|
|
||||||
return Math.max(calculatedInterval, minInterval);
|
|
||||||
})(),
|
|
||||||
fontSize: 12,
|
|
||||||
rotate: 0,
|
|
||||||
margin: 10
|
|
||||||
},
|
|
||||||
splitLine: {
|
|
||||||
show: true,
|
|
||||||
lineStyle: {
|
|
||||||
color: '#bfbfbf',
|
|
||||||
type: 'solid'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
type: 'value',
|
|
||||||
name: '水温(°C)',
|
|
||||||
min: 0,
|
|
||||||
max: yAxisMax,
|
|
||||||
scale: false,
|
|
||||||
axisLine: {
|
|
||||||
lineStyle: {
|
|
||||||
color: '#000000',
|
|
||||||
width: 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
axisTick: {
|
|
||||||
show: true,
|
|
||||||
length: 4
|
|
||||||
},
|
|
||||||
axisLabel: {
|
|
||||||
fontSize: 12,
|
|
||||||
formatter: '{value}'
|
|
||||||
},
|
|
||||||
splitLine: {
|
|
||||||
show: true,
|
|
||||||
lineStyle: {
|
|
||||||
color: '#bfbfbf',
|
|
||||||
type: 'solid'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: '现在水温',
|
|
||||||
type: 'line',
|
|
||||||
data: wtData,
|
|
||||||
smooth: true,
|
|
||||||
symbol: 'circle',
|
|
||||||
symbolSize: 6,
|
|
||||||
lineStyle: { color: '#009dff', width: 2 },
|
|
||||||
itemStyle: { color: '#009dff' },
|
|
||||||
connectNulls: false,
|
|
||||||
// 移除 markArea
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '上一年同期水温',
|
|
||||||
type: 'line',
|
|
||||||
data: beforeWtData,
|
|
||||||
smooth: true,
|
|
||||||
symbol: 'circle',
|
|
||||||
symbolSize: 6,
|
|
||||||
lineStyle: { color: '#fee588', width: 2 },
|
|
||||||
itemStyle: { color: '#fee588' },
|
|
||||||
connectNulls: false
|
|
||||||
},
|
|
||||||
// 新增辅助系列,专门承载 markArea
|
|
||||||
{
|
|
||||||
name: '温度区间背景', // 名字不会被放入 legend,所以不显示
|
|
||||||
type: 'line',
|
|
||||||
data: new Array(xAxisData.length).fill(0), // 假数据,保证坐标匹配
|
|
||||||
showSymbol: false,
|
|
||||||
symbol: 'none',
|
|
||||||
lineStyle: { opacity: 0 },
|
|
||||||
itemStyle: { opacity: 0 },
|
|
||||||
legendHoverLink: false, // 图例悬停时不联动
|
|
||||||
silent: true, // 不响应鼠标事件
|
|
||||||
markArea: markAreaConfig // 温度区间背景绑定于此
|
|
||||||
}
|
|
||||||
],
|
|
||||||
dataZoom: [
|
|
||||||
{
|
|
||||||
type: 'inside',
|
|
||||||
xAxisIndex: [0],
|
|
||||||
throttle: 50,
|
|
||||||
start: 0,
|
|
||||||
end: 100
|
|
||||||
}
|
|
||||||
],
|
|
||||||
toolbox: {
|
|
||||||
show: true,
|
|
||||||
feature: {
|
|
||||||
saveAsImage: {
|
|
||||||
title: '导出图片',
|
|
||||||
type: 'png',
|
|
||||||
pixelRatio: 2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
right: 20,
|
|
||||||
top: 10
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// 初始化图表
|
|
||||||
const initChart = () => {
|
|
||||||
if (!chartRef.value) return;
|
|
||||||
|
|
||||||
if (chartInstance) {
|
|
||||||
chartInstance.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
chartInstance = echarts.init(chartRef.value);
|
|
||||||
updateChart();
|
|
||||||
|
|
||||||
// 监听窗口resize
|
|
||||||
window.addEventListener('resize', handleResize);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 更新图表
|
|
||||||
const updateChart = () => {
|
|
||||||
if (!chartInstance) return;
|
|
||||||
|
|
||||||
const option = getChartOption.value;
|
|
||||||
if (Object.keys(option).length > 0) {
|
|
||||||
chartInstance.setOption(option, true);
|
|
||||||
} else {
|
|
||||||
// 如果没有数据,清空图表或显示空状态
|
|
||||||
chartInstance.clear();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处理窗口resize
|
|
||||||
const handleResize = () => {
|
|
||||||
if (chartInstance) {
|
|
||||||
chartInstance.resize();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 销毁图表
|
|
||||||
const destroyChart = () => {
|
|
||||||
if (chartInstance) {
|
|
||||||
chartInstance.dispose();
|
|
||||||
chartInstance = null;
|
|
||||||
}
|
|
||||||
window.removeEventListener('resize', handleResize);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
getSelectOption()
|
|
||||||
getEchartsData()
|
|
||||||
gerTableData()
|
|
||||||
// 初始化图表
|
|
||||||
setTimeout(() => {
|
|
||||||
initChart()
|
|
||||||
}, 200)
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
destroyChart()
|
|
||||||
});
|
|
||||||
watch(
|
|
||||||
() => modelStore.params.stcd,
|
|
||||||
(newStcd, oldStcd) => {
|
|
||||||
// 避免初始化时的无效触发
|
|
||||||
if (newStcd && newStcd !== oldStcd) {
|
|
||||||
console.log('【BasicInfo】stcd 变化,重新加载数据:', newStcd);
|
|
||||||
getSelectOption()
|
|
||||||
getEchartsData()
|
|
||||||
gerTableData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.body_one {
|
|
||||||
width: 100%;
|
|
||||||
min-height: 600px;
|
|
||||||
|
|
||||||
.search_one {
|
|
||||||
width: 100%;
|
|
||||||
height: 32px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
.search_left {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.body_body {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
height: 540px;
|
|
||||||
|
|
||||||
.echarts {
|
|
||||||
flex: 1;
|
|
||||||
height: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table {
|
|
||||||
flex: 1;
|
|
||||||
height: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.quick-date-options {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 8px 0;
|
|
||||||
|
|
||||||
.ant-btn {
|
|
||||||
border-color: #1890ff !important;
|
|
||||||
color: #1890ff !important;
|
|
||||||
background-color: #e6f7ff !important;
|
|
||||||
font-size: 12px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: #40a9ff !important;
|
|
||||||
color: #40a9ff !important;
|
|
||||||
background-color: #bae7ff !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -0,0 +1,779 @@
|
|||||||
|
<template>
|
||||||
|
<div class="body_one">
|
||||||
|
<div class="search_one">
|
||||||
|
<div class="search_left">
|
||||||
|
<div>鱼类选择:</div>
|
||||||
|
<a-select
|
||||||
|
v-model:value="selectValue"
|
||||||
|
mode="multiple"
|
||||||
|
style="width: 260px"
|
||||||
|
placeholder=" "
|
||||||
|
:options="selectOption"
|
||||||
|
@change="handleChange"
|
||||||
|
></a-select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a-range-picker
|
||||||
|
v-model:value="rangePicker"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
:disabled-date="disabledDate"
|
||||||
|
:allow-clear="false"
|
||||||
|
@change="handleDateChange"
|
||||||
|
>
|
||||||
|
<template #renderExtraFooter>
|
||||||
|
<div class="quick-date-options">
|
||||||
|
<a-button size="small" @click="setQuickRange(7)"
|
||||||
|
>最近七天</a-button
|
||||||
|
>
|
||||||
|
<a-button size="small" @click="setQuickRange(30)"
|
||||||
|
>最近一个月</a-button
|
||||||
|
>
|
||||||
|
<a-button size="small" @click="setQuickRange(90)"
|
||||||
|
>最近三个月</a-button
|
||||||
|
>
|
||||||
|
<a-button size="small" @click="setQuickRange(180)"
|
||||||
|
>最近六个月</a-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-range-picker>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="body_body">
|
||||||
|
<div class="echarts" ref="chartRef"></div>
|
||||||
|
<div class="table">
|
||||||
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
|
:scrollY="460"
|
||||||
|
:columns="columns"
|
||||||
|
:list-url="fishGetKendoListCust"
|
||||||
|
:search-params="{}"
|
||||||
|
:transform-data="customTransform"
|
||||||
|
>
|
||||||
|
<!-- 同期对比列的自定义渲染 -->
|
||||||
|
<template #contrast="{ record }">
|
||||||
|
<span
|
||||||
|
v-if="record.wt && record.beforeWt"
|
||||||
|
:style="{
|
||||||
|
color:
|
||||||
|
record.wt - record.beforeWt > 0
|
||||||
|
? 'rgb(255, 85, 0)'
|
||||||
|
: 'rgb(135, 208, 104)'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ record.wt - record.beforeWt > 0 ? '+' : ''
|
||||||
|
}}{{ (record.wt - record.beforeWt).toFixed(2) }}
|
||||||
|
</span>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch, onMounted, onUnmounted, computed } from 'vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import { infoGetKendoListCust, fishGetKendoListCust } from '@/api/sw';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
const selectOption = ref([]);
|
||||||
|
const selectValue = ref([]);
|
||||||
|
const tableRef = ref<any>(null);
|
||||||
|
const chartRef = ref<HTMLDivElement | null>(null);
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
const chartData = ref<any[]>([]);
|
||||||
|
|
||||||
|
const hasLoaded = ref(false); // 标记是否已经请求过
|
||||||
|
const props = defineProps({
|
||||||
|
isActive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '时间',
|
||||||
|
dataIndex: 'dt',
|
||||||
|
width: 180,
|
||||||
|
customRender: ({ text }: any) =>
|
||||||
|
text ? dayjs(text).format('YYYY-MM-DD') : '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '水温°C',
|
||||||
|
dataIndex: 'wt',
|
||||||
|
customRender: ({ text }: any) =>
|
||||||
|
text !== undefined && text !== null ? text : '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '上一年水温',
|
||||||
|
dataIndex: 'beforeWt',
|
||||||
|
customRender: ({ text }: any) =>
|
||||||
|
text !== undefined && text !== null ? text : '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '同期对比',
|
||||||
|
dataIndex: 'contrast',
|
||||||
|
width: 120,
|
||||||
|
slots: { customRender: 'contrast' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '适宜产卵鱼',
|
||||||
|
dataIndex: 'spawnFish',
|
||||||
|
width: 200,
|
||||||
|
customRender: ({ record }: any) => {
|
||||||
|
if (
|
||||||
|
record.fishList &&
|
||||||
|
Array.isArray(record.fishList) &&
|
||||||
|
record.fishList.length > 0
|
||||||
|
) {
|
||||||
|
return record.fishList.map(fish => fish.name).join('、');
|
||||||
|
}
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
// 初始化日期范围:7天前到当天
|
||||||
|
const initDateRange = () => {
|
||||||
|
const endDate = dayjs();
|
||||||
|
const startDate = dayjs().subtract(7, 'day');
|
||||||
|
return [startDate, endDate] as [Dayjs, Dayjs];
|
||||||
|
};
|
||||||
|
|
||||||
|
const rangePicker = ref<[Dayjs, Dayjs] | undefined>(initDateRange());
|
||||||
|
// 禁用超过当前日期的选项
|
||||||
|
const disabledDate = (current: Dayjs) => {
|
||||||
|
// 不能选择今天之后的日期
|
||||||
|
return current && current.isAfter(dayjs(), 'day');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设置快捷日期范围
|
||||||
|
const setQuickRange = (days: number) => {
|
||||||
|
const endDate = dayjs();
|
||||||
|
const startDate = dayjs().subtract(days, 'day');
|
||||||
|
rangePicker.value = [startDate, endDate];
|
||||||
|
// 手动触发变更事件
|
||||||
|
handleDateChange(rangePicker.value, [
|
||||||
|
startDate.format('YYYY-MM-DD'),
|
||||||
|
endDate.format('YYYY-MM-DD')
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 日期改变时的处理方法(内容待定)
|
||||||
|
const handleDateChange = (dates: any, dateStrings: string[]) => {
|
||||||
|
console.log('日期改变:', dates, dateStrings);
|
||||||
|
getEchartsData();
|
||||||
|
gerTableData();
|
||||||
|
// TODO: 在此处添加日期改变后的业务逻辑
|
||||||
|
};
|
||||||
|
// 下拉框数据
|
||||||
|
const getSelectOption = async () => {
|
||||||
|
//
|
||||||
|
// modelStore.params.date
|
||||||
|
let params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: modelStore.params.stcd
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let res = await infoGetKendoListCust(params);
|
||||||
|
let data = res?.data?.data || res?.data;
|
||||||
|
console.log(res);
|
||||||
|
selectOption.value = data.map(item => {
|
||||||
|
return {
|
||||||
|
label: item.name,
|
||||||
|
value: item.id,
|
||||||
|
pretemp: item.pretemp
|
||||||
|
};
|
||||||
|
});
|
||||||
|
// 根据数据长度智能选择ID:2个及以上取前2个,少于2个取第1个
|
||||||
|
selectValue.value =
|
||||||
|
data.length >= 2
|
||||||
|
? data.slice(0, 2).map(item => item.id)
|
||||||
|
: data.length > 0
|
||||||
|
? [data[0].id]
|
||||||
|
: [];
|
||||||
|
};
|
||||||
|
//echarts 数据获取
|
||||||
|
const getEchartsData = async () => {
|
||||||
|
const params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: modelStore.params.stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'startTime',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: rangePicker.value[0].format('YYYY-MM-DD') + ' 00:00:00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'endTime',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: rangePicker.value[1].format('YYYY-MM-DD') + ' 23:59:59'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sort: [
|
||||||
|
{
|
||||||
|
field: 'dt',
|
||||||
|
dir: 'desc'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
let res = await fishGetKendoListCust(params);
|
||||||
|
console.log('ECharts原始数据:', res);
|
||||||
|
|
||||||
|
// 存储原始数据到响应式变量
|
||||||
|
const rawData = res?.data?.data || res?.data?.records || [];
|
||||||
|
chartData.value = rawData;
|
||||||
|
|
||||||
|
// 延迟更新图表,确保DOM已渲染
|
||||||
|
setTimeout(() => {
|
||||||
|
updateChart();
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
//table 数据获取
|
||||||
|
const gerTableData = async () => {
|
||||||
|
const filter = {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: modelStore.params.stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'startTime',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: rangePicker.value[0].format('YYYY-MM-DD') + ' 00:00:00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'endTime',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: rangePicker.value[1].format('YYYY-MM-DD') + ' 23:59:59'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
tableRef.value?.getList(filter);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (value: any) => {
|
||||||
|
console.log(value);
|
||||||
|
getEchartsData();
|
||||||
|
// gerTableData()
|
||||||
|
};
|
||||||
|
const customTransform = (res: any) => {
|
||||||
|
console.log('表格数据:', res);
|
||||||
|
return {
|
||||||
|
records: res?.data?.data || [],
|
||||||
|
total: res?.data?.total || 0
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 过滤和处理图表数据
|
||||||
|
const processChartData = (rawData: any[]) => {
|
||||||
|
if (!rawData || !Array.isArray(rawData)) return [];
|
||||||
|
|
||||||
|
// 过滤掉 wt 或 beforeWt 为 null 的记录
|
||||||
|
const filtered = rawData.filter(
|
||||||
|
item => item.wt !== null && item.wt !== undefined
|
||||||
|
);
|
||||||
|
// && item.beforeWt !== null && item.beforeWt !== undefined
|
||||||
|
|
||||||
|
// 按 dt 升序排序
|
||||||
|
return filtered.sort(
|
||||||
|
(a, b) => new Date(a.dt).getTime() - new Date(b.dt).getTime()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 计算 Y 轴最大值(向上取整到个位)
|
||||||
|
const calculateYAxisMax = (data: any[]) => {
|
||||||
|
if (!data || data.length === 0) return 10;
|
||||||
|
|
||||||
|
const allValues = [
|
||||||
|
...data.map(item => item.wt),
|
||||||
|
...data.map(item => item.beforeWt)
|
||||||
|
].filter(v => v !== null && v !== undefined);
|
||||||
|
|
||||||
|
const max = Math.max(...allValues);
|
||||||
|
return Math.ceil(max);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生成随机颜色(HSL转HEX)
|
||||||
|
const generateRandomColor = () => {
|
||||||
|
const hue = Math.random() * 360; // 色相:0-360°
|
||||||
|
const saturation = 40 + Math.random() * 30; // 饱和度:40%-70%
|
||||||
|
const lightness = 45 + Math.random() * 20; // 亮度:45%-65%
|
||||||
|
|
||||||
|
// HSL转RGB
|
||||||
|
const c = ((1 - Math.abs((2 * lightness) / 100 - 1)) * saturation) / 100;
|
||||||
|
const x = c * (1 - Math.abs(((hue / 60) % 2) - 1));
|
||||||
|
const m = lightness / 100 - c / 2;
|
||||||
|
|
||||||
|
let r = 0,
|
||||||
|
g = 0,
|
||||||
|
b = 0;
|
||||||
|
|
||||||
|
if (hue < 60) {
|
||||||
|
r = c;
|
||||||
|
g = x;
|
||||||
|
b = 0;
|
||||||
|
} else if (hue < 120) {
|
||||||
|
r = x;
|
||||||
|
g = c;
|
||||||
|
b = 0;
|
||||||
|
} else if (hue < 180) {
|
||||||
|
r = 0;
|
||||||
|
g = c;
|
||||||
|
b = x;
|
||||||
|
} else if (hue < 240) {
|
||||||
|
r = 0;
|
||||||
|
g = x;
|
||||||
|
b = c;
|
||||||
|
} else if (hue < 300) {
|
||||||
|
r = x;
|
||||||
|
g = 0;
|
||||||
|
b = c;
|
||||||
|
} else {
|
||||||
|
r = c;
|
||||||
|
g = 0;
|
||||||
|
b = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RGB转HEX
|
||||||
|
const toHex = (value: number) => {
|
||||||
|
const hex = Math.round((value + m) * 255).toString(16);
|
||||||
|
return hex.length === 1 ? '0' + hex : hex;
|
||||||
|
};
|
||||||
|
|
||||||
|
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 计算选中鱼类的温度范围数组
|
||||||
|
const calculateTempRanges = () => {
|
||||||
|
if (!selectValue.value || selectValue.value.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 selectOption 中找到选中的鱼类
|
||||||
|
const selectedFish = selectOption.value.filter(fish =>
|
||||||
|
selectValue.value.includes(fish.value)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (selectedFish.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为每个鱼类生成温度范围和随机颜色
|
||||||
|
return selectedFish
|
||||||
|
.map(fish => {
|
||||||
|
if (
|
||||||
|
!fish.pretemp ||
|
||||||
|
!Array.isArray(fish.pretemp) ||
|
||||||
|
fish.pretemp.length !== 2
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const min = parseFloat(fish.pretemp[0]);
|
||||||
|
const max = parseFloat(fish.pretemp[1]);
|
||||||
|
|
||||||
|
if (isNaN(min) || isNaN(max)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: fish.label,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
color: generateRandomColor()
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(item => item !== null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生成 ECharts 配置
|
||||||
|
const getChartOption = computed(() => {
|
||||||
|
const data = processChartData(chartData.value);
|
||||||
|
|
||||||
|
if (data.length === 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// const yAxisMax = calculateYAxisMax(data);
|
||||||
|
const tempRanges = calculateTempRanges();
|
||||||
|
const dataMax = calculateYAxisMax(data);
|
||||||
|
const tempMax =
|
||||||
|
tempRanges.length > 0 ? Math.max(...tempRanges.map(r => r.max)) : 0;
|
||||||
|
const yAxisMax = Math.max(dataMax, tempMax);
|
||||||
|
// 提取时间轴和数据
|
||||||
|
const xAxisData = data.map(item => dayjs(item.dt).format('YYYY-MM-DD'));
|
||||||
|
const wtData = data.map(item => item.wt);
|
||||||
|
const beforeWtData = data.map(item => item.beforeWt);
|
||||||
|
|
||||||
|
// 构建 markArea 配置(为每个鱼类生成独立的背景区域)
|
||||||
|
const markAreaConfig =
|
||||||
|
tempRanges.length > 0
|
||||||
|
? {
|
||||||
|
silent: true,
|
||||||
|
data: tempRanges.map(range => [
|
||||||
|
{
|
||||||
|
yAxis: range.min,
|
||||||
|
name: `${range.name}: ${range.min}-${range.max}°C`,
|
||||||
|
itemStyle: { color: `${range.color}26` }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
yAxis: range.max
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
backgroundColor: 'rgba(50, 50, 50, 0.9)',
|
||||||
|
textStyle: {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 14
|
||||||
|
},
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatter: (params: any) => {
|
||||||
|
if (!params || params.length === 0) return '';
|
||||||
|
|
||||||
|
const date = params[0].axisValue;
|
||||||
|
let html = `<div style="font-weight:bold;margin-bottom:8px;">${date}</div>`;
|
||||||
|
|
||||||
|
params.forEach((param: any) => {
|
||||||
|
const dataIndex = param.dataIndex;
|
||||||
|
const record = data[dataIndex];
|
||||||
|
const value = param.value != null ? param.value : '-';
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<div style="display:flex;align-items:center;margin:4px 0;">
|
||||||
|
<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${param.color};margin-right:8px;"></span>
|
||||||
|
<span>${param.seriesName}: ${value}°C</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
top: 10,
|
||||||
|
left: 'center',
|
||||||
|
data: ['现在水温', '上一年同期水温'],
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 13
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 60,
|
||||||
|
right: 40,
|
||||||
|
top: 60,
|
||||||
|
bottom: 50
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: xAxisData,
|
||||||
|
boundaryGap: false,
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#000000'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
interval: (() => {
|
||||||
|
const dataLen = xAxisData.length;
|
||||||
|
if (dataLen <= 8) return 0; // 数据少时全部显示
|
||||||
|
|
||||||
|
// 计算最大允许的间隔,确保首尾都能显示
|
||||||
|
// 需要显示的首尾2个 + 中间最多6个 = 总共8个标签
|
||||||
|
const maxLabels = 8;
|
||||||
|
const minInterval = Math.ceil((dataLen - 1) / (maxLabels - 1));
|
||||||
|
|
||||||
|
// 如果原有计算的间隔更大,使用原有逻辑;否则使用最小间隔
|
||||||
|
const calculatedInterval = Math.floor((dataLen - 1) / 7);
|
||||||
|
return Math.max(calculatedInterval, minInterval);
|
||||||
|
})(),
|
||||||
|
fontSize: 12,
|
||||||
|
rotate: 0,
|
||||||
|
margin: 10
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#bfbfbf',
|
||||||
|
type: 'solid'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
name: '水温(°C)',
|
||||||
|
min: 0,
|
||||||
|
max: yAxisMax,
|
||||||
|
scale: false,
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#000000',
|
||||||
|
width: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: true,
|
||||||
|
length: 4
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
fontSize: 12,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#bfbfbf',
|
||||||
|
type: 'solid'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '现在水温',
|
||||||
|
type: 'line',
|
||||||
|
data: wtData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6,
|
||||||
|
lineStyle: { color: '#009dff', width: 2 },
|
||||||
|
itemStyle: { color: '#009dff' },
|
||||||
|
connectNulls: false
|
||||||
|
// 移除 markArea
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '上一年同期水温',
|
||||||
|
type: 'line',
|
||||||
|
data: beforeWtData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6,
|
||||||
|
lineStyle: { color: '#fee588', width: 2 },
|
||||||
|
itemStyle: { color: '#fee588' },
|
||||||
|
connectNulls: false
|
||||||
|
},
|
||||||
|
// 新增辅助系列,专门承载 markArea
|
||||||
|
{
|
||||||
|
name: '温度区间背景', // 名字不会被放入 legend,所以不显示
|
||||||
|
type: 'line',
|
||||||
|
data: new Array(xAxisData.length).fill(0), // 假数据,保证坐标匹配
|
||||||
|
showSymbol: false,
|
||||||
|
symbol: 'none',
|
||||||
|
lineStyle: { opacity: 0 },
|
||||||
|
itemStyle: { opacity: 0 },
|
||||||
|
legendHoverLink: false, // 图例悬停时不联动
|
||||||
|
silent: true, // 不响应鼠标事件
|
||||||
|
markArea: markAreaConfig // 温度区间背景绑定于此
|
||||||
|
}
|
||||||
|
],
|
||||||
|
dataZoom: [
|
||||||
|
{
|
||||||
|
type: 'inside',
|
||||||
|
xAxisIndex: [0],
|
||||||
|
throttle: 50,
|
||||||
|
start: 0,
|
||||||
|
end: 100
|
||||||
|
}
|
||||||
|
],
|
||||||
|
toolbox: {
|
||||||
|
show: true,
|
||||||
|
feature: {
|
||||||
|
saveAsImage: {
|
||||||
|
title: '导出图片',
|
||||||
|
type: 'png',
|
||||||
|
pixelRatio: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
right: 20,
|
||||||
|
top: 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化图表
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartRef.value) return;
|
||||||
|
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
updateChart();
|
||||||
|
|
||||||
|
// 监听窗口resize
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新图表
|
||||||
|
const updateChart = () => {
|
||||||
|
if (!chartInstance) return;
|
||||||
|
|
||||||
|
const option = getChartOption.value;
|
||||||
|
if (Object.keys(option).length > 0) {
|
||||||
|
chartInstance.setOption(option, true);
|
||||||
|
} else {
|
||||||
|
// 如果没有数据,清空图表或显示空状态
|
||||||
|
chartInstance.clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理窗口resize
|
||||||
|
const handleResize = () => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.resize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 销毁图表
|
||||||
|
const destroyChart = () => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
chartInstance = null;
|
||||||
|
}
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
};
|
||||||
|
const initData = () => {
|
||||||
|
if (hasLoaded.value) return;
|
||||||
|
getSelectOption();
|
||||||
|
getEchartsData();
|
||||||
|
gerTableData();
|
||||||
|
// 初始化图表
|
||||||
|
setTimeout(() => {
|
||||||
|
initChart();
|
||||||
|
}, 200);
|
||||||
|
hasLoaded.value = true; // 标记为已请求过
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.isActive,
|
||||||
|
active => {
|
||||||
|
if (active && !hasLoaded.value) {
|
||||||
|
initData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true } // 立即执行一次
|
||||||
|
);
|
||||||
|
// onMounted(() => {
|
||||||
|
// getSelectOption()
|
||||||
|
// getEchartsData()
|
||||||
|
// gerTableData()
|
||||||
|
// // 初始化图表
|
||||||
|
// setTimeout(() => {
|
||||||
|
// initChart()
|
||||||
|
// }, 200)
|
||||||
|
// });
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
destroyChart();
|
||||||
|
});
|
||||||
|
watch(
|
||||||
|
() => modelStore.params.stcd,
|
||||||
|
(newStcd, oldStcd) => {
|
||||||
|
// 避免初始化时的无效触发
|
||||||
|
if (newStcd && newStcd !== oldStcd) {
|
||||||
|
console.log('【BasicInfo】stcd 变化,重新加载数据:', newStcd);
|
||||||
|
getSelectOption();
|
||||||
|
getEchartsData();
|
||||||
|
gerTableData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.body_one {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 600px;
|
||||||
|
|
||||||
|
.search_one {
|
||||||
|
width: 100%;
|
||||||
|
height: 32px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.search_left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.body_body {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
height: 540px;
|
||||||
|
|
||||||
|
.echarts {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-date-options {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 0;
|
||||||
|
|
||||||
|
.ant-btn {
|
||||||
|
border-color: #1890ff !important;
|
||||||
|
color: #1890ff !important;
|
||||||
|
background-color: #e6f7ff !important;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #40a9ff !important;
|
||||||
|
color: #40a9ff !important;
|
||||||
|
background-color: #bae7ff !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
674
frontend/src/components/MapModal/components/videoInfo.vue
Normal file
674
frontend/src/components/MapModal/components/videoInfo.vue
Normal file
@ -0,0 +1,674 @@
|
|||||||
|
<template>
|
||||||
|
<div class="video-info-container">
|
||||||
|
<a-row class="video-info-row" type="flex" style="height: 100%" :gutter="16">
|
||||||
|
<!-- 左侧列表 15% -->
|
||||||
|
<a-col flex="15%" class="left-panel">
|
||||||
|
<div class="panel-header"><a-divider type="vertical" />视频列表</div>
|
||||||
|
<div class="list-container">
|
||||||
|
<a-spin :spinning="loading">
|
||||||
|
<div
|
||||||
|
v-for="item in videoList"
|
||||||
|
:key="item.stcd"
|
||||||
|
class="list-item"
|
||||||
|
:class="{ active: currentVideo?.stcd === item.stcd }"
|
||||||
|
@click="handleSelectVideo(item)"
|
||||||
|
:title="item.stnm"
|
||||||
|
>
|
||||||
|
{{ item.stnm }}
|
||||||
|
</div>
|
||||||
|
<a-empty
|
||||||
|
v-if="!loading && videoList.length === 0"
|
||||||
|
description="暂无数据"
|
||||||
|
/>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<!-- 中间视频区域 55% -->
|
||||||
|
<a-col flex="55%" class="center-panel">
|
||||||
|
<!-- 视频宫格 -->
|
||||||
|
<div class="video-grid" :class="`layout-${viewLayout}`">
|
||||||
|
<div
|
||||||
|
v-for="index in parseInt(viewLayout)"
|
||||||
|
:key="index"
|
||||||
|
class="video-item"
|
||||||
|
>
|
||||||
|
<LiveVideoBox
|
||||||
|
:video-data="videoSlots[index - 1]"
|
||||||
|
:monitor-type="'live'"
|
||||||
|
:show-title="viewLayout !== '1'"
|
||||||
|
height="100%"
|
||||||
|
@close="handleCloseVideo(index - 1)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<!-- 右侧录像列表 30% -->
|
||||||
|
<a-col flex="30%" class="right-panel">
|
||||||
|
<div class="panel-header">
|
||||||
|
<a-divider type="vertical" />
|
||||||
|
回放列表
|
||||||
|
<!-- 右上角宫格切换 -->
|
||||||
|
<div class="layout-switcher">
|
||||||
|
<a-radio-group v-model:value="viewLayout" button-style="solid">
|
||||||
|
<a-radio-button value="1">
|
||||||
|
<i class="iconfont icon-view1"></i>
|
||||||
|
</a-radio-button>
|
||||||
|
<a-radio-button value="4">
|
||||||
|
<i class="iconfont icon-view4"></i>
|
||||||
|
</a-radio-button>
|
||||||
|
<a-radio-button value="9">
|
||||||
|
<i class="iconfont icon-view9"></i>
|
||||||
|
</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 时间选择 -->
|
||||||
|
<div class="date-picker">
|
||||||
|
<a-range-picker
|
||||||
|
v-model:value="dateRange"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
style="width: 100%"
|
||||||
|
:allowClear="false"
|
||||||
|
:presets="DateSetting.RangeButton.days"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 录像列表 -->
|
||||||
|
<div class="record-list">
|
||||||
|
<a-spin :spinning="recordLoading">
|
||||||
|
<div v-if="paginatedRecords.length === 0" class="empty-record">
|
||||||
|
<a-empty />
|
||||||
|
</div>
|
||||||
|
<div v-else class="record-grid">
|
||||||
|
<div
|
||||||
|
class="record-item"
|
||||||
|
v-for="item in paginatedRecords"
|
||||||
|
:key="item.id"
|
||||||
|
@click="handlePlayRecord(item)"
|
||||||
|
>
|
||||||
|
<div class="record-image-wrapper">
|
||||||
|
<a-image :src="item.imgPath" :preview="false" alt="" />
|
||||||
|
<div class="play-btn">
|
||||||
|
<i class="icon iconfont icon-play"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="record-info">
|
||||||
|
<div class="record-name">{{ item.name }}</div>
|
||||||
|
<div class="record-time">
|
||||||
|
{{ dayjs(item.time).format('YYYY-MM-DD HH') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<div class="pagination-wrapper">
|
||||||
|
<a-pagination
|
||||||
|
v-model:current="currentPage"
|
||||||
|
:total="recordTotal"
|
||||||
|
:pageSize="pageSize"
|
||||||
|
size="small"
|
||||||
|
show-less-items
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, watch } from 'vue';
|
||||||
|
import {
|
||||||
|
getVideoList,
|
||||||
|
getVideoDetail,
|
||||||
|
getVideoPlayDetail
|
||||||
|
} from '@/api/mapModal';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import { DateSetting } from '@/utils/enumeration';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import type { Dayjs } from 'dayjs';
|
||||||
|
import LiveVideoBox from '@/views/shiPinJianKong/components/LiveVideoBox.vue';
|
||||||
|
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
|
||||||
|
const hasLoaded = ref(false);
|
||||||
|
const loading = ref(false);
|
||||||
|
const recordLoading = ref(false);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
isActive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 左侧视频列表数据
|
||||||
|
const videoList = ref<any[]>([]);
|
||||||
|
|
||||||
|
// 中间视频数据
|
||||||
|
const videoSlots = ref<any[]>(Array(9).fill(null));
|
||||||
|
|
||||||
|
// 当前播放的视频
|
||||||
|
const currentVideo = ref<any>(null);
|
||||||
|
|
||||||
|
// 宫格布局
|
||||||
|
const viewLayout = ref<'1' | '4' | '9'>('1');
|
||||||
|
|
||||||
|
// 日期范围
|
||||||
|
const dateRange = ref<[Dayjs, Dayjs]>([dayjs().subtract(7, 'day'), dayjs()]);
|
||||||
|
|
||||||
|
// 右侧录像列表
|
||||||
|
const recordList = ref<any[]>([]);
|
||||||
|
const recordTotal = ref(0);
|
||||||
|
|
||||||
|
// 分页相关
|
||||||
|
const currentPage = ref(1);
|
||||||
|
const pageSize = 4; // 每页4个,一行2个,共2行
|
||||||
|
|
||||||
|
// 当前选中的stcd(用于右侧接口)
|
||||||
|
const selectedStcd = ref<string>('');
|
||||||
|
|
||||||
|
// 分页后的录像数据
|
||||||
|
const paginatedRecords = computed(() => {
|
||||||
|
return recordList.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载右侧录像列表
|
||||||
|
const loadRecordList = async () => {
|
||||||
|
if (!selectedStcd.value) {
|
||||||
|
recordList.value = [];
|
||||||
|
recordTotal.value = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
recordLoading.value = true;
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'in',
|
||||||
|
value: [selectedStcd.value]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dateRange.value[0].format('YYYY-MM-DD 00:00:00')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dateRange.value[1].format('YYYY-MM-DD 23:59:59')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sort: [{ field: 'tm', dir: 'desc' }],
|
||||||
|
skip: (currentPage.value - 1) * pageSize,
|
||||||
|
take: pageSize
|
||||||
|
};
|
||||||
|
|
||||||
|
const res: any = await getVideoPlayDetail(params);
|
||||||
|
if (res?.data?.data) {
|
||||||
|
recordList.value = res.data.data;
|
||||||
|
recordTotal.value = res.data.total || 0;
|
||||||
|
} else {
|
||||||
|
recordList.value = [];
|
||||||
|
recordTotal.value = 0;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
recordList.value = [];
|
||||||
|
recordTotal.value = 0;
|
||||||
|
} finally {
|
||||||
|
recordLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化数据
|
||||||
|
const initData = async () => {
|
||||||
|
if (hasLoaded.value) return;
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{ field: 'sttpCode', operator: 'startswith', value: 'VD_' },
|
||||||
|
{
|
||||||
|
logic: 'or',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'rstcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: modelStore.params.stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: modelStore.params.stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'fhstcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: modelStore.params.stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'stCode',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: modelStore.params.stcd
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
select: ['stcd', 'stnm', 'sttpCode', 'sttpName', 'playWay', 'url'],
|
||||||
|
sort: [{ field: 'stnm', dir: 'asc' }]
|
||||||
|
};
|
||||||
|
const res: any = await getVideoList(params);
|
||||||
|
if (res?.data?.data) {
|
||||||
|
videoList.value = res.data.data;
|
||||||
|
// 默认选中第一条并加载右侧录像
|
||||||
|
if (res.data.data.length > 0) {
|
||||||
|
const firstItem = res.data.data[0];
|
||||||
|
currentVideo.value = firstItem;
|
||||||
|
selectedStcd.value = firstItem.stcd;
|
||||||
|
// 中间显示第一条视频
|
||||||
|
videoSlots.value[0] = {
|
||||||
|
flnm: firstItem.stnm,
|
||||||
|
stnm: firstItem.stnm,
|
||||||
|
url: firstItem.url,
|
||||||
|
key: firstItem.stcd,
|
||||||
|
...firstItem
|
||||||
|
};
|
||||||
|
loadRecordList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hasLoaded.value = true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取视频列表失败:', error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选择视频播放
|
||||||
|
const handleSelectVideo = (item: any) => {
|
||||||
|
currentVideo.value = item;
|
||||||
|
selectedStcd.value = item.stcd;
|
||||||
|
currentPage.value = 1; // 重置分页
|
||||||
|
|
||||||
|
// 只有在 viewLayout 为 1 时才替换中间视频
|
||||||
|
if (viewLayout.value === '1') {
|
||||||
|
const videoData = {
|
||||||
|
flnm: item.stnm,
|
||||||
|
stnm: item.stnm,
|
||||||
|
url: item.url,
|
||||||
|
key: item.stcd,
|
||||||
|
...item
|
||||||
|
};
|
||||||
|
videoSlots.value[0] = videoData;
|
||||||
|
// 清空其他槽位
|
||||||
|
for (let i = 1; i < videoSlots.value.length; i++) {
|
||||||
|
videoSlots.value[i] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新右侧录像列表
|
||||||
|
loadRecordList();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 关闭视频
|
||||||
|
const handleCloseVideo = (index: number) => {
|
||||||
|
videoSlots.value[index] = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 播放录像
|
||||||
|
const handlePlayRecord = (record: any) => {
|
||||||
|
const videoData = {
|
||||||
|
flnm: record.name,
|
||||||
|
stnm: record.name,
|
||||||
|
url: record.url,
|
||||||
|
key: record.id,
|
||||||
|
...record
|
||||||
|
};
|
||||||
|
|
||||||
|
// 找第一个空位
|
||||||
|
const emptyIndex = videoSlots.value.findIndex(
|
||||||
|
(v, i) => i < parseInt(viewLayout.value) && !v
|
||||||
|
);
|
||||||
|
if (emptyIndex !== -1) {
|
||||||
|
videoSlots.value[emptyIndex] = videoData;
|
||||||
|
} else {
|
||||||
|
// 满了替换第一个
|
||||||
|
videoSlots.value[0] = videoData;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听布局变化,调整视频数组长度
|
||||||
|
watch(viewLayout, newLayout => {
|
||||||
|
const maxCount = parseInt(newLayout);
|
||||||
|
while (videoSlots.value.length < maxCount) {
|
||||||
|
videoSlots.value.push(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换到 1 宫格时,确保中间显示视频
|
||||||
|
if (newLayout === '1') {
|
||||||
|
const firstVideo = videoSlots.value.find(
|
||||||
|
v => v !== null && v !== undefined
|
||||||
|
);
|
||||||
|
if (firstVideo) {
|
||||||
|
// 中间有视频,保留第一个
|
||||||
|
videoSlots.value[0] = firstVideo;
|
||||||
|
for (let i = 1; i < videoSlots.value.length; i++) {
|
||||||
|
videoSlots.value[i] = null;
|
||||||
|
}
|
||||||
|
} else if (currentVideo.value) {
|
||||||
|
// 中间没有视频,显示左侧选中的
|
||||||
|
videoSlots.value[0] = {
|
||||||
|
flnm: currentVideo.value.stnm,
|
||||||
|
stnm: currentVideo.value.stnm,
|
||||||
|
url: currentVideo.value.url,
|
||||||
|
key: currentVideo.value.stcd,
|
||||||
|
...currentVideo.value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听日期范围变化,重新加载录像列表
|
||||||
|
watch(dateRange, () => {
|
||||||
|
currentPage.value = 1;
|
||||||
|
loadRecordList();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听页码变化,重新加载录像列表
|
||||||
|
watch(currentPage, () => {
|
||||||
|
loadRecordList();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.isActive,
|
||||||
|
active => {
|
||||||
|
if (active && !hasLoaded.value) {
|
||||||
|
initData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.video-info-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 630px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #fff;
|
||||||
|
|
||||||
|
.video-info-row {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 左侧面板
|
||||||
|
.left-panel {
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
height: 40px;
|
||||||
|
padding: 0 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgba(0, 0, 0, 0.85);
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
:deep(.ant-divider) {
|
||||||
|
margin-right: 6px;
|
||||||
|
height: 1.2em !important;
|
||||||
|
border-left: 3px solid rgb(47, 107, 152) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-container {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px 0;
|
||||||
|
|
||||||
|
.list-item {
|
||||||
|
padding: 8px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 2px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #005293;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 中间面板
|
||||||
|
.center-panel {
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.video-grid {
|
||||||
|
flex: 1;
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
&.layout-1 {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.layout-4 {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
grid-template-rows: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.layout-9 {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
grid-template-rows: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-item {
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右侧面板
|
||||||
|
.right-panel {
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
position: relative;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgba(0, 0, 0, 0.85);
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
|
||||||
|
:deep(.ant-divider) {
|
||||||
|
margin-right: 6px;
|
||||||
|
height: 1.2em !important;
|
||||||
|
border-left: 3px solid rgb(47, 107, 152) !important;
|
||||||
|
}
|
||||||
|
.layout-switcher {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
:deep(.ant-radio-group) {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
.ant-radio-button-wrapper {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
padding: 0 !important;
|
||||||
|
border: none !important;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.iconfont {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
&:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker {
|
||||||
|
padding: 12px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.empty-record {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.record-item {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.play-btn {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-image-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 80px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-bottom: none;
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
|
||||||
|
:deep(.ant-image) {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
background-color: rgba(12, 51, 88, 0.8);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0.7;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-info {
|
||||||
|
height: 50px;
|
||||||
|
padding: 6px;
|
||||||
|
text-align: center;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 4px 4px;
|
||||||
|
|
||||||
|
.record-name {
|
||||||
|
font-size: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-time {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-wrapper {
|
||||||
|
padding: 12px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,48 +1,108 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-modal :open="visible" :title="title" width="80vw" :footer="null" :closable="true" @cancel="handleClose"
|
<a-modal
|
||||||
:destroyOnClose="true" class="map-modal">
|
:open="visible"
|
||||||
|
:title="title"
|
||||||
|
width="80vw"
|
||||||
|
:footer="null"
|
||||||
|
:closable="true"
|
||||||
|
@cancel="handleClose"
|
||||||
|
:destroyOnClose="true"
|
||||||
|
class="map-modal"
|
||||||
|
>
|
||||||
<div v-if="modelStore.showStcdSelector" class="stcd-selector-wrapper">
|
<div v-if="modelStore.showStcdSelector" class="stcd-selector-wrapper">
|
||||||
<a-select v-model:value="modelStore.params.stcd" placeholder="请选择测站" style="width: 240px; margin-bottom: 12px;"
|
<a-select
|
||||||
:options="modelStore.stcdOptions" show-search :filter-option="filterOption" @change="handleStcdChange">
|
v-model:value="modelStore.params.stcd"
|
||||||
|
placeholder="请选择测站"
|
||||||
|
style="width: 240px; margin-bottom: 12px"
|
||||||
|
:options="modelStore.stcdOptions"
|
||||||
|
show-search
|
||||||
|
:filter-option="filterOption"
|
||||||
|
@change="handleStcdChange"
|
||||||
|
>
|
||||||
</a-select>
|
</a-select>
|
||||||
</div>
|
</div>
|
||||||
<a-tabs :active-key="currentActiveKey" @change="onTabChange">
|
<a-tabs :active-key="currentActiveKey" @change="onTabChange">
|
||||||
<a-tab-pane v-for="tab in tabsConfig" :key="tab.key" :tab="tab.name">
|
<a-tab-pane v-for="tab in tabsConfig" :key="tab.key" :tab="tab.name">
|
||||||
<div class="content">
|
|
||||||
<!-- 基本信息组件 -->
|
|
||||||
<BasicInfo v-if="currentActiveKey === 'basicInfo'" :url="tab.url" />
|
|
||||||
<!-- 水温-监测数据 -->
|
|
||||||
<WaterTemperature v-if="currentActiveKey === 'WaterTemperature'" />
|
|
||||||
<!-- 水温-鱼类繁殖适宜性分析 -->
|
|
||||||
<WaterTemperatureContrast v-if="currentActiveKey === 'WaterTemperatureRep'" />
|
|
||||||
<!-- 地图组件 -->
|
|
||||||
<!-- <MapView v-else-if="currentActiveKey === 'mapView'" :data="modalData" /> -->
|
|
||||||
<!-- 周边配套组件 -->
|
|
||||||
<!-- <SurroundingInfo
|
|
||||||
v-else-if="currentActiveKey === 'surrounding'"
|
|
||||||
:data="modalData"
|
|
||||||
/> -->
|
|
||||||
</div>
|
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<template #rightExtra>
|
<template #rightExtra>
|
||||||
<a-tooltip :title="!isEngConfig ? '' : '该电站无专题配置'">
|
<a-tooltip :title="!isEngConfig ? '' : '该电站无专题配置'">
|
||||||
<a-button type="primary" :disabled="isEngConfig">
|
<a-button type="primary" :disabled="isEngConfig">
|
||||||
<i class="icon iconfont icon-topic mr-[5px]"></i>
|
<i class="icon iconfont icon-topic mr-[5px]"></i>
|
||||||
电站专题
|
电站专题
|
||||||
</a-button></a-tooltip>
|
</a-button></a-tooltip
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
|
<!-- 内容区域 - 使用动态组件 + keep-alive 实现按需加载和缓存 -->
|
||||||
|
<keep-alive>
|
||||||
|
<div class="content">
|
||||||
|
<!-- 基本信息 -->
|
||||||
|
<BasicInfo
|
||||||
|
v-show="currentActiveKey === 'basicInfo'"
|
||||||
|
:url="getTabUrl('basicInfo')"
|
||||||
|
:is-active="currentActiveKey === 'basicInfo'"
|
||||||
|
/>
|
||||||
|
<!-- 实时视频 -->
|
||||||
|
<VideoInfo
|
||||||
|
v-show="currentActiveKey === 'videoInfo'"
|
||||||
|
:is-active="currentActiveKey === 'videoInfo'"
|
||||||
|
/>
|
||||||
|
<!-- 全景影像 -->
|
||||||
|
<PanoramaInfo
|
||||||
|
v-show="currentActiveKey === 'panoramaInfo'"
|
||||||
|
:url="getTabUrl('panoramaInfo')"
|
||||||
|
:is-active="currentActiveKey === 'panoramaInfo'"
|
||||||
|
/>
|
||||||
|
<!-- 监测数据 -->
|
||||||
|
<MonitorInfo
|
||||||
|
v-show="currentActiveKey === 'monitorInfo'"
|
||||||
|
:is-active="currentActiveKey === 'monitorInfo'"
|
||||||
|
:code="getTabCode('monitorInfo')"
|
||||||
|
/>
|
||||||
|
<!-- 预警提示 -->
|
||||||
|
<EarlyWarningAlert
|
||||||
|
v-show="currentActiveKey === 'tableTabs'"
|
||||||
|
:tabs-items="getTabChildren('tableTabs')"
|
||||||
|
:is-active="currentActiveKey === 'tableTabs'"
|
||||||
|
/>
|
||||||
|
<!-- 鱼类繁殖适宜性分析 -->
|
||||||
|
<WaterTemperatureRep
|
||||||
|
v-show="currentActiveKey === 'WaterTemperatureRep'"
|
||||||
|
:is-active="currentActiveKey === 'WaterTemperatureRep'"
|
||||||
|
/>
|
||||||
|
<!-- 查看报告 -->
|
||||||
|
<Attachment
|
||||||
|
v-show="currentActiveKey === 'attachment'"
|
||||||
|
:is-active="currentActiveKey === 'attachment'"
|
||||||
|
tabKey="attachment"
|
||||||
|
:show-download="false"
|
||||||
|
:url="getTabUrl('attachment')"
|
||||||
|
/>
|
||||||
|
<!-- 批复文件 -->
|
||||||
|
<Attachment
|
||||||
|
v-show="currentActiveKey === 'approval'"
|
||||||
|
:is-active="currentActiveKey === 'approval'"
|
||||||
|
tabKey="approval"
|
||||||
|
:show-download="true"
|
||||||
|
:url="getTabUrl('approval')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</keep-alive>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watch } from "vue";
|
import { ref, watch, reactive, computed } from 'vue';
|
||||||
// 导入预定义的 Tab 内容组件
|
// 导入预定义的 Tab 内容组件
|
||||||
import BasicInfo from "./components/BasicInfo.vue";
|
import BasicInfo from './components/BasicInfo.vue'; // 基本信息
|
||||||
import WaterTemperature from "./components/WaterTemperature.vue";
|
import VideoInfo from './components/videoInfo.vue'; // 实时视频
|
||||||
import WaterTemperatureContrast from "./components/WaterTemperatureContrast.vue";
|
import PanoramaInfo from './components/PanoramaInfo.vue'; // 全景影像
|
||||||
import { useModelStore } from "@/store/modules/model";
|
import MonitorInfo from './components/MonitorInfo.vue'; // 监测数据
|
||||||
import { handleTabs } from "./setting.config";
|
import EarlyWarningAlert from './components/EarlyWarningAlert.vue'; // 预警提示
|
||||||
|
import Attachment from './components/Attachment.vue'; // 查看报告/批复文件
|
||||||
|
import WaterTemperatureRep from './components/WaterTemperatureRep.vue'; // 水温对比数据
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import { handleTabs } from './setting.config';
|
||||||
|
|
||||||
const modelStore = useModelStore();
|
const modelStore = useModelStore();
|
||||||
const tabsConfig = ref([]);
|
const tabsConfig = ref([]);
|
||||||
@ -57,7 +117,22 @@ interface TabItem {
|
|||||||
title: string;
|
title: string;
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
// 获取 Tab 配置项的 URL
|
||||||
|
const getTabUrl = (key: string) => {
|
||||||
|
const tab = tabsConfig.value.find((item: any) => item.key === key);
|
||||||
|
return tab?.url || '';
|
||||||
|
};
|
||||||
|
// 获取 Tab 配置项的子 Tab
|
||||||
|
const getTabChildren = (key: string) => {
|
||||||
|
console.log(tabsConfig.value);
|
||||||
|
const tab = tabsConfig.value.find((item: any) => item.key === key);
|
||||||
|
return tab?.tabs || [];
|
||||||
|
};
|
||||||
|
// 获取 Tab 配置项的 code
|
||||||
|
const getTabCode = (key: string) => {
|
||||||
|
const tab = tabsConfig.value.find((item: any) => item.key === key);
|
||||||
|
return tab?.code || '';
|
||||||
|
};
|
||||||
// 定义 Props
|
// 定义 Props
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
@ -69,17 +144,17 @@ const props = defineProps<{
|
|||||||
// 定义 Emits
|
// 定义 Emits
|
||||||
// 添加 'update:activeKey' 以支持 v-model:active-key
|
// 添加 'update:activeKey' 以支持 v-model:active-key
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "update:visible", value: boolean): void;
|
(e: 'update:visible', value: boolean): void;
|
||||||
(e: "change", key: string): void;
|
(e: 'change', key: string): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// 内部维护的 activeKey
|
// 内部维护的 activeKey
|
||||||
const currentActiveKey = ref<string>("");
|
const currentActiveKey = ref<string>('');
|
||||||
|
|
||||||
// 监听外部传入的 activeKey 变化,同步到内部状态
|
// 监听外部传入的 activeKey 变化,同步到内部状态
|
||||||
watch(
|
watch(
|
||||||
() => props.activeKey,
|
() => props.activeKey,
|
||||||
(newVal) => {
|
newVal => {
|
||||||
if (newVal && newVal !== currentActiveKey.value) {
|
if (newVal && newVal !== currentActiveKey.value) {
|
||||||
currentActiveKey.value = newVal;
|
currentActiveKey.value = newVal;
|
||||||
}
|
}
|
||||||
@ -88,7 +163,8 @@ watch(
|
|||||||
);
|
);
|
||||||
watch(
|
watch(
|
||||||
() => modelStore.params,
|
() => modelStore.params,
|
||||||
(newVal) => {
|
newVal => {
|
||||||
|
if (!newVal || Object.keys(newVal).length === 0) return;
|
||||||
console.log(newVal);
|
console.log(newVal);
|
||||||
tabsConfig.value = handleTabs(newVal);
|
tabsConfig.value = handleTabs(newVal);
|
||||||
let value = tabsConfig.value.find((item: any) => item.default);
|
let value = tabsConfig.value.find((item: any) => item.default);
|
||||||
@ -101,12 +177,13 @@ const onTabChange = (key: string) => {
|
|||||||
currentActiveKey.value = key;
|
currentActiveKey.value = key;
|
||||||
// 通知父组件更新 activeKey (支持 v-model)
|
// 通知父组件更新 activeKey (支持 v-model)
|
||||||
// 通知父组件发生了切换事件 (用于业务逻辑,如加载数据)
|
// 通知父组件发生了切换事件 (用于业务逻辑,如加载数据)
|
||||||
emit("change", key);
|
emit('change', key);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 关闭弹窗
|
// 关闭弹窗
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
emit("update:visible", false);
|
emit('update:visible', false);
|
||||||
|
modelStore.params = {};
|
||||||
// 清理状态,避免影响下次打开
|
// 清理状态,避免影响下次打开
|
||||||
modelStore.showStcdSelector = false;
|
modelStore.showStcdSelector = false;
|
||||||
modelStore.stcdOptions = [];
|
modelStore.stcdOptions = [];
|
||||||
@ -116,7 +193,7 @@ const handleClose = () => {
|
|||||||
const modalData = ref(props.data);
|
const modalData = ref(props.data);
|
||||||
watch(
|
watch(
|
||||||
() => props.data,
|
() => props.data,
|
||||||
(newVal) => {
|
newVal => {
|
||||||
modalData.value = newVal;
|
modalData.value = newVal;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@ -20,12 +20,12 @@ const ENGTabs: Array<any> = [
|
|||||||
url: '/api/dec-lygk-base-server/base/msstbprpt/getStcdInfo',
|
url: '/api/dec-lygk-base-server/base/msstbprpt/getStcdInfo',
|
||||||
default: true // 默认显示
|
default: true // 默认显示
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
name: '阶段属性',
|
// name: '阶段属性',
|
||||||
key: 'basicFilter',
|
// key: 'basicFilter',
|
||||||
type: 'basicFilter',
|
// type: 'basicFilter',
|
||||||
url: '/eng/engBasisInfo/getEngBaseInfo'
|
// url: '/eng/engBasisInfo/getEngBaseInfo'
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
name: '实时视频',
|
name: '实时视频',
|
||||||
key: 'videoInfo',
|
key: 'videoInfo',
|
||||||
@ -92,12 +92,14 @@ const ENGTabs: Array<any> = [
|
|||||||
{
|
{
|
||||||
name: '查看报告',
|
name: '查看报告',
|
||||||
key: 'attachment',
|
key: 'attachment',
|
||||||
type: 'attachment'
|
type: 'attachment',
|
||||||
|
url: '/eq/fid/GetKendoListCust'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '批复文件',
|
name: '批复文件',
|
||||||
key: 'approval',
|
key: 'approval',
|
||||||
type: 'approval'
|
type: 'approval',
|
||||||
|
url: '/eng/base/eiaapproval/GetKendoList'
|
||||||
}
|
}
|
||||||
// {
|
// {
|
||||||
// name: "特征曲线",
|
// name: "特征曲线",
|
||||||
@ -333,7 +335,7 @@ const FPTabs: Array<any> =
|
|||||||
name: '运行情况',
|
name: '运行情况',
|
||||||
key: 'FishFacilityRunState', //Normal1
|
key: 'FishFacilityRunState', //Normal1
|
||||||
type: 'FishFacilityRunState', //Normal
|
type: 'FishFacilityRunState', //Normal
|
||||||
code: 'gyss.tabs.yxqk',
|
code: 'gyss.tabs.yxqk'
|
||||||
// tabs: [
|
// tabs: [
|
||||||
// {
|
// {
|
||||||
// name: '',
|
// name: '',
|
||||||
@ -349,9 +351,9 @@ const FPTabs: Array<any> =
|
|||||||
name: '综合分析',
|
name: '综合分析',
|
||||||
key: 'FishZHFX',
|
key: 'FishZHFX',
|
||||||
type: 'FishZHFX',
|
type: 'FishZHFX',
|
||||||
code: "gyss.tabs.zhfx"
|
code: 'gyss.tabs.zhfx'
|
||||||
},
|
}
|
||||||
]
|
];
|
||||||
|
|
||||||
// // 过鱼设施在建状态
|
// // 过鱼设施在建状态
|
||||||
// const FPTabs1: Array<any> = [
|
// const FPTabs1: Array<any> = [
|
||||||
@ -405,7 +407,7 @@ const FBTabs: Array<any> = [
|
|||||||
name: '过程图',
|
name: '过程图',
|
||||||
key: 'ProcessDiagram',
|
key: 'ProcessDiagram',
|
||||||
type: 'ProcessDiagram',
|
type: 'ProcessDiagram',
|
||||||
code: "ylzzz.tabs.gct"
|
code: 'ylzzz.tabs.gct'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '增殖放流情况',
|
name: '增殖放流情况',
|
||||||
@ -457,7 +459,7 @@ const FBTabs: Array<any> = [
|
|||||||
// key: "panoramaInfo",
|
// key: "panoramaInfo",
|
||||||
// type: "panorama"
|
// type: "panorama"
|
||||||
// },
|
// },
|
||||||
]
|
];
|
||||||
|
|
||||||
// const FBTabs1: Array<any> = [
|
// const FBTabs1: Array<any> = [
|
||||||
// {
|
// {
|
||||||
@ -590,7 +592,7 @@ const FHTabs: Array<any> = [
|
|||||||
{
|
{
|
||||||
name: '流量监测',
|
name: '流量监测',
|
||||||
key: 'FlowMeasure',
|
key: 'FlowMeasure',
|
||||||
type: 'FlowMeasure',
|
type: 'FlowMeasure'
|
||||||
// tabs: [
|
// tabs: [
|
||||||
// {
|
// {
|
||||||
// url: '/wmp-eng-server/eng/river/GetKendoListCust',
|
// url: '/wmp-eng-server/eng/river/GetKendoListCust',
|
||||||
@ -622,7 +624,7 @@ const FHTabs: Array<any> = [
|
|||||||
// key: "panoramaInfo",
|
// key: "panoramaInfo",
|
||||||
// type: "panorama"
|
// type: "panorama"
|
||||||
// }
|
// }
|
||||||
]
|
];
|
||||||
// const ZQTabs: Array<any> = [
|
// const ZQTabs: Array<any> = [
|
||||||
// {
|
// {
|
||||||
// name: '基础信息',
|
// name: '基础信息',
|
||||||
@ -675,7 +677,7 @@ const WQFBTabs: Array<any> = [
|
|||||||
type: 'video',
|
type: 'video',
|
||||||
url: '/video/dataStcdFrame/getVideoMonitorList'
|
url: '/video/dataStcdFrame/getVideoMonitorList'
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
// const WQTabs: Array<any> = [
|
// const WQTabs: Array<any> = [
|
||||||
// ...WQFBTabs,
|
// ...WQFBTabs,
|
||||||
// {
|
// {
|
||||||
@ -723,7 +725,7 @@ const FLOWTabs: Array<any> = [
|
|||||||
key: 'FlowMeasure',
|
key: 'FlowMeasure',
|
||||||
type: 'FlowMeasure'
|
type: 'FlowMeasure'
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
// //水温监测站 √
|
// //水温监测站 √
|
||||||
const WTTabs: Array<any> = [
|
const WTTabs: Array<any> = [
|
||||||
{
|
{
|
||||||
@ -748,9 +750,9 @@ const WTTabs: Array<any> = [
|
|||||||
name: '鱼类繁殖适宜性分析',
|
name: '鱼类繁殖适宜性分析',
|
||||||
key: 'WaterTemperatureRep',
|
key: 'WaterTemperatureRep',
|
||||||
type: 'WaterTemperatureRep',
|
type: 'WaterTemperatureRep',
|
||||||
code: 'swjc.tabs.ylfzsyxfx',
|
code: 'swjc.tabs.ylfzsyxfx'
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
// //垂向水温 √
|
// //垂向水温 √
|
||||||
// const WTTabs1: Array<any> = [
|
// const WTTabs1: Array<any> = [
|
||||||
// {
|
// {
|
||||||
@ -774,7 +776,7 @@ const DWTabs: Array<any> = [
|
|||||||
type: 'basic',
|
type: 'basic',
|
||||||
default: true, // 默认显示
|
default: true, // 默认显示
|
||||||
url: '/api/dec-lygk-base-server/base/msstbprpt/getStcdInfo'
|
url: '/api/dec-lygk-base-server/base/msstbprpt/getStcdInfo'
|
||||||
},
|
}
|
||||||
// {
|
// {
|
||||||
// name: '实时视频',
|
// name: '实时视频',
|
||||||
// key: 'videoInfo',
|
// key: 'videoInfo',
|
||||||
@ -791,7 +793,7 @@ const DWTabs: Array<any> = [
|
|||||||
// key: 'AIYXSB',
|
// key: 'AIYXSB',
|
||||||
// type: 'AIYXSB',
|
// type: 'AIYXSB',
|
||||||
// }
|
// }
|
||||||
]
|
];
|
||||||
// //低温水减缓设施这是旧的
|
// //低温水减缓设施这是旧的
|
||||||
// const DWTabs2: Array<any> = [
|
// const DWTabs2: Array<any> = [
|
||||||
// {
|
// {
|
||||||
@ -834,7 +836,7 @@ const WETabs: Array<any> = [
|
|||||||
key: 'basicInfo',
|
key: 'basicInfo',
|
||||||
type: 'basic',
|
type: 'basic',
|
||||||
url: '/bbi/siteBipc/getSiteBasicInfo',
|
url: '/bbi/siteBipc/getSiteBasicInfo',
|
||||||
default: true, // 默认显示
|
default: true // 默认显示
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '监测数据',
|
name: '监测数据',
|
||||||
@ -887,7 +889,7 @@ const WETabs: Array<any> = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
//鱼类调查装置
|
//鱼类调查装置
|
||||||
const DEVICETABS: Array<any> = [
|
const DEVICETABS: Array<any> = [
|
||||||
{
|
{
|
||||||
@ -895,9 +897,9 @@ const DEVICETABS: Array<any> = [
|
|||||||
key: 'basicInfo',
|
key: 'basicInfo',
|
||||||
type: 'basic',
|
type: 'basic',
|
||||||
url: '/api/dec-lygk-base-server/base/msstbprpt/getStcdInfo',
|
url: '/api/dec-lygk-base-server/base/msstbprpt/getStcdInfo',
|
||||||
default: true, // 默认显示
|
default: true // 默认显示
|
||||||
},
|
}
|
||||||
]
|
];
|
||||||
// //电站告警
|
// //电站告警
|
||||||
const DZGJ: Array<any> = [
|
const DZGJ: Array<any> = [
|
||||||
{
|
{
|
||||||
@ -905,7 +907,7 @@ const DZGJ: Array<any> = [
|
|||||||
key: 'basicInfo',
|
key: 'basicInfo',
|
||||||
type: 'basic',
|
type: 'basic',
|
||||||
url: '/bbi/siteBipc/getSiteBasicInfo',
|
url: '/bbi/siteBipc/getSiteBasicInfo',
|
||||||
default: true, // 默认显示
|
default: true // 默认显示
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '监测数据',
|
name: '监测数据',
|
||||||
@ -958,7 +960,7 @@ const DZGJ: Array<any> = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
|
|
||||||
//珍稀植物园
|
//珍稀植物园
|
||||||
const VPTabs: Array<any> = [
|
const VPTabs: Array<any> = [
|
||||||
@ -967,7 +969,7 @@ const VPTabs: Array<any> = [
|
|||||||
key: 'basicInfo',
|
key: 'basicInfo',
|
||||||
type: 'basic',
|
type: 'basic',
|
||||||
url: '/bbi/siteBipc/getSiteBasicInfo',
|
url: '/bbi/siteBipc/getSiteBasicInfo',
|
||||||
default: true, // 默认显示
|
default: true // 默认显示
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// name: '实时视频',
|
// name: '实时视频',
|
||||||
@ -1011,7 +1013,7 @@ const VPTabs: Array<any> = [
|
|||||||
// type: "FishSystemRunState", //Normal
|
// type: "FishSystemRunState", //Normal
|
||||||
// tabs: []
|
// tabs: []
|
||||||
// }
|
// }
|
||||||
]
|
];
|
||||||
// //视频站
|
// //视频站
|
||||||
const videoTabs: Array<any> = [
|
const videoTabs: Array<any> = [
|
||||||
{
|
{
|
||||||
@ -1027,7 +1029,7 @@ const videoTabs: Array<any> = [
|
|||||||
type: 'video',
|
type: 'video',
|
||||||
url: '/video/dataStcdFrame/getVideoMonitorList'
|
url: '/video/dataStcdFrame/getVideoMonitorList'
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
// //视频站
|
// //视频站
|
||||||
// const noLiveVideoTabs: Array<any> = [
|
// const noLiveVideoTabs: Array<any> = [
|
||||||
// {
|
// {
|
||||||
@ -1050,7 +1052,7 @@ const VATabs: Array<any> = [
|
|||||||
key: 'basicInfo',
|
key: 'basicInfo',
|
||||||
type: 'basic',
|
type: 'basic',
|
||||||
url: '/bbi/siteBipc/getSiteBasicInfo',
|
url: '/bbi/siteBipc/getSiteBasicInfo',
|
||||||
default: true, // 默认显示
|
default: true // 默认显示
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// name: '实时视频',
|
// name: '实时视频',
|
||||||
@ -1076,7 +1078,7 @@ const VATabs: Array<any> = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
|
|
||||||
// // //河道水情站
|
// // //河道水情站
|
||||||
// // const hdsqz: Array<any> = [
|
// // const hdsqz: Array<any> = [
|
||||||
@ -1177,20 +1179,19 @@ const WATabs: Array<any> = [
|
|||||||
key: 'basicInfo',
|
key: 'basicInfo',
|
||||||
type: 'basic',
|
type: 'basic',
|
||||||
url: '/bbi/siteBipc/getSiteBasicInfo',
|
url: '/bbi/siteBipc/getSiteBasicInfo',
|
||||||
default: true, // 默认显示
|
default: true // 默认显示
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '现场录像',
|
name: '现场录像',
|
||||||
key: 'liveFootage',
|
key: 'liveFootage',
|
||||||
type: 'liveFootage',
|
type: 'liveFootage'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '现场图片',
|
name: '现场图片',
|
||||||
key: 'onSitePhoto',
|
key: 'onSitePhoto',
|
||||||
type: 'onSitePhoto',
|
type: 'onSitePhoto'
|
||||||
},
|
}
|
||||||
|
];
|
||||||
]
|
|
||||||
// //mway : 1-是人工 , 2-是自动 dtinType: 0-自建 1-国家 2-人工
|
// //mway : 1-是人工 , 2-是自动 dtinType: 0-自建 1-国家 2-人工
|
||||||
const handleTabs = (modaldata: any) => {
|
const handleTabs = (modaldata: any) => {
|
||||||
if (!modaldata?.sttp) return;
|
if (!modaldata?.sttp) return;
|
||||||
@ -1206,7 +1207,7 @@ const handleTabs = (modaldata: any) => {
|
|||||||
case 'FH_POINT':
|
case 'FH_POINT':
|
||||||
if (modaldata.show) {
|
if (modaldata.show) {
|
||||||
return FHTabs;
|
return FHTabs;
|
||||||
}else{
|
} else {
|
||||||
let FHTabsTwo = FHTabs.slice(0, 1);
|
let FHTabsTwo = FHTabs.slice(0, 1);
|
||||||
return FHTabsTwo;
|
return FHTabsTwo;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ declare global {
|
|||||||
__mapMode?: string; // 建议同时声明代码中用到的其他全局变量
|
__mapMode?: string; // 建议同时声明代码中用到的其他全局变量
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log(import.meta.env.VITE_APP_PREVIEW_URL);
|
console.log(import.meta.env.VITE_APP_ATTACHMENT_URL);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据镜头高度获取地图级别
|
* 根据镜头高度获取地图级别
|
||||||
@ -60,11 +60,7 @@ export const legendData2Obj = (data: any[]) => {
|
|||||||
* @param index - item的索引
|
* @param index - item的索引
|
||||||
* @param labelType - 标签类型
|
* @param labelType - 标签类型
|
||||||
*/
|
*/
|
||||||
export const appendOffsetPropties = (
|
export const appendOffsetPropties = (item: any, index: any, labelType = 2) => {
|
||||||
item: any,
|
|
||||||
index: any,
|
|
||||||
labelType = 2
|
|
||||||
) => {
|
|
||||||
let drawDotImg = null;
|
let drawDotImg = null;
|
||||||
let offset = null;
|
let offset = null;
|
||||||
if (labelType == 2) {
|
if (labelType == 2) {
|
||||||
|
|||||||
@ -144,6 +144,7 @@ export class MapOl implements MapInterface {
|
|||||||
|
|
||||||
this.view.on('change:resolution', () => {});
|
this.view.on('change:resolution', () => {});
|
||||||
this.map.on('click', evt => {
|
this.map.on('click', evt => {
|
||||||
|
this.popupManager.showPopup(undefined, undefined);
|
||||||
this.popupManager.handleMapClick(evt.pixel, detectedFeature => {
|
this.popupManager.handleMapClick(evt.pixel, detectedFeature => {
|
||||||
modelStore.modalVisible = true;
|
modelStore.modalVisible = true;
|
||||||
modelStore.params = detectedFeature.values_;
|
modelStore.params = detectedFeature.values_;
|
||||||
|
|||||||
51
frontend/src/components/pdf/pdfView.vue
Normal file
51
frontend/src/components/pdf/pdfView.vue
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<template>
|
||||||
|
<div class="pdf-view-container">
|
||||||
|
<iframe
|
||||||
|
ref="iframeRef"
|
||||||
|
:src="iframeUrl"
|
||||||
|
class="pdf-iframe"
|
||||||
|
frameborder="0"
|
||||||
|
:title="fileId"
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
fileId: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const iframeUrl = computed(() => {
|
||||||
|
if (!props.fileId) return '';
|
||||||
|
return (
|
||||||
|
import.meta.env.VITE_APP_ATTACHMENT_URL +
|
||||||
|
`/ShowDocument?fileKey=${props.fileId}&backgroundColor=transparent`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.pdf-view-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.empty-state {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
color: #ccc;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,65 +1,82 @@
|
|||||||
<!-- SidePanelItem.vue -->
|
<!-- SidePanelItem.vue -->
|
||||||
<template>
|
<template>
|
||||||
<SidePanelItem title="设施类型介绍">
|
<SidePanelItem title="设施类型介绍">
|
||||||
<div class="container" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave">
|
<div
|
||||||
<!-- 跑马灯轨道容器 -->
|
class="container"
|
||||||
<div class="carousel-track" :class="{ 'no-transition': isTransitioning }"
|
@mouseenter="handleMouseEnter"
|
||||||
:style="{ transform: `translateX(-${currentIndex * 100}%)` }">
|
@mouseleave="handleMouseLeave"
|
||||||
<!-- 遍历所有媒体项(包含克隆项) -->
|
>
|
||||||
<div v-for="(item, index) in renderMediaData" :key="index" class="carousel-item"
|
<!-- 跑马灯轨道容器 -->
|
||||||
@click="handleItemClick(originalMediaData)">
|
<div
|
||||||
<!-- 图片 -->
|
class="carousel-track"
|
||||||
<img v-if="item.url" :src="item.url" alt="" />
|
:class="{ 'no-transition': isTransitioning }"
|
||||||
<!-- 说明文字(随媒体项移动) -->
|
:style="{ transform: `translateX(-${currentIndex * 100}%)` }"
|
||||||
<!-- <div class="text">{{ item.text }}</div> -->
|
>
|
||||||
</div>
|
<!-- 遍历所有媒体项(包含克隆项) -->
|
||||||
</div>
|
<div
|
||||||
|
v-for="(item, index) in renderMediaData"
|
||||||
<!-- 面板指示器(固定在底部右侧) -->
|
:key="index"
|
||||||
<div class="pagination-dots-fixed">
|
class="carousel-item"
|
||||||
<span v-for="(dot, index) in originalMediaData" :key="index" class="dot"
|
@click="handleItemClick(originalMediaData)"
|
||||||
:class="{ active: getCurrentRealIndex() === index }" @click="goToSlide(index)"></span>
|
>
|
||||||
</div>
|
<!-- 图片 -->
|
||||||
|
<img v-if="item.url" :src="item.url" alt="" />
|
||||||
|
<!-- 说明文字(随媒体项移动) -->
|
||||||
|
<!-- <div class="text">{{ item.text }}</div> -->
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 独立的文字说明区域(随跑马灯切换而变化) -->
|
<!-- 面板指示器(固定在底部右侧) -->
|
||||||
<div class="description-text" @click="handleItemClick(originalMediaData)">
|
<div class="pagination-dots-fixed">
|
||||||
{{ currentDescription }}
|
<span
|
||||||
</div>
|
v-for="(dot, index) in originalMediaData"
|
||||||
|
:key="index"
|
||||||
</SidePanelItem>
|
class="dot"
|
||||||
<!-- 设施详情弹框 -->
|
:class="{ active: getCurrentRealIndex() === index }"
|
||||||
<a-modal v-model:open="modalVisible" :title="'设施类型介绍'" width="1536px" :footer="null">
|
@click="goToSlide(index)"
|
||||||
<!-- 你在这里编写弹框内容 -->
|
></span>
|
||||||
<div v-if="currentItem">
|
</div>
|
||||||
<ArtsDetail :dataSource='currentItem' :index="getCurrentRealIndex()" />
|
</div>
|
||||||
</div>
|
|
||||||
</a-modal>
|
|
||||||
|
|
||||||
|
<!-- 独立的文字说明区域(随跑马灯切换而变化) -->
|
||||||
|
<div class="description-text" @click="handleItemClick(originalMediaData)">
|
||||||
|
{{ currentDescription }}
|
||||||
|
</div>
|
||||||
|
</SidePanelItem>
|
||||||
|
<!-- 设施详情弹框 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="modalVisible"
|
||||||
|
:title="'设施类型介绍'"
|
||||||
|
width="1536px"
|
||||||
|
:footer="null"
|
||||||
|
>
|
||||||
|
<!-- 你在这里编写弹框内容 -->
|
||||||
|
<div v-if="currentItem">
|
||||||
|
<ArtsDetail :dataSource="currentItem" :index="getCurrentRealIndex()" />
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted, onUnmounted, computed } from 'vue';
|
import { ref, onMounted, onUnmounted, computed } from 'vue';
|
||||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
import ArtsDetail from '@/components/carouselIntroduce/ArtsDetail.vue';
|
import ArtsDetail from '@/components/carouselIntroduce/ArtsDetail.vue';
|
||||||
import { sttpbGetKendoList } from "@/api/sw";
|
import { sttpbGetKendoList } from '@/api/sw';
|
||||||
// 定义组件名(便于调试和递归)
|
// 定义组件名(便于调试和递归)
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'diwenshuijianhuansheshileixingzuchengjijieruqingkuang'
|
name: 'diwenshuijianhuansheshileixingzuchengjijieruqingkuang'
|
||||||
});
|
});
|
||||||
|
|
||||||
// 媒体类型定义
|
// 媒体类型定义
|
||||||
interface MediaItem {
|
interface MediaItem {
|
||||||
type: 'image' | 'video';
|
type: 'image' | 'video';
|
||||||
url: string;
|
url: string;
|
||||||
text: string;
|
text: string;
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 原始媒体数据(3条测试数据)
|
// 原始媒体数据(3条测试数据)
|
||||||
const originalMediaData = ref<MediaItem[]>([
|
const originalMediaData = ref<MediaItem[]>([]);
|
||||||
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 克隆首尾项后的渲染数组(用于无缝循环)
|
// 克隆首尾项后的渲染数组(用于无缝循环)
|
||||||
// 结构:[最后一项克隆, 原始数据..., 第一项克隆]
|
// 结构:[最后一项克隆, 原始数据..., 第一项克隆]
|
||||||
@ -81,252 +98,250 @@ const modalVisible = ref(false);
|
|||||||
const currentItem = ref<any>(null);
|
const currentItem = ref<any>(null);
|
||||||
// 初始化渲染数组(克隆首尾项)
|
// 初始化渲染数组(克隆首尾项)
|
||||||
const initRenderData = () => {
|
const initRenderData = () => {
|
||||||
const length = originalMediaData.value.length;
|
const length = originalMediaData.value.length;
|
||||||
if (length === 0) return;
|
if (length === 0) return;
|
||||||
|
|
||||||
renderMediaData.value = [
|
renderMediaData.value = [
|
||||||
originalMediaData.value[length - 1], // 克隆最后一项
|
originalMediaData.value[length - 1], // 克隆最后一项
|
||||||
...originalMediaData.value, // 原始数据
|
...originalMediaData.value, // 原始数据
|
||||||
originalMediaData.value[0] // 克隆第一项
|
originalMediaData.value[0] // 克隆第一项
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
// 启动自动轮播
|
// 启动自动轮播
|
||||||
const startAutoPlay = () => {
|
const startAutoPlay = () => {
|
||||||
if (timer) clearInterval(timer);
|
if (timer) clearInterval(timer);
|
||||||
timer = setInterval(() => {
|
timer = setInterval(() => {
|
||||||
if (!isHovering.value && !isTransitioning.value) {
|
if (!isHovering.value && !isTransitioning.value) {
|
||||||
nextSlide();
|
nextSlide();
|
||||||
}
|
}
|
||||||
}, 4000);
|
}, 4000);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 切换到下一张
|
// 切换到下一张
|
||||||
const nextSlide = () => {
|
const nextSlide = () => {
|
||||||
currentIndex.value++;
|
currentIndex.value++;
|
||||||
|
|
||||||
// 延迟检查是否需要无缝跳转
|
// 延迟检查是否需要无缝跳转
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
checkSeamlessJump();
|
checkSeamlessJump();
|
||||||
}, 500); // 与transition时间一致
|
}, 500); // 与transition时间一致
|
||||||
};
|
};
|
||||||
|
|
||||||
// 检查是否需要无缝跳转(克隆项处理)
|
// 检查是否需要无缝跳转(克隆项处理)
|
||||||
const checkSeamlessJump = () => {
|
const checkSeamlessJump = () => {
|
||||||
const realLength = originalMediaData.value.length;
|
const realLength = originalMediaData.value.length;
|
||||||
|
|
||||||
// 如果到达克隆的最后一项(索引 = realLength + 1)
|
// 如果到达克隆的最后一项(索引 = realLength + 1)
|
||||||
if (currentIndex.value >= realLength + 1) {
|
if (currentIndex.value >= realLength + 1) {
|
||||||
// 1. 禁用过渡动画,实现瞬间跳转
|
// 1. 禁用过渡动画,实现瞬间跳转
|
||||||
isTransitioning.value = true;
|
isTransitioning.value = true;
|
||||||
|
|
||||||
// 2. 瞬间跳转到真实的第二项(索引1),用户看不到跳变
|
// 2. 瞬间跳转到真实的第二项(索引1),用户看不到跳变
|
||||||
currentIndex.value = 1;
|
currentIndex.value = 1;
|
||||||
|
|
||||||
// 3. 等待两帧后恢复过渡动画(确保DOM已更新)
|
// 3. 等待两帧后恢复过渡动画(确保DOM已更新)
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
isTransitioning.value = false;
|
isTransitioning.value = false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理鼠标进入
|
// 处理鼠标进入
|
||||||
const handleMouseEnter = () => {
|
const handleMouseEnter = () => {
|
||||||
isHovering.value = true;
|
isHovering.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理鼠标离开
|
// 处理鼠标离开
|
||||||
const handleMouseLeave = () => {
|
const handleMouseLeave = () => {
|
||||||
isHovering.value = false;
|
isHovering.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 计算当前显示的描述文字
|
// 计算当前显示的描述文字
|
||||||
const currentDescription = computed(() => {
|
const currentDescription = computed(() => {
|
||||||
const realIndex = getCurrentRealIndex();
|
const realIndex = getCurrentRealIndex();
|
||||||
return originalMediaData.value[realIndex]?.description || '';
|
return originalMediaData.value[realIndex]?.description || '';
|
||||||
});
|
});
|
||||||
const getListData = async () => {
|
const getListData = async () => {
|
||||||
try {
|
try {
|
||||||
const params = {
|
const params = {
|
||||||
"filter": {
|
filter: {
|
||||||
"logic": "and",
|
logic: 'and',
|
||||||
"filters": [
|
filters: [
|
||||||
{
|
{
|
||||||
"field": "fullPath",
|
field: 'fullPath',
|
||||||
"operator": "contains",
|
operator: 'contains',
|
||||||
"dataType": "string",
|
dataType: 'string',
|
||||||
"value": "DW,DW"
|
value: 'DW,DW'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"field": "logo",
|
field: 'logo',
|
||||||
"operator": "isnotnull",
|
operator: 'isnotnull',
|
||||||
"dataType": "string"
|
dataType: 'string'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"field": "introduce",
|
field: 'introduce',
|
||||||
"operator": "isnotnull",
|
operator: 'isnotnull',
|
||||||
"dataType": "string"
|
dataType: 'string'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
let res = await sttpbGetKendoList(params);
|
let res = await sttpbGetKendoList(params);
|
||||||
let data = res?.data?.data || res?.data || [];
|
let data = res?.data?.data || res?.data || [];
|
||||||
|
|
||||||
// 将接口返回的数据转换为轮播图格式
|
// 将接口返回的数据转换为轮播图格式
|
||||||
const baseUrl = import.meta.env.VITE_APP_PREVIEW_URL;
|
const baseUrl = import.meta.env.VITE_APP_ATTACHMENT_URL;
|
||||||
originalMediaData.value = data.map((item: any) => ({
|
originalMediaData.value = data.map((item: any) => ({
|
||||||
text: item.sttpName,
|
text: item.sttpName,
|
||||||
description: item.introduce,
|
description: item.introduce,
|
||||||
url: item.logo ? `${baseUrl}/?${item.logo}` : ''
|
url: item.logo ? `${baseUrl}/?${item.logo}` : ''
|
||||||
}));
|
}));
|
||||||
console.log(originalMediaData.value);
|
console.log(originalMediaData.value);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
originalMediaData.value = [];
|
||||||
originalMediaData.value = [];
|
} finally {
|
||||||
} finally {
|
}
|
||||||
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理项目点击事件
|
// 处理项目点击事件
|
||||||
const handleItemClick = (item: any) => {
|
const handleItemClick = (item: any) => {
|
||||||
console.log('轮播图项目被点击:', item,getCurrentRealIndex());
|
console.log('轮播图项目被点击:', item, getCurrentRealIndex());
|
||||||
currentItem.value = item;
|
currentItem.value = item;
|
||||||
modalVisible.value = true;
|
modalVisible.value = true;
|
||||||
};
|
};
|
||||||
// 页面加载时执行
|
// 页面加载时执行
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 先获取数据
|
// 先获取数据
|
||||||
await getListData();
|
await getListData();
|
||||||
// 数据加载完成后初始化渲染数组
|
// 数据加载完成后初始化渲染数组
|
||||||
initRenderData();
|
initRenderData();
|
||||||
// 最后启动自动轮播
|
// 最后启动自动轮播
|
||||||
startAutoPlay();
|
startAutoPlay();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 组件卸载时清理
|
// 组件卸载时清理
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (timer) clearInterval(timer);
|
if (timer) clearInterval(timer);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 获取当前真实的索引(排除克隆项的影响)
|
// 获取当前真实的索引(排除克隆项的影响)
|
||||||
const getCurrentRealIndex = () => {
|
const getCurrentRealIndex = () => {
|
||||||
const realLength = originalMediaData.value.length;
|
const realLength = originalMediaData.value.length;
|
||||||
let realIndex = currentIndex.value - 1; // 减去克隆的首项偏移
|
let realIndex = currentIndex.value - 1; // 减去克隆的首项偏移
|
||||||
|
|
||||||
// 处理边界情况(无缝循环时的克隆项)
|
// 处理边界情况(无缝循环时的克隆项)
|
||||||
if (realIndex < 0) realIndex = realLength - 1;
|
if (realIndex < 0) realIndex = realLength - 1;
|
||||||
if (realIndex >= realLength) realIndex = 0;
|
if (realIndex >= realLength) realIndex = 0;
|
||||||
|
|
||||||
return realIndex;
|
return realIndex;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 跳转到指定幻灯片
|
// 跳转到指定幻灯片
|
||||||
const goToSlide = (targetIndex: number) => {
|
const goToSlide = (targetIndex: number) => {
|
||||||
if (isTransitioning.value) return;
|
if (isTransitioning.value) return;
|
||||||
|
|
||||||
// 计算目标索引(考虑克隆项偏移)
|
// 计算目标索引(考虑克隆项偏移)
|
||||||
currentIndex.value = targetIndex + 1;
|
currentIndex.value = targetIndex + 1;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.container {
|
.container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 228px;
|
height: 228px;
|
||||||
// border: 1px solid #7fd6ff;
|
// border: 1px solid #7fd6ff;
|
||||||
// border-radius: 5px;
|
// border-radius: 5px;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden; // 隐藏超出容器的内容
|
overflow: hidden; // 隐藏超出容器的内容
|
||||||
|
|
||||||
// 跑马灯轨道
|
// 跑马灯轨道
|
||||||
.carousel-track {
|
.carousel-track {
|
||||||
display: flex; // 横向排列所有媒体项
|
display: flex; // 横向排列所有媒体项
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
transition: transform 0.5s ease-in-out; // 0.5秒平滑过渡
|
||||||
|
|
||||||
|
// 禁用过渡动画(用于无缝跳转)
|
||||||
|
&.no-transition {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单个媒体项
|
||||||
|
.carousel-item {
|
||||||
|
min-width: 100%; // 每个项目占满容器宽度
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0; // 防止被压缩
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
img,
|
||||||
|
video {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
transition: transform 0.5s ease-in-out; // 0.5秒平滑过渡
|
object-fit: cover; // 保持比例填充
|
||||||
|
}
|
||||||
|
|
||||||
// 禁用过渡动画(用于无缝跳转)
|
.text {
|
||||||
&.no-transition {
|
|
||||||
transition: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 单个媒体项
|
|
||||||
.carousel-item {
|
|
||||||
min-width: 100%; // 每个项目占满容器宽度
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
flex-shrink: 0; // 防止被压缩
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
img,
|
|
||||||
video {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover; // 保持比例填充
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 22px;
|
|
||||||
line-height: 22px;
|
|
||||||
background: rgba(0, 0, 0, 0.1);
|
|
||||||
color: #fff;
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 固定的面板指示器
|
|
||||||
.pagination-dots-fixed {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 10px;
|
bottom: 0;
|
||||||
right: 10px;
|
left: 0;
|
||||||
display: flex;
|
width: 100%;
|
||||||
gap: 6px;
|
height: 22px;
|
||||||
z-index: 10; // 确保在文字上方
|
line-height: 22px;
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
.dot {
|
color: #fff;
|
||||||
width: 5px;
|
padding-left: 10px;
|
||||||
height: 5px;
|
}
|
||||||
border-radius: 50%;
|
|
||||||
background-color: #D8D8D8;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.3s ease;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background-color: #005293;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 固定的面板指示器
|
||||||
|
.pagination-dots-fixed {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
right: 10px;
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
z-index: 10; // 确保在文字上方
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #d8d8d8;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #005293;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 独立的文字说明区域
|
// 独立的文字说明区域
|
||||||
.description-text {
|
.description-text {
|
||||||
// padding: 8px 12px;
|
// padding: 8px 12px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
// min-height: 40px;
|
// min-height: 40px;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
// 多行文本溢出省略
|
// 多行文本溢出省略
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -1,9 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="live-video-box" :style="{ height }">
|
<div
|
||||||
|
class="live-video-box"
|
||||||
|
:class="{ 'no-title': !showTitle }"
|
||||||
|
:style="{ height }"
|
||||||
|
>
|
||||||
<!-- 有视频时显示播放器 -->
|
<!-- 有视频时显示播放器 -->
|
||||||
<div v-if="videoUrl" class="video-container">
|
<div v-if="videoUrl" class="video-container">
|
||||||
<!-- 视频标题和关闭按钮 -->
|
<!-- 视频标题和关闭按钮 -->
|
||||||
<div class="live-video-title">
|
<div v-if="showTitle" class="live-video-title">
|
||||||
{{ videoData?.flnm }}
|
{{ videoData?.flnm }}
|
||||||
<div class="title-actions">
|
<div class="title-actions">
|
||||||
<CloseOutlined @click="handleClose" />
|
<CloseOutlined @click="handleClose" />
|
||||||
@ -21,11 +25,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, watch, computed } from "vue";
|
import { ref, onMounted, onUnmounted, watch, computed } from 'vue';
|
||||||
import { CloseOutlined } from "@ant-design/icons-vue";
|
import { CloseOutlined } from '@ant-design/icons-vue';
|
||||||
import videojs from "video.js";
|
import videojs from 'video.js';
|
||||||
import "video.js/dist/video-js.css";
|
import 'video.js/dist/video-js.css';
|
||||||
const baseUrl = import.meta.env.VITE_APP_PREVIEW_URL;
|
const baseUrl = import.meta.env.VITE_APP_ATTACHMENT_URL;
|
||||||
|
|
||||||
// 注册 HLS 支持(video.js 8.x 内置了 HLS 支持)
|
// 注册 HLS 支持(video.js 8.x 内置了 HLS 支持)
|
||||||
|
|
||||||
@ -37,11 +41,17 @@ interface VideoData {
|
|||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = withDefaults(
|
||||||
videoData?: VideoData | null;
|
defineProps<{
|
||||||
height?: string;
|
videoData?: VideoData | null;
|
||||||
monitorType?: "live" | "record"; // 监控类型:live-实时视频, record-录像
|
height?: string;
|
||||||
}>();
|
monitorType?: 'live' | 'record'; // 监控类型:live-实时视频, record-录像
|
||||||
|
showTitle?: boolean; // 是否显示标题栏
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
showTitle: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
close: [nodeKey?: string];
|
close: [nodeKey?: string];
|
||||||
@ -53,7 +63,7 @@ let player: any = null;
|
|||||||
|
|
||||||
// 获取第一个有效的URL
|
// 获取第一个有效的URL
|
||||||
const videoUrl = computed(() => {
|
const videoUrl = computed(() => {
|
||||||
if (!props.videoData) return "";
|
if (!props.videoData) return '';
|
||||||
|
|
||||||
// 优先使用url字段
|
// 优先使用url字段
|
||||||
if (props.videoData.url) return props.videoData.url;
|
if (props.videoData.url) return props.videoData.url;
|
||||||
@ -67,7 +77,7 @@ const videoUrl = computed(() => {
|
|||||||
return props.videoData.urls[0];
|
return props.videoData.urls[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
return '';
|
||||||
});
|
});
|
||||||
|
|
||||||
// 初始化 video.js 播放器
|
// 初始化 video.js 播放器
|
||||||
@ -81,49 +91,49 @@ const initPlayer = () => {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// 创建 video 元素
|
// 创建 video 元素
|
||||||
const videoElement = document.createElement("video");
|
const videoElement = document.createElement('video');
|
||||||
videoElement.className = "video-js vjs-default-skin vjs-big-play-centered";
|
videoElement.className = 'video-js vjs-default-skin vjs-big-play-centered';
|
||||||
videoElement.setAttribute("id", playerId);
|
videoElement.setAttribute('id', playerId);
|
||||||
videoElement.setAttribute("playsinline", "playsinline");
|
videoElement.setAttribute('playsinline', 'playsinline');
|
||||||
videoElement.setAttribute("webkit-playsinline", "webkit-playsinline");
|
videoElement.setAttribute('webkit-playsinline', 'webkit-playsinline');
|
||||||
|
|
||||||
// 将 video 元素添加到容器
|
// 将 video 元素添加到容器
|
||||||
const container = document.getElementById(playerId);
|
const container = document.getElementById(playerId);
|
||||||
if (container) {
|
if (container) {
|
||||||
container.innerHTML = "";
|
container.innerHTML = '';
|
||||||
container.appendChild(videoElement);
|
container.appendChild(videoElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据 monitorType 判断是否为直播流
|
// 根据 monitorType 判断是否为直播流
|
||||||
// live: 实时视频流(m3u8),显示直播UI
|
// live: 实时视频流(m3u8),显示直播UI
|
||||||
// record: 录像文件,显示进度条
|
// record: 录像文件,显示进度条
|
||||||
const isLiveStream = props.monitorType === "live";
|
const isLiveStream = props.monitorType === 'live';
|
||||||
|
|
||||||
// 根据 URL 后缀或监控类型确定视频 MIME Type
|
// 根据 URL 后缀或监控类型确定视频 MIME Type
|
||||||
const getVideoType = (url: string): string => {
|
const getVideoType = (url: string): string => {
|
||||||
const lowerUrl = url.toLowerCase();
|
const lowerUrl = url.toLowerCase();
|
||||||
|
|
||||||
// 优先根据 URL 后缀判断
|
// 优先根据 URL 后缀判断
|
||||||
if (lowerUrl.includes(".m3u8")) {
|
if (lowerUrl.includes('.m3u8')) {
|
||||||
return "application/x-mpegURL"; // HLS 流
|
return 'application/x-mpegURL'; // HLS 流
|
||||||
} else if (lowerUrl.includes(".mp4")) {
|
} else if (lowerUrl.includes('.mp4')) {
|
||||||
return "video/mp4";
|
return 'video/mp4';
|
||||||
} else if (lowerUrl.includes(".webm")) {
|
} else if (lowerUrl.includes('.webm')) {
|
||||||
return "video/webm";
|
return 'video/webm';
|
||||||
} else if (lowerUrl.includes(".ogg") || lowerUrl.includes(".ogv")) {
|
} else if (lowerUrl.includes('.ogg') || lowerUrl.includes('.ogv')) {
|
||||||
return "video/ogg";
|
return 'video/ogg';
|
||||||
} else if (lowerUrl.includes(".avi")) {
|
} else if (lowerUrl.includes('.avi')) {
|
||||||
return "video/x-msvideo";
|
return 'video/x-msvideo';
|
||||||
} else if (lowerUrl.includes(".mov")) {
|
} else if (lowerUrl.includes('.mov')) {
|
||||||
return "video/quicktime";
|
return 'video/quicktime';
|
||||||
} else if (lowerUrl.includes(".wmv")) {
|
} else if (lowerUrl.includes('.wmv')) {
|
||||||
return "video/x-ms-wmv";
|
return 'video/x-ms-wmv';
|
||||||
} else if (lowerUrl.includes(".flv")) {
|
} else if (lowerUrl.includes('.flv')) {
|
||||||
return "video/x-flv";
|
return 'video/x-flv';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果 URL 中没有明确后缀,根据监控类型判断
|
// 如果 URL 中没有明确后缀,根据监控类型判断
|
||||||
return isLiveStream ? "application/x-mpegURL" : "video/mp4";
|
return isLiveStream ? 'application/x-mpegURL' : 'video/mp4';
|
||||||
};
|
};
|
||||||
|
|
||||||
const videoType = getVideoType(videoUrl.value);
|
const videoType = getVideoType(videoUrl.value);
|
||||||
@ -134,56 +144,56 @@ const initPlayer = () => {
|
|||||||
muted: true,
|
muted: true,
|
||||||
controls: true,
|
controls: true,
|
||||||
fluid: false,
|
fluid: false,
|
||||||
width: "100%",
|
width: '100%',
|
||||||
height: "100%",
|
height: '100%',
|
||||||
preload: "auto",
|
preload: 'auto',
|
||||||
playbackRates: [0.5, 1, 1.5, 2],
|
playbackRates: [0.5, 1, 1.5, 2],
|
||||||
liveui: isLiveStream, // 直播流显示直播UI
|
liveui: isLiveStream, // 直播流显示直播UI
|
||||||
// 录像模式下禁用直播相关功能
|
// 录像模式下禁用直播相关功能
|
||||||
inactivityTimeout: 0, // 禁用不活动超时
|
inactivityTimeout: 0, // 禁用不活动超时
|
||||||
controlBar: {
|
controlBar: {
|
||||||
children: [
|
children: [
|
||||||
"playToggle",
|
'playToggle',
|
||||||
"volumePanel",
|
'volumePanel',
|
||||||
"currentTimeDisplay",
|
'currentTimeDisplay',
|
||||||
"timeDivider",
|
'timeDivider',
|
||||||
"durationDisplay",
|
'durationDisplay',
|
||||||
"progressControl",
|
'progressControl',
|
||||||
"liveDisplay",
|
'liveDisplay',
|
||||||
"remainingTimeDisplay",
|
'remainingTimeDisplay',
|
||||||
"customControlSpacer",
|
'customControlSpacer',
|
||||||
"playbackRateMenuButton",
|
'playbackRateMenuButton',
|
||||||
"fullscreenToggle",
|
'fullscreenToggle'
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
html5: {
|
html5: {
|
||||||
hls: {
|
hls: {
|
||||||
enableLowInitialPlaylist: true,
|
enableLowInitialPlaylist: true,
|
||||||
smoothQualityChange: true,
|
smoothQualityChange: true
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
sources: [
|
sources: [
|
||||||
{
|
{
|
||||||
src: videoUrl.value,
|
src: videoUrl.value,
|
||||||
type: videoType, // 指定视频类型
|
type: videoType // 指定视频类型
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
// 如果是录像模式,隐藏直播指示器
|
// 如果是录像模式,隐藏直播指示器
|
||||||
if (!isLiveStream) {
|
if (!isLiveStream) {
|
||||||
player.ready(() => {
|
player.ready(() => {
|
||||||
const liveDisplay = player.controlBar.getChild("liveDisplay");
|
const liveDisplay = player.controlBar.getChild('liveDisplay');
|
||||||
if (liveDisplay) {
|
if (liveDisplay) {
|
||||||
liveDisplay.el().style.display = "none";
|
liveDisplay.el().style.display = 'none';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听错误事件
|
// 监听错误事件
|
||||||
player.on("error", () => {
|
player.on('error', () => {
|
||||||
const error = player.error();
|
const error = player.error();
|
||||||
console.error("播放器错误:", error);
|
console.error('播放器错误:', error);
|
||||||
|
|
||||||
// 保持最后一帧画面,不显示错误提示
|
// 保持最后一帧画面,不显示错误提示
|
||||||
// 在后台静默重试
|
// 在后台静默重试
|
||||||
@ -197,7 +207,7 @@ const initPlayer = () => {
|
|||||||
try {
|
try {
|
||||||
player.src({
|
player.src({
|
||||||
src: videoUrl.value,
|
src: videoUrl.value,
|
||||||
type: videoType, // 指定视频类型
|
type: videoType // 指定视频类型
|
||||||
});
|
});
|
||||||
player.load();
|
player.load();
|
||||||
player.play();
|
player.play();
|
||||||
@ -214,16 +224,16 @@ const initPlayer = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 监听加载完成
|
// 监听加载完成
|
||||||
player.on("loadeddata", () => {});
|
player.on('loadeddata', () => {});
|
||||||
|
|
||||||
// 监听播放状态
|
// 监听播放状态
|
||||||
player.on("play", () => {});
|
player.on('play', () => {});
|
||||||
|
|
||||||
// 监听暂停
|
// 监听暂停
|
||||||
player.on("pause", () => {});
|
player.on('pause', () => {});
|
||||||
|
|
||||||
// 监听等待(缓冲)
|
// 监听等待(缓冲)
|
||||||
player.on("waiting", () => {});
|
player.on('waiting', () => {});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 静默处理错误
|
// 静默处理错误
|
||||||
}
|
}
|
||||||
@ -232,7 +242,7 @@ const initPlayer = () => {
|
|||||||
// 关闭视频
|
// 关闭视频
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
// 传递节点的 key 给父组件
|
// 传递节点的 key 给父组件
|
||||||
emit("close", props.videoData?.key);
|
emit('close', props.videoData?.key);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监听视频URL变化
|
// 监听视频URL变化
|
||||||
@ -323,6 +333,12 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.no-title {
|
||||||
|
.video-container {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.video-empty {
|
.video-empty {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
:list-url="statistics"
|
:list-url="statistics"
|
||||||
:transform-data="customTransform"
|
:transform-data="customTransform"
|
||||||
>
|
>
|
||||||
<template #headerCell="{ column }">
|
<template #bodyCell="{ column }">
|
||||||
<!-- 判断列的 key,如果是 selection,就显示自定义内容 -->
|
<!-- 判断列的 key,如果是 selection,就显示自定义内容 -->
|
||||||
<span v-if="column.key === 'selection'">
|
<span v-if="column.key === 'selection'">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@ -32,12 +32,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import dayjs from "dayjs";
|
import dayjs from 'dayjs';
|
||||||
import { ref, onMounted, h, computed } from "vue";
|
import { ref, onMounted, h, computed } from 'vue';
|
||||||
import { Checkbox, Tooltip, message, Modal } from "ant-design-vue";
|
import { Checkbox, Tooltip, message, Modal } from 'ant-design-vue';
|
||||||
import BasicTable from "@/components/BasicTable/index.vue";
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
import GuoYuDaoShuTongJiSearch from "./guoYuDaoShuTongJiSearch.vue";
|
import GuoYuDaoShuTongJiSearch from './guoYuDaoShuTongJiSearch.vue';
|
||||||
import { statistics, batchUrgeContent } from "@/api/guoYuSheShiShuJuTianBao";
|
import { statistics, batchUrgeContent } from '@/api/guoYuSheShiShuJuTianBao';
|
||||||
const searchRef = ref();
|
const searchRef = ref();
|
||||||
const selectedRowKeys = ref<string[]>([]);
|
const selectedRowKeys = ref<string[]>([]);
|
||||||
const detailTableRef = ref();
|
const detailTableRef = ref();
|
||||||
@ -46,12 +46,13 @@ const batchData = ref([]);
|
|||||||
const tableDataList = ref<any[]>([]); // 用于存储当前表格数据,方便查找
|
const tableDataList = ref<any[]>([]); // 用于存储当前表格数据,方便查找
|
||||||
|
|
||||||
// ✅ 1. 定义获取唯一 Key 的方法 (请根据实际数据字段调整,比如 id, stcd 等)
|
// ✅ 1. 定义获取唯一 Key 的方法 (请根据实际数据字段调整,比如 id, stcd 等)
|
||||||
const getUniqueKey = (item: any) => item.id || item.stcd || JSON.stringify(item);
|
const getUniqueKey = (item: any) =>
|
||||||
|
item.id || item.stcd || JSON.stringify(item);
|
||||||
|
|
||||||
// ✅ 2. 获取同一组的所有 Key (用于联动选中)
|
// ✅ 2. 获取同一组的所有 Key (用于联动选中)
|
||||||
const getGroupKeys = (currentRecord: any) => {
|
const getGroupKeys = (currentRecord: any) => {
|
||||||
const keys: string[] = [];
|
const keys: string[] = [];
|
||||||
tableDataList.value.forEach((item) => {
|
tableDataList.value.forEach(item => {
|
||||||
if (
|
if (
|
||||||
item.basinNames === currentRecord.basinNames &&
|
item.basinNames === currentRecord.basinNames &&
|
||||||
item.stationNames === currentRecord.stationNames
|
item.stationNames === currentRecord.stationNames
|
||||||
@ -69,9 +70,9 @@ const handleGroupSelect = (checked: boolean, groupKeys: string[]) => {
|
|||||||
const newSelectedKeys = new Set(selectedRowKeys.value);
|
const newSelectedKeys = new Set(selectedRowKeys.value);
|
||||||
|
|
||||||
if (checked) {
|
if (checked) {
|
||||||
groupKeys.forEach((key) => newSelectedKeys.add(key));
|
groupKeys.forEach(key => newSelectedKeys.add(key));
|
||||||
} else {
|
} else {
|
||||||
groupKeys.forEach((key) => newSelectedKeys.delete(key));
|
groupKeys.forEach(key => newSelectedKeys.delete(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedRowKeys.value = Array.from(newSelectedKeys);
|
selectedRowKeys.value = Array.from(newSelectedKeys);
|
||||||
@ -85,8 +86,8 @@ const headerCheckboxState = computed(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 1. 只获取【有效行】(hasData != 1)的 Key
|
// 1. 只获取【有效行】(hasData != 1)的 Key
|
||||||
const validItems = tableDataList.value.filter((item) => item.hasData != 1);
|
const validItems = tableDataList.value.filter(item => item.hasData != 1);
|
||||||
const validKeys = validItems.map((item) => getUniqueKey(item));
|
const validKeys = validItems.map(item => getUniqueKey(item));
|
||||||
|
|
||||||
// 如果没有有效行,直接返回未选中
|
// 如果没有有效行,直接返回未选中
|
||||||
if (validKeys.length === 0) {
|
if (validKeys.length === 0) {
|
||||||
@ -94,8 +95,9 @@ const headerCheckboxState = computed(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. 计算有多少【有效行】被选中
|
// 2. 计算有多少【有效行】被选中
|
||||||
const selectedCount = validKeys.filter((key) => selectedRowKeys.value.includes(key))
|
const selectedCount = validKeys.filter(key =>
|
||||||
.length;
|
selectedRowKeys.value.includes(key)
|
||||||
|
).length;
|
||||||
|
|
||||||
// 3. 判断状态
|
// 3. 判断状态
|
||||||
const isAllSelected = selectedCount === validKeys.length;
|
const isAllSelected = selectedCount === validKeys.length;
|
||||||
@ -103,12 +105,12 @@ const headerCheckboxState = computed(() => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
checked: isAllSelected,
|
checked: isAllSelected,
|
||||||
indeterminate: isIndeterminate,
|
indeterminate: isIndeterminate
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const updateBatchData = () => {
|
const updateBatchData = () => {
|
||||||
// 从 tableDataList 中找出所有 key 在 selectedRowKeys 中的完整对象
|
// 从 tableDataList 中找出所有 key 在 selectedRowKeys 中的完整对象
|
||||||
batchData.value = tableDataList.value.filter((item) =>
|
batchData.value = tableDataList.value.filter(item =>
|
||||||
selectedRowKeys.value.includes(getUniqueKey(item))
|
selectedRowKeys.value.includes(getUniqueKey(item))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -117,8 +119,8 @@ const handleHeaderSelectAll = (e: any) => {
|
|||||||
const checked = e.target.checked;
|
const checked = e.target.checked;
|
||||||
if (checked) {
|
if (checked) {
|
||||||
// 选中所有有效行
|
// 选中所有有效行
|
||||||
const validItems = tableDataList.value.filter((item) => item.hasData != 1);
|
const validItems = tableDataList.value.filter(item => item.hasData != 1);
|
||||||
const allKeys = validItems.map((item) => getUniqueKey(item));
|
const allKeys = validItems.map(item => getUniqueKey(item));
|
||||||
selectedRowKeys.value = allKeys;
|
selectedRowKeys.value = allKeys;
|
||||||
} else {
|
} else {
|
||||||
// 清空所有选中
|
// 清空所有选中
|
||||||
@ -129,14 +131,14 @@ const handleHeaderSelectAll = (e: any) => {
|
|||||||
|
|
||||||
let columns = ref([
|
let columns = ref([
|
||||||
{
|
{
|
||||||
title: "选择",
|
title: '选择',
|
||||||
key: "selection",
|
key: 'selection',
|
||||||
width: 60,
|
width: 60,
|
||||||
align: "center",
|
align: 'center',
|
||||||
// ✅ 合并选择列
|
// ✅ 合并选择列
|
||||||
customCell: (record) => {
|
customCell: record => {
|
||||||
return {
|
return {
|
||||||
rowSpan: record._selectionRowSpan || 0,
|
rowSpan: record._selectionRowSpan || 0
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
// ✅ 使用 h 函数渲染 Checkbox
|
// ✅ 使用 h 函数渲染 Checkbox
|
||||||
@ -148,8 +150,8 @@ let columns = ref([
|
|||||||
// 判断状态
|
// 判断状态
|
||||||
const isAllSelected =
|
const isAllSelected =
|
||||||
groupKeys.length > 0 &&
|
groupKeys.length > 0 &&
|
||||||
groupKeys.every((key) => selectedRowKeys.value.includes(key));
|
groupKeys.every(key => selectedRowKeys.value.includes(key));
|
||||||
const isPartiallySelected = groupKeys.some((key) =>
|
const isPartiallySelected = groupKeys.some(key =>
|
||||||
selectedRowKeys.value.includes(key)
|
selectedRowKeys.value.includes(key)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -157,33 +159,33 @@ let columns = ref([
|
|||||||
checked: isAllSelected,
|
checked: isAllSelected,
|
||||||
disabled: record.hasData == 1,
|
disabled: record.hasData == 1,
|
||||||
indeterminate: isPartiallySelected && !isAllSelected,
|
indeterminate: isPartiallySelected && !isAllSelected,
|
||||||
onChange: (e: any) => handleGroupSelect(e.target.checked, groupKeys),
|
onChange: (e: any) => handleGroupSelect(e.target.checked, groupKeys)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return null; // 其他行不渲染
|
return null; // 其他行不渲染
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: "basinNames",
|
dataIndex: 'basinNames',
|
||||||
key: "basinNames",
|
key: 'basinNames',
|
||||||
title: "流域",
|
title: '流域',
|
||||||
width: 160,
|
width: 160,
|
||||||
customCell: (record, index) => {
|
customCell: (record, index) => {
|
||||||
return {
|
return {
|
||||||
rowSpan: record._basinRowSpan || 0,
|
rowSpan: record._basinRowSpan || 0
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
customRender: ({ text }: any) => {
|
customRender: ({ text }: any) => {
|
||||||
if (!text) return "-";
|
if (!text) return '-';
|
||||||
|
|
||||||
const str = String(text);
|
const str = String(text);
|
||||||
// 按逗号分割(支持中文逗号和英文逗号)
|
// 按逗号分割(支持中文逗号和英文逗号)
|
||||||
const items = str.split(/[,,]/);
|
const items = str.split(/[,,]/);
|
||||||
|
|
||||||
// 如果数量超过6个,截取前6个并添加省略号
|
// 如果数量超过6个,截取前6个并添加省略号
|
||||||
let displayText = "";
|
let displayText = '';
|
||||||
if (items.length > 6) {
|
if (items.length > 6) {
|
||||||
displayText = items.slice(0, 6).join(",") + " ...";
|
displayText = items.slice(0, 6).join(',') + ' ...';
|
||||||
} else {
|
} else {
|
||||||
displayText = str;
|
displayText = str;
|
||||||
}
|
}
|
||||||
@ -193,34 +195,34 @@ let columns = ref([
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
{
|
{
|
||||||
title: str, // Tooltip 显示的全部内容
|
title: str, // Tooltip 显示的全部内容
|
||||||
placement: "topLeft", // 可选:提示框位置
|
placement: 'topLeft' // 可选:提示框位置
|
||||||
},
|
},
|
||||||
() => h("span", displayText)
|
() => h('span', displayText)
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: "stationNames",
|
dataIndex: 'stationNames',
|
||||||
key: "stationNames",
|
key: 'stationNames',
|
||||||
title: "电站名称",
|
title: '电站名称',
|
||||||
width: 160,
|
width: 160,
|
||||||
customCell: (record, index) => {
|
customCell: (record, index) => {
|
||||||
return {
|
return {
|
||||||
rowSpan: record._stationRowSpan || 0,
|
rowSpan: record._stationRowSpan || 0
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
customRender: ({ text }: any) => {
|
customRender: ({ text }: any) => {
|
||||||
if (!text) return "-";
|
if (!text) return '-';
|
||||||
|
|
||||||
const str = String(text);
|
const str = String(text);
|
||||||
// 按逗号分割(支持中文逗号和英文逗号)
|
// 按逗号分割(支持中文逗号和英文逗号)
|
||||||
const items = str.split(/[,,]/);
|
const items = str.split(/[,,]/);
|
||||||
|
|
||||||
// 如果数量超过6个,截取前6个并添加省略号
|
// 如果数量超过6个,截取前6个并添加省略号
|
||||||
let displayText = "";
|
let displayText = '';
|
||||||
if (items.length > 6) {
|
if (items.length > 6) {
|
||||||
displayText = items.slice(0, 6).join(",") + " ...";
|
displayText = items.slice(0, 6).join(',') + ' ...';
|
||||||
} else {
|
} else {
|
||||||
displayText = str;
|
displayText = str;
|
||||||
}
|
}
|
||||||
@ -230,33 +232,38 @@ let columns = ref([
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
{
|
{
|
||||||
title: str, // Tooltip 显示的全部内容
|
title: str, // Tooltip 显示的全部内容
|
||||||
placement: "topLeft", // 可选:提示框位置
|
placement: 'topLeft' // 可选:提示框位置
|
||||||
},
|
},
|
||||||
() => h("span", displayText)
|
() => h('span', displayText)
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{ dataIndex: "reportMonth", key: "reportMonth", title: "月份", width: 80 },
|
{ dataIndex: 'reportMonth', key: 'reportMonth', title: '月份', width: 80 },
|
||||||
{
|
{
|
||||||
dataIndex: "minStrdt",
|
dataIndex: 'minStrdt',
|
||||||
key: "minStrdt",
|
key: 'minStrdt',
|
||||||
title: "过鱼开始时间",
|
title: '过鱼开始时间',
|
||||||
width: 140,
|
width: 140,
|
||||||
customRender: ({ text }) => {
|
customRender: ({ text }) => {
|
||||||
return text ? dayjs(text).format("YYYY-MM-DD") : "";
|
return text ? dayjs(text).format('YYYY-MM-DD') : '';
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: "maxEnddt",
|
dataIndex: 'maxEnddt',
|
||||||
key: "maxEnddt",
|
key: 'maxEnddt',
|
||||||
title: "过鱼结束时间",
|
title: '过鱼结束时间',
|
||||||
width: 140,
|
width: 140,
|
||||||
customRender: ({ text }) => {
|
customRender: ({ text }) => {
|
||||||
return text ? dayjs(text).format("YYYY-MM-DD") : "";
|
return text ? dayjs(text).format('YYYY-MM-DD') : '';
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{ dataIndex: "totalFcnt", key: "totalFcnt", title: "本月过鱼总数", width: 140 },
|
{
|
||||||
{ dataIndex: "contact", key: "contact", title: "联系人/电话", width: 250 },
|
dataIndex: 'totalFcnt',
|
||||||
|
key: 'totalFcnt',
|
||||||
|
title: '本月过鱼总数',
|
||||||
|
width: 140
|
||||||
|
},
|
||||||
|
{ dataIndex: 'contact', key: 'contact', title: '联系人/电话', width: 250 }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const customTransform = (res: any) => {
|
const customTransform = (res: any) => {
|
||||||
@ -279,7 +286,7 @@ const customTransform = (res: any) => {
|
|||||||
...item,
|
...item,
|
||||||
_basinRowSpan: 1,
|
_basinRowSpan: 1,
|
||||||
_stationRowSpan: 1,
|
_stationRowSpan: 1,
|
||||||
_selectionRowSpan: 1, // ✅ 新增选择列合并标记
|
_selectionRowSpan: 1 // ✅ 新增选择列合并标记
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 3. 遍历计算合并行
|
// 3. 遍历计算合并行
|
||||||
@ -321,7 +328,7 @@ const customTransform = (res: any) => {
|
|||||||
// 4. 返回结果
|
// 4. 返回结果
|
||||||
return {
|
return {
|
||||||
records: modifiedRecords,
|
records: modifiedRecords,
|
||||||
total: res.data.total || 0,
|
total: res.data.total || 0
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -329,16 +336,16 @@ const customTransform = (res: any) => {
|
|||||||
const handleDetailSearchFinish = (values: any) => {
|
const handleDetailSearchFinish = (values: any) => {
|
||||||
const filters = [
|
const filters = [
|
||||||
values.strdt && {
|
values.strdt && {
|
||||||
field: "maxEnddt",
|
field: 'maxEnddt',
|
||||||
operator: "gte",
|
operator: 'gte',
|
||||||
dataType: "date",
|
dataType: 'date',
|
||||||
value: values.strdt[0] + " 00:00:00",
|
value: values.strdt[0] + ' 00:00:00'
|
||||||
},
|
},
|
||||||
values.strdt && {
|
values.strdt && {
|
||||||
field: "minStrdt",
|
field: 'minStrdt',
|
||||||
operator: "lte",
|
operator: 'lte',
|
||||||
dataType: "date",
|
dataType: 'date',
|
||||||
value: values.strdt[1] + " 23:59:59",
|
value: values.strdt[1] + ' 23:59:59'
|
||||||
},
|
},
|
||||||
values.rvcd !== "all" && {
|
values.rvcd !== "all" && {
|
||||||
field: "basinCode",
|
field: "basinCode",
|
||||||
@ -347,16 +354,16 @@ const handleDetailSearchFinish = (values: any) => {
|
|||||||
value: values.rvcd,
|
value: values.rvcd,
|
||||||
},
|
},
|
||||||
values.rstcd && {
|
values.rstcd && {
|
||||||
field: "stationCode",
|
field: 'stationCode',
|
||||||
operator: "eq",
|
operator: 'eq',
|
||||||
dataType: "string",
|
dataType: 'string',
|
||||||
value: values.rstcd,
|
value: values.rstcd
|
||||||
},
|
}
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
|
|
||||||
const filter = {
|
const filter = {
|
||||||
logic: "and",
|
logic: 'and',
|
||||||
filters: filters,
|
filters: filters
|
||||||
};
|
};
|
||||||
detailTableRef.value?.getList(filter);
|
detailTableRef.value?.getList(filter);
|
||||||
};
|
};
|
||||||
@ -367,17 +374,17 @@ const handleDetailReset = (values: any) => {
|
|||||||
};
|
};
|
||||||
const urgeBtn = async () => {
|
const urgeBtn = async () => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: "是否催促选择的用户?",
|
title: '是否催促选择的用户?',
|
||||||
content: h("div", {}, [
|
content: h('div', {}, [
|
||||||
"将对选中的 ",
|
'将对选中的 ',
|
||||||
h(
|
h(
|
||||||
"span",
|
'span',
|
||||||
{ style: { color: "#ff4d4f", fontWeight: "bold" } },
|
{ style: { color: '#ff4d4f', fontWeight: 'bold' } },
|
||||||
batchData.value.length + " 位用户"
|
batchData.value.length + ' 位用户'
|
||||||
),
|
),
|
||||||
"发送【",
|
'发送【',
|
||||||
h("span", { style: { color: "#ff4d4f" } }, "过鱼数据填报催促通知"),
|
h('span', { style: { color: '#ff4d4f' } }, '过鱼数据填报催促通知'),
|
||||||
"】",
|
'】'
|
||||||
]),
|
]),
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
let ids = [];
|
let ids = [];
|
||||||
@ -389,15 +396,15 @@ const urgeBtn = async () => {
|
|||||||
|
|
||||||
let res: any = await batchUrgeContent({ userIds: ids });
|
let res: any = await batchUrgeContent({ userIds: ids });
|
||||||
if (res && res?.code == 0) {
|
if (res && res?.code == 0) {
|
||||||
message.success("催促成功");
|
message.success('催促成功');
|
||||||
detailTableRef.value.loading = false;
|
detailTableRef.value.loading = false;
|
||||||
searchRef.value.btnLoading = false;
|
searchRef.value.btnLoading = false;
|
||||||
} else {
|
} else {
|
||||||
message.error("催促失败");
|
message.error('催促失败');
|
||||||
detailTableRef.value.loading = false;
|
detailTableRef.value.loading = false;
|
||||||
searchRef.value.btnLoading = false;
|
searchRef.value.btnLoading = false;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// 初始化
|
// 初始化
|
||||||
|
|||||||
@ -17,9 +17,13 @@
|
|||||||
:transform-data="customTransform"
|
:transform-data="customTransform"
|
||||||
>
|
>
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.key === 'action' || column.dataIndex === 'action'">
|
<template
|
||||||
|
v-if="column.key === 'action' || column.dataIndex === 'action'"
|
||||||
|
>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<a-button type="link" size="small" @click="handleView(record)">查看</a-button>
|
<a-button type="link" size="small" @click="handleView(record)"
|
||||||
|
>查看</a-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
@ -46,16 +50,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, onMounted, h, nextTick } from "vue";
|
import { ref, computed, onMounted, h, nextTick } from 'vue';
|
||||||
import BasicTable from "@/components/BasicTable/index.vue";
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
import PreviewMedia from "@/components/previewMedia/index.vue";
|
import PreviewMedia from '@/components/previewMedia/index.vue';
|
||||||
import GuoYuSheShiShuJuHistorySearch from "./guoYuSheShiShuJuHistorySearch.vue";
|
import GuoYuSheShiShuJuHistorySearch from './guoYuSheShiShuJuHistorySearch.vue';
|
||||||
import EditModal from "../guoYuSheShiShuJuTianBao/guoYuSheShiShuJuTianBaoForm.vue";
|
import EditModal from '../guoYuSheShiShuJuTianBao/guoYuSheShiShuJuTianBaoForm.vue';
|
||||||
import { getFishDraftPage } from "@/api/guoYuSheShiShuJuTianBao";
|
import { getFishDraftPage } from '@/api/guoYuSheShiShuJuTianBao';
|
||||||
import { Tag } from "ant-design-vue";
|
import { Tag } from 'ant-design-vue';
|
||||||
import { getDictItemsByCode } from "@/api/dict";
|
import { getDictItemsByCode } from '@/api/dict';
|
||||||
|
|
||||||
const baseUrl = import.meta.env.VITE_APP_PREVIEW_URL;
|
const baseUrl = import.meta.env.VITE_APP_ATTACHMENT_URL;
|
||||||
|
|
||||||
interface FormData {
|
interface FormData {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
@ -77,73 +81,86 @@ const guoyuStatus = ref<any>([]);
|
|||||||
|
|
||||||
const baseColumnsConfig: ColumnConfig[] = [
|
const baseColumnsConfig: ColumnConfig[] = [
|
||||||
{
|
{
|
||||||
dataIndex: "hbrvnm",
|
dataIndex: 'hbrvnm',
|
||||||
key: "hbrvnm",
|
key: 'hbrvnm',
|
||||||
title: "流域",
|
title: '流域',
|
||||||
width: 120,
|
width: 120,
|
||||||
fixed: "left",
|
fixed: 'left'
|
||||||
},
|
},
|
||||||
{ dataIndex: "ennm", key: "ennm", title: "电站名称", width: 120, fixed: "left" },
|
|
||||||
{ dataIndex: "stnm", key: "stnm", title: "过鱼设施名称", width: 150, fixed: "left" },
|
|
||||||
{ dataIndex: "strdt", key: "strdt", title: "过鱼时间", width: 150 },
|
|
||||||
{ dataIndex: "ftpName", key: "ftpName", title: "鱼种类", width: 120 },
|
|
||||||
{
|
{
|
||||||
dataIndex: "isfs",
|
dataIndex: 'ennm',
|
||||||
key: "isfs",
|
key: 'ennm',
|
||||||
title: "是否鱼苗",
|
title: '电站名称',
|
||||||
|
width: 120,
|
||||||
|
fixed: 'left'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'stnm',
|
||||||
|
key: 'stnm',
|
||||||
|
title: '过鱼设施名称',
|
||||||
|
width: 150,
|
||||||
|
fixed: 'left'
|
||||||
|
},
|
||||||
|
{ dataIndex: 'strdt', key: 'strdt', title: '过鱼时间', width: 150 },
|
||||||
|
{ dataIndex: 'ftpName', key: 'ftpName', title: '鱼种类', width: 120 },
|
||||||
|
{
|
||||||
|
dataIndex: 'isfs',
|
||||||
|
key: 'isfs',
|
||||||
|
title: '是否鱼苗',
|
||||||
width: 74,
|
width: 74,
|
||||||
customRender: ({ text }: any) => {
|
customRender: ({ text }: any) => {
|
||||||
const isYes = text === 1 || text === "1";
|
const isYes = text === 1 || text === '1';
|
||||||
return h(
|
return h(
|
||||||
Tag,
|
Tag,
|
||||||
{
|
{
|
||||||
color: isYes ? "success" : "error",
|
color: isYes ? 'success' : 'error',
|
||||||
style: { margin: 0 },
|
style: { margin: 0 }
|
||||||
},
|
},
|
||||||
() => (isYes ? "是" : "否")
|
() => (isYes ? '是' : '否')
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: "direction",
|
dataIndex: 'direction',
|
||||||
key: "direction",
|
key: 'direction',
|
||||||
title: "游向",
|
title: '游向',
|
||||||
width: 80,
|
width: 80,
|
||||||
customRender: ({ text }: any) =>
|
customRender: ({ text }: any) =>
|
||||||
direction.value.find((item: any) => item.itemCode === text)?.dictName || "-",
|
direction.value.find((item: any) => item.itemCode === text)?.dictName ||
|
||||||
|
'-'
|
||||||
},
|
},
|
||||||
{ dataIndex: "fcnt", key: "fcnt", title: "过鱼数量(尾)", width: 120 },
|
{ dataIndex: 'fcnt', key: 'fcnt', title: '过鱼数量(尾)', width: 120 },
|
||||||
{ dataIndex: "fsz", key: "fsz", title: "体长(cm)", width: 110 },
|
{ dataIndex: 'fsz', key: 'fsz', title: '体长(cm)', width: 110 },
|
||||||
{ dataIndex: "fwet", key: "fwet", title: "平均体重(g)", width: 110 },
|
{ dataIndex: 'fwet', key: 'fwet', title: '平均体重(g)', width: 110 },
|
||||||
{ dataIndex: "wt", key: "wt", title: "水温(℃)", width: 80 },
|
{ dataIndex: 'wt', key: 'wt', title: '水温(℃)', width: 80 },
|
||||||
{ dataIndex: "tm", key: "tm", title: "填报时间", width: 150 },
|
{ dataIndex: 'tm', key: 'tm', title: '填报时间', width: 150 },
|
||||||
{
|
{
|
||||||
dataIndex: "status",
|
dataIndex: 'status',
|
||||||
key: "status",
|
key: 'status',
|
||||||
title: "状态",
|
title: '状态',
|
||||||
width: 74,
|
width: 74,
|
||||||
customRender: ({ text }: any) => {
|
customRender: ({ text }: any) => {
|
||||||
let data = guoyuStatus.value.find((item: any) => item.itemCode === text);
|
let data = guoyuStatus.value.find((item: any) => item.itemCode === text);
|
||||||
return h(
|
return h(
|
||||||
Tag,
|
Tag,
|
||||||
{
|
{
|
||||||
color: data?.custom1 || "error",
|
color: data?.custom1 || 'error'
|
||||||
},
|
},
|
||||||
() => data?.dictName || "-"
|
() => data?.dictName || '-'
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const editModalVisible = ref(false);
|
const editModalVisible = ref(false);
|
||||||
const currentRecord = ref<FormData | null>(null);
|
const currentRecord = ref<FormData | null>(null);
|
||||||
|
|
||||||
const mediaPreviewVisible = ref(false);
|
const mediaPreviewVisible = ref(false);
|
||||||
const videoPreviewTitle = ref("视频预览");
|
const videoPreviewTitle = ref('视频预览');
|
||||||
|
|
||||||
interface MediaItem {
|
interface MediaItem {
|
||||||
id: string;
|
id: string;
|
||||||
type: "image" | "video";
|
type: 'image' | 'video';
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
@ -152,35 +169,35 @@ const previewList = ref<MediaItem[]>([]);
|
|||||||
const previewListIndex = ref(0);
|
const previewListIndex = ref(0);
|
||||||
const columns = computed(() => {
|
const columns = computed(() => {
|
||||||
return [
|
return [
|
||||||
...baseColumnsConfig.map((col) => {
|
...baseColumnsConfig.map(col => {
|
||||||
if (col.dataIndex === "level5") {
|
if (col.dataIndex === 'level5') {
|
||||||
return {
|
return {
|
||||||
...col,
|
...col,
|
||||||
customRender: ({ text }: any) => {
|
customRender: ({ text }: any) => {
|
||||||
if (!text) return "-";
|
if (!text) return '-';
|
||||||
return `<span style="color:#52c41a; cursor:pointer">查看图片</span>`;
|
return `<span style="color:#52c41a; cursor:pointer">查看图片</span>`;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (col.dataIndex === "level6") {
|
if (col.dataIndex === 'level6') {
|
||||||
return {
|
return {
|
||||||
...col,
|
...col,
|
||||||
customRender: ({ text }: any) => {
|
customRender: ({ text }: any) => {
|
||||||
if (!text) return "-";
|
if (!text) return '-';
|
||||||
return `<span style="color:#1890ff; cursor:pointer">播放视频</span>`;
|
return `<span style="color:#1890ff; cursor:pointer">播放视频</span>`;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { ...col, visible: true };
|
return { ...col, visible: true };
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
title: "操作",
|
title: '操作',
|
||||||
key: "action",
|
key: 'action',
|
||||||
dataIndex: "action",
|
dataIndex: 'action',
|
||||||
fixed: "right",
|
fixed: 'right',
|
||||||
width: 100,
|
width: 100,
|
||||||
align: "center",
|
align: 'center'
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -195,26 +212,26 @@ const editModalCancel = () => {
|
|||||||
|
|
||||||
const handlePreviewClick = (record: any, type: string, index: number) => {
|
const handlePreviewClick = (record: any, type: string, index: number) => {
|
||||||
const mixedList: MediaItem[] = [];
|
const mixedList: MediaItem[] = [];
|
||||||
if (type === "image") {
|
if (type === 'image') {
|
||||||
videoPreviewTitle.value = "图片预览";
|
videoPreviewTitle.value = '图片预览';
|
||||||
const nameList = JSON.parse(JSON.stringify(record)).picpthList;
|
const nameList = JSON.parse(JSON.stringify(record)).picpthList;
|
||||||
nameList.forEach((item: any) => {
|
nameList.forEach((item: any) => {
|
||||||
mixedList.push({
|
mixedList.push({
|
||||||
id: record.id,
|
id: record.id,
|
||||||
type: "image",
|
type: 'image',
|
||||||
name: item.name,
|
name: item.name,
|
||||||
url: item.url,
|
url: item.url
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else if (type === "video") {
|
} else if (type === 'video') {
|
||||||
videoPreviewTitle.value = "视频预览";
|
videoPreviewTitle.value = '视频预览';
|
||||||
const nameList = JSON.parse(JSON.stringify(record)).vdpthList;
|
const nameList = JSON.parse(JSON.stringify(record)).vdpthList;
|
||||||
nameList.forEach((item: any) => {
|
nameList.forEach((item: any) => {
|
||||||
mixedList.push({
|
mixedList.push({
|
||||||
id: record.id,
|
id: record.id,
|
||||||
type: "video",
|
type: 'video',
|
||||||
name: item.name,
|
name: item.name,
|
||||||
url: item.url,
|
url: item.url
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -232,63 +249,64 @@ const customTransform = (res: any) => {
|
|||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
picpthList: item.picpth
|
picpthList: item.picpth
|
||||||
? item.picpth.split(",").map((item: string) => baseUrl + "/?" + item) || []
|
? item.picpth.split(',').map((item: string) => baseUrl + '/?' + item) ||
|
||||||
: [],
|
[]
|
||||||
|
: []
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
records: modifiedRecords,
|
records: modifiedRecords,
|
||||||
total: res?.data?.total || 0,
|
total: res?.data?.total || 0
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReset = (values) => {
|
const handleReset = values => {
|
||||||
handleSearchFinish(values);
|
handleSearchFinish(values);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearchFinish = (values: any) => {
|
const handleSearchFinish = (values: any) => {
|
||||||
const filters = [
|
const filters = [
|
||||||
values.ftp && {
|
values.ftp && {
|
||||||
field: "ftp",
|
field: 'ftp',
|
||||||
operator: "eq",
|
operator: 'eq',
|
||||||
dataType: "string",
|
dataType: 'string',
|
||||||
value: values.ftp,
|
value: values.ftp
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: "strdt",
|
field: 'strdt',
|
||||||
operator: "gte",
|
operator: 'gte',
|
||||||
dataType: "date",
|
dataType: 'date',
|
||||||
value: values.strdt[0] + " 00:00:00",
|
value: values.strdt[0] + ' 00:00:00'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: "strdt",
|
field: 'strdt',
|
||||||
operator: "lte",
|
operator: 'lte',
|
||||||
dataType: "date",
|
dataType: 'date',
|
||||||
value: values.strdt[1] + " 23:59:59",
|
value: values.strdt[1] + ' 23:59:59'
|
||||||
},
|
},
|
||||||
values.direction && {
|
values.direction && {
|
||||||
field: "direction",
|
field: 'direction',
|
||||||
operator: "eq",
|
operator: 'eq',
|
||||||
dataType: "string",
|
dataType: 'string',
|
||||||
value: values.direction,
|
value: values.direction
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: "status",
|
field: 'status',
|
||||||
operator: "eq",
|
operator: 'eq',
|
||||||
dataType: "string",
|
dataType: 'string',
|
||||||
value: "APPROVED",
|
value: 'APPROVED'
|
||||||
},
|
},
|
||||||
values.stcd && {
|
values.stcd && {
|
||||||
field: "stcd",
|
field: 'stcd',
|
||||||
operator: "eq",
|
operator: 'eq',
|
||||||
dataType: "string",
|
dataType: 'string',
|
||||||
value: values.stcd,
|
value: values.stcd
|
||||||
},
|
},
|
||||||
values.rstcd && {
|
values.rstcd && {
|
||||||
field: "rstcd",
|
field: 'rstcd',
|
||||||
operator: "eq",
|
operator: 'eq',
|
||||||
dataType: "string",
|
dataType: 'string',
|
||||||
value: values.rstcd,
|
value: values.rstcd
|
||||||
},
|
},
|
||||||
values.rvcd !== "all" && {
|
values.rvcd !== "all" && {
|
||||||
field: "rvcd",
|
field: "rvcd",
|
||||||
@ -299,21 +317,21 @@ const handleSearchFinish = (values: any) => {
|
|||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
|
|
||||||
const filter = {
|
const filter = {
|
||||||
logic: "and",
|
logic: 'and',
|
||||||
filters: filters,
|
filters: filters
|
||||||
};
|
};
|
||||||
tableRef.value?.getList(filter);
|
tableRef.value?.getList(filter);
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getDictItemsByCode({ dictCode: "direction" }).then((res) => {
|
getDictItemsByCode({ dictCode: 'direction' }).then(res => {
|
||||||
direction.value = res.data;
|
direction.value = res.data;
|
||||||
});
|
});
|
||||||
|
|
||||||
getDictItemsByCode({ dictCode: "approvalStatus" }).then((res) => {
|
getDictItemsByCode({ dictCode: 'approvalStatus' }).then(res => {
|
||||||
guoyuStatus.value.length = 0;
|
guoyuStatus.value.length = 0;
|
||||||
res.data.forEach((item: any) => {
|
res.data.forEach((item: any) => {
|
||||||
if (item.itemCode == "APPROVED") {
|
if (item.itemCode == 'APPROVED') {
|
||||||
guoyuStatus.value.push(item);
|
guoyuStatus.value.push(item);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -240,7 +240,11 @@
|
|||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12" class="imgupload" :class="{ imgupload_hidden: isView }">
|
<a-col
|
||||||
|
:span="12"
|
||||||
|
class="imgupload"
|
||||||
|
:class="{ imgupload_hidden: isView }"
|
||||||
|
>
|
||||||
<a-form-item label="图片" name="picpth">
|
<a-form-item label="图片" name="picpth">
|
||||||
<a-upload
|
<a-upload
|
||||||
v-model:file-list="imageFileList"
|
v-model:file-list="imageFileList"
|
||||||
@ -281,7 +285,10 @@
|
|||||||
上传视频 (MP4)
|
上传视频 (MP4)
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-upload>
|
</a-upload>
|
||||||
<a-button v-else-if="videoFileList.length > 0" @click="handleVideoPreview">
|
<a-button
|
||||||
|
v-else-if="videoFileList.length > 0"
|
||||||
|
@click="handleVideoPreview"
|
||||||
|
>
|
||||||
点击预览视频
|
点击预览视频
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@ -292,19 +299,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, reactive, watch, computed } from "vue";
|
import { ref, reactive, watch, computed } from 'vue';
|
||||||
import dayjs from "dayjs";
|
import dayjs from 'dayjs';
|
||||||
import { Upload, message } from "ant-design-vue";
|
import { Upload, message } from 'ant-design-vue';
|
||||||
import type { UploadProps } from "ant-design-vue";
|
import type { UploadProps } from 'ant-design-vue';
|
||||||
import type { Rule } from "ant-design-vue/es/form";
|
import type { Rule } from 'ant-design-vue/es/form';
|
||||||
import { UploadOutlined, PlusOutlined } from "@ant-design/icons-vue";
|
import { UploadOutlined, PlusOutlined } from '@ant-design/icons-vue';
|
||||||
import fishSearch from "@/components/fishSearch/index.vue";
|
import fishSearch from '@/components/fishSearch/index.vue';
|
||||||
import {
|
import {
|
||||||
getSelectForDropdown,
|
getSelectForDropdown,
|
||||||
getEngInfoDropdown,
|
getEngInfoDropdown,
|
||||||
getFpssDropdown,
|
getFpssDropdown,
|
||||||
uploadFile,
|
uploadFile
|
||||||
} from "@/api/select";
|
} from '@/api/select';
|
||||||
|
|
||||||
// 定义 Props
|
// 定义 Props
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -333,12 +340,12 @@ const videoFileList = ref<any[]>([]);
|
|||||||
const disabledDate = (current: any) => {
|
const disabledDate = (current: any) => {
|
||||||
// current 是 moment/dayjs 对象
|
// current 是 moment/dayjs 对象
|
||||||
// 返回 true 表示禁用
|
// 返回 true 表示禁用
|
||||||
return current && current > dayjs().endOf("day");
|
return current && current > dayjs().endOf('day');
|
||||||
};
|
};
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
visible: false,
|
visible: false,
|
||||||
initialValues: null,
|
initialValues: null,
|
||||||
loading: false,
|
loading: false
|
||||||
});
|
});
|
||||||
// 获取基地
|
// 获取基地
|
||||||
// const getBaseDropdownSelect = async () => {
|
// const getBaseDropdownSelect = async () => {
|
||||||
@ -367,7 +374,7 @@ const getHbrvcdDropdownSelect = async () => {
|
|||||||
}
|
}
|
||||||
hbrvcdOption.value = list;
|
hbrvcdOption.value = list;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取流域列表失败:", error);
|
console.error('获取流域列表失败:', error);
|
||||||
} finally {
|
} finally {
|
||||||
hbrvcdLoading.value = false;
|
hbrvcdLoading.value = false;
|
||||||
}
|
}
|
||||||
@ -390,7 +397,7 @@ const getEngInfoDropdownSelect = async (rvcd: string) => {
|
|||||||
const res = await getEngInfoDropdown({ rvcd });
|
const res = await getEngInfoDropdown({ rvcd });
|
||||||
engOption.value = res.data;
|
engOption.value = res.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取电站列表失败", error);
|
console.error('获取电站列表失败', error);
|
||||||
} finally {
|
} finally {
|
||||||
engLoading.value = false;
|
engLoading.value = false;
|
||||||
}
|
}
|
||||||
@ -405,28 +412,34 @@ const getFpssDropdownSelect = async (rstcd: string, rvcd: string) => {
|
|||||||
const res = await getFpssDropdown({ rstcd, rvcd });
|
const res = await getFpssDropdown({ rstcd, rvcd });
|
||||||
fpssOption.value = res.data;
|
fpssOption.value = res.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取流量列表失败", error);
|
console.error('获取流量列表失败', error);
|
||||||
} finally {
|
} finally {
|
||||||
fpssLoading.value = false;
|
fpssLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const modalVisible = computed({
|
const modalVisible = computed({
|
||||||
get: () => props.visible,
|
get: () => props.visible,
|
||||||
set: (val) => emit("update:visible", val),
|
set: val => emit('update:visible', val)
|
||||||
});
|
});
|
||||||
// 定义 Emits
|
// 定义 Emits
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "update:visible", value: boolean): void;
|
(e: 'update:visible', value: boolean): void;
|
||||||
(e: "cancel"): void;
|
(e: 'cancel'): void;
|
||||||
(e: "ok", values: any): void;
|
(e: 'ok', values: any): void;
|
||||||
(e: "preview-click", record: any, type: string, index: number, action: string): void;
|
(
|
||||||
|
e: 'preview-click',
|
||||||
|
record: any,
|
||||||
|
type: string,
|
||||||
|
index: number,
|
||||||
|
action: string
|
||||||
|
): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// 表单引用
|
// 表单引用
|
||||||
const formRef = ref();
|
const formRef = ref();
|
||||||
// 1. 新增用于存储手动验证错误信息的响应式变量
|
// 1. 新增用于存储手动验证错误信息的响应式变量
|
||||||
const bodyLengthError = ref<string>("");
|
const bodyLengthError = ref<string>('');
|
||||||
const weightError = ref<string>("");
|
const weightError = ref<string>('');
|
||||||
|
|
||||||
// 表单数据模型
|
// 表单数据模型
|
||||||
const defaultFormData = reactive({
|
const defaultFormData = reactive({
|
||||||
@ -447,7 +460,7 @@ const defaultFormData = reactive({
|
|||||||
bodyLengthMin: undefined,
|
bodyLengthMin: undefined,
|
||||||
bodyLengthMax: undefined,
|
bodyLengthMax: undefined,
|
||||||
weightMin: undefined,
|
weightMin: undefined,
|
||||||
weightMax: undefined,
|
weightMax: undefined
|
||||||
});
|
});
|
||||||
const formData: any = reactive({ ...defaultFormData });
|
const formData: any = reactive({ ...defaultFormData });
|
||||||
const filterOption = (inputValue: string, option: any) => {
|
const filterOption = (inputValue: string, option: any) => {
|
||||||
@ -473,15 +486,15 @@ const validateBodyLength = () => {
|
|||||||
const max = formData.bodyLengthMax;
|
const max = formData.bodyLengthMax;
|
||||||
|
|
||||||
// 重置错误
|
// 重置错误
|
||||||
bodyLengthError.value = "";
|
bodyLengthError.value = '';
|
||||||
|
|
||||||
// 判断是否有值 (排除 undefined, null, 和空字符串)
|
// 判断是否有值 (排除 undefined, null, 和空字符串)
|
||||||
const hasMin = min !== undefined && min !== null && min !== "";
|
const hasMin = min !== undefined && min !== null && min !== '';
|
||||||
const hasMax = max !== undefined && max !== null && max !== "";
|
const hasMax = max !== undefined && max !== null && max !== '';
|
||||||
|
|
||||||
// 1. 如果只填了一个值,提示两个都必填
|
// 1. 如果只填了一个值,提示两个都必填
|
||||||
if ((hasMin && !hasMax) || (!hasMin && hasMax)) {
|
if ((hasMin && !hasMax) || (!hasMin && hasMax)) {
|
||||||
bodyLengthError.value = "最小体长和最大体长均需填写";
|
bodyLengthError.value = '最小体长和最大体长均需填写';
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -489,7 +502,7 @@ const validateBodyLength = () => {
|
|||||||
if (hasMin && hasMax) {
|
if (hasMin && hasMax) {
|
||||||
// 允许相等,只禁止 min > max
|
// 允许相等,只禁止 min > max
|
||||||
if (Number(min) > Number(max)) {
|
if (Number(min) > Number(max)) {
|
||||||
bodyLengthError.value = "最小体长不能大于最大体长";
|
bodyLengthError.value = '最小体长不能大于最大体长';
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -502,15 +515,15 @@ const validateWeight = () => {
|
|||||||
const max = formData.weightMax;
|
const max = formData.weightMax;
|
||||||
|
|
||||||
// 重置错误
|
// 重置错误
|
||||||
weightError.value = "";
|
weightError.value = '';
|
||||||
|
|
||||||
// 判断是否有值
|
// 判断是否有值
|
||||||
const hasMin = min !== undefined && min !== null && min !== "";
|
const hasMin = min !== undefined && min !== null && min !== '';
|
||||||
const hasMax = max !== undefined && max !== null && max !== "";
|
const hasMax = max !== undefined && max !== null && max !== '';
|
||||||
|
|
||||||
// 1. 如果只填了一个值,提示两个都必填
|
// 1. 如果只填了一个值,提示两个都必填
|
||||||
if ((hasMin && !hasMax) || (!hasMin && hasMax)) {
|
if ((hasMin && !hasMax) || (!hasMin && hasMax)) {
|
||||||
weightError.value = "最小体重和最大体重均需填写";
|
weightError.value = '最小体重和最大体重均需填写';
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -518,7 +531,7 @@ const validateWeight = () => {
|
|||||||
if (hasMin && hasMax) {
|
if (hasMin && hasMax) {
|
||||||
// 允许相等,只禁止 min > max
|
// 允许相等,只禁止 min > max
|
||||||
if (Number(min) > Number(max)) {
|
if (Number(min) > Number(max)) {
|
||||||
weightError.value = "最小体重不能大于最大体重";
|
weightError.value = '最小体重不能大于最大体重';
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -526,20 +539,20 @@ const validateWeight = () => {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
// 图片上传前校验
|
// 图片上传前校验
|
||||||
const beforeImageUpload: UploadProps["beforeUpload"] = (file) => {
|
const beforeImageUpload: UploadProps['beforeUpload'] = file => {
|
||||||
const isJpgOrPng =
|
const isJpgOrPng =
|
||||||
file.type === "image/jpeg" ||
|
file.type === 'image/jpeg' ||
|
||||||
file.type === "image/png" ||
|
file.type === 'image/png' ||
|
||||||
file.name.toLowerCase().endsWith(".jpg") ||
|
file.name.toLowerCase().endsWith('.jpg') ||
|
||||||
file.name.toLowerCase().endsWith(".jpeg") ||
|
file.name.toLowerCase().endsWith('.jpeg') ||
|
||||||
file.name.toLowerCase().endsWith(".png");
|
file.name.toLowerCase().endsWith('.png');
|
||||||
if (!isJpgOrPng) {
|
if (!isJpgOrPng) {
|
||||||
message.error("只能上传 JPG/PNG/JPEG 格式的图片!");
|
message.error('只能上传 JPG/PNG/JPEG 格式的图片!');
|
||||||
return Upload.LIST_IGNORE;
|
return Upload.LIST_IGNORE;
|
||||||
}
|
}
|
||||||
const isLt2M = file.size / 1024 / 1024 < 10; // 限制10M,可根据需求调整
|
const isLt2M = file.size / 1024 / 1024 < 10; // 限制10M,可根据需求调整
|
||||||
if (!isLt2M) {
|
if (!isLt2M) {
|
||||||
message.error("图片大小不能超过 10MB!");
|
message.error('图片大小不能超过 10MB!');
|
||||||
return Upload.LIST_IGNORE;
|
return Upload.LIST_IGNORE;
|
||||||
}
|
}
|
||||||
// 返回 false 阻止自动上传,我们手动处理
|
// 返回 false 阻止自动上传,我们手动处理
|
||||||
@ -547,15 +560,16 @@ const beforeImageUpload: UploadProps["beforeUpload"] = (file) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 视频上传前校验
|
// 视频上传前校验
|
||||||
const beforeVideoUpload: UploadProps["beforeUpload"] = (file) => {
|
const beforeVideoUpload: UploadProps['beforeUpload'] = file => {
|
||||||
const isMp4 = file.type === "video/mp4" || file.name.toLowerCase().endsWith(".mp4");
|
const isMp4 =
|
||||||
|
file.type === 'video/mp4' || file.name.toLowerCase().endsWith('.mp4');
|
||||||
if (!isMp4) {
|
if (!isMp4) {
|
||||||
message.error("只能上传 MP4 格式的视频!");
|
message.error('只能上传 MP4 格式的视频!');
|
||||||
return Upload.LIST_IGNORE;
|
return Upload.LIST_IGNORE;
|
||||||
}
|
}
|
||||||
const isLt50M = file.size / 1024 / 1024 < 20; // 限制20M,可根据需求调整
|
const isLt50M = file.size / 1024 / 1024 < 20; // 限制20M,可根据需求调整
|
||||||
if (!isLt50M) {
|
if (!isLt50M) {
|
||||||
message.error("视频大小不能超过 20MB!");
|
message.error('视频大小不能超过 20MB!');
|
||||||
return Upload.LIST_IGNORE;
|
return Upload.LIST_IGNORE;
|
||||||
}
|
}
|
||||||
// 返回 false 阻止自动上传
|
// 返回 false 阻止自动上传
|
||||||
@ -587,7 +601,7 @@ const initForm = () => {
|
|||||||
// --- 编辑模式:回填数据 ---
|
// --- 编辑模式:回填数据 ---
|
||||||
const values = props.initialValues;
|
const values = props.initialValues;
|
||||||
if (values.fwet) {
|
if (values.fwet) {
|
||||||
const weights = values.fwet.split("~");
|
const weights = values.fwet.split('~');
|
||||||
if (weights.length === 2) {
|
if (weights.length === 2) {
|
||||||
formData.weightMin = weights[0];
|
formData.weightMin = weights[0];
|
||||||
formData.weightMax = weights[1];
|
formData.weightMax = weights[1];
|
||||||
@ -601,7 +615,7 @@ const initForm = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (values.fsz) {
|
if (values.fsz) {
|
||||||
const sizes = values.fsz.split("~");
|
const sizes = values.fsz.split('~');
|
||||||
if (sizes.length === 2) {
|
if (sizes.length === 2) {
|
||||||
formData.bodyLengthMin = sizes[0];
|
formData.bodyLengthMin = sizes[0];
|
||||||
formData.bodyLengthMax = sizes[1];
|
formData.bodyLengthMax = sizes[1];
|
||||||
@ -616,35 +630,37 @@ const initForm = () => {
|
|||||||
if (values.picpth) {
|
if (values.picpth) {
|
||||||
const paths = Array.isArray(values.picpth)
|
const paths = Array.isArray(values.picpth)
|
||||||
? values.picpth
|
? values.picpth
|
||||||
: values.picpth.split(",");
|
: values.picpth.split(',');
|
||||||
imageFileList.value = paths.map((path: string, index: number) => ({
|
imageFileList.value = paths.map((path: string, index: number) => ({
|
||||||
uid: `-${index}`,
|
uid: `-${index}`,
|
||||||
name: path.split("/").pop() || `image-${index}`,
|
name: path.split('/').pop() || `image-${index}`,
|
||||||
status: "done",
|
status: 'done',
|
||||||
path: path,
|
path: path,
|
||||||
url: import.meta.env.VITE_APP_PREVIEW_URL + "/?" + path, // 这里需要是完整的可访问URL
|
url: import.meta.env.VITE_APP_ATTACHMENT_URL + '/?' + path // 这里需要是完整的可访问URL
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理视频回显
|
// 处理视频回显
|
||||||
if (values.vdpth) {
|
if (values.vdpth) {
|
||||||
const paths = Array.isArray(values.vdpth) ? values.vdpth : values.vdpth.split(",");
|
const paths = Array.isArray(values.vdpth)
|
||||||
|
? values.vdpth
|
||||||
|
: values.vdpth.split(',');
|
||||||
videoFileList.value = paths.map((path: string, index: number) => ({
|
videoFileList.value = paths.map((path: string, index: number) => ({
|
||||||
uid: `-${index}`,
|
uid: `-${index}`,
|
||||||
name: path.split("/").pop() || `image-${index}`,
|
name: path.split('/').pop() || `image-${index}`,
|
||||||
status: "done",
|
status: 'done',
|
||||||
path: path,
|
path: path,
|
||||||
url: import.meta.env.VITE_APP_PREVIEW_URL + "/?" + path, // 这里需要是完整的可访问URL
|
url: import.meta.env.VITE_APP_ATTACHMENT_URL + '/?' + path // 这里需要是完整的可访问URL
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
// 回填其他普通字段
|
// 回填其他普通字段
|
||||||
Object.keys(formData).forEach((key) => {
|
Object.keys(formData).forEach(key => {
|
||||||
// 跳过已经特殊处理的字段
|
// 跳过已经特殊处理的字段
|
||||||
if (
|
if (
|
||||||
key === "weightMin" ||
|
key === 'weightMin' ||
|
||||||
key === "weightMax" ||
|
key === 'weightMax' ||
|
||||||
key === "bodyLengthMin" ||
|
key === 'bodyLengthMin' ||
|
||||||
key === "bodyLengthMax"
|
key === 'bodyLengthMax'
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -660,12 +676,24 @@ const initForm = () => {
|
|||||||
|
|
||||||
// 视频预览
|
// 视频预览
|
||||||
const handleVideoPreview = () => {
|
const handleVideoPreview = () => {
|
||||||
emit("preview-click", { vdpthList: videoFileList.value }, "video", 0, "preview");
|
emit(
|
||||||
|
'preview-click',
|
||||||
|
{ vdpthList: videoFileList.value },
|
||||||
|
'video',
|
||||||
|
0,
|
||||||
|
'preview'
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 预览图片
|
// 预览图片
|
||||||
const handleImagePreview = async (file: any) => {
|
const handleImagePreview = async (file: any) => {
|
||||||
emit("preview-click", { picpthList: imageFileList.value }, "image", 0, "preview");
|
emit(
|
||||||
|
'preview-click',
|
||||||
|
{ picpthList: imageFileList.value },
|
||||||
|
'image',
|
||||||
|
0,
|
||||||
|
'preview'
|
||||||
|
);
|
||||||
};
|
};
|
||||||
// 确认操作
|
// 确认操作
|
||||||
const handleOk = async () => {
|
const handleOk = async () => {
|
||||||
@ -674,7 +702,7 @@ const handleOk = async () => {
|
|||||||
const isBodyLenValid = validateBodyLength();
|
const isBodyLenValid = validateBodyLength();
|
||||||
const isWeightValid = validateWeight();
|
const isWeightValid = validateWeight();
|
||||||
if (!isBodyLenValid || !isWeightValid) {
|
if (!isBodyLenValid || !isWeightValid) {
|
||||||
message.error("请检查体长或体重填写是否正确");
|
message.error('请检查体长或体重填写是否正确');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 验证表单
|
// 验证表单
|
||||||
@ -683,16 +711,16 @@ const handleOk = async () => {
|
|||||||
let uploadedPicPaths: any[] = [];
|
let uploadedPicPaths: any[] = [];
|
||||||
// 过滤出新生成的文件(没有 url 属性的通常是新选择的文件)
|
// 过滤出新生成的文件(没有 url 属性的通常是新选择的文件)
|
||||||
const newImageFiles = imageFileList.value.filter(
|
const newImageFiles = imageFileList.value.filter(
|
||||||
(file) => !file.url && file.originFileObj
|
file => !file.url && file.originFileObj
|
||||||
);
|
);
|
||||||
|
|
||||||
localLoading.value = true;
|
localLoading.value = true;
|
||||||
if (newImageFiles.length > 0) {
|
if (newImageFiles.length > 0) {
|
||||||
// 这里需要循环上传,或者使用 Promise.all 并行上传
|
// 这里需要循环上传,或者使用 Promise.all 并行上传
|
||||||
// 伪代码:假设 uploadFile 返回 { data: { url: '...' } }
|
// 伪代码:假设 uploadFile 返回 { data: { url: '...' } }
|
||||||
const uploadPromises = newImageFiles.map((file) => {
|
const uploadPromises = newImageFiles.map(file => {
|
||||||
const formDataUpload = new FormData();
|
const formDataUpload = new FormData();
|
||||||
formDataUpload.append("file", file.originFileObj);
|
formDataUpload.append('file', file.originFileObj);
|
||||||
return uploadFile(formDataUpload); // 替换为真实的上传接口调用
|
return uploadFile(formDataUpload); // 替换为真实的上传接口调用
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -701,30 +729,30 @@ const handleOk = async () => {
|
|||||||
path: item.message,
|
path: item.message,
|
||||||
uid: `-${index}`,
|
uid: `-${index}`,
|
||||||
name: item.fileMD5,
|
name: item.fileMD5,
|
||||||
status: "done",
|
status: 'done',
|
||||||
url: item.fullFilePath, // 这里需要是完整的可访问URL
|
url: item.fullFilePath // 这里需要是完整的可访问URL
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 合并已存在的图片路径(编辑模式下回显的)和新上传的
|
// 合并已存在的图片路径(编辑模式下回显的)和新上传的
|
||||||
const existingPics = imageFileList.value
|
const existingPics = imageFileList.value
|
||||||
.filter((file) => file.path)
|
.filter(file => file.path)
|
||||||
.map((file) => file.path);
|
.map(file => file.path);
|
||||||
const uploadedPicPaths1 = uploadedPicPaths
|
const uploadedPicPaths1 = uploadedPicPaths
|
||||||
.filter((file) => file.path)
|
.filter(file => file.path)
|
||||||
.map((file) => file.path);
|
.map(file => file.path);
|
||||||
const finalPicPaths = [...existingPics, ...uploadedPicPaths1];
|
const finalPicPaths = [...existingPics, ...uploadedPicPaths1];
|
||||||
|
|
||||||
// 2. 处理视频上传
|
// 2. 处理视频上传
|
||||||
let uploadedVideoPath = [];
|
let uploadedVideoPath = [];
|
||||||
const newVideoFiles = videoFileList.value.filter(
|
const newVideoFiles = videoFileList.value.filter(
|
||||||
(file) => !file.url && file.originFileObj
|
file => !file.url && file.originFileObj
|
||||||
);
|
);
|
||||||
|
|
||||||
if (newVideoFiles.length > 0) {
|
if (newVideoFiles.length > 0) {
|
||||||
const uploadPromises = newVideoFiles.map((file) => {
|
const uploadPromises = newVideoFiles.map(file => {
|
||||||
const formDataUpload = new FormData();
|
const formDataUpload = new FormData();
|
||||||
formDataUpload.append("file", file.originFileObj);
|
formDataUpload.append('file', file.originFileObj);
|
||||||
return uploadFile(formDataUpload); // 替换为真实的上传接口调用
|
return uploadFile(formDataUpload); // 替换为真实的上传接口调用
|
||||||
});
|
});
|
||||||
const results = await Promise.all(uploadPromises);
|
const results = await Promise.all(uploadPromises);
|
||||||
@ -732,31 +760,34 @@ const handleOk = async () => {
|
|||||||
path: item.message,
|
path: item.message,
|
||||||
uid: `-${index}`,
|
uid: `-${index}`,
|
||||||
name: item.fileMD5,
|
name: item.fileMD5,
|
||||||
status: "done",
|
status: 'done',
|
||||||
url: item.fullFilePath, // 这里需要是完整的可访问URL
|
url: item.fullFilePath // 这里需要是完整的可访问URL
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
const existingVideos = videoFileList.value
|
const existingVideos = videoFileList.value
|
||||||
.filter((file) => file.path)
|
.filter(file => file.path)
|
||||||
.map((file) => file.path);
|
.map(file => file.path);
|
||||||
const uploadedVideoPaths1 = uploadedVideoPath
|
const uploadedVideoPaths1 = uploadedVideoPath
|
||||||
.filter((file) => file.path)
|
.filter(file => file.path)
|
||||||
.map((file) => file.path);
|
.map(file => file.path);
|
||||||
const finalVideoPaths = [...existingVideos, ...uploadedVideoPaths1];
|
const finalVideoPaths = [...existingVideos, ...uploadedVideoPaths1];
|
||||||
|
|
||||||
let fwet = "";
|
let fwet = '';
|
||||||
if (
|
if (
|
||||||
formData.weightMin == formData.weightMax &&
|
formData.weightMin == formData.weightMax &&
|
||||||
formData.weightMin != undefined &&
|
formData.weightMin != undefined &&
|
||||||
formData.weightMax != undefined
|
formData.weightMax != undefined
|
||||||
) {
|
) {
|
||||||
fwet = formData.weightMin;
|
fwet = formData.weightMin;
|
||||||
} else if (formData.weightMin == undefined && formData.weightMax == undefined) {
|
} else if (
|
||||||
fwet = "";
|
formData.weightMin == undefined &&
|
||||||
|
formData.weightMax == undefined
|
||||||
|
) {
|
||||||
|
fwet = '';
|
||||||
} else {
|
} else {
|
||||||
fwet = formData.weightMin + "~" + formData.weightMax;
|
fwet = formData.weightMin + '~' + formData.weightMax;
|
||||||
}
|
}
|
||||||
let fsz = "";
|
let fsz = '';
|
||||||
if (
|
if (
|
||||||
formData.bodyLengthMin == formData.bodyLengthMax &&
|
formData.bodyLengthMin == formData.bodyLengthMax &&
|
||||||
formData.bodyLengthMin != undefined &&
|
formData.bodyLengthMin != undefined &&
|
||||||
@ -767,26 +798,26 @@ const handleOk = async () => {
|
|||||||
formData.bodyLengthMin == undefined &&
|
formData.bodyLengthMin == undefined &&
|
||||||
formData.bodyLengthMax == undefined
|
formData.bodyLengthMax == undefined
|
||||||
) {
|
) {
|
||||||
fsz = "";
|
fsz = '';
|
||||||
} else {
|
} else {
|
||||||
fsz = formData.bodyLengthMin + "~" + formData.bodyLengthMax;
|
fsz = formData.bodyLengthMin + '~' + formData.bodyLengthMax;
|
||||||
}
|
}
|
||||||
// 准备提交数据
|
// 准备提交数据
|
||||||
const submitValues = {
|
const submitValues = {
|
||||||
...formData,
|
...formData,
|
||||||
fwet: fwet,
|
fwet: fwet,
|
||||||
fsz: fsz,
|
fsz: fsz,
|
||||||
strdt: formData.strdt + " 00:00:00",
|
strdt: formData.strdt + ' 00:00:00',
|
||||||
picpth: finalPicPaths.join(","), // 根据后端要求,可能是数组或逗号分隔字符串
|
picpth: finalPicPaths.join(','), // 根据后端要求,可能是数组或逗号分隔字符串
|
||||||
vdpth: finalVideoPaths.join(","),
|
vdpth: finalVideoPaths.join(',')
|
||||||
};
|
};
|
||||||
if (!formData.id) submitValues.tm = dayjs().format("YYYY-MM-DD HH:mm:ss");
|
if (!formData.id) submitValues.tm = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||||
// console.log(submitValues);
|
// console.log(submitValues);
|
||||||
// return;
|
// return;
|
||||||
emit("ok", submitValues);
|
emit('ok', submitValues);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Validate Failed:", error);
|
console.error('Validate Failed:', error);
|
||||||
message.error("请检查表单填写是否正确");
|
message.error('请检查表单填写是否正确');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 重置表单
|
// 重置表单
|
||||||
@ -796,8 +827,8 @@ const resetForm = () => {
|
|||||||
}
|
}
|
||||||
Object.assign(formData, defaultFormData);
|
Object.assign(formData, defaultFormData);
|
||||||
// 清空手动验证的错误信息
|
// 清空手动验证的错误信息
|
||||||
bodyLengthError.value = "";
|
bodyLengthError.value = '';
|
||||||
weightError.value = "";
|
weightError.value = '';
|
||||||
// 清空文件列表
|
// 清空文件列表
|
||||||
imageFileList.value = [];
|
imageFileList.value = [];
|
||||||
videoFileList.value = [];
|
videoFileList.value = [];
|
||||||
@ -805,14 +836,14 @@ const resetForm = () => {
|
|||||||
|
|
||||||
// 取消操作
|
// 取消操作
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
emit("update:visible", false);
|
emit('update:visible', false);
|
||||||
emit("cancel");
|
emit('cancel');
|
||||||
resetForm();
|
resetForm();
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.visible,
|
() => props.visible,
|
||||||
(newVisible) => {
|
newVisible => {
|
||||||
if (newVisible) {
|
if (newVisible) {
|
||||||
// 弹窗打开时,初始化数据
|
// 弹窗打开时,初始化数据
|
||||||
// getBaseDropdownSelect();// 基地
|
// getBaseDropdownSelect();// 基地
|
||||||
@ -826,7 +857,7 @@ watch(
|
|||||||
);
|
);
|
||||||
// 暴露变量
|
// 暴露变量
|
||||||
defineExpose({
|
defineExpose({
|
||||||
localLoading,
|
localLoading
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -1,18 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="guoYuSheShiShuJuTianBao-search">
|
<div class="guoYuSheShiShuJuTianBao-search">
|
||||||
<BasicSearch ref="basicSearchRef" :searchList="searchList" :initial-values="initSearchData" :zhujianfujian="'fu'"
|
<BasicSearch
|
||||||
@reset="handleReset" @finish="onSearchFinish" @values-change="onValuesChange">
|
ref="basicSearchRef"
|
||||||
|
:searchList="searchList"
|
||||||
|
:initial-values="initSearchData"
|
||||||
|
:zhujianfujian="'fu'"
|
||||||
|
@reset="handleReset"
|
||||||
|
@finish="onSearchFinish"
|
||||||
|
@values-change="onValuesChange"
|
||||||
|
>
|
||||||
<template #ftp="{ onChange }">
|
<template #ftp="{ onChange }">
|
||||||
<fishSearch v-model="localTypeDate" width="200px" @update:modelValue="onChange" />
|
<fishSearch
|
||||||
|
v-model="localTypeDate"
|
||||||
|
width="200px"
|
||||||
|
@update:modelValue="onChange"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<a-tooltip title="手动新增">
|
<a-tooltip title="手动新增">
|
||||||
<a-button v-hasPerm="['sjtb:import-add']" @click="props.handleAdd" type="primary">
|
<a-button
|
||||||
|
v-hasPerm="['sjtb:import-add']"
|
||||||
|
@click="props.handleAdd"
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
手动新增
|
手动新增
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip title="数据批量导入zip">
|
<a-tooltip title="数据批量导入zip">
|
||||||
<a-button v-hasPerm="['sjtb:import-add']" @click="props.importBtn" type="primary">
|
<a-button
|
||||||
|
v-hasPerm="['sjtb:import-add']"
|
||||||
|
@click="props.importBtn"
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
数据批量导入zip
|
数据批量导入zip
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
@ -69,7 +88,10 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip title="操作手册下载">
|
<a-tooltip title="操作手册下载">
|
||||||
<a-button>
|
<a-button>
|
||||||
<a href="/file/过鱼设施数据填报操作手册.docx" download="过鱼设施数据填报操作手册.docx">
|
<a
|
||||||
|
href="/file/过鱼设施数据填报操作手册.docx"
|
||||||
|
download="过鱼设施数据填报操作手册.docx"
|
||||||
|
>
|
||||||
操作手册下载
|
操作手册下载
|
||||||
</a>
|
</a>
|
||||||
</a-button>
|
</a-button>
|
||||||
@ -80,18 +102,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, onMounted, watch } from "vue";
|
import { ref, computed, onMounted, watch } from 'vue';
|
||||||
import {
|
import {
|
||||||
SaveOutlined,
|
SaveOutlined,
|
||||||
CheckSquareOutlined,
|
CheckSquareOutlined,
|
||||||
QuestionOutlined,
|
QuestionOutlined
|
||||||
} from "@ant-design/icons-vue";
|
} from '@ant-design/icons-vue';
|
||||||
import dayjs from "dayjs";
|
import dayjs from 'dayjs';
|
||||||
import BasicSearch from "@/components/BasicSearch/index.vue"; // 确保路径正确
|
import BasicSearch from '@/components/BasicSearch/index.vue'; // 确保路径正确
|
||||||
import { DateSetting } from "@/utils/enumeration";
|
import { DateSetting } from '@/utils/enumeration';
|
||||||
import fishSearch from "@/components/fishSearch/index.vue";
|
import fishSearch from '@/components/fishSearch/index.vue';
|
||||||
import { useShuJuTianBaoStore } from "@/store/modules/shuJuTianBao";
|
import { useShuJuTianBaoStore } from '@/store/modules/shuJuTianBao';
|
||||||
import { downloadFileByUrl } from "@/utils/index";
|
import { downloadFileByUrl } from '@/utils/index';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
direction: any[];
|
direction: any[];
|
||||||
@ -109,8 +131,8 @@ const shuJuTianBaoStore = useShuJuTianBaoStore();
|
|||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "reset", values: any): void;
|
(e: 'reset', values: any): void;
|
||||||
(e: "searchFinish", values: any): void;
|
(e: 'searchFinish', values: any): void;
|
||||||
}>();
|
}>();
|
||||||
const localTypeDate = ref<string>(null);
|
const localTypeDate = ref<string>(null);
|
||||||
const basicSearchRef = ref<any>();
|
const basicSearchRef = ref<any>();
|
||||||
@ -124,9 +146,9 @@ const initSearchData = {
|
|||||||
status: 'DRAFT',
|
status: 'DRAFT',
|
||||||
direction: null,
|
direction: null,
|
||||||
strdt: [
|
strdt: [
|
||||||
dayjs().subtract(1, 'month').startOf('month').format("YYYY-MM-DD"),
|
dayjs().subtract(1, 'month').startOf('month').format('YYYY-MM-DD'),
|
||||||
dayjs().format("YYYY-MM-DD"),
|
dayjs().format('YYYY-MM-DD')
|
||||||
],
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchData = ref<any>({ ...initSearchData });
|
const searchData = ref<any>({ ...initSearchData });
|
||||||
@ -136,67 +158,67 @@ const searchList: any = computed(() => [
|
|||||||
name: "rvcd",
|
name: "rvcd",
|
||||||
label: "流域",
|
label: "流域",
|
||||||
fieldProps: {
|
fieldProps: {
|
||||||
allowClear: true,
|
allowClear: true
|
||||||
},
|
},
|
||||||
options: [],
|
options: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "Select",
|
type: 'Select',
|
||||||
name: "stcd",
|
name: 'stcd',
|
||||||
label: "过鱼设施",
|
label: '过鱼设施',
|
||||||
values: { name: "stnm", value: "stcd" },
|
values: { name: 'stnm', value: 'stcd' },
|
||||||
fieldProps: {
|
fieldProps: {
|
||||||
allowClear: true,
|
allowClear: true
|
||||||
},
|
},
|
||||||
options: shuJuTianBaoStore.fpssOption,
|
options: shuJuTianBaoStore.fpssOption
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "Select",
|
type: 'Select',
|
||||||
name: "direction",
|
name: 'direction',
|
||||||
label: "游向",
|
label: '游向',
|
||||||
width: 120,
|
width: 120,
|
||||||
options: props.direction,
|
options: props.direction,
|
||||||
fieldProps: {
|
fieldProps: {
|
||||||
allowClear: true,
|
allowClear: true
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "custom",
|
type: 'custom',
|
||||||
name: "ftp",
|
name: 'ftp',
|
||||||
label: "鱼种类",
|
label: '鱼种类',
|
||||||
fieldProps: {
|
fieldProps: {
|
||||||
allowClear: true,
|
allowClear: true
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
width: 120,
|
width: 120,
|
||||||
type: "Select",
|
type: 'Select',
|
||||||
name: "status",
|
name: 'status',
|
||||||
label: "数据状态",
|
label: '数据状态',
|
||||||
fieldProps: {
|
fieldProps: {
|
||||||
allowClear: true,
|
allowClear: true
|
||||||
},
|
},
|
||||||
options: props.guoyuStatus,
|
options: props.guoyuStatus
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
span: 12,
|
span: 12,
|
||||||
type: "RangePicker",
|
type: 'RangePicker',
|
||||||
name: "strdt",
|
name: 'strdt',
|
||||||
label: "过鱼时间",
|
label: '过鱼时间',
|
||||||
picker: "date",
|
picker: 'date',
|
||||||
fieldProps: {
|
fieldProps: {
|
||||||
format: "YYYY-MM-DD",
|
format: 'YYYY-MM-DD',
|
||||||
valueFormat: "YYYY-MM-DD",
|
valueFormat: 'YYYY-MM-DD',
|
||||||
allowClear: false,
|
allowClear: false
|
||||||
},
|
},
|
||||||
presets: DateSetting.RangeButton.days,
|
presets: DateSetting.RangeButton.days
|
||||||
},
|
}
|
||||||
]);
|
]);
|
||||||
// --- Methods ---
|
// --- Methods ---
|
||||||
|
|
||||||
// 2. 搜索表单逻辑
|
// 2. 搜索表单逻辑
|
||||||
const onSearchFinish = (values: any) => {
|
const onSearchFinish = (values: any) => {
|
||||||
emit("searchFinish", values);
|
emit('searchFinish', values);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onValuesChange = (changedValues: any, allValues: any) => {
|
const onValuesChange = (changedValues: any, allValues: any) => {
|
||||||
@ -214,24 +236,28 @@ const onValuesChange = (changedValues: any, allValues: any) => {
|
|||||||
|
|
||||||
// 下载模板
|
// 下载模板
|
||||||
const downloadTemplate = () => {
|
const downloadTemplate = () => {
|
||||||
downloadFileByUrl(import.meta.env.VITE_APP_PREVIEW_URL + "/?20260429090252740092641634938251", "过鱼设施数据填报模板");
|
downloadFileByUrl(
|
||||||
|
import.meta.env.VITE_APP_ATTACHMENT_URL +
|
||||||
|
'/?20260429090252740092641634938251',
|
||||||
|
'过鱼设施数据填报模板'
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
localTypeDate.value = null;
|
localTypeDate.value = null;
|
||||||
emit("reset", initSearchData);
|
emit('reset', initSearchData);
|
||||||
};
|
};
|
||||||
watch(
|
watch(
|
||||||
() => initSearchData.ftp,
|
() => initSearchData.ftp,
|
||||||
(newVal) => {
|
newVal => {
|
||||||
localTypeDate.value = newVal || "";
|
localTypeDate.value = newVal || '';
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
emit("searchFinish", initSearchData);
|
emit('searchFinish', initSearchData);
|
||||||
|
|
||||||
shuJuTianBaoStore.getFpssOption("", "");
|
shuJuTianBaoStore.getFpssOption('', '');
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss"></style>
|
<style lang="scss"></style>
|
||||||
|
|||||||
@ -28,7 +28,9 @@
|
|||||||
>
|
>
|
||||||
<!-- 使用 bodyCell 插槽自定义单元格渲染 -->
|
<!-- 使用 bodyCell 插槽自定义单元格渲染 -->
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.key === 'action' || column.dataIndex === 'action'">
|
<template
|
||||||
|
v-if="column.key === 'action' || column.dataIndex === 'action'"
|
||||||
|
>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<a-button
|
<a-button
|
||||||
v-hasPerm="['sjtb:import-add']"
|
v-hasPerm="['sjtb:import-add']"
|
||||||
@ -68,9 +70,12 @@
|
|||||||
size="small"
|
size="small"
|
||||||
@click="handleEdit(record, 'view')"
|
@click="handleEdit(record, 'view')"
|
||||||
v-if="
|
v-if="
|
||||||
(checkPerm(['sjtb:edit-review']) && record.status === 'DRAFT') ||
|
(checkPerm(['sjtb:edit-review']) &&
|
||||||
(checkPerm(['sjtb:import-add']) && record.status === 'PENDING') ||
|
record.status === 'DRAFT') ||
|
||||||
(checkPerm(['sjtb:edit-review']) && record.status === 'REJECTED') ||
|
(checkPerm(['sjtb:import-add']) &&
|
||||||
|
record.status === 'PENDING') ||
|
||||||
|
(checkPerm(['sjtb:edit-review']) &&
|
||||||
|
record.status === 'REJECTED') ||
|
||||||
record.status === 'APPROVED'
|
record.status === 'APPROVED'
|
||||||
"
|
"
|
||||||
>查看</a-button
|
>查看</a-button
|
||||||
@ -111,7 +116,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="button">
|
<div class="button">
|
||||||
<a-tooltip title="全部提交">
|
<a-tooltip title="全部提交">
|
||||||
<a-button v-hasPerm="['sjtb:import-add']" @click="allSubmitBtn" size="large">
|
<a-button
|
||||||
|
v-hasPerm="['sjtb:import-add']"
|
||||||
|
@click="allSubmitBtn"
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
全部提交
|
全部提交
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
@ -156,7 +165,7 @@
|
|||||||
:direction="direction"
|
:direction="direction"
|
||||||
@preview-click="handlePreviewClick"
|
@preview-click="handlePreviewClick"
|
||||||
v-model:file-loading="fileLoading"
|
v-model:file-loading="fileLoading"
|
||||||
@update:file-table-data="(val) => (fileTableData = val)"
|
@update:file-table-data="val => (fileTableData = val)"
|
||||||
/>
|
/>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<a-button key="back" @click="handleCustomCancel">取消导入</a-button>
|
<a-button key="back" @click="handleCustomCancel">取消导入</a-button>
|
||||||
@ -197,14 +206,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, onMounted, h, nextTick, watch } from "vue";
|
import { ref, computed, onMounted, h, nextTick, watch } from 'vue';
|
||||||
import { message, Modal } from "ant-design-vue"; // 假设使用 ant-design-vue
|
import { message, Modal } from 'ant-design-vue'; // 假设使用 ant-design-vue
|
||||||
import BasicTable from "@/components/BasicTable/index.vue";
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
import GuoYuSheShiShuJuTianBaoSearch from "./guoYuSheShiShuJuTianBaoSearch.vue";
|
import GuoYuSheShiShuJuTianBaoSearch from './guoYuSheShiShuJuTianBaoSearch.vue';
|
||||||
import GuoYuSheShiShuJuTianBaoTable from "./guoYuSheShiShuJuTianBaoTable.vue";
|
import GuoYuSheShiShuJuTianBaoTable from './guoYuSheShiShuJuTianBaoTable.vue';
|
||||||
import EditModal from "./guoYuSheShiShuJuTianBaoForm.vue";
|
import EditModal from './guoYuSheShiShuJuTianBaoForm.vue';
|
||||||
import PreviewMedia from "@/components/previewMedia/index.vue";
|
import PreviewMedia from '@/components/previewMedia/index.vue';
|
||||||
import { checkPerm } from "@/directive/permission";
|
import { checkPerm } from '@/directive/permission';
|
||||||
import {
|
import {
|
||||||
getFishDraftPage,
|
getFishDraftPage,
|
||||||
addFishDraft,
|
addFishDraft,
|
||||||
@ -218,13 +227,13 @@ import {
|
|||||||
checkImportStatus,
|
checkImportStatus,
|
||||||
batchSaveDraft,
|
batchSaveDraft,
|
||||||
getLastImportResult,
|
getLastImportResult,
|
||||||
batchApproveAll,
|
batchApproveAll
|
||||||
} from "@/api/guoYuSheShiShuJuTianBao";
|
} from '@/api/guoYuSheShiShuJuTianBao';
|
||||||
import { Tag } from "ant-design-vue"; // 确保导入 Tag
|
import { Tag } from 'ant-design-vue'; // 确保导入 Tag
|
||||||
import { getDictItemsByCode } from "@/api/dict";
|
import { getDictItemsByCode } from '@/api/dict';
|
||||||
import { calcTableScrollY } from "@/utils/index";
|
import { calcTableScrollY } from '@/utils/index';
|
||||||
|
|
||||||
const baseUrl = import.meta.env.VITE_APP_PREVIEW_URL;
|
const baseUrl = import.meta.env.VITE_APP_ATTACHMENT_URL;
|
||||||
const baseUrlApi = import.meta.env.VITE_APP_BASE_API_URL;
|
const baseUrlApi = import.meta.env.VITE_APP_BASE_API_URL;
|
||||||
// --- 类型定义 ---
|
// --- 类型定义 ---
|
||||||
interface FormData {
|
interface FormData {
|
||||||
@ -247,62 +256,75 @@ const guoyuStatus = ref<any>([]);
|
|||||||
// --- 基础配置 ---
|
// --- 基础配置 ---
|
||||||
const baseColumnsConfig: ColumnConfig[] = [
|
const baseColumnsConfig: ColumnConfig[] = [
|
||||||
{
|
{
|
||||||
dataIndex: "hbrvnm",
|
dataIndex: 'hbrvnm',
|
||||||
key: "hbrvnm",
|
key: 'hbrvnm',
|
||||||
title: "流域",
|
title: '流域',
|
||||||
width: 120,
|
width: 120,
|
||||||
fixed: "left",
|
fixed: 'left'
|
||||||
},
|
},
|
||||||
{ dataIndex: "ennm", key: "ennm", title: "电站名称", width: 120, fixed: "left" },
|
|
||||||
{ dataIndex: "stnm", key: "stnm", title: "过鱼设施名称", width: 150, fixed: "left" },
|
|
||||||
{ dataIndex: "strdt", key: "strdt", title: "过鱼时间", width: 150 },
|
|
||||||
{ dataIndex: "ftpName", key: "ftpName", title: "鱼种类", width: 120 },
|
|
||||||
{
|
{
|
||||||
dataIndex: "isfs",
|
dataIndex: 'ennm',
|
||||||
key: "isfs",
|
key: 'ennm',
|
||||||
title: "是否鱼苗",
|
title: '电站名称',
|
||||||
|
width: 120,
|
||||||
|
fixed: 'left'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'stnm',
|
||||||
|
key: 'stnm',
|
||||||
|
title: '过鱼设施名称',
|
||||||
|
width: 150,
|
||||||
|
fixed: 'left'
|
||||||
|
},
|
||||||
|
{ dataIndex: 'strdt', key: 'strdt', title: '过鱼时间', width: 150 },
|
||||||
|
{ dataIndex: 'ftpName', key: 'ftpName', title: '鱼种类', width: 120 },
|
||||||
|
{
|
||||||
|
dataIndex: 'isfs',
|
||||||
|
key: 'isfs',
|
||||||
|
title: '是否鱼苗',
|
||||||
width: 74,
|
width: 74,
|
||||||
customRender: ({ text }: any) => {
|
customRender: ({ text }: any) => {
|
||||||
const isYes = text === 1 || text === "1";
|
const isYes = text === 1 || text === '1';
|
||||||
return h(
|
return h(
|
||||||
Tag,
|
Tag,
|
||||||
{
|
{
|
||||||
color: isYes ? "success" : "error", // Antdv Tag 的颜色预设
|
color: isYes ? 'success' : 'error', // Antdv Tag 的颜色预设
|
||||||
style: { margin: 0 }, // 去除默认 margin,使其在表格中对齐更好
|
style: { margin: 0 } // 去除默认 margin,使其在表格中对齐更好
|
||||||
},
|
},
|
||||||
() => (isYes ? "是" : "否")
|
() => (isYes ? '是' : '否')
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: "direction",
|
dataIndex: 'direction',
|
||||||
key: "direction",
|
key: 'direction',
|
||||||
title: "游向",
|
title: '游向',
|
||||||
width: 80,
|
width: 80,
|
||||||
customRender: ({ text }: any) =>
|
customRender: ({ text }: any) =>
|
||||||
direction.value.find((item: any) => item.itemCode === text)?.dictName || "-",
|
direction.value.find((item: any) => item.itemCode === text)?.dictName ||
|
||||||
|
'-'
|
||||||
},
|
},
|
||||||
{ dataIndex: "fcnt", key: "fcnt", title: "过鱼数量(尾)", width: 120 },
|
{ dataIndex: 'fcnt', key: 'fcnt', title: '过鱼数量(尾)', width: 120 },
|
||||||
{ dataIndex: "fsz", key: "fsz", title: "体长(cm)", width: 110 },
|
{ dataIndex: 'fsz', key: 'fsz', title: '体长(cm)', width: 110 },
|
||||||
{ dataIndex: "fwet", key: "fwet", title: "平均体重(g)", width: 110 },
|
{ dataIndex: 'fwet', key: 'fwet', title: '平均体重(g)', width: 110 },
|
||||||
{ dataIndex: "wt", key: "wt", title: "水温(℃)", width: 80 },
|
{ dataIndex: 'wt', key: 'wt', title: '水温(℃)', width: 80 },
|
||||||
{ dataIndex: "tm", key: "tm", title: "填报时间", width: 150 },
|
{ dataIndex: 'tm', key: 'tm', title: '填报时间', width: 150 },
|
||||||
{
|
{
|
||||||
dataIndex: "status",
|
dataIndex: 'status',
|
||||||
key: "status",
|
key: 'status',
|
||||||
title: "状态",
|
title: '状态',
|
||||||
width: 74,
|
width: 74,
|
||||||
customRender: ({ text }: any) => {
|
customRender: ({ text }: any) => {
|
||||||
let data = guoyuStatus.value.find((item: any) => item.itemCode === text);
|
let data = guoyuStatus.value.find((item: any) => item.itemCode === text);
|
||||||
return h(
|
return h(
|
||||||
Tag,
|
Tag,
|
||||||
{
|
{
|
||||||
color: data?.custom1 || "error", // Antdv Tag 的颜色预设
|
color: data?.custom1 || 'error' // Antdv Tag 的颜色预设
|
||||||
},
|
},
|
||||||
() => data?.dictName || "-"
|
() => data?.dictName || '-'
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// --- 状态定义 ---
|
// --- 状态定义 ---
|
||||||
@ -316,10 +338,10 @@ const currentRecord = ref<FormData | null>(null);
|
|||||||
|
|
||||||
const mediaPreviewVisible = ref(false);
|
const mediaPreviewVisible = ref(false);
|
||||||
const isPreviewMediaDelete = ref(false);
|
const isPreviewMediaDelete = ref(false);
|
||||||
const videoPreviewTitle = ref("视频预览");
|
const videoPreviewTitle = ref('视频预览');
|
||||||
interface MediaItem {
|
interface MediaItem {
|
||||||
id: string;
|
id: string;
|
||||||
type: "image" | "video";
|
type: 'image' | 'video';
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
@ -333,41 +355,41 @@ const batchData = ref<any[]>([]);
|
|||||||
|
|
||||||
const modalTableRef = ref<any>(null);
|
const modalTableRef = ref<any>(null);
|
||||||
const fileLoading = ref(false);
|
const fileLoading = ref(false);
|
||||||
const taskId = ref<string>("");
|
const taskId = ref<string>('');
|
||||||
|
|
||||||
// 主表格 Columns
|
// 主表格 Columns
|
||||||
const columns = computed(() => {
|
const columns = computed(() => {
|
||||||
return [
|
return [
|
||||||
...baseColumnsConfig.map((col) => {
|
...baseColumnsConfig.map(col => {
|
||||||
if (col.dataIndex === "level5") {
|
if (col.dataIndex === 'level5') {
|
||||||
return {
|
return {
|
||||||
...col,
|
...col,
|
||||||
customRender: ({ text }: any) => {
|
customRender: ({ text }: any) => {
|
||||||
if (!text) return "-";
|
if (!text) return '-';
|
||||||
// 实际应渲染 Icon 和点击事件,此处简化
|
// 实际应渲染 Icon 和点击事件,此处简化
|
||||||
return `<span style="color:#52c41a; cursor:pointer">查看图片</span>`;
|
return `<span style="color:#52c41a; cursor:pointer">查看图片</span>`;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (col.dataIndex === "level6") {
|
if (col.dataIndex === 'level6') {
|
||||||
return {
|
return {
|
||||||
...col,
|
...col,
|
||||||
customRender: ({ text }: any) => {
|
customRender: ({ text }: any) => {
|
||||||
if (!text) return "-";
|
if (!text) return '-';
|
||||||
return `<span style="color:#1890ff; cursor:pointer">播放视频</span>`;
|
return `<span style="color:#1890ff; cursor:pointer">播放视频</span>`;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { ...col, visible: true };
|
return { ...col, visible: true };
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
title: "操作",
|
title: '操作',
|
||||||
key: "action",
|
key: 'action',
|
||||||
dataIndex: "action",
|
dataIndex: 'action',
|
||||||
fixed: "right",
|
fixed: 'right',
|
||||||
width: 200,
|
width: 200,
|
||||||
align: "center",
|
align: 'center'
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -380,7 +402,7 @@ const handleAdd = () => {
|
|||||||
};
|
};
|
||||||
// 修改
|
// 修改
|
||||||
const handleEdit = (record: any, type: string) => {
|
const handleEdit = (record: any, type: string) => {
|
||||||
if (type == "view") {
|
if (type == 'view') {
|
||||||
isView.value = true;
|
isView.value = true;
|
||||||
} else {
|
} else {
|
||||||
isView.value = false;
|
isView.value = false;
|
||||||
@ -392,14 +414,14 @@ const handleEdit = (record: any, type: string) => {
|
|||||||
// 单个/批量 删除过鱼数据
|
// 单个/批量 删除过鱼数据
|
||||||
const handleDelete = (ids: any[]) => {
|
const handleDelete = (ids: any[]) => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: "是否确认 删除 选中数据吗?",
|
title: '是否确认 删除 选中数据吗?',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
let res: any = await delFishDraft(ids);
|
let res: any = await delFishDraft(ids);
|
||||||
if (res && res?.code == 0) {
|
if (res && res?.code == 0) {
|
||||||
message.success("删除成功");
|
message.success('删除成功');
|
||||||
tableRef.value?.refresh();
|
tableRef.value?.refresh();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// 批量删除 - 按钮
|
// 批量删除 - 按钮
|
||||||
@ -409,14 +431,14 @@ const batchDelBtn = () => {
|
|||||||
//单个/ 批量提交过鱼数据
|
//单个/ 批量提交过鱼数据
|
||||||
const handleSubmit = (ids: any[]) => {
|
const handleSubmit = (ids: any[]) => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: "是否 提交 选中数据吗?",
|
title: '是否 提交 选中数据吗?',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
let res: any = await submitFishDraft(ids);
|
let res: any = await submitFishDraft(ids);
|
||||||
if (res && res?.code == 0) {
|
if (res && res?.code == 0) {
|
||||||
message.success("提交成功");
|
message.success('提交成功');
|
||||||
tableRef.value?.refresh();
|
tableRef.value?.refresh();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// 批量提交-按钮
|
// 批量提交-按钮
|
||||||
@ -426,58 +448,61 @@ const submitBtn = async () => {
|
|||||||
//全部提交按钮
|
//全部提交按钮
|
||||||
const allSubmitBtn = async () => {
|
const allSubmitBtn = async () => {
|
||||||
if (tableRef.value.tableData.length === 0) {
|
if (tableRef.value.tableData.length === 0) {
|
||||||
message.warning("请先添加数据!");
|
message.warning('请先添加数据!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: "是否 提交 全部数据吗?",
|
title: '是否 提交 全部数据吗?',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
let res: any = await batchApproveAll();
|
let res: any = await batchApproveAll();
|
||||||
if (res && res?.code == 0) {
|
if (res && res?.code == 0) {
|
||||||
message.success("提交成功");
|
message.success('提交成功');
|
||||||
tableRef.value?.refresh();
|
tableRef.value?.refresh();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 单个/ 批量审批过鱼数据
|
// 单个/ 批量审批过鱼数据
|
||||||
const handleSuccess = (ids: any[]) => {
|
const handleSuccess = (ids: any[]) => {
|
||||||
let rejectReason = "";
|
let rejectReason = '';
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: "是否确认 审批通过 选中数据?",
|
title: '是否确认 审批通过 选中数据?',
|
||||||
// 使用 h 函数创建内容
|
// 使用 h 函数创建内容
|
||||||
content: h("div", { style: "margin-top: 10px;" }, [
|
content: h('div', { style: 'margin-top: 10px;' }, [
|
||||||
h("div", { style: "margin-bottom: 8px;" }, [
|
h('div', { style: 'margin-bottom: 8px;' }, [
|
||||||
h("span", { style: "color: red;" }, "* "),
|
h('span', { style: 'color: red;' }, '* '),
|
||||||
h("span", "审批原因:"),
|
h('span', '审批原因:')
|
||||||
]),
|
]),
|
||||||
h("textarea", {
|
h('textarea', {
|
||||||
class: "ant-input", // 使用 antdv 的样式类,使其看起来像 Antdv 输入框
|
class: 'ant-input', // 使用 antdv 的样式类,使其看起来像 Antdv 输入框
|
||||||
placeholder: "请输入审批原因",
|
placeholder: '请输入审批原因',
|
||||||
rows: 4,
|
rows: 4,
|
||||||
style: {
|
style: {
|
||||||
width: "100%",
|
width: '100%',
|
||||||
resize: "none",
|
resize: 'none',
|
||||||
border: "1px solid #d9d9d9",
|
border: '1px solid #d9d9d9',
|
||||||
padding: "8px",
|
padding: '8px',
|
||||||
borderRadius: "4px",
|
borderRadius: '4px'
|
||||||
},
|
},
|
||||||
// 监听输入事件,更新局部变量
|
// 监听输入事件,更新局部变量
|
||||||
onInput: (e: Event) => {
|
onInput: (e: Event) => {
|
||||||
rejectReason = (e.target as HTMLTextAreaElement).value;
|
rejectReason = (e.target as HTMLTextAreaElement).value;
|
||||||
},
|
}
|
||||||
}),
|
})
|
||||||
]),
|
]),
|
||||||
okText: "确认审批",
|
okText: '确认审批',
|
||||||
cancelText: "取消",
|
cancelText: '取消',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
let res: any = await successFishDraft({ ids: ids, approveComment: rejectReason });
|
let res: any = await successFishDraft({
|
||||||
|
ids: ids,
|
||||||
|
approveComment: rejectReason
|
||||||
|
});
|
||||||
if (res && res?.code == 0) {
|
if (res && res?.code == 0) {
|
||||||
message.success("审批成功");
|
message.success('审批成功');
|
||||||
tableRef.value?.refresh();
|
tableRef.value?.refresh();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// 批量审批-按钮
|
// 批量审批-按钮
|
||||||
@ -487,55 +512,59 @@ const successBtn = async () => {
|
|||||||
|
|
||||||
// 批量驳回过鱼数据
|
// 批量驳回过鱼数据
|
||||||
const handleReject = (id: any) => {
|
const handleReject = (id: any) => {
|
||||||
let rejectReason = "";
|
let rejectReason = '';
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: "是否确认 驳回 选中数据?",
|
title: '是否确认 驳回 选中数据?',
|
||||||
// 使用 h 函数创建内容
|
// 使用 h 函数创建内容
|
||||||
content: h("div", { style: "margin-top: 10px;" }, [
|
content: h('div', { style: 'margin-top: 10px;' }, [
|
||||||
h("div", { style: "margin-bottom: 8px;" }, [
|
h('div', { style: 'margin-bottom: 8px;' }, [
|
||||||
h("span", { style: "color: red;" }, "* "),
|
h('span', { style: 'color: red;' }, '* '),
|
||||||
h("span", "驳回原因:"),
|
h('span', '驳回原因:')
|
||||||
]),
|
]),
|
||||||
h("textarea", {
|
h('textarea', {
|
||||||
class: "ant-input", // 使用 antdv 的样式类,使其看起来像 Antdv 输入框
|
class: 'ant-input', // 使用 antdv 的样式类,使其看起来像 Antdv 输入框
|
||||||
placeholder: "请输入驳回原因",
|
placeholder: '请输入驳回原因',
|
||||||
rows: 4,
|
rows: 4,
|
||||||
style: {
|
style: {
|
||||||
width: "100%",
|
width: '100%',
|
||||||
resize: "none",
|
resize: 'none',
|
||||||
border: "1px solid #d9d9d9",
|
border: '1px solid #d9d9d9',
|
||||||
padding: "8px",
|
padding: '8px',
|
||||||
borderRadius: "4px",
|
borderRadius: '4px'
|
||||||
},
|
},
|
||||||
// 监听输入事件,更新局部变量
|
// 监听输入事件,更新局部变量
|
||||||
onInput: (e: Event) => {
|
onInput: (e: Event) => {
|
||||||
rejectReason = (e.target as HTMLTextAreaElement).value;
|
rejectReason = (e.target as HTMLTextAreaElement).value;
|
||||||
},
|
}
|
||||||
}),
|
})
|
||||||
]),
|
]),
|
||||||
okText: "确认驳回",
|
okText: '确认驳回',
|
||||||
cancelText: "取消",
|
cancelText: '取消',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
// 校验驳回原因不能为空
|
// 校验驳回原因不能为空
|
||||||
if (!rejectReason || rejectReason.trim() === "") {
|
if (!rejectReason || rejectReason.trim() === '') {
|
||||||
message.warning("请输入驳回原因");
|
message.warning('请输入驳回原因');
|
||||||
return Promise.reject(); // 阻止 Modal 关闭
|
return Promise.reject(); // 阻止 Modal 关闭
|
||||||
}
|
}
|
||||||
|
|
||||||
let res: any = await rejectFishDraft({ id: id, rejectReason: rejectReason });
|
let res: any = await rejectFishDraft({
|
||||||
|
id: id,
|
||||||
|
rejectReason: rejectReason
|
||||||
|
});
|
||||||
if (res && res?.code == 0) {
|
if (res && res?.code == 0) {
|
||||||
message.success("驳回成功");
|
message.success('驳回成功');
|
||||||
tableRef.value?.refresh();
|
tableRef.value?.refresh();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// 多选禁用
|
// 多选禁用
|
||||||
const getCheckboxProps = (record: any) => {
|
const getCheckboxProps = (record: any) => {
|
||||||
return {
|
return {
|
||||||
disabled: [!checkPerm(["sjtb:edit-review"]) && "PENDING", "APPROVED"].includes(
|
disabled: [
|
||||||
record.status
|
!checkPerm(['sjtb:edit-review']) && 'PENDING',
|
||||||
),
|
'APPROVED'
|
||||||
|
].includes(record.status)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
// 多选
|
// 多选
|
||||||
@ -551,10 +580,10 @@ const handleEditSubmit = async (values: FormData) => {
|
|||||||
if (currentRecord.value) {
|
if (currentRecord.value) {
|
||||||
// 编辑逻辑
|
// 编辑逻辑
|
||||||
let res: any = await editFishDraft({
|
let res: any = await editFishDraft({
|
||||||
...values,
|
...values
|
||||||
});
|
});
|
||||||
if (res && res?.code == 0) {
|
if (res && res?.code == 0) {
|
||||||
message.success("编辑成功");
|
message.success('编辑成功');
|
||||||
editModalVisible.value = false;
|
editModalVisible.value = false;
|
||||||
tableRef.value?.refresh();
|
tableRef.value?.refresh();
|
||||||
}
|
}
|
||||||
@ -562,10 +591,10 @@ const handleEditSubmit = async (values: FormData) => {
|
|||||||
} else {
|
} else {
|
||||||
// 新增逻辑
|
// 新增逻辑
|
||||||
let res: any = await addFishDraft({
|
let res: any = await addFishDraft({
|
||||||
...values,
|
...values
|
||||||
});
|
});
|
||||||
if (res && res?.code == 0) {
|
if (res && res?.code == 0) {
|
||||||
message.success("新增成功");
|
message.success('新增成功');
|
||||||
editModalVisible.value = false;
|
editModalVisible.value = false;
|
||||||
tableRef.value?.refresh();
|
tableRef.value?.refresh();
|
||||||
}
|
}
|
||||||
@ -575,7 +604,7 @@ const handleEditSubmit = async (values: FormData) => {
|
|||||||
// 提交导入
|
// 提交导入
|
||||||
const handleModalOk = () => {
|
const handleModalOk = () => {
|
||||||
if (modalTableRef.value.editingData != undefined) {
|
if (modalTableRef.value.editingData != undefined) {
|
||||||
message.warning("请点击保存后提交数据!");
|
message.warning('请点击保存后提交数据!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (let i = 0; i < fileTableData.value.length; i++) {
|
for (let i = 0; i < fileTableData.value.length; i++) {
|
||||||
@ -588,91 +617,91 @@ const handleModalOk = () => {
|
|||||||
message.warning("请检查流域,流域填写有误!");
|
message.warning("请检查流域,流域填写有误!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (fileTableData.value[i].warnings.includes("rstcd")) {
|
if (fileTableData.value[i].warnings.includes('rstcd')) {
|
||||||
message.warning("请检查电站,电站填写有误!");
|
message.warning('请检查电站,电站填写有误!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (fileTableData.value[i].warnings.includes("stcd")) {
|
if (fileTableData.value[i].warnings.includes('stcd')) {
|
||||||
message.warning("请检查过鱼设施,过鱼设施填写有误!");
|
message.warning('请检查过鱼设施,过鱼设施填写有误!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (fileTableData.value[i].warnings.includes("strdt")) {
|
if (fileTableData.value[i].warnings.includes('strdt')) {
|
||||||
message.warning("请检查过鱼时间,过鱼时间填写有误!");
|
message.warning('请检查过鱼时间,过鱼时间填写有误!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (fileTableData.value[i].warnings.includes("ftp")) {
|
if (fileTableData.value[i].warnings.includes('ftp')) {
|
||||||
message.warning("请检查鱼种类,鱼种类填写有误!");
|
message.warning('请检查鱼种类,鱼种类填写有误!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (fileTableData.value[i].warnings.includes("direction")) {
|
if (fileTableData.value[i].warnings.includes('direction')) {
|
||||||
message.warning("请检查游向,游向填写有误!");
|
message.warning('请检查游向,游向填写有误!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (fileTableData.value[i].warnings.includes("isfs")) {
|
if (fileTableData.value[i].warnings.includes('isfs')) {
|
||||||
message.warning("请检查是否鱼苗,是否鱼苗填写有误!");
|
message.warning('请检查是否鱼苗,是否鱼苗填写有误!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (fileTableData.value[i].warnings.includes("fcnt")) {
|
if (fileTableData.value[i].warnings.includes('fcnt')) {
|
||||||
message.warning("请检查过鱼数量,过鱼数量填写有误!");
|
message.warning('请检查过鱼数量,过鱼数量填写有误!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fileTableData.value[i].picpthsWarnings?.length > 0) {
|
if (fileTableData.value[i].picpthsWarnings?.length > 0) {
|
||||||
message.warning("请检查图片,图片路径有误!");
|
message.warning('请检查图片,图片路径有误!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (fileTableData.value[i].vdpthsWarnings?.length > 0) {
|
if (fileTableData.value[i].vdpthsWarnings?.length > 0) {
|
||||||
message.warning("请检查视频,视频路径有误!");
|
message.warning('请检查视频,视频路径有误!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: "是否提交导入数据?",
|
title: '是否提交导入数据?',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
try {
|
try {
|
||||||
let res: any = await batchSaveDraft({
|
let res: any = await batchSaveDraft({
|
||||||
taskId: taskId.value,
|
taskId: taskId.value
|
||||||
});
|
});
|
||||||
if (res && res?.code == 0) {
|
if (res && res?.code == 0) {
|
||||||
fileTableData.value = [];
|
fileTableData.value = [];
|
||||||
taskId.value = "";
|
taskId.value = '';
|
||||||
tableRef.value?.refresh();
|
tableRef.value?.refresh();
|
||||||
visible.value = false;
|
visible.value = false;
|
||||||
message.success("导入成功");
|
message.success('导入成功');
|
||||||
} else {
|
} else {
|
||||||
message.error("导入失败,请检查数据是否正确");
|
message.error('导入失败,请检查数据是否正确');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error("导入数据失败");
|
message.error('导入数据失败');
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 关闭导入弹窗
|
// 关闭导入弹窗
|
||||||
const handleCustomCancel = () => {
|
const handleCustomCancel = () => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: "是否取消导入数据?",
|
title: '是否取消导入数据?',
|
||||||
content: "未提交的数据将丢失",
|
content: '未提交的数据将丢失',
|
||||||
okText: "确定",
|
okText: '确定',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
// 可选:调用取消接口
|
// 可选:调用取消接口
|
||||||
let res: any = await cancelImportTask({ taskId: taskId.value });
|
let res: any = await cancelImportTask({ taskId: taskId.value });
|
||||||
if (res && res?.code == 0) {
|
if (res && res?.code == 0) {
|
||||||
fileTableData.value = [];
|
fileTableData.value = [];
|
||||||
taskId.value = "";
|
taskId.value = '';
|
||||||
message.success("取消成功");
|
message.success('取消成功');
|
||||||
tableRef.value?.refresh();
|
tableRef.value?.refresh();
|
||||||
visible.value = false;
|
visible.value = false;
|
||||||
} else {
|
} else {
|
||||||
message.error("取消失败");
|
message.error('取消失败');
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const importBtn = async () => {
|
const importBtn = async () => {
|
||||||
let res: any = await checkImportStatus();
|
let res: any = await checkImportStatus();
|
||||||
taskId.value = "";
|
taskId.value = '';
|
||||||
if (res?.code == 0) {
|
if (res?.code == 0) {
|
||||||
const { hasImportingTask, currentTask } = res?.data || {};
|
const { hasImportingTask, currentTask } = res?.data || {};
|
||||||
if (currentTask) {
|
if (currentTask) {
|
||||||
@ -681,17 +710,17 @@ const importBtn = async () => {
|
|||||||
if (hasImportingTask) {
|
if (hasImportingTask) {
|
||||||
visible.value = true;
|
visible.value = true;
|
||||||
fileLoading.value = true;
|
fileLoading.value = true;
|
||||||
if (currentTask.status == "UPLOADED") {
|
if (currentTask.status == 'UPLOADED') {
|
||||||
if (visible.value) {
|
if (visible.value) {
|
||||||
// message.warning(currentTask.statusText)
|
// message.warning(currentTask.statusText)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (visible.value) importBtn();
|
if (visible.value) importBtn();
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
} else if (currentTask.status == "FAILED") {
|
} else if (currentTask.status == 'FAILED') {
|
||||||
message.error(currentTask.statusText);
|
message.error(currentTask.statusText);
|
||||||
fileLoading.value = false;
|
fileLoading.value = false;
|
||||||
} else if (currentTask.status == "VALIDATED") {
|
} else if (currentTask.status == 'VALIDATED') {
|
||||||
nextTick(async () => {
|
nextTick(async () => {
|
||||||
modalTableRef.value.editingRowIndex = null;
|
modalTableRef.value.editingRowIndex = null;
|
||||||
getFileList();
|
getFileList();
|
||||||
@ -701,7 +730,7 @@ const importBtn = async () => {
|
|||||||
fileInputRef.value?.click();
|
fileInputRef.value?.click();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
message.error("导入查询失败");
|
message.error('导入查询失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const getFileList = async () => {
|
const getFileList = async () => {
|
||||||
@ -715,7 +744,7 @@ const fileChange = (file: File) => {
|
|||||||
fileLoading.value = true;
|
fileLoading.value = true;
|
||||||
fileTableData.value = [];
|
fileTableData.value = [];
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", file);
|
formData.append('file', file);
|
||||||
let res: any = await importFishZip(formData);
|
let res: any = await importFishZip(formData);
|
||||||
if (res && res?.code == 0) {
|
if (res && res?.code == 0) {
|
||||||
if (visible.value) importBtn();
|
if (visible.value) importBtn();
|
||||||
@ -734,19 +763,19 @@ const handleFileSelect = (e: Event) => {
|
|||||||
// 校验文件大小 (300MB)
|
// 校验文件大小 (300MB)
|
||||||
const maxSize = 300 * 1024 * 1024;
|
const maxSize = 300 * 1024 * 1024;
|
||||||
if (file.size > maxSize) {
|
if (file.size > maxSize) {
|
||||||
message.error("文件大小不能超过300MB");
|
message.error('文件大小不能超过300MB');
|
||||||
resetFileInput();
|
resetFileInput();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 校验文件类型
|
// 校验文件类型
|
||||||
const isZip =
|
const isZip =
|
||||||
file.name.toLowerCase().endsWith(".zip") ||
|
file.name.toLowerCase().endsWith('.zip') ||
|
||||||
file.type === "application/zip" ||
|
file.type === 'application/zip' ||
|
||||||
file.type === "application/x-zip-compressed";
|
file.type === 'application/x-zip-compressed';
|
||||||
|
|
||||||
if (!isZip) {
|
if (!isZip) {
|
||||||
message.error("请选择.zip格式的压缩包");
|
message.error('请选择.zip格式的压缩包');
|
||||||
resetFileInput();
|
resetFileInput();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -756,7 +785,7 @@ const handleFileSelect = (e: Event) => {
|
|||||||
|
|
||||||
const resetFileInput = () => {
|
const resetFileInput = () => {
|
||||||
if (fileInputRef.value) {
|
if (fileInputRef.value) {
|
||||||
fileInputRef.value.value = "";
|
fileInputRef.value.value = '';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const fileTableaAnalysis = (res: any) => {
|
const fileTableaAnalysis = (res: any) => {
|
||||||
@ -771,7 +800,7 @@ const fileTableaAnalysis = (res: any) => {
|
|||||||
return keyA - keyB;
|
return keyA - keyB;
|
||||||
});
|
});
|
||||||
|
|
||||||
list.forEach((item) => {
|
list.forEach(item => {
|
||||||
data.push({
|
data.push({
|
||||||
...item.data,
|
...item.data,
|
||||||
rowIndex: item.rowIndex,
|
rowIndex: item.rowIndex,
|
||||||
@ -779,13 +808,13 @@ const fileTableaAnalysis = (res: any) => {
|
|||||||
vdpthsWarnings: item.vdpthsWarnings,
|
vdpthsWarnings: item.vdpthsWarnings,
|
||||||
picpthList: item.picpthList,
|
picpthList: item.picpthList,
|
||||||
picpthsWarnings: item.picpthsWarnings,
|
picpthsWarnings: item.picpthsWarnings,
|
||||||
warnings: item.warnings,
|
warnings: item.warnings
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
fileTableData.value = data || [];
|
fileTableData.value = data || [];
|
||||||
fileLoading.value = false;
|
fileLoading.value = false;
|
||||||
};
|
};
|
||||||
const handleReset = (values) => {
|
const handleReset = values => {
|
||||||
handleSearchFinish(values);
|
handleSearchFinish(values);
|
||||||
};
|
};
|
||||||
// 自定义数据转换函数
|
// 自定义数据转换函数
|
||||||
@ -795,60 +824,61 @@ const customTransform = (res: any) => {
|
|||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
picpthList: item.picpth
|
picpthList: item.picpth
|
||||||
? item.picpth.split(",").map((item: string) => baseUrl + "/?" + item) || []
|
? item.picpth.split(',').map((item: string) => baseUrl + '/?' + item) ||
|
||||||
: [],
|
[]
|
||||||
|
: []
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
records: modifiedRecords,
|
records: modifiedRecords,
|
||||||
total: res?.data?.total || 0,
|
total: res?.data?.total || 0
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
// 搜索-按钮
|
// 搜索-按钮
|
||||||
const handleSearchFinish = (values: any) => {
|
const handleSearchFinish = (values: any) => {
|
||||||
const filters = [
|
const filters = [
|
||||||
values.ftp && {
|
values.ftp && {
|
||||||
field: "ftp",
|
field: 'ftp',
|
||||||
operator: "eq",
|
operator: 'eq',
|
||||||
dataType: "string",
|
dataType: 'string',
|
||||||
value: values.ftp,
|
value: values.ftp
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: "strdt",
|
field: 'strdt',
|
||||||
operator: "gte",
|
operator: 'gte',
|
||||||
dataType: "date",
|
dataType: 'date',
|
||||||
value: values.strdt[0] + " 00:00:00",
|
value: values.strdt[0] + ' 00:00:00'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: "strdt",
|
field: 'strdt',
|
||||||
operator: "lte",
|
operator: 'lte',
|
||||||
dataType: "date",
|
dataType: 'date',
|
||||||
value: values.strdt[1] + " 23:59:59",
|
value: values.strdt[1] + ' 23:59:59'
|
||||||
},
|
},
|
||||||
values.direction && {
|
values.direction && {
|
||||||
field: "direction",
|
field: 'direction',
|
||||||
operator: "eq",
|
operator: 'eq',
|
||||||
dataType: "string",
|
dataType: 'string',
|
||||||
value: values.direction,
|
value: values.direction
|
||||||
},
|
},
|
||||||
// status: 'DRAFT',
|
// status: 'DRAFT',
|
||||||
values.status && {
|
values.status && {
|
||||||
field: "status",
|
field: 'status',
|
||||||
operator: "eq",
|
operator: 'eq',
|
||||||
dataType: "string",
|
dataType: 'string',
|
||||||
value: values.status,
|
value: values.status
|
||||||
},
|
},
|
||||||
values.stcd && {
|
values.stcd && {
|
||||||
field: "stcd",
|
field: 'stcd',
|
||||||
operator: "eq",
|
operator: 'eq',
|
||||||
dataType: "string",
|
dataType: 'string',
|
||||||
value: values.stcd,
|
value: values.stcd
|
||||||
},
|
},
|
||||||
values.rstcd && {
|
values.rstcd && {
|
||||||
field: "rstcd",
|
field: 'rstcd',
|
||||||
operator: "eq",
|
operator: 'eq',
|
||||||
dataType: "string",
|
dataType: 'string',
|
||||||
value: values.rstcd,
|
value: values.rstcd
|
||||||
},
|
},
|
||||||
values.rvcd !== "all" && {
|
values.rvcd !== "all" && {
|
||||||
field: "rvcd",
|
field: "rvcd",
|
||||||
@ -865,65 +895,70 @@ const handleSearchFinish = (values: any) => {
|
|||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
|
|
||||||
const filter = {
|
const filter = {
|
||||||
logic: "and",
|
logic: 'and',
|
||||||
filters: filters,
|
filters: filters
|
||||||
};
|
};
|
||||||
tableRef.value?.getList(filter);
|
tableRef.value?.getList(filter);
|
||||||
};
|
};
|
||||||
// 处理预览点击 (由子组件触发)
|
// 处理预览点击 (由子组件触发)
|
||||||
const handlePreviewClick = (record: any, type: string, index: number, action: string) => {
|
const handlePreviewClick = (
|
||||||
|
record: any,
|
||||||
|
type: string,
|
||||||
|
index: number,
|
||||||
|
action: string
|
||||||
|
) => {
|
||||||
const mixedList: MediaItem[] = [];
|
const mixedList: MediaItem[] = [];
|
||||||
if (action === "delete") {
|
if (action === 'delete') {
|
||||||
isPreviewMediaDelete.value = true;
|
isPreviewMediaDelete.value = true;
|
||||||
tablePreviewRecord.value = record;
|
tablePreviewRecord.value = record;
|
||||||
} else if (action === "preview") {
|
} else if (action === 'preview') {
|
||||||
isPreviewMediaDelete.value = false;
|
isPreviewMediaDelete.value = false;
|
||||||
}
|
}
|
||||||
if (type === "image") {
|
if (type === 'image') {
|
||||||
videoPreviewTitle.value = "图片预览";
|
videoPreviewTitle.value = '图片预览';
|
||||||
const nameList = record.picpthList;
|
const nameList = record.picpthList;
|
||||||
|
|
||||||
nameList.forEach((item: any) => {
|
nameList.forEach((item: any) => {
|
||||||
let url = "";
|
let url = '';
|
||||||
if (action === "preview") {
|
if (action === 'preview') {
|
||||||
url = item.url;
|
url = item.url;
|
||||||
} else if (action === "delete") {
|
} else if (action === 'delete') {
|
||||||
url =
|
url =
|
||||||
baseUrlApi +
|
baseUrlApi +
|
||||||
"/data/fishDraft/previewFile?filename=" +
|
'/data/fishDraft/previewFile?filename=' +
|
||||||
item.name +
|
item.name +
|
||||||
"&taskId=" +
|
'&taskId=' +
|
||||||
taskId.value +
|
taskId.value +
|
||||||
"&type=1";
|
'&type=1';
|
||||||
}
|
}
|
||||||
mixedList.push({
|
mixedList.push({
|
||||||
id: record.id,
|
id: record.id,
|
||||||
type: "image",
|
type: 'image',
|
||||||
name: item.name,
|
name: item.name,
|
||||||
url: url,
|
url: url
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else if (type === "video") {
|
} else if (type === 'video') {
|
||||||
videoPreviewTitle.value = "视频预览";
|
videoPreviewTitle.value = '视频预览';
|
||||||
const nameList = record.vdpthList;
|
const nameList = record.vdpthList;
|
||||||
nameList.forEach((item: any) => {
|
nameList.forEach((item: any) => {
|
||||||
let url = "";
|
let url = '';
|
||||||
if (action === "preview") {
|
if (action === 'preview') {
|
||||||
url = item.url;
|
url = item.url;
|
||||||
} else if (action === "delete") {
|
} else if (action === 'delete') {
|
||||||
url =
|
url =
|
||||||
baseUrlApi +
|
baseUrlApi +
|
||||||
"/data/fishDraft/previewFile?filename=" +
|
'/data/fishDraft/previewFile?filename=' +
|
||||||
item.name +
|
item.name +
|
||||||
"&taskId=" +
|
'&taskId=' +
|
||||||
taskId.value +
|
taskId.value +
|
||||||
"&type=2";
|
'&type=2';
|
||||||
}
|
}
|
||||||
mixedList.push({
|
mixedList.push({
|
||||||
id: record.id,
|
id: record.id,
|
||||||
type: "video",
|
type: 'video',
|
||||||
name: item.name, // 视频通常没有单独的名称字段,可根据需要调整
|
name: item.name, // 视频通常没有单独的名称字段,可根据需要调整
|
||||||
url: url,
|
url: url
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -938,15 +973,16 @@ const handlePreviewClick = (record: any, type: string, index: number, action: st
|
|||||||
|
|
||||||
const handleDeleteMedia = (item: any, type: string, index: number) => {
|
const handleDeleteMedia = (item: any, type: string, index: number) => {
|
||||||
previewList.value.splice(index, 1);
|
previewList.value.splice(index, 1);
|
||||||
if (type == "图片预览") {
|
if (type == '图片预览') {
|
||||||
if (previewList.value.length == 0) {
|
if (previewList.value.length == 0) {
|
||||||
tablePreviewRecord.value.picpthList = [];
|
tablePreviewRecord.value.picpthList = [];
|
||||||
tablePreviewRecord.value.picpthsWarnings = [];
|
tablePreviewRecord.value.picpthsWarnings = [];
|
||||||
} else {
|
} else {
|
||||||
tablePreviewRecord.value.picpthList.splice(index, 1);
|
tablePreviewRecord.value.picpthList.splice(index, 1);
|
||||||
tablePreviewRecord.value.picpthsWarnings = tablePreviewRecord.value.picpthsWarnings.filter(
|
tablePreviewRecord.value.picpthsWarnings =
|
||||||
(warningName: any) => warningName !== item.name
|
tablePreviewRecord.value.picpthsWarnings.filter(
|
||||||
);
|
(warningName: any) => warningName !== item.name
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (previewList.value.length == 0) {
|
if (previewList.value.length == 0) {
|
||||||
@ -954,9 +990,10 @@ const handleDeleteMedia = (item: any, type: string, index: number) => {
|
|||||||
tablePreviewRecord.value.vdpthsWarnings = [];
|
tablePreviewRecord.value.vdpthsWarnings = [];
|
||||||
} else {
|
} else {
|
||||||
tablePreviewRecord.value.vdpthList.splice(index, 1);
|
tablePreviewRecord.value.vdpthList.splice(index, 1);
|
||||||
tablePreviewRecord.value.vdpthsWarnings = tablePreviewRecord.value.vdpthsWarnings.filter(
|
tablePreviewRecord.value.vdpthsWarnings =
|
||||||
(warningName: any) => warningName !== item.name
|
tablePreviewRecord.value.vdpthsWarnings.filter(
|
||||||
);
|
(warningName: any) => warningName !== item.name
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -976,13 +1013,13 @@ onMounted(() => {
|
|||||||
observer.observe(tableContainerRef.value);
|
observer.observe(tableContainerRef.value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
getDictItemsByCode({ dictCode: "direction" }).then((res) => {
|
getDictItemsByCode({ dictCode: 'direction' }).then(res => {
|
||||||
direction.value = res.data;
|
direction.value = res.data;
|
||||||
});
|
});
|
||||||
getDictItemsByCode({ dictCode: "approvalStatus" }).then((res) => {
|
getDictItemsByCode({ dictCode: 'approvalStatus' }).then(res => {
|
||||||
guoyuStatus.value.length = 0;
|
guoyuStatus.value.length = 0;
|
||||||
res.data.forEach((item: any) => {
|
res.data.forEach((item: any) => {
|
||||||
if (item.itemCode != "APPROVED") {
|
if (item.itemCode != 'APPROVED') {
|
||||||
guoyuStatus.value.push(item);
|
guoyuStatus.value.push(item);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user