2025-12-24 发版,具体内容见发版日志

This commit is contained in:
whm
2025-12-24 22:15:55 +08:00
parent 9ea7d46df0
commit 2f581b34ba
370 changed files with 94577 additions and 66778 deletions

View File

@@ -21,4 +21,3 @@ selenium-debug.log
package-lock.json
yarn.lock
vite.config.js

View File

@@ -13,7 +13,9 @@
"@vueup/vue-quill": "1.2.0",
"@vueuse/core": "10.6.1",
"axios": "0.27.2",
"china-division": "^2.7.0",
"d3": "^7.9.0",
"dayjs": "^1.11.19",
"decimal.js": "^10.5.0",
"echarts": "5.4.3",
"element-china-area-data": "^6.1.0",
@@ -24,6 +26,7 @@
"js-cookie": "3.0.5",
"jsencrypt": "3.3.2",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"moment": "^2.30.1",
"nprogress": "0.2.0",
"pinia": "2.1.7",
@@ -38,14 +41,17 @@
"vue-router": "4.2.5"
},
"devDependencies": {
"@types/node": "^25.0.1",
"@vitejs/plugin-vue": "4.5.0",
"@vue/compiler-sfc": "3.3.9",
"sass": "1.69.5",
"typescript": "^5.9.3",
"unplugin-auto-import": "0.17.1",
"unplugin-vue-setup-extend-plus": "1.0.0",
"vite": "5.0.4",
"vite-plugin-compression": "0.5.1",
"vite-plugin-svg-icons": "2.0.1"
"vite-plugin-svg-icons": "2.0.1",
"vue-tsc": "^3.1.8"
}
},
"node_modules/@antfu/utils": {
@@ -668,9 +674,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.53.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.2.tgz",
"integrity": "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==",
"version": "4.53.3",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz",
"integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==",
"cpu": [
"arm"
],
@@ -682,9 +688,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.53.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.2.tgz",
"integrity": "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==",
"version": "4.53.3",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz",
"integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==",
"cpu": [
"arm64"
],
@@ -696,9 +702,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.53.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.2.tgz",
"integrity": "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==",
"version": "4.53.3",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz",
"integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==",
"cpu": [
"arm64"
],
@@ -710,9 +716,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.53.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.2.tgz",
"integrity": "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==",
"version": "4.53.3",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz",
"integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==",
"cpu": [
"x64"
],
@@ -724,9 +730,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.53.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.2.tgz",
"integrity": "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==",
"version": "4.53.3",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz",
"integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==",
"cpu": [
"arm64"
],
@@ -738,9 +744,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.53.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.2.tgz",
"integrity": "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==",
"version": "4.53.3",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz",
"integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==",
"cpu": [
"x64"
],
@@ -752,9 +758,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.53.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.2.tgz",
"integrity": "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==",
"version": "4.53.3",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz",
"integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==",
"cpu": [
"arm"
],
@@ -766,9 +772,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.53.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.2.tgz",
"integrity": "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==",
"version": "4.53.3",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz",
"integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==",
"cpu": [
"arm"
],
@@ -780,9 +786,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.53.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.2.tgz",
"integrity": "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==",
"version": "4.53.3",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz",
"integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==",
"cpu": [
"arm64"
],
@@ -794,9 +800,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.53.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.2.tgz",
"integrity": "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==",
"version": "4.53.3",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz",
"integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==",
"cpu": [
"arm64"
],
@@ -808,9 +814,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.53.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.2.tgz",
"integrity": "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==",
"version": "4.53.3",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz",
"integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==",
"cpu": [
"loong64"
],
@@ -822,9 +828,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.53.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.2.tgz",
"integrity": "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==",
"version": "4.53.3",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz",
"integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==",
"cpu": [
"ppc64"
],
@@ -836,9 +842,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.53.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.2.tgz",
"integrity": "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==",
"version": "4.53.3",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz",
"integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==",
"cpu": [
"riscv64"
],
@@ -850,9 +856,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.53.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.2.tgz",
"integrity": "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==",
"version": "4.53.3",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz",
"integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==",
"cpu": [
"riscv64"
],
@@ -864,9 +870,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.53.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.2.tgz",
"integrity": "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==",
"version": "4.53.3",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz",
"integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==",
"cpu": [
"s390x"
],
@@ -878,9 +884,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.53.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.2.tgz",
"integrity": "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==",
"version": "4.53.3",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz",
"integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==",
"cpu": [
"x64"
],
@@ -892,9 +898,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.53.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.2.tgz",
"integrity": "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==",
"version": "4.53.3",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz",
"integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==",
"cpu": [
"x64"
],
@@ -906,9 +912,9 @@
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.53.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.2.tgz",
"integrity": "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==",
"version": "4.53.3",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz",
"integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==",
"cpu": [
"arm64"
],
@@ -920,9 +926,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.53.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.2.tgz",
"integrity": "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==",
"version": "4.53.3",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz",
"integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==",
"cpu": [
"arm64"
],
@@ -934,9 +940,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.53.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.2.tgz",
"integrity": "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==",
"version": "4.53.3",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz",
"integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==",
"cpu": [
"ia32"
],
@@ -948,9 +954,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.53.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.2.tgz",
"integrity": "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==",
"version": "4.53.3",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz",
"integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==",
"cpu": [
"x64"
],
@@ -962,9 +968,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.53.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.2.tgz",
"integrity": "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==",
"version": "4.53.3",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz",
"integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==",
"cpu": [
"x64"
],
@@ -999,9 +1005,9 @@
"license": "MIT"
},
"node_modules/@types/lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",
"version": "4.17.21",
"resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==",
"license": "MIT"
},
"node_modules/@types/lodash-es": {
@@ -1014,9 +1020,9 @@
}
},
"node_modules/@types/node": {
"version": "24.10.0",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-24.10.0.tgz",
"integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==",
"version": "25.0.2",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-25.0.2.tgz",
"integrity": "sha512-gWEkeiyYE4vqjON/+Obqcoeffmk0NF15WSBwSs7zwVA2bAbTaE0SJ7P0WNGoJn8uE7fiaV5a7dKYIJriEqOrmA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1078,6 +1084,35 @@
"vue": "^3.2.25"
}
},
"node_modules/@volar/language-core": {
"version": "2.4.26",
"resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.26.tgz",
"integrity": "sha512-hH0SMitMxnB43OZpyF1IFPS9bgb2I3bpCh76m2WEK7BE0A0EzpYsRp0CCH2xNKshr7kacU5TQBLYn4zj7CG60A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@volar/source-map": "2.4.26"
}
},
"node_modules/@volar/source-map": {
"version": "2.4.26",
"resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.26.tgz",
"integrity": "sha512-JJw0Tt/kSFsIRmgTQF4JSt81AUSI1aEye5Zl65EeZ8H35JHnTvFGmpDOBn5iOxd48fyGE+ZvZBp5FcgAy/1Qhw==",
"dev": true,
"license": "MIT"
},
"node_modules/@volar/typescript": {
"version": "2.4.26",
"resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.26.tgz",
"integrity": "sha512-N87ecLD48Sp6zV9zID/5yuS1+5foj0DfuYGdQ6KHj/IbKvyKv1zNX6VCmnKYwtmHadEO6mFc2EKISiu3RDPAvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@volar/language-core": "2.4.26",
"path-browserify": "^1.0.1",
"vscode-uri": "^3.0.8"
}
},
"node_modules/@vue/compiler-core": {
"version": "3.3.9",
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.3.9.tgz",
@@ -1138,13 +1173,95 @@
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
"license": "MIT"
},
"node_modules/@vue/reactivity": {
"version": "3.5.24",
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.24.tgz",
"integrity": "sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==",
"node_modules/@vue/language-core": {
"version": "3.1.8",
"resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.1.8.tgz",
"integrity": "sha512-PfwAW7BLopqaJbneChNL6cUOTL3GL+0l8paYP5shhgY5toBNidWnMXWM+qDwL7MC9+zDtzCF2enT8r6VPu64iw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vue/shared": "3.5.24"
"@volar/language-core": "2.4.26",
"@vue/compiler-dom": "^3.5.0",
"@vue/shared": "^3.5.0",
"alien-signals": "^3.0.0",
"muggle-string": "^0.4.1",
"path-browserify": "^1.0.1",
"picomatch": "^4.0.2"
},
"peerDependencies": {
"typescript": "*"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@vue/language-core/node_modules/@vue/compiler-core": {
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.25.tgz",
"integrity": "sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.28.5",
"@vue/shared": "3.5.25",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/language-core/node_modules/@vue/compiler-dom": {
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.25.tgz",
"integrity": "sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vue/compiler-core": "3.5.25",
"@vue/shared": "3.5.25"
}
},
"node_modules/@vue/language-core/node_modules/@vue/shared": {
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.25.tgz",
"integrity": "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==",
"dev": true,
"license": "MIT"
},
"node_modules/@vue/language-core/node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/@vue/language-core/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@vue/reactivity": {
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.25.tgz",
"integrity": "sha512-5xfAypCQepv4Jog1U4zn8cZIcbKKFka3AgWHEFQeK65OW+Ys4XybP6z2kKgws4YB43KGpqp5D/K3go2UPPunLA==",
"license": "MIT",
"dependencies": {
"@vue/shared": "3.5.25"
}
},
"node_modules/@vue/reactivity-transform": {
@@ -1162,95 +1279,95 @@
}
},
"node_modules/@vue/reactivity/node_modules/@vue/shared": {
"version": "3.5.24",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.24.tgz",
"integrity": "sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==",
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.25.tgz",
"integrity": "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==",
"license": "MIT"
},
"node_modules/@vue/runtime-core": {
"version": "3.5.24",
"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.24.tgz",
"integrity": "sha512-RYP/byyKDgNIqfX/gNb2PB55dJmM97jc9wyF3jK7QUInYKypK2exmZMNwnjueWwGceEkP6NChd3D2ZVEp9undQ==",
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.25.tgz",
"integrity": "sha512-Z751v203YWwYzy460bzsYQISDfPjHTl+6Zzwo/a3CsAf+0ccEjQ8c+0CdX1WsumRTHeywvyUFtW6KvNukT/smA==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.24",
"@vue/shared": "3.5.24"
"@vue/reactivity": "3.5.25",
"@vue/shared": "3.5.25"
}
},
"node_modules/@vue/runtime-core/node_modules/@vue/shared": {
"version": "3.5.24",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.24.tgz",
"integrity": "sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==",
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.25.tgz",
"integrity": "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==",
"license": "MIT"
},
"node_modules/@vue/runtime-dom": {
"version": "3.5.24",
"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.24.tgz",
"integrity": "sha512-Z8ANhr/i0XIluonHVjbUkjvn+CyrxbXRIxR7wn7+X7xlcb7dJsfITZbkVOeJZdP8VZwfrWRsWdShH6pngMxRjw==",
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.25.tgz",
"integrity": "sha512-a4WrkYFbb19i9pjkz38zJBg8wa/rboNERq3+hRRb0dHiJh13c+6kAbgqCPfMaJ2gg4weWD3APZswASOfmKwamA==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.24",
"@vue/runtime-core": "3.5.24",
"@vue/shared": "3.5.24",
"@vue/reactivity": "3.5.25",
"@vue/runtime-core": "3.5.25",
"@vue/shared": "3.5.25",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/runtime-dom/node_modules/@vue/shared": {
"version": "3.5.24",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.24.tgz",
"integrity": "sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==",
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.25.tgz",
"integrity": "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==",
"license": "MIT"
},
"node_modules/@vue/server-renderer": {
"version": "3.5.24",
"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.24.tgz",
"integrity": "sha512-Yh2j2Y4G/0/4z/xJ1Bad4mxaAk++C2v4kaa8oSYTMJBJ00/ndPuxCnWeot0/7/qafQFLh5pr6xeV6SdMcE/G1w==",
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.25.tgz",
"integrity": "sha512-UJaXR54vMG61i8XNIzTSf2Q7MOqZHpp8+x3XLGtE3+fL+nQd+k7O5+X3D/uWrnQXOdMw5VPih+Uremcw+u1woQ==",
"license": "MIT",
"dependencies": {
"@vue/compiler-ssr": "3.5.24",
"@vue/shared": "3.5.24"
"@vue/compiler-ssr": "3.5.25",
"@vue/shared": "3.5.25"
},
"peerDependencies": {
"vue": "3.5.24"
"vue": "3.5.25"
}
},
"node_modules/@vue/server-renderer/node_modules/@vue/compiler-core": {
"version": "3.5.24",
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.24.tgz",
"integrity": "sha512-eDl5H57AOpNakGNAkFDH+y7kTqrQpJkZFXhWZQGyx/5Wh7B1uQYvcWkvZi11BDhscPgj8N7XV3oRwiPnx1Vrig==",
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.25.tgz",
"integrity": "sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.28.5",
"@vue/shared": "3.5.24",
"@vue/shared": "3.5.25",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/server-renderer/node_modules/@vue/compiler-dom": {
"version": "3.5.24",
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.24.tgz",
"integrity": "sha512-1QHGAvs53gXkWdd3ZMGYuvQFXHW4ksKWPG8HP8/2BscrbZ0brw183q2oNWjMrSWImYLHxHrx1ItBQr50I/q2zw==",
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.25.tgz",
"integrity": "sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==",
"license": "MIT",
"dependencies": {
"@vue/compiler-core": "3.5.24",
"@vue/shared": "3.5.24"
"@vue/compiler-core": "3.5.25",
"@vue/shared": "3.5.25"
}
},
"node_modules/@vue/server-renderer/node_modules/@vue/compiler-ssr": {
"version": "3.5.24",
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.24.tgz",
"integrity": "sha512-trOvMWNBMQ/odMRHW7Ae1CdfYx+7MuiQu62Jtu36gMLXcaoqKvAyh+P73sYG9ll+6jLB6QPovqoKGGZROzkFFg==",
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.25.tgz",
"integrity": "sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.24",
"@vue/shared": "3.5.24"
"@vue/compiler-dom": "3.5.25",
"@vue/shared": "3.5.25"
}
},
"node_modules/@vue/server-renderer/node_modules/@vue/shared": {
"version": "3.5.24",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.24.tgz",
"integrity": "sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==",
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.25.tgz",
"integrity": "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==",
"license": "MIT"
},
"node_modules/@vue/server-renderer/node_modules/entities": {
@@ -1399,6 +1516,13 @@
"node": ">=0.4.0"
}
},
"node_modules/alien-signals": {
"version": "3.1.1",
"resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-3.1.1.tgz",
"integrity": "sha512-ogkIWbVrLwKtHY6oOAXaYkAxP+cTH7V5FZ5+Tm4NZFd8VDZ6uNMDrfzqctTZ42eTMCSR3ne3otpcxmqSnFfPYA==",
"dev": true,
"license": "MIT"
},
"node_modules/ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-2.1.1.tgz",
@@ -1739,9 +1863,9 @@
}
},
"node_modules/bwip-js": {
"version": "4.7.0",
"resolved": "https://registry.npmmirror.com/bwip-js/-/bwip-js-4.7.0.tgz",
"integrity": "sha512-b7oQcgbWUl8rpcZayQ32SQrBCNteiZFuLkimKKBRlPwIHCeUN2VNeUE3HCMYShe04Evxd+ucS9uUAOsvNKjQbA==",
"version": "4.8.0",
"resolved": "https://registry.npmmirror.com/bwip-js/-/bwip-js-4.8.0.tgz",
"integrity": "sha512-gUDkDHSTv8/DJhomSIbO0fX/Dx0MO/sgllLxJyJfu4WixCQe9nfGJzmHm64ZCbxo+gUYQEsQcRmqcwcwPRwUkg==",
"license": "MIT",
"bin": {
"bwip-js": "bin/bwip-js.js"
@@ -2046,9 +2170,9 @@
}
},
"node_modules/core-js": {
"version": "3.46.0",
"resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.46.0.tgz",
"integrity": "sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==",
"version": "3.47.0",
"resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.47.0.tgz",
"integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==",
"hasInstallScript": true,
"license": "MIT",
"funding": {
@@ -2222,9 +2346,9 @@
}
},
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"version": "3.2.3",
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT"
},
"node_modules/d3": {
@@ -3540,9 +3664,9 @@
}
},
"node_modules/form-data": {
"version": "4.0.4",
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"version": "4.0.5",
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
@@ -4702,12 +4826,12 @@
}
},
"node_modules/jspdf": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/jspdf/-/jspdf-3.0.3.tgz",
"integrity": "sha512-eURjAyz5iX1H8BOYAfzvdPfIKK53V7mCpBTe7Kb16PaM8JSXEcUQNBQaiWMI8wY5RvNOPj4GccMjTlfwRBd+oQ==",
"version": "3.0.4",
"resolved": "https://registry.npmmirror.com/jspdf/-/jspdf-3.0.4.tgz",
"integrity": "sha512-dc6oQ8y37rRcHn316s4ngz/nOjayLF/FFxBF4V9zamQKRqXxyiH1zagkCdktdWhtoQId5K20xt1lB90XzkB+hQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.9",
"@babel/runtime": "^7.28.4",
"fast-png": "^6.2.0",
"fflate": "^0.8.1"
},
@@ -5007,6 +5131,13 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/muggle-string": {
"version": "0.4.1",
"resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz",
"integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
"dev": true,
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
@@ -5390,6 +5521,13 @@
"node": ">=0.10.0"
}
},
"node_modules/path-browserify": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz",
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
"dev": true,
"license": "MIT"
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz",
@@ -6014,9 +6152,9 @@
"license": "Unlicense"
},
"node_modules/rollup": {
"version": "4.53.2",
"resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.53.2.tgz",
"integrity": "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==",
"version": "4.53.3",
"resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.53.3.tgz",
"integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6030,28 +6168,28 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.53.2",
"@rollup/rollup-android-arm64": "4.53.2",
"@rollup/rollup-darwin-arm64": "4.53.2",
"@rollup/rollup-darwin-x64": "4.53.2",
"@rollup/rollup-freebsd-arm64": "4.53.2",
"@rollup/rollup-freebsd-x64": "4.53.2",
"@rollup/rollup-linux-arm-gnueabihf": "4.53.2",
"@rollup/rollup-linux-arm-musleabihf": "4.53.2",
"@rollup/rollup-linux-arm64-gnu": "4.53.2",
"@rollup/rollup-linux-arm64-musl": "4.53.2",
"@rollup/rollup-linux-loong64-gnu": "4.53.2",
"@rollup/rollup-linux-ppc64-gnu": "4.53.2",
"@rollup/rollup-linux-riscv64-gnu": "4.53.2",
"@rollup/rollup-linux-riscv64-musl": "4.53.2",
"@rollup/rollup-linux-s390x-gnu": "4.53.2",
"@rollup/rollup-linux-x64-gnu": "4.53.2",
"@rollup/rollup-linux-x64-musl": "4.53.2",
"@rollup/rollup-openharmony-arm64": "4.53.2",
"@rollup/rollup-win32-arm64-msvc": "4.53.2",
"@rollup/rollup-win32-ia32-msvc": "4.53.2",
"@rollup/rollup-win32-x64-gnu": "4.53.2",
"@rollup/rollup-win32-x64-msvc": "4.53.2",
"@rollup/rollup-android-arm-eabi": "4.53.3",
"@rollup/rollup-android-arm64": "4.53.3",
"@rollup/rollup-darwin-arm64": "4.53.3",
"@rollup/rollup-darwin-x64": "4.53.3",
"@rollup/rollup-freebsd-arm64": "4.53.3",
"@rollup/rollup-freebsd-x64": "4.53.3",
"@rollup/rollup-linux-arm-gnueabihf": "4.53.3",
"@rollup/rollup-linux-arm-musleabihf": "4.53.3",
"@rollup/rollup-linux-arm64-gnu": "4.53.3",
"@rollup/rollup-linux-arm64-musl": "4.53.3",
"@rollup/rollup-linux-loong64-gnu": "4.53.3",
"@rollup/rollup-linux-ppc64-gnu": "4.53.3",
"@rollup/rollup-linux-riscv64-gnu": "4.53.3",
"@rollup/rollup-linux-riscv64-musl": "4.53.3",
"@rollup/rollup-linux-s390x-gnu": "4.53.3",
"@rollup/rollup-linux-x64-gnu": "4.53.3",
"@rollup/rollup-linux-x64-musl": "4.53.3",
"@rollup/rollup-openharmony-arm64": "4.53.3",
"@rollup/rollup-win32-arm64-msvc": "4.53.3",
"@rollup/rollup-win32-ia32-msvc": "4.53.3",
"@rollup/rollup-win32-x64-gnu": "4.53.3",
"@rollup/rollup-win32-x64-msvc": "4.53.3",
"fsevents": "~2.3.2"
}
},
@@ -7351,6 +7489,20 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/ufo": {
"version": "1.6.1",
"resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.1.tgz",
@@ -7796,17 +7948,24 @@
"dev": true,
"license": "MIT"
},
"node_modules/vscode-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz",
"integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
"dev": true,
"license": "MIT"
},
"node_modules/vue": {
"version": "3.5.24",
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.24.tgz",
"integrity": "sha512-uTHDOpVQTMjcGgrqFPSb8iO2m1DUvo+WbGqoXQz8Y1CeBYQ0FXf2z1gLRaBtHjlRz7zZUBHxjVB5VTLzYkvftg==",
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.25.tgz",
"integrity": "sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.24",
"@vue/compiler-sfc": "3.5.24",
"@vue/runtime-dom": "3.5.24",
"@vue/server-renderer": "3.5.24",
"@vue/shared": "3.5.24"
"@vue/compiler-dom": "3.5.25",
"@vue/compiler-sfc": "3.5.25",
"@vue/runtime-dom": "3.5.25",
"@vue/server-renderer": "3.5.25",
"@vue/shared": "3.5.25"
},
"peerDependencies": {
"typescript": "*"
@@ -7901,40 +8060,57 @@
"vue": "^3.2.0"
}
},
"node_modules/vue-tsc": {
"version": "3.1.8",
"resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-3.1.8.tgz",
"integrity": "sha512-deKgwx6exIHeZwF601P1ktZKNF0bepaSN4jBU3AsbldPx9gylUc1JDxYppl82yxgkAgaz0Y0LCLOi+cXe9HMYA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@volar/typescript": "2.4.26",
"@vue/language-core": "3.1.8"
},
"bin": {
"vue-tsc": "bin/vue-tsc.js"
},
"peerDependencies": {
"typescript": ">=5.0.0"
}
},
"node_modules/vue/node_modules/@vue/compiler-core": {
"version": "3.5.24",
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.24.tgz",
"integrity": "sha512-eDl5H57AOpNakGNAkFDH+y7kTqrQpJkZFXhWZQGyx/5Wh7B1uQYvcWkvZi11BDhscPgj8N7XV3oRwiPnx1Vrig==",
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.25.tgz",
"integrity": "sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.28.5",
"@vue/shared": "3.5.24",
"@vue/shared": "3.5.25",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
}
},
"node_modules/vue/node_modules/@vue/compiler-dom": {
"version": "3.5.24",
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.24.tgz",
"integrity": "sha512-1QHGAvs53gXkWdd3ZMGYuvQFXHW4ksKWPG8HP8/2BscrbZ0brw183q2oNWjMrSWImYLHxHrx1ItBQr50I/q2zw==",
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.25.tgz",
"integrity": "sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==",
"license": "MIT",
"dependencies": {
"@vue/compiler-core": "3.5.24",
"@vue/shared": "3.5.24"
"@vue/compiler-core": "3.5.25",
"@vue/shared": "3.5.25"
}
},
"node_modules/vue/node_modules/@vue/compiler-sfc": {
"version": "3.5.24",
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.24.tgz",
"integrity": "sha512-8EG5YPRgmTB+YxYBM3VXy8zHD9SWHUJLIGPhDovo3Z8VOgvP+O7UP5vl0J4BBPWYD9vxtBabzW1EuEZ+Cqs14g==",
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.25.tgz",
"integrity": "sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.28.5",
"@vue/compiler-core": "3.5.24",
"@vue/compiler-dom": "3.5.24",
"@vue/compiler-ssr": "3.5.24",
"@vue/shared": "3.5.24",
"@vue/compiler-core": "3.5.25",
"@vue/compiler-dom": "3.5.25",
"@vue/compiler-ssr": "3.5.25",
"@vue/shared": "3.5.25",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.21",
"postcss": "^8.5.6",
@@ -7942,19 +8118,19 @@
}
},
"node_modules/vue/node_modules/@vue/compiler-ssr": {
"version": "3.5.24",
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.24.tgz",
"integrity": "sha512-trOvMWNBMQ/odMRHW7Ae1CdfYx+7MuiQu62Jtu36gMLXcaoqKvAyh+P73sYG9ll+6jLB6QPovqoKGGZROzkFFg==",
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.25.tgz",
"integrity": "sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.24",
"@vue/shared": "3.5.24"
"@vue/compiler-dom": "3.5.25",
"@vue/shared": "3.5.25"
}
},
"node_modules/vue/node_modules/@vue/shared": {
"version": "3.5.24",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.24.tgz",
"integrity": "sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==",
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.25.tgz",
"integrity": "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==",
"license": "MIT"
},
"node_modules/vue/node_modules/entities": {

View File

@@ -48,13 +48,16 @@
"vue-router": "4.2.5"
},
"devDependencies": {
"@types/node": "^25.0.1",
"@vitejs/plugin-vue": "4.5.0",
"@vue/compiler-sfc": "3.3.9",
"sass": "1.69.5",
"typescript": "^5.9.3",
"unplugin-auto-import": "0.17.1",
"unplugin-vue-setup-extend-plus": "1.0.0",
"vite": "5.0.4",
"vite-plugin-compression": "0.5.1",
"vite-plugin-svg-icons": "2.0.1"
"vite-plugin-svg-icons": "2.0.1",
"vue-tsc": "^3.1.8"
}
}

View File

@@ -1,48 +1,48 @@
import request from '@/utils/request'
import request from '@/utils/request';
// 获取追溯码
export function searchTraceNo(data) {
return request({
url:'/app-common/search-trace-no',
url: '/app-common/search-trace-no',
method: 'get',
params: data,
})
});
}
// 获取处方打印数据
export function advicePrint(data) {
return request({
url:'/app-common/advice-print',
url: '/app-common/advice-print',
method: 'get',
params: data,
})
});
}
// 获取全部科室列表
export function getOrgList(data) {
return request({
url:'/app-common/department-list',
url: '/app-common/department-list',
method: 'get',
params: data,
})
});
}
// 获取全部病区列表
export function getWardList(data) {
return request({
url:'/app-common/ward-list',
url: '/app-common/ward-list',
method: 'get',
params: data,
})
});
}
// 获取全部供应商列表
export function getSupplierList(data) {
return request({
url:'/app-common/supplier',
url: '/app-common/supplier',
method: 'get',
params: data,
})
});
}
/**
@@ -52,8 +52,8 @@ export function getAdjustPriceSwitchState(params) {
return request({
url: '/change/price/getAdjustPriceSwitchState',
method: 'get',
params: params
})
params: params,
});
}
/**
@@ -63,34 +63,33 @@ export function lotNumberMatch(params) {
return request({
url: '/app-common/lot-number-match',
method: 'get',
params: params
})
params: params,
});
}
import axios from 'axios';
const env = import.meta.env.MODE;
export function invokeYbPlugin(data) {
export function invokeYbPlugin5001(data) {
// if(env == 'development'){
// return axios.create(
// {
// // axios中请求配置有baseURL选项表示请求URL公共部分
// baseURL: '/ybplugin',//ybplugin
// // 超时
// timeout: 60000
// }
// ).post('/api/data/', data);
return axios
.create({
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: '',
// 超时
timeout: 60000,
})
.post('http://localhost:5001/api/data/', data);
}
// }
// } else {
return axios.create(
{
export function invokeYbPlugin5000(data) {
return axios
.create({
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: '',
// 超时
timeout: 60000
}
).post('http://localhost:5000/api/data/', data);
}
// }
timeout: 60000,
})
.post('http://localhost:5000/api/data/', data);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -0,0 +1,51 @@
<template>
<div class="page-container">
<div v-if="$slots.header" class="page-header">
<slot name="header" />
</div>
<div class="page-content">
<slot />
</div>
<div v-if="$slots.footer" class="page-footer">
<slot name="footer" />
</div>
</div>
</template>
<style scoped lang="scss">
.page-container {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
overflow: hidden;
padding: 16px;
box-sizing: border-box;
}
.page-header {
flex-shrink: 0;
display: flex;
justify-content: flex-end;
align-items: center;
margin-bottom: 16px;
}
.page-content {
flex: 1;
min-height: 0;
overflow-y: auto;
}
.page-footer {
flex-shrink: 0;
display: flex;
justify-content: center;
align-items: center;
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #ebeef5;
}
</style>

View File

@@ -0,0 +1,12 @@
import request from '@/utils/request';
/**
* 获取住院患者列表
*/
export function getPatientList(queryParams) {
return request({
url: '/reg-doctorstation/advice-manage/reg-patient-zk',
method: 'get',
params: queryParams,
});
}

View File

@@ -0,0 +1,695 @@
<template>
<div class="patientList-container" :class="{ 'patientList-container-unexpand': !currentExpand }">
<div
class="patientList-operate"
:class="{ 'patientList-operate-unexpand': !currentExpand }"
v-if="currentExpand"
>
<el-input
class="patientList-search-input"
placeholder="床号/住院号/姓名"
v-model="searchKeyword"
@keyup.enter="handleSearch"
:prefix-icon="Search"
/>
<el-button class="icon-btn" circle @click="handleRefresh" type="text" plain>
<el-icon icon-class="Refresh" size="24" :class="{ 'is-rotating': refreshing }">
<Refresh />
</el-icon>
</el-button>
</div>
<transition name="patient-list-toggle" mode="out-in">
<div key="expanded" class="patientList-list" v-if="currentExpand">
<div class="patient-cards" v-loading="isLoading">
<template v-if="filteredCardData && filteredCardData.length > 0">
<el-scrollbar ref="expandScrollbarRef" class="patient-cards-scrollbar">
<div
class="patient-card"
v-for="item in filteredCardData"
:key="item.encounterId"
:id="item.encounterId"
@click="handleItemClick(item)"
:class="{ actived: activeCardId === item.encounterId }"
>
<div class="patient-card-header">
<div class="header-top">
<div class="bed-container">
<div class="bed">
<div class="bed-info">
<div v-if="item.houseName" class="house-name">{{ item.houseName }}</div>
<div class="bed-name">{{ item.bedName || '未分床' }}</div>
</div>
</div>
</div>
<el-space>
<el-tag
v-if="item.statusEnum_enumText"
size="small"
class="payer-tag-status"
effect="light"
:style="getStatusStyle(item.statusEnum)"
>
{{ item.statusEnum_enumText }}
</el-tag>
<el-tag
v-if="item.contractName"
size="small"
class="payer-tag"
effect="light"
>
{{ item.contractName }}
</el-tag>
</el-space>
</div>
<div class="header-bottom">
<span class="bus-no">住院号{{ item.busNo || '-' }}</span>
<span class="insurance-type" v-if="item.insutype_dictText">
险种类型{{ item.insutype_dictText }}
</span>
</div>
</div>
<div class="doctor-parent-line" />
<div class="patient-card-body">
<div class="personal-info-container">
<div class="name-container">
<div class="name">
<el-text :text="item.patientName" tclass="name" width="auto">
{{ item.patientName || '-' }}
</el-text>
</div>
<div class="age">
<el-tag
size="small"
class="age-tag"
effect="plain"
:class="{ 'age-tag-female': item.genderEnum_enumText === '女性' }"
>
{{ item.genderEnum_enumText || '-' }}
<span v-if="item.age"> · {{ item.age }}</span>
</el-tag>
</div>
</div>
</div>
</div>
</div>
</el-scrollbar>
</template>
<el-empty v-else description="暂无数据" />
</div>
</div>
<div
key="collapsed"
class="patientList-list"
v-else
v-loading="isLoading"
:class="{ 'patientList-list-unexpand': !currentExpand }"
>
<el-scrollbar ref="contractScrollbarRef" class="patient-cards-scrollbar">
<template v-if="filteredCardData && filteredCardData.length > 0">
<el-tooltip
v-for="item in filteredCardData"
:show-after="200"
:key="item.encounterId"
:show-arrow="true"
placement="right"
effect="light"
:offset="4"
>
<template #content>
<div class="card-tooltip">
<div class="card-tooltip-main">
<span class="card-tooltip-bed">{{ item.bedName }}</span>
<span class="card-tooltip-name">{{ item.patientName }}</span>
</div>
<div class="card-tooltip-sex">
<span class="card-tooltip-sex-text">
{{ item.genderEnum_enumText || '-' }}
<span v-if="item.age"> · {{ item.age }}</span>
</span>
</div>
</div>
</template>
<div>
<div
class="card-small"
:class="{ 'patient-active': activeCardId === item.encounterId }"
@click="handleSmallCardClick(item)"
:key="item.encounterId"
>
{{ item.bedName }}
</div>
<div class="patient-card-small-border"></div>
</div>
</el-tooltip>
</template>
<el-empty v-else description="暂无数据" :image-size="50" />
</el-scrollbar>
</div>
</transition>
<div
class="patientList-toggle-btn-wrap"
:class="{ 'patientList-toggle-btn-wrap-unexpand': !currentExpand }"
>
<el-button class="icon-btn" circle @click="updateExpand">
<el-icon class="svg-sty-menu" size="24">
<Expand v-if="!currentExpand" />
<Fold v-if="currentExpand" />
</el-icon>
</el-button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue';
import { Search, Refresh, Expand, Fold } from '@element-plus/icons-vue';
import type { ElScrollbar } from 'element-plus';
interface PatientCardItem {
encounterId: string;
bedName?: string;
busNo?: string;
patientName?: string;
genderEnum_enumText?: string;
age?: number | string;
contractName?: string;
[key: string]: any;
}
interface Props {
// 过滤后的卡片数据
filteredCardData?: PatientCardItem[];
// 当前激活的卡片ID
activeCardId?: string;
// 是否展开(不传则为不受控模式,组件内部自己管理)
expand?: boolean;
// 加载状态
loading?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
filteredCardData: () => [],
activeCardId: '',
expand: undefined,
loading: false,
});
interface Emits {
(e: 'item-click', item: PatientCardItem): void;
(e: 'search', keyword: string): void;
(e: 'refresh'): void;
(e: 'update:expand', value: boolean): void;
}
const emit = defineEmits<Emits>();
const searchKeyword = ref<string>('');
const refreshing = ref<boolean>(false);
const refreshLoading = ref<boolean>(false);
const internalExpand = ref<boolean>(true);
const isControlled = computed<boolean>(() => props.expand !== undefined);
const currentExpand = computed<boolean>(() => {
return isControlled.value ? props.expand! : internalExpand.value;
});
const isLoading = computed<boolean>(() => {
return props.loading || refreshLoading.value;
});
const handleItemClick = (item: PatientCardItem): void => {
emit('item-click', item);
};
const handleSmallCardClick = (item: PatientCardItem): void => {
emit('item-click', item);
};
const handleSearch = (): void => {
emit('search', searchKeyword.value);
};
const handleRefresh = (): void => {
if (refreshing.value) return;
refreshing.value = true;
refreshLoading.value = true;
emit('refresh');
setTimeout(() => {
refreshing.value = false;
}, 600);
setTimeout(() => {
if (!props.loading) {
refreshLoading.value = false;
}
}, 500);
};
const updateExpand = (): void => {
const newValue = !currentExpand.value;
if (isControlled.value) {
emit('update:expand', newValue);
} else {
internalExpand.value = newValue;
}
};
// 根据状态枚举值返回文本颜色
const getStatusColor = (statusEnum?: number): string => {
if (statusEnum === undefined || statusEnum === null) {
return '';
}
switch (statusEnum) {
case 2: // REGISTERED - 待入科
return '#E6A23C'; // 橙色
case 3: // AWAITING_DISCHARGE - 待出院
return '#F56C6C'; // 红色
case 4: // DISCHARGED_FROM_HOSPITAL - 待出院结算
return '#909399'; // 灰色
case 5: // ADMITTED_TO_THE_HOSPITAL - 已入院
return '#67C23A'; // 绿色
case 6: // PENDING_TRANSFER - 待转科
return '#409EFF'; // 蓝色
default:
return '';
}
};
// 根据状态枚举值返回带透明背景的样式
const getStatusStyle = (statusEnum?: number): Record<string, string> => {
const color = getStatusColor(statusEnum);
if (!color) {
return {};
}
// 不同状态对应的半透明背景色
let backgroundColor = '';
switch (statusEnum) {
case 2: // 橙色
backgroundColor = 'rgba(230, 162, 60, 0.12)';
break;
case 3: // 红色
backgroundColor = 'rgba(245, 108, 108, 0.12)';
break;
case 4: // 灰色
backgroundColor = 'rgba(144, 147, 153, 0.12)';
break;
case 5: // 绿色
backgroundColor = 'rgba(103, 194, 58, 0.12)';
break;
case 6: // 蓝色
backgroundColor = 'rgba(64, 158, 255, 0.12)';
break;
default:
backgroundColor = 'rgba(148, 163, 184, 0.12)';
}
return {
color,
backgroundColor,
borderColor: 'transparent',
};
};
// 保险类型映射表
const insuranceTypeMap: Record<number, string> = {
310: '职工基本医疗保险',
320: '公务员医疗补助',
330: '大额医疗费用补助',
340: '离休人员医疗保障',
350: '一至六级残废军人医疗补助',
360: '老红军医疗保障',
370: '企业补充医疗保险',
380: '新型农村合作医疗',
390: '城乡居民基本医疗保险',
391: '城镇居民基本医疗保险',
392: '城乡居民大病医疗保险',
399: '其他特殊人员医疗保障',
410: '长期照护保险',
};
watch(
() => props.loading,
(newLoading) => {
if (!newLoading && refreshLoading.value) {
refreshLoading.value = false;
}
}
);
</script>
<style lang="scss" scoped>
.patient-card {
width: 100%;
overflow: hidden;
background-color: #fff;
border-radius: 6px;
border: 1px solid rgba(0, 0, 0, 0.04);
box-shadow: 0 2px 6px 0 rgba(15, 35, 52, 0.12);
cursor: pointer;
transition: transform 0.2s ease;
&:hover {
box-shadow: 0 4px 12px rgba(15, 35, 52, 0.18);
transform: translateY(-1px);
}
&.actived {
background-color: rgba(7, 155, 140, 0.06);
border-color: var(--el-color-primary);
box-shadow: 0 0 0 1px rgba(7, 155, 140, 0.3), 0 4px 14px rgba(7, 155, 140, 0.25);
}
.patient-card-header {
display: flex;
flex-direction: column;
padding: 10px 12px 4px;
.header-top {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
.bed-container {
display: flex;
flex: 1;
align-items: center;
min-width: 0;
.bed {
flex-grow: 0;
flex-shrink: 1;
min-width: 0;
.bed-info {
display: flex;
flex-direction: column;
line-height: 1.4;
.house-name {
color: #1f2933;
font-weight: 600;
font-size: 16px;
white-space: normal;
word-break: break-all;
}
.bed-name {
color: #1f2933;
font-weight: 600;
font-size: 16px;
white-space: normal;
word-break: break-all;
}
}
}
}
.payer-tag {
max-width: 120px;
font-size: 12px;
border-radius: 999px;
font-weight: bolder;
}
.payer-tag-status {
font-weight: bolder;
border-radius: 999px;
}
}
.header-bottom {
margin-top: 4px;
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 2px;
color: #6b7280;
font-size: 12px;
.bus-no {
white-space: nowrap;
}
.insurance-type {
white-space: nowrap;
}
}
}
.doctor-parent-line {
margin: 6px 12px 0;
border-bottom: 1px dashed #e5e7eb;
}
.patient-card-body {
padding: 8px 12px 10px;
.personal-info-container {
display: block;
.name-container {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
width: 100%;
.name {
color: #111827;
font-weight: 600;
font-size: 16px;
}
.age {
flex-shrink: 0;
.age-tag {
border-radius: 999px;
padding: 0 8px;
}
.age-tag-female {
border-color: rgb(255, 55, 158);
color: rgb(255, 126, 184);
}
}
}
}
}
}
.patientList-container {
height: 100%;
display: flex;
flex-direction: column;
height: 100%;
border-right: 1px solid #ebeef5;
background-color: #ffffff;
width: 240px;
min-width: 240px;
&-unexpand {
width: 84px;
min-width: 84px;
}
.patientList-operate {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 44px;
border-bottom: 1px solid #ebeef5;
flex: none;
padding: 0 0 0 8px;
&-unexpand {
justify-content: space-around;
}
}
.patientList-list {
display: flex;
flex: 1;
flex-direction: column;
height: 0;
width: 240px;
&-unexpand {
width: 84px;
}
.search-operate {
padding: 0 8px;
height: 48px;
display: flex;
align-items: center;
flex: none;
}
.patient-cards {
flex: 1;
padding: 0 8px;
overflow: hidden;
:deep(.patient-cards-scrollbar) {
width: 100%;
height: 100%;
.el-scrollbar__bar {
width: 0;
}
}
}
.card-small {
height: 44px;
padding: 0 10px 0 12px;
overflow: hidden;
font-size: 14px;
line-height: 44px;
white-space: nowrap;
text-overflow: ellipsis;
border-right: none;
cursor: pointer;
}
.patient-active {
background-color: #e6f7ff;
font-weight: 600;
color: var(--el-color-primary);
}
.patient-card-small-border {
display: block;
width: 100%;
height: 2px;
background-color: #f1faff;
}
}
}
.patientList-toggle-btn-wrap {
display: flex;
justify-content: flex-end;
padding: 4px 16px 8px;
&-unexpand {
justify-content: center;
}
}
.patient-list-toggle-enter-active,
.patient-list-toggle-leave-active {
transition: transform 0.2s ease;
}
.patient-list-toggle-enter-from,
.patient-list-toggle-leave-to {
opacity: 0;
transform: translateY(-8px);
}
.card-tooltip {
display: inline-flex;
align-items: center;
justify-content: space-between;
min-width: 140px;
max-width: 220px;
padding: 6px 10px;
border-radius: 6px;
background-color: #ffffff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
}
.card-tooltip-main {
display: flex;
flex-direction: column;
margin-right: 8px;
}
.card-tooltip-bed {
font-size: 15px;
font-weight: 600;
color: #111827;
margin-bottom: 2px;
}
.card-tooltip-name {
font-size: 13px;
color: #4b5563;
}
.card-tooltip-sex {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: flex-end;
}
.card-tooltip-sex-text {
font-size: 12px;
color: #6b7280;
}
.svg-gray {
fill: var(--hip-color-text-unit);
}
.icon-btn {
border: none;
background-color: transparent;
box-shadow: none;
padding: 4px;
}
.is-rotating {
animation: patient-refresh-rotate 0.6s linear;
}
@keyframes patient-refresh-rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
:deep(.scrollbar) {
width: 100%;
height: 100%;
.el-scrollbar__bar {
width: 0;
}
}
.f-16 {
font-weight: 600;
font-size: 16px;
}
.f-14 {
font-size: 14px;
}
.empty-wrap {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
.empty-text-sty {
margin-top: 0;
}
}
</style>

View File

@@ -0,0 +1,110 @@
<template>
<BasePatientList
:filtered-card-data="filteredCardData"
:active-card-id="cardId"
:loading="queryloading"
@item-click="handleItemClick"
@search="handleSearch"
@refresh="getList"
/>
</template>
<script setup>
import { computed, onMounted, reactive, ref, watch } from 'vue';
import BasePatientList from './index.vue';
import { getPatientList } from '@/views/inpatientDoctor/home/components/api';
defineOptions({
name: 'PatientListWithData',
});
const props = defineProps({
/** 接口 status 参数,默认 5在院 */
status: {
type: [String, Number],
default: undefined,
},
/** 首次加载自动选中第一条 */
autoSelectFirst: {
type: Boolean,
default: true,
},
/** 外部已选中的患者信息(用于避免重复自动选中) */
selectedPatient: {
type: Object,
default: null,
},
/**
* 选中患者时回调(给你外部写 store 用)
* (patient) => void
*/
onSelect: {
type: Function,
default: null,
},
});
const emit = defineEmits(['item-click']);
// 这段逻辑就是你说的 “@index.vue (4-11) 那套带数据的”
const searchData = reactive({
keyword: '',
patientType: 1,
type: 1,
timeLimit: 3,
});
const cardId = ref('');
const cardAllData = ref([]);
const isFirstLoad = ref(true);
const queryloading = ref(false);
const filteredCardData = computed(() => cardAllData.value);
const getList = () => {
queryloading.value = true;
getPatientList({ status: props.status, searchKey: searchData.keyword })
.then((res) => {
cardAllData.value = res?.data?.records || [];
})
.finally(() => {
queryloading.value = false;
});
};
watch(
() => filteredCardData.value,
(newData) => {
if (!props.autoSelectFirst) return;
if (
newData &&
newData.length > 0 &&
!cardId.value &&
isFirstLoad.value &&
!props.selectedPatient?.encounterId
) {
const firstPatient = newData[0];
if (firstPatient?.encounterId) {
handleItemClick(firstPatient);
isFirstLoad.value = false;
}
}
},
{ immediate: true }
);
const handleItemClick = (node) => {
cardId.value = node.encounterId;
props.onSelect?.(node);
emit('item-click', node);
};
const handleSearch = (keyword) => {
searchData.keyword = keyword;
getList();
};
onMounted(() => {
getList();
});
</script>

View File

@@ -0,0 +1,318 @@
<template>
<div class="pending-patient-list">
<div class="patient-cards" v-loading="loading">
<template v-if="list && list.length > 0">
<el-scrollbar class="patient-cards-scrollbar">
<div
class="patient-card"
v-for="(item, index) in list"
:key="item[idKey] ?? index"
:class="{ actived: !!item.active || activeId === item[idKey] }"
draggable="true"
@click="emit('item-click', item, index)"
@dblclick="emit('item-dblclick', item)"
@dragstart="(e) => emit('dragstart', e, item)"
@dragend="(e) => emit('dragend', e)"
>
<div class="patient-card-header">
<div class="header-top">
<div class="bed-container">
<div class="bed">
<el-text truncated tclass="bed-font" width="auto">
{{ item.bedName || '-' }}
</el-text>
</div>
</div>
<div class="header-tags">
<el-tag v-if="item.contractName" size="small" class="payer-tag" effect="light">
{{ item.contractName }}
</el-tag>
<el-tag
v-if="item.encounterStatus_enumText"
size="small"
class="status-tag"
effect="light"
:class="getStatusClass(item)"
>
{{ item.encounterStatus_enumText }}
</el-tag>
</div>
</div>
<div class="header-bottom">
<span class="bus-no">住院号{{ item.busNo || '-' }}</span>
</div>
<div class="meta">
<div class="meta-row">
<span class="meta-label">入院时间</span>
<span class="meta-value">{{
item.startTime ? formatDate(item.startTime) : '-'
}}</span>
</div>
<div class="meta-row">
<span class="meta-label">入科时间</span>
<span class="meta-value">{{
item.admissionTime ? formatDate(item.admissionTime) : '-'
}}</span>
</div>
</div>
</div>
<div class="doctor-parent-line" />
<div class="patient-card-body">
<div class="personal-info-container">
<div class="name-container">
<div class="name">
<el-text :text="item.patientName" tclass="name" width="auto">
{{ item.patientName || '-' }}
</el-text>
</div>
<div class="age">
<el-tag
size="small"
class="age-tag"
effect="plain"
:class="{
'age-tag-female': item.genderEnum_enumText === '女性',
'age-tag-male': item.genderEnum_enumText === '男性',
}"
>
{{ item.genderEnum_enumText || '-' }}
<span v-if="item.age"> · {{ item.age }}</span>
<span v-if="item.priorityEnum_enumText">
· {{ item.priorityEnum_enumText }}</span
>
</el-tag>
</div>
</div>
</div>
<div class="meta"></div>
</div>
</div>
</el-scrollbar>
</template>
<el-empty v-else description="暂无数据" />
</div>
</div>
</template>
<script setup lang="ts">
import { formatDate } from '@/utils/index';
withDefaults(
defineProps<{
list: any[];
activeId?: string | number;
idKey?: string;
loading?: boolean;
}>(),
{
list: () => [],
activeId: '',
idKey: 'id',
loading: false,
}
);
const emit = defineEmits<{
(e: 'item-click', item: any, index: number): void;
(e: 'item-dblclick', item: any): void;
(e: 'dragstart', event: DragEvent, item: any): void;
(e: 'dragend', event: DragEvent): void;
}>();
const getStatusClass = (item: any) => {
if (item?.encounterStatus == 2) return 'status-tag--blue';
if (item?.encounterStatus == 5) return 'status-tag--green';
return '';
};
</script>
<style scoped lang="scss" name="PendingPatientList">
.pending-patient-list {
height: 100%;
display: flex;
flex-direction: column;
min-height: 0;
min-width: 240px;
width: 240px;
}
.patient-cards {
flex: 1 1 auto;
min-height: 0;
overflow: hidden;
:deep(.patient-cards-scrollbar) {
width: 100%;
height: 100%;
.el-scrollbar__bar {
width: 0;
}
}
}
.patient-card {
width: 100%;
overflow: hidden;
background-color: #fff;
border-radius: 6px;
border: 1px solid rgba(0, 0, 0, 0.04);
box-shadow: 0 2px 6px 0 rgba(15, 35, 52, 0.12);
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
&:hover {
box-shadow: 0 4px 12px rgba(15, 35, 52, 0.18);
transform: translateY(-1px);
}
&.actived {
background-color: rgba(7, 155, 140, 0.06);
border-color: var(--el-color-primary);
box-shadow: 0 0 0 1px rgba(7, 155, 140, 0.3), 0 4px 14px rgba(7, 155, 140, 0.25);
}
}
.patient-card-header {
display: flex;
flex-direction: column;
padding: 10px 12px 4px;
.header-top {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
.bed-container {
display: flex;
flex: 1;
align-items: center;
min-width: 0;
.bed {
flex-grow: 0;
flex-shrink: 1;
min-width: 0;
:deep(.bed-font) {
color: #1f2933;
font-weight: 600;
font-size: 16px;
}
}
}
.payer-tag {
max-width: 120px;
font-size: 12px;
border-radius: 999px;
}
.header-tags {
flex-shrink: 0;
display: flex;
align-items: flex-end;
justify-content: flex-end;
gap: 6px;
}
}
.header-bottom {
margin-top: 4px;
width: 100%;
display: flex;
justify-content: flex-start;
color: #6b7280;
font-size: 12px;
.bus-no {
white-space: nowrap;
}
}
}
.doctor-parent-line {
margin: 6px 12px 0;
border-bottom: 1px dashed #e5e7eb;
}
.patient-card-body {
padding: 8px 12px 10px;
}
.personal-info-container {
display: block;
.name-container {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
width: 100%;
.name {
color: #111827;
font-weight: 600;
font-size: 14px;
}
.age {
flex-shrink: 0;
}
}
}
.age-tag {
border-radius: 999px;
padding: 0 8px;
}
.age-tag-female {
border-color: rgb(255, 55, 158);
color: rgb(255, 126, 184);
}
.age-tag-male {
border-color: #91d5ff;
color: #1677ff;
}
.status-tag {
border-radius: 999px;
padding: 0 8px;
flex-shrink: 0;
font-weight: 600;
}
.status-tag--blue {
border-color: #91d5ff;
background-color: #1677ff;
color: white;
}
.status-tag--green {
border-color: #b7eb8f;
background-color: #389e0d;
color: white;
}
.meta {
margin-top: 6px;
color: #6b7280;
font-size: 12px;
line-height: 18px;
}
.meta-row {
display: flex;
}
.meta-label {
flex-shrink: 0;
}
</style>

View File

@@ -240,7 +240,8 @@
"columnId": "totalPrice",
"fixed": false,
"rowspan": 1,
"colspan": 1
"colspan": 1,
"formatter2": "function(value, row, index, options, rowIndex, column) {\n if (!value) return '';\n return `<span>${value}元</span>`;\n}"
},
{
"title": "类别",

View File

@@ -1,239 +0,0 @@
{
"panels": [
{
"index": 1,
"name": 2,
"paperType": "自定义",
"height": 50,
"width": 70,
"paperList": {
"type": "自定义",
"width": 70,
"height": 50
},
"paperHeader": 0,
"paperFooter": 138.8976377952756,
"paperNumberDisabled": true,
"paperNumberContinue": true,
"panelAngle": 0,
"overPrintOptions": {
"content": "",
"opacity": 0.01,
"type": 1
},
"watermarkOptions": {
"content": "",
"fillStyle": "rgba(87, 13, 248, 0.5)",
"fontSize": "10px",
"rotate": 0,
"width": 100,
"height": 100,
"timestamp": false,
"format": "YYYY-MM-DD HH:mm"
},
"panelLayoutOptions": {
"layoutType": "column",
"layoutRowGap": 0,
"layoutColumnGap": 0
},
"printElements": [
{
"options": {
"left": 6,
"top": 7.5,
"height": 15,
"width": 51,
"title": "文本",
"field": "patientName",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0,
"hideTitle": true,
"fontSize": 10.5,
"fontWeight": "bold"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 90,
"top": 7.5,
"height": 15,
"width": 33,
"title": "文本",
"field": "genderEnum_enumText",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0,
"hideTitle": true,
"fontSize": 10.5,
"fontWeight": "bold"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 147,
"top": 7.5,
"height": 15,
"width": 45,
"title": "文本",
"field": "age",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0,
"hideTitle": true,
"fontSize": 10.5,
"fontWeight": "bold",
"textAlign": "right"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 6,
"top": 33,
"height": 15,
"width": 81,
"title": "频次 qd",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0,
"fontSize": 10.5,
"fontWeight": "bold"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 122,
"top": 33,
"height": 15,
"width": 70.5,
"title": "文本",
"field": "date",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0,
"hideTitle": true,
"fontSize": 10.5,
"fontWeight": "bold",
"textAlign": "right"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 4.5,
"top": 52.5,
"height": 36,
"width": 189,
"title": "undefined+beforeDragIn",
"field": "infuseData",
"coordinateSync": false,
"widthHeightSync": false,
"tableHeaderBackground": "#ffffff",
"tableBodyCellBorder": "noBorder",
"rowsColumnsMerge": "function(data, col, colIndex, rowIndex, tableData, printData){ \n // 合并前三列 (columnIndex 0-2)\n if (colIndex >= 0 && colIndex <= 2) {\n // 第一列显示合并后的单元格\n if (colIndex === 0) {\n return [1, 3]; // rowspan=1, colspan=3\n } \n // 其他两列不显示(被合并)\n else {\n return [0, 0]; // rowspan=0, colspan=0\n }\n }\n // 其他列正常显示\n return [1, 1]; // rowspan=1, colspan=1\n}",
"tableBodyRowBorder": "topBottomBorder",
"columns": [
[
{
"title": "用法",
"titleSync": false,
"halign": "center",
"tableQRCodeLevel": 0,
"tableSummaryTitle": true,
"tableSummary": "",
"width": 45.48982644423766,
"field": "data",
"checked": true,
"columnId": "data",
"fixed": false,
"rowspan": 1,
"colspan": 1
},
{
"title": "剂量",
"titleSync": false,
"align": "center",
"tableQRCodeLevel": 0,
"tableSummaryTitle": true,
"tableSummary": "",
"width": 47.79422504530706,
"checked": true,
"fixed": false,
"rowspan": 1,
"colspan": 1
},
{
"title": "速度",
"titleSync": false,
"align": "center",
"tableQRCodeLevel": 0,
"tableSummaryTitle": true,
"tableSummary": "",
"width": 48.74985321589121,
"checked": true,
"fixed": false,
"rowspan": 1,
"colspan": 1
},
{
"title": "数量",
"titleSync": false,
"align": "center",
"tableQRCodeLevel": 0,
"tableSummaryTitle": true,
"tableSummary": "",
"formatter2": "function(value,row,index,options,rowIndex,column){ return value + ' ' + row.unitCode_dictText; }",
"width": 46.96609529456407,
"field": "quantity",
"checked": true,
"columnId": "quantity",
"fixed": false,
"rowspan": 1,
"colspan": 1
}
]
]
},
"printElementType": {
"title": "表格",
"type": "table",
"editable": true,
"columnDisplayEditable": true,
"columnDisplayIndexEditable": true,
"columnTitleEditable": true,
"columnResizable": true,
"columnAlignEditable": true,
"isEnableEditField": true,
"isEnableContextMenu": true,
"isEnableInsertRow": true,
"isEnableDeleteRow": true,
"isEnableInsertColumn": true,
"isEnableDeleteColumn": true,
"isEnableMergeCell": true
}
}
],
"paperNumberLeft": 151.5,
"paperNumberTop": 91
}
]
}

View File

@@ -4,32 +4,31 @@
"index": 1,
"name": 2,
"paperType": "自定义",
"height": 34,
"width": 58,
"height": 60,
"width": 80,
"paperList": {
"type": "自定义",
"width": 60,
"height": 40
"width": 80,
"height": 60
},
"paperHeader": 0,
"paperFooter": 91.5,
"paperFooter": 166.5,
"paperNumberDisabled": true,
"paperNumberContinue": true,
"expandCss": "",
"panelAngle": 0,
"overPrintOptions": {
"content": "",
"opacity": 0.01,
"opacity": 0.7,
"type": 1
},
"watermarkOptions": {
"content": "",
"fillStyle": "rgba(87, 13, 248, 0.5)",
"fontSize": "10px",
"fontSize": "36px",
"rotate": 25,
"width": 100,
"height": 100,
"timestamp": false,
"width": 413,
"height": 310,
"timestamp": true,
"format": "YYYY-MM-DD HH:mm"
},
"panelLayoutOptions": {
@@ -43,13 +42,12 @@
"left": 6,
"top": 7.5,
"height": 10,
"width": 51,
"title": "文本",
"width": 69,
"title": "病区",
"field": "patientName",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0,
"hideTitle": true,
"fontSize": 7.5,
"fontWeight": "bold"
},
@@ -60,16 +58,15 @@
},
{
"options": {
"left": 70,
"left": 81,
"top": 7.5,
"height": 10,
"width": 33,
"title": "文本",
"field": "genderEnum_enumText",
"width": 52.5,
"title": "姓名",
"field": "patientName",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0,
"hideTitle": true,
"fontSize": 7.5,
"fontWeight": "bold"
},
@@ -80,19 +77,17 @@
},
{
"options": {
"left": 119,
"left": 147,
"top": 7.5,
"height": 10,
"width": 45,
"title": "文本",
"field": "age",
"width": 72,
"title": "床位号",
"field": "bedNo",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0,
"hideTitle": true,
"fontSize": 7.5,
"fontWeight": "bold",
"textAlign": "right"
"fontWeight": "bold"
},
"printElementType": {
"title": "文本",
@@ -102,7 +97,7 @@
{
"options": {
"left": 6,
"top": 19.5,
"top": 22,
"height": 12,
"width": 81,
"title": "频次 qd",
@@ -119,19 +114,17 @@
},
{
"options": {
"left": 93,
"top": 19.5,
"left": 147,
"top": 24,
"height": 10,
"width": 70.5,
"title": "文本",
"title": "日期",
"field": "date",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0,
"hideTitle": true,
"fontSize": 7.5,
"fontWeight": "bold",
"textAlign": "right"
"fontWeight": "bold"
},
"printElementType": {
"title": "文本",
@@ -141,9 +134,9 @@
{
"options": {
"left": 4.5,
"top": 36,
"top": 45,
"height": 30,
"width": 156,
"width": 216,
"title": "undefined+beforeDragIn",
"field": "infuseData",
"coordinateSync": false,
@@ -166,7 +159,7 @@
"tableQRCodeLevel": 0,
"tableSummaryTitle": true,
"tableSummary": "",
"width": 37.54715833492632,
"width": 51.988373079128756,
"field": "data",
"checked": true,
"columnId": "data",
@@ -181,21 +174,23 @@
"tableQRCodeLevel": 0,
"tableSummaryTitle": true,
"tableSummary": "",
"width": 39.4492016246979,
"width": 55.016471480350916,
"checked": true,
"fixed": false,
"rowspan": 1,
"colspan": 1
},
{
"title": "速",
"title": "速",
"titleSync": false,
"align": "center",
"tableQRCodeLevel": 0,
"tableSummaryTitle": true,
"tableSummary": "",
"width": 40.23797408295783,
"width": 55.31961796101854,
"field": "",
"checked": true,
"columnId": "",
"fixed": false,
"rowspan": 1,
"colspan": 1
@@ -208,7 +203,7 @@
"tableSummaryTitle": true,
"tableSummary": "",
"formatter2": "function(value,row,index,options,rowIndex,column){ return value + ' ' + row.unitCode_dictText; }",
"width": 38.765665957417966,
"width": 53.6755374795018,
"field": "quantity",
"checked": true,
"columnId": "quantity",
@@ -236,6 +231,56 @@
"isEnableDeleteColumn": true,
"isEnableMergeCell": true
}
},
{
"options": {
"left": 10,
"top": 144,
"height": 9,
"width": 210,
"borderWidth": "0.75",
"title": "undefined+beforeDragIn",
"coordinateSync": false,
"widthHeightSync": false
},
"printElementType": {
"title": "横线",
"type": "hline"
}
},
{
"options": {
"left": 10,
"top": 153,
"height": 9.75,
"width": 82.5,
"title": "执行人",
"field": "prepareName",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 111,
"top": 153,
"height": 9.75,
"width": 82.5,
"title": "时间",
"field": "date",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0
},
"printElementType": {
"title": "文本",
"type": "text"
}
}
],
"paperNumberLeft": 151.5,

View File

@@ -4,31 +4,32 @@
"index": 1,
"name": 2,
"paperType": "自定义",
"height": 60,
"width": 80,
"height": 34,
"width": 58,
"paperList": {
"type": "自定义",
"width": 80,
"height": 60
"width": 60,
"height": 40
},
"paperHeader": 0,
"paperFooter": 166.5,
"paperFooter": 91.5,
"paperNumberDisabled": true,
"paperNumberContinue": true,
"expandCss": "",
"panelAngle": 0,
"overPrintOptions": {
"content": "",
"opacity": 0.7,
"opacity": 0.01,
"type": 1
},
"watermarkOptions": {
"content": "",
"fillStyle": "rgba(87, 13, 248, 0.5)",
"fontSize": "36px",
"fontSize": "10px",
"rotate": 25,
"width": 413,
"height": 310,
"timestamp": true,
"width": 100,
"height": 100,
"timestamp": false,
"format": "YYYY-MM-DD HH:mm"
},
"panelLayoutOptions": {
@@ -42,12 +43,13 @@
"left": 6,
"top": 7.5,
"height": 10,
"width": 69,
"title": "病区",
"width": 51,
"title": "文本",
"field": "patientName",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0,
"hideTitle": true,
"fontSize": 7.5,
"fontWeight": "bold"
},
@@ -58,15 +60,16 @@
},
{
"options": {
"left": 81,
"left": 70,
"top": 7.5,
"height": 10,
"width": 52.5,
"title": "姓名",
"field": "patientName",
"width": 33,
"title": "文本",
"field": "genderEnum_enumText",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0,
"hideTitle": true,
"fontSize": 7.5,
"fontWeight": "bold"
},
@@ -77,17 +80,19 @@
},
{
"options": {
"left": 147,
"left": 119,
"top": 7.5,
"height": 10,
"width": 72,
"title": "床位号",
"field": "bedNo",
"width": 45,
"title": "文本",
"field": "age",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0,
"hideTitle": true,
"fontSize": 7.5,
"fontWeight": "bold"
"fontWeight": "bold",
"textAlign": "right"
},
"printElementType": {
"title": "文本",
@@ -97,7 +102,7 @@
{
"options": {
"left": 6,
"top": 22,
"top": 19.5,
"height": 12,
"width": 81,
"title": "频次 qd",
@@ -114,17 +119,19 @@
},
{
"options": {
"left": 147,
"top": 24,
"left": 93,
"top": 19.5,
"height": 10,
"width": 70.5,
"title": "日期",
"title": "文本",
"field": "date",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0,
"hideTitle": true,
"fontSize": 7.5,
"fontWeight": "bold"
"fontWeight": "bold",
"textAlign": "right"
},
"printElementType": {
"title": "文本",
@@ -134,9 +141,9 @@
{
"options": {
"left": 4.5,
"top": 45,
"top": 36,
"height": 30,
"width": 216,
"width": 156,
"title": "undefined+beforeDragIn",
"field": "infuseData",
"coordinateSync": false,
@@ -159,7 +166,7 @@
"tableQRCodeLevel": 0,
"tableSummaryTitle": true,
"tableSummary": "",
"width": 51.988373079128756,
"width": 37.54715833492632,
"field": "data",
"checked": true,
"columnId": "data",
@@ -174,23 +181,21 @@
"tableQRCodeLevel": 0,
"tableSummaryTitle": true,
"tableSummary": "",
"width": 55.016471480350916,
"width": 39.4492016246979,
"checked": true,
"fixed": false,
"rowspan": 1,
"colspan": 1
},
{
"title": "速",
"title": "速",
"titleSync": false,
"align": "center",
"tableQRCodeLevel": 0,
"tableSummaryTitle": true,
"tableSummary": "",
"width": 55.31961796101854,
"field": "",
"width": 40.23797408295783,
"checked": true,
"columnId": "",
"fixed": false,
"rowspan": 1,
"colspan": 1
@@ -203,7 +208,7 @@
"tableSummaryTitle": true,
"tableSummary": "",
"formatter2": "function(value,row,index,options,rowIndex,column){ return value + ' ' + row.unitCode_dictText; }",
"width": 53.6755374795018,
"width": 38.765665957417966,
"field": "quantity",
"checked": true,
"columnId": "quantity",
@@ -231,56 +236,6 @@
"isEnableDeleteColumn": true,
"isEnableMergeCell": true
}
},
{
"options": {
"left": 10,
"top": 144,
"height": 9,
"width": 210,
"borderWidth": "0.75",
"title": "undefined+beforeDragIn",
"coordinateSync": false,
"widthHeightSync": false
},
"printElementType": {
"title": "横线",
"type": "hline"
}
},
{
"options": {
"left": 10,
"top": 153,
"height": 9.75,
"width": 82.5,
"title": "执行人",
"field": "prepareName",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 111,
"top": 153,
"height": 9.75,
"width": 82.5,
"title": "时间",
"field": "date",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0
},
"printElementType": {
"title": "文本",
"type": "text"
}
}
],
"paperNumberLeft": 151.5,

View File

@@ -225,7 +225,8 @@
"columnId": "totalPrice",
"fixed": false,
"rowspan": 1,
"colspan": 1
"colspan": 1,
"formatter2": "function(value, row, index, options, rowIndex, column) {\n if (!value) return '';\n return `<span>${value}元</span>`;\n}"
},
{
"title": "类别",

View File

@@ -0,0 +1,60 @@
<template>
<div class="table-section" v-loading="loading">
<EditableTable ref="editableTableRef" v-bind="$attrs" class="editable-table">
<template v-for="(_, slotName) in $slots" :key="slotName" #[slotName]="slotProps">
<slot :name="slotName" v-bind="slotProps" />
</template>
</EditableTable>
</div>
</template>
<script setup>
import { ref } from 'vue';
import EditableTable from './EditableTable.vue';
defineOptions({
name: 'EditTable',
inheritAttrs: false,
});
const props = defineProps({
loading: {
type: Boolean,
default: false,
},
});
const editableTableRef = ref(null);
defineExpose({
get formRef() {
return editableTableRef.value?.formRef;
},
get tableRef() {
return editableTableRef.value?.tableRef;
},
validate: (...args) => editableTableRef.value?.validate(...args),
validateField: (...args) => editableTableRef.value?.validateField(...args),
resetFields: (...args) => editableTableRef.value?.resetFields(...args),
clearValidate: (...args) => editableTableRef.value?.clearValidate(...args),
get tableData() {
return editableTableRef.value?.tableData;
},
});
</script>
<style scoped lang="scss">
.table-section {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
.editable-table {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
}
</style>

View File

@@ -0,0 +1,567 @@
<template>
<el-form ref="formRef" :model="{ tableData }" :rules="rules" class="editable-table-form">
<div
v-if="showAddButton || showDeleteButton || searchFields.length > 0"
class="editable-table-toolbar"
>
<div class="toolbar-left">
<el-button v-if="showAddButton" type="primary" icon="Plus" @click="handleToolbarAdd">
添加行
</el-button>
<el-button
v-if="showDeleteButton"
type="danger"
icon="Delete"
:disabled="selectedRows.length === 0"
@click="handleToolbarDelete"
>
删除行
</el-button>
</div>
<div class="toolbar-right">
<el-input
v-if="searchFields.length > 0"
v-model="searchKeyword"
:placeholder="searchPlaceholder"
clearable
style="width: 300px"
@input="handleSearch"
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
</div>
</div>
<el-table
ref="tableRef"
:data="filteredTableData"
:border="border"
:stripe="stripe"
:max-height="maxHeight || undefined"
:min-height="minHeight || undefined"
:height="!maxHeight && !minHeight ? '100%' : undefined"
:row-key="getRowKey"
:virtualized="useVirtualized"
v-bind="$attrs"
@selection-change="handleSelectionChange"
class="editable-table-inner"
>
<el-table-column v-if="showSelection" type="selection" width="55" align="center" />
<el-table-column
v-if="showRowActions"
:width="rowActionsColumnWidth"
align="center"
fixed="left"
>
<template #header>
<div
v-if="showSelection && selectedRows.length > 0 && !showDeleteButton"
style="display: flex; align-items: center; justify-content: center; gap: 4px"
>
<el-button type="danger" size="small" icon="Delete" link @click="handleDeleteSelected">
删除选中({{ selectedRows.length }})
</el-button>
</div>
<span v-else></span>
</template>
<template #default="scope">
<el-button
v-if="showRowAddButton"
type="primary"
link
icon="CirclePlus"
class="action-btn"
@click="handleAdd(scope.$index)"
title="增加"
/>
<el-button
v-if="showRowDeleteButton"
type="danger"
link
icon="Delete"
class="action-btn"
@click="handleDelete(scope.$index)"
title="删除"
/>
</template>
</el-table-column>
<el-table-column
v-for="col in filteredColumns"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:fixed="col.fixed"
:align="col.align || 'center'"
:formatter="col.formatter"
>
<template #default="scope">
<template v-if="col.type === 'input'">
<el-form-item
:prop="`tableData.${scope.$index}.${col.prop}`"
:rules="col.rules"
style="margin-bottom: 0"
>
<el-input
v-model="scope.row[col.prop]"
:placeholder="col.placeholder || `请输入${col.label}`"
:disabled="col.disabled"
:clearable="col.clearable !== false"
@blur="col.onBlur && col.onBlur(scope.row, scope.$index)"
@input="col.onInput && col.onInput(scope.row, scope.$index)"
@change="col.onChange && col.onChange(scope.row, scope.$index)"
>
<template v-if="col.suffix" #suffix>{{ col.suffix }}</template>
</el-input>
</el-form-item>
</template>
<template v-else-if="col.type === 'number'">
<el-form-item
:prop="`tableData.${scope.$index}.${col.prop}`"
:rules="col.rules"
style="margin-bottom: 0"
>
<el-input-number
v-model="scope.row[col.prop]"
:placeholder="col.placeholder || `请输入${col.label}`"
:disabled="col.disabled"
:min="col.min"
:max="col.max"
:precision="col.precision"
:controls="false"
style="width: 100%"
@change="col.onChange && col.onChange(scope.row, scope.$index)"
/>
</el-form-item>
</template>
<template v-else-if="col.type === 'select'">
<el-form-item
:prop="`tableData.${scope.$index}.${col.prop}`"
:rules="col.rules"
style="margin-bottom: 0"
>
<el-select
v-model="scope.row[col.prop]"
:placeholder="col.placeholder || `请选择${col.label}`"
:disabled="col.disabled"
:clearable="col.clearable !== false"
:filterable="col.filterable"
:multiple="col.multiple"
style="width: 100%"
:class="scope.row.error ? 'error-border' : ''"
@change="
async (value) => {
const checkBeforeChange = col.extraprops?.checkBeforeChange;
if (checkBeforeChange && typeof checkBeforeChange === 'function') {
const result = await checkBeforeChange(scope.row, scope.$index, value);
if (result === false) {
return;
}
}
if (col.onChange) {
col.onChange(scope.row, scope.$index, value);
}
}
"
>
<el-option
v-for="option in typeof col.options === 'function'
? col.options(scope.row, scope.$index)
: col.options || []"
:key="option.value"
:label="option.label"
:value="option.value"
@click="option.onClick && option.onClick(scope.row, option)"
/>
</el-select>
</el-form-item>
</template>
<template v-else-if="col.type === 'date'">
<el-form-item
:prop="`tableData.${scope.$index}.${col.prop}`"
:rules="col.rules"
style="margin-bottom: 0"
>
<el-date-picker
v-model="scope.row[col.prop]"
:type="col.dateType || 'date'"
:placeholder="col.placeholder || `请选择${col.label}`"
:disabled="col.disabled"
:clearable="col.clearable !== false"
:value-format="col.valueFormat || 'YYYY-MM-DD'"
style="width: 100%"
@change="col.onChange && col.onChange(scope.row, scope.$index)"
/>
</el-form-item>
</template>
<template v-else-if="col.type === 'slot'">
<el-form-item
:prop="`tableData.${scope.$index}.${col.prop}`"
:rules="col.rules"
style="margin-bottom: 0"
>
<slot :name="col.slot || col.prop" :row="scope.row" :index="scope.$index" />
</el-form-item>
</template>
<template v-else>
<span>{{
col.formatter
? col.formatter(scope.row, scope.column, scope.row[col.prop])
: scope.row[col.prop]
}}</span>
</template>
</template>
</el-table-column>
</el-table>
<div v-if="$slots.footer" class="editable-table-footer">
<slot name="footer" :tableData="tableData" />
</div>
</el-form>
</template>
<script setup lang="ts">
import { ref, watch, nextTick, computed } from 'vue';
import { Search } from '@element-plus/icons-vue';
import type { EditableTableProps } from '../types/EditableTable.d';
defineOptions({
name: 'EditableTable',
});
const props = withDefaults(defineProps<EditableTableProps>(), {
modelValue: () => [],
rules: () => ({}),
defaultRow: () => ({}),
border: true,
stripe: false,
showSelection: false,
showAddButton: false,
showDeleteButton: false,
showRowActions: true,
showRowAddButton: true,
showRowDeleteButton: true,
searchFields: () => [],
virtualizedThreshold: 100,
});
const emit = defineEmits<{
'update:modelValue': [value: Record<string, any>[]];
add: [row: Record<string, any>, index: number];
delete: [row: Record<string, any>, index: number, isClear: boolean];
'selection-change': [selection: Record<string, any>[]];
'toolbar-add': [];
'toolbar-delete': [rows: Record<string, any>[]];
}>();
const formRef = ref<InstanceType<typeof import('element-plus').ElForm> | null>(null);
const tableRef = ref<InstanceType<typeof import('element-plus').ElTable> | null>(null);
const selectedRows = ref<Record<string, any>[]>([]);
const searchKeyword = ref('');
const tableData = ref([...props.modelValue]);
// 行唯一 key用于虚拟滚动等
const autoRowId = ref(0);
const getRowKey = (row: Record<string, any>) => {
if (row.rowKey !== undefined && row.rowKey !== null) return row.rowKey;
if (row.id !== undefined && row.id !== null) return row.id;
if (!row._etKey) {
row._etKey = `et-${autoRowId.value++}`;
}
return row._etKey;
};
// 是否开启虚拟滚动:优先使用外部传入,其次根据数据量自动开启
const useVirtualized = computed(() => {
if (typeof props.virtualized === 'boolean') {
return props.virtualized;
}
const threshold = props.virtualizedThreshold ?? 100;
return tableData.value.length > threshold;
});
// 过滤列(支持条件显示)
const filteredColumns = computed(() => {
return props.columns.filter((col) => !col.vIf || col.vIf());
});
// 行操作列宽度:同时显示“增加+删除”则宽一点;只显示一个则缩窄
const rowActionsColumnWidth = computed(() => {
const showAdd = !!props.showRowAddButton;
const showDel = !!props.showRowDeleteButton;
if (showAdd && showDel) return 100;
if (showAdd || showDel) return 60;
// 如果两者都不显示,列也不会渲染;这里给个兜底
return 0;
});
const searchPlaceholder = computed(() => {
if (props.searchFields.length === 0) {
return '请输入搜索关键词';
}
const fieldLabels = props.searchFields
.map((field) => {
const column = props.columns.find((col) => col.prop === field);
return column?.label || field;
})
.filter(Boolean);
if (fieldLabels.length === 0) {
return '请输入搜索关键词';
}
if (fieldLabels.length === 1) {
return `请输入${fieldLabels[0]}`;
}
return `请输入${fieldLabels.join('')}`;
});
// 根据搜索关键词过滤表格数据
const filteredTableData = computed(() => {
if (!searchKeyword.value || props.searchFields.length === 0) {
return tableData.value;
}
const keyword = searchKeyword.value.toLowerCase();
return tableData.value.filter((row) => {
return props.searchFields.some((field) => {
const value = row[field];
if (value === null || value === undefined) {
return false;
}
return String(value).toLowerCase().includes(keyword);
});
});
});
watch(
() => props.modelValue,
(newVal) => {
if (newVal !== tableData.value) {
tableData.value = [...newVal];
}
},
{ deep: true }
);
watch(
tableData,
(newVal) => {
emit('update:modelValue', newVal);
},
{ deep: true }
);
const handleAdd = (index) => {
const newRow = { ...props.defaultRow };
tableData.value.splice(index + 1, 0, newRow);
nextTick(() => {
emit('add', newRow, index + 1);
});
};
const handleDelete = (index) => {
if (tableData.value.length === 1) {
Object.keys(tableData.value[0]).forEach((key) => {
tableData.value[0][key] = '';
});
Object.assign(tableData.value[0], { ...props.defaultRow });
emit('delete', tableData.value[0], index, true);
} else {
const deletedRow = tableData.value.splice(index, 1)[0];
emit('delete', deletedRow, index, false);
}
};
const handleSelectionChange = (selection) => {
selectedRows.value = selection;
emit('selection-change', selection);
};
// 删除所有选中的行
const handleDeleteSelected = () => {
if (selectedRows.value.length === 0) {
return;
}
// 获取选中行的索引
const selectedIndexes = selectedRows.value.map((row) => tableData.value.indexOf(row));
// 从后往前删除,避免索引变化问题
selectedIndexes.sort((a, b) => b - a);
// 如果选中了所有行且只剩一行,清空数据而不是删除
if (tableData.value.length === selectedRows.value.length && tableData.value.length === 1) {
Object.keys(tableData.value[0]).forEach((key) => {
tableData.value[0][key] = '';
});
Object.assign(tableData.value[0], { ...props.defaultRow });
emit('delete', tableData.value[0], 0, true);
} else {
// 删除选中的行
selectedIndexes.forEach((index) => {
if (index !== -1 && tableData.value.length > 1) {
const deletedRow = tableData.value.splice(index, 1)[0];
emit('delete', deletedRow, index, false);
}
});
}
// 清空选中状态
if (tableRef.value) {
tableRef.value.clearSelection();
}
selectedRows.value = [];
};
// 工具栏新增按钮
const handleToolbarAdd = () => {
const newRow = { ...props.defaultRow };
tableData.value.push(newRow);
nextTick(() => {
emit('toolbar-add');
emit('add', newRow, tableData.value.length - 1);
});
};
// 工具栏删除按钮
const handleToolbarDelete = () => {
if (selectedRows.value.length === 0) {
return;
}
emit('toolbar-delete', selectedRows.value);
handleDeleteSelected();
};
// 搜索处理
const handleSearch = () => {
// 搜索逻辑已在 computed 中处理
};
const validate = (callback) => {
if (formRef.value) {
return formRef.value.validate(callback);
}
};
const validateField = (props, callback) => {
if (formRef.value) {
return formRef.value.validateField(props, callback);
}
};
const resetFields = () => {
if (formRef.value) {
formRef.value.resetFields();
}
};
const clearValidate = (props) => {
if (formRef.value) {
formRef.value.clearValidate(props);
}
};
defineExpose({
formRef,
tableRef,
validate,
validateField,
resetFields,
clearValidate,
tableData,
});
</script>
<style scoped lang="scss">
.editable-table-form {
display: flex;
flex-direction: column;
height: 100%;
.editable-table-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding: 0 4px;
.toolbar-left {
display: flex;
gap: 8px;
}
.toolbar-right {
display: flex;
align-items: center;
}
}
:deep(.el-table.editable-table-inner) {
flex: 1;
display: flex;
flex-direction: column;
.el-table__body-wrapper {
flex: 1;
overflow: auto;
}
.el-table__cell {
position: relative;
overflow: visible;
vertical-align: top;
.cell {
position: relative;
overflow: visible;
}
}
}
:deep(.el-table__cell) {
overflow: visible;
vertical-align: top;
.cell {
overflow: visible;
}
}
// 错误信息往下撑开行高不影响上面布局
:deep(.el-form-item) {
margin-bottom: 0;
.el-form-item__error {
position: static;
line-height: 1.5;
padding-top: 4px;
font-size: 12px;
color: var(--el-color-danger);
display: block;
white-space: nowrap;
}
}
.action-btn {
margin: 4px;
:deep(.el-icon) {
font-size: 18px;
}
}
}
.editable-table-footer {
flex-shrink: 0;
margin-top: 16px;
}
</style>

View File

@@ -0,0 +1,157 @@
<template>
<div v-if="show" class="query-form-wrapper">
<el-form
ref="queryFormRef"
:model="queryParams"
:inline="true"
class="query-form"
:label-width="labelWidth"
>
<template v-for="item in displayedFormItems" :key="item.prop">
<FormItem
:item="item"
:model-value="queryParams[item.prop]"
:on-enter="handleQuery"
@update:model-value="(value) => (queryParams[item.prop] = value)"
@change="(value) => item.onChange && item.onChange(value)"
>
<template v-for="(_, slotName) in $slots" :key="slotName" #[slotName]="slotProps">
<slot :name="slotName" v-bind="slotProps" />
</template>
</FormItem>
</template>
<el-form-item v-if="showDefaultButtons" style="margin-left: 20px">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
<el-button v-if="needCollapse" type="text" @click="toggleExpand" style="margin-left: 16px">
{{ isExpanded ? '收起' : '展开' }}
<el-icon class="el-icon--right">
<DArrowLeft v-if="isExpanded" class="collapse-arrow collapse-arrow--up" />
<DArrowRight v-else class="collapse-arrow collapse-arrow--down" />
</el-icon>
</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import FormItem from './FormItem.vue';
import type { FilterProps } from '../types/Filter.d';
defineOptions({
name: 'Filter'
});
const props = withDefaults(defineProps<FilterProps>(), {
formItems: () => [],
show: true,
showDefaultButtons: true,
labelWidth: '120px',
showLabelColon: true,
});
const emit = defineEmits<{
query: [queryParams: Record<string, any>];
reset: [];
}>();
const queryFormRef = ref<InstanceType<typeof import('element-plus').ElForm> | null>(null);
const isExpanded = ref(true);
const itemsPerRow = 4;
const normalizedFormItems = computed(() =>
(props.formItems || []).map((item) => ({
...item,
labelSuffix: item.labelSuffix ?? (props.showLabelColon ? '' : ''),
}))
);
const needCollapse = computed(() => {
if (!normalizedFormItems.value || normalizedFormItems.value.length === 0) return false;
let totalWidth = 0;
normalizedFormItems.value.forEach((item) => {
if (item.type === 'custom' || item.type === 'daterange') {
totalWidth += 2;
} else {
totalWidth += 1;
}
});
return totalWidth > itemsPerRow * 2;
});
const displayedFormItems = computed(() => {
if (!needCollapse.value || isExpanded.value) {
return normalizedFormItems.value;
}
const maxItems = itemsPerRow * 2;
let count = 0;
const result: any[] = [];
for (const item of normalizedFormItems.value) {
const itemWidth = item.type === 'custom' || item.type === 'daterange' ? 2 : 1;
if (count + itemWidth > maxItems) {
break;
}
result.push(item);
count += itemWidth;
}
return result;
});
const toggleExpand = () => {
isExpanded.value = !isExpanded.value;
};
const handleQuery = () => {
emit('query', props.queryParams);
};
const resetQuery = () => {
if (queryFormRef.value) {
queryFormRef.value.resetFields();
}
if (props.queryParams && Object.prototype.hasOwnProperty.call(props.queryParams, 'pageNum')) {
props.queryParams.pageNum = 1;
}
emit('reset');
};
defineExpose({
queryFormRef,
handleQuery,
resetQuery,
});
</script>
<style scoped lang="scss">
.query-form-wrapper {
flex-shrink: 0;
width: 100%;
.query-form {
width: 100%;
}
}
.collapse-arrow {
transition: transform 0.2s ease;
&.collapse-arrow--up {
transform: rotate(90deg);
}
&.collapse-arrow--down {
transform: rotate(90deg);
}
}
</style>

View File

@@ -0,0 +1,115 @@
<template>
<el-form
ref="formRef"
:model="model"
:rules="rules"
:label-width="labelWidth"
:inline="inline"
:label-position="labelPosition"
class="table-layout-form"
>
<template v-for="item in normalizedFormItems" :key="item.prop">
<FormItem
:item="item"
:model-value="model[item.prop]"
@update:model-value="(value) => (model[item.prop] = value)"
@change="(value) => item.onChange && item.onChange(value)"
>
<template v-for="(_, slotName) in $slots" :key="slotName" #[slotName]="slotProps">
<slot :name="slotName" v-bind="slotProps" />
</template>
</FormItem>
</template>
</el-form>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import FormItem from './FormItem.vue';
import type { FormProps } from '../types/Form.d';
defineOptions({
name: 'Form'
});
const props = withDefaults(defineProps<FormProps>(), {
formItems: () => [],
rules: () => ({}),
labelWidth: '120px',
inline: false,
labelPosition: 'right',
showLabelColon: true,
});
const emit = defineEmits<{
validate: [callback?: (valid: boolean) => void];
}>();
const formRef = ref<InstanceType<typeof import('element-plus').ElForm> | null>(null);
const normalizedFormItems = computed(() =>
(props.formItems || []).map((item) => ({
...item,
labelSuffix: item.labelSuffix ?? (props.showLabelColon ? '' : ''),
}))
);
// 表单验证
const validate = (callback) => {
if (formRef.value) {
return formRef.value.validate(callback);
}
};
// 验证指定字段
const validateField = (props, callback) => {
if (formRef.value) {
return formRef.value.validateField(props, callback);
}
};
// 重置表单
const resetFields = () => {
if (formRef.value) {
formRef.value.resetFields();
}
};
// 清除验证
const clearValidate = (props) => {
if (formRef.value) {
formRef.value.clearValidate(props);
}
};
// 滚动到指定字段
const scrollToField = (prop) => {
if (formRef.value) {
formRef.value.scrollToField(prop);
}
};
defineExpose({
formRef,
validate,
validateField,
resetFields,
clearValidate,
scrollToField,
});
</script>
<style scoped lang="scss">
.table-layout-form {
width: 100%;
// 非内联表单样式
&:not(.el-form--inline) {
:deep(.el-form-item) {
display: flex;
margin-right: 0;
}
}
}
</style>

View File

@@ -0,0 +1,196 @@
<template>
<el-form-item
:label="labelWithSuffix"
:prop="item.prop"
:required="item.required"
:class="{ 'form-item-double': item.type === 'custom' || item.type === 'daterange' }"
>
<el-input
v-if="item.type === 'input'"
:model-value="modelValue"
:placeholder="item.placeholder || `请输入${item.label}`"
:clearable="item.clearable !== false"
:style="item.style || { width: item.width || '200px' }"
v-bind="item.extraprops || {}"
@keyup.enter="handleEnter"
@update:model-value="handleUpdate"
/>
<el-select
v-else-if="item.type === 'select'"
:model-value="modelValue"
:placeholder="item.placeholder || `请选择${item.label}`"
:clearable="item.clearable !== false"
:style="item.style || { width: item.width || '200px' }"
:disabled="item.disabled"
v-bind="item.extraprops || {}"
:multiple="item.multiple !== false"
:filterable="item.filterable !== false"
:collapse-tags="item.collapseTags !== false"
@change="handleChange"
@update:model-value="(value) => handleUpdateWithCheck(value, item.checkBeforeChange)"
>
<el-option
v-for="option in item.options || []"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
<el-radio-group
v-else-if="item.type === 'radio'"
:model-value="modelValue"
v-bind="item.extraprops || {}"
@change="handleChange"
@update:model-value="handleUpdate"
>
<el-radio v-for="option in item.options || []" :key="option.value" :label="option.value">
{{ option.label }}
</el-radio>
</el-radio-group>
<!-- 单独日期 -->
<el-date-picker
v-else-if="item.type === 'date'"
:model-value="modelValue"
type="date"
:placeholder="item.placeholder || `请选择${item.label}`"
:clearable="item.clearable !== false"
:value-format="item.valueFormat || 'YYYY-MM-DD'"
:style="item.style || { width: item.width || '200px' }"
:disabled="item.disabled"
v-bind="item.extraprops || {}"
@change="handleChange"
@update:model-value="handleUpdate"
/>
<!-- 日期区间 -->
<QuickDateRange
v-else-if="item.type === 'daterange'"
:model-value="daterangeValue"
:start-placeholder="item.startPlaceholder || '开始日期'"
:end-placeholder="item.endPlaceholder || '结束日期'"
:value-format="item.valueFormat || 'YYYY-MM-DD'"
:clearable="item.clearable !== false"
:date-picker-style="daterangeStyle"
:attrs="item.extraprops || {}"
@change="handleChange"
@update:model-value="handleUpdate"
/>
<!-- 纯文本展示 -->
<span
v-else-if="item.type === 'text'"
:style="item.style || { width: item.width || '200px' }"
class="form-item-text"
>
{{ item.formatter ? item.formatter(modelValue) : modelValue ?? '' }}
</span>
<slot
v-else-if="item.type === 'custom'"
:name="item.slot || item.prop"
:item="item"
:modelValue="modelValue"
:updateModelValue="handleUpdate"
/>
</el-form-item>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import type { FormItemProps } from '../types/FormItem.d';
import QuickDateRange from './QuickDateRange.vue';
defineOptions({
name: 'FormItem',
});
const props = defineProps<FormItemProps>();
const emit = defineEmits<{
'update:modelValue': [value: any];
change: [value: any];
}>();
const labelWithSuffix = computed(() => {
const suffix = props.item.labelSuffix || '';
return `${props.item.label || ''}${suffix}`;
});
// 日期区间组件的值处理
const daterangeValue = computed<string[]>(() => {
if (props.item.type === 'daterange') {
if (Array.isArray(props.modelValue)) {
return props.modelValue.map((v: any) => String(v));
}
return [];
}
return [];
});
// 日期区间组件的样式处理
const daterangeStyle = computed(() => {
if (props.item.type === 'daterange') {
if (
typeof props.item.style === 'object' &&
props.item.style !== null &&
!Array.isArray(props.item.style)
) {
return props.item.style;
}
return { width: props.item.width || 'calc(316px + 7em)' };
}
return {};
});
const handleUpdate = (value: any) => {
emit('update:modelValue', value);
};
const handleUpdateWithCheck = async (value: any, shouldCheck = false) => {
if (shouldCheck) {
if (props.item.onChange && typeof props.item.onChange === 'function') {
const result = await props.item.onChange(value);
if (result === false) {
return;
}
}
}
handleUpdate(value);
};
const handleChange = (value: any) => {
emit('change', value);
};
const handleEnter = () => {
if (props.onEnter && typeof props.onEnter === 'function') {
props.onEnter();
}
};
</script>
<style scoped lang="scss">
:deep(.el-form-item) {
margin-bottom: 16px;
display: inline-flex;
align-items: flex-start;
vertical-align: top;
margin-right: 16px;
.el-form-item__label {
width: 7em !important;
min-width: 7em;
white-space: normal;
word-break: break-all;
line-height: 1.5;
padding-right: 8px;
text-align: right;
padding-top: 0;
display: flex;
align-items: center;
justify-content: flex-end;
}
.el-form-item__content {
display: flex;
align-items: center;
}
}
</style>

View File

@@ -0,0 +1,143 @@
<template>
<el-form
ref="formRef"
:model="model"
:rules="rules"
:label-width="labelWidth"
:label-position="labelPosition"
class="form-layout-form"
>
<div class="form-items-container" :class="columns > 0 ? `form-layout-${columns}col` : ''">
<template v-for="(item, index) in normalizedFormItems" :key="item.prop">
<FormItem
:item="item"
:model-value="model[item.prop]"
@update:model-value="
async (value) => {
if (item.onChange && typeof item.onChange === 'function') {
const result = await item.onChange(value);
if (result === false) {
return;
}
}
model[item.prop] = value;
}
"
>
<template v-for="(_, slotName) in $slots" :key="slotName" #[slotName]="slotProps">
<slot :name="slotName" v-bind="slotProps" />
</template>
</FormItem>
<span
v-if="
columns > 0 &&
index > 0 &&
(index + 1) % columns === 0 &&
index < normalizedFormItems.length - 1
"
class="form-item-break"
/>
</template>
</div>
</el-form>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import FormItem from './FormItem.vue';
import type { FormLayoutProps } from '../types/FormLayout.d';
defineOptions({
name: 'FormLayout',
});
const props = withDefaults(defineProps<FormLayoutProps>(), {
formItems: () => [],
rules: () => ({}),
labelWidth: '120px',
labelPosition: 'right',
showLabelColon: true,
columns: 0,
});
const formRef = ref<InstanceType<typeof import('element-plus').ElForm> | null>(null);
const normalizedFormItems = computed(() =>
(props.formItems || []).map((item) => ({
...item,
labelSuffix: item.labelSuffix ?? (props.showLabelColon ? '' : ''),
}))
);
const validate = (callback) => {
if (formRef.value) {
return formRef.value.validate(callback);
}
};
const validateField = (props, callback) => {
if (formRef.value) {
return formRef.value.validateField(props, callback);
}
};
const resetFields = () => {
if (formRef.value) {
formRef.value.resetFields();
}
};
const clearValidate = (props) => {
if (formRef.value) {
formRef.value.clearValidate(props);
}
};
const scrollToField = (prop) => {
if (formRef.value) {
formRef.value.scrollToField(prop);
}
};
defineExpose({
formRef,
validate,
validateField,
resetFields,
clearValidate,
scrollToField,
});
</script>
<style scoped lang="scss">
.form-layout-form {
width: 100%;
.form-items-container {
display: flex;
flex-wrap: wrap;
column-gap: 16px;
row-gap: 16px;
.form-item-break {
flex-basis: 100%;
width: 0;
height: 0;
margin: 0;
padding: 0;
border: none;
}
}
:deep(.el-form-item) {
margin-bottom: 0;
justify-content: flex-start;
flex: 0 0 auto;
.el-form-item__content {
justify-content: flex-start;
text-align: left;
}
}
}
</style>

View File

@@ -0,0 +1,18 @@
<template>
<div class="form-section">
<slot />
</div>
</template>
<script setup>
defineOptions({
name: 'FormSection',
});
</script>
<style scoped lang="scss">
.form-section {
flex-shrink: 0;
margin-bottom: 1rem;
}
</style>

View File

@@ -0,0 +1,39 @@
<template>
<div class="form-section">
<FormLayout ref="formLayoutRef" v-bind="$attrs">
<template v-for="(_, slotName) in $slots" :key="slotName" #[slotName]="slotProps">
<slot :name="slotName" v-bind="slotProps" />
</template>
</FormLayout>
</div>
</template>
<script setup>
import { ref } from 'vue';
import FormLayout from './FormLayout.vue';
defineOptions({
name: 'FormSectionLayout',
inheritAttrs: false,
});
const formLayoutRef = ref(null);
defineExpose({
get formRef() {
return formLayoutRef.value?.formRef;
},
validate: (...args) => formLayoutRef.value?.validate(...args),
validateField: (...args) => formLayoutRef.value?.validateField(...args),
resetFields: (...args) => formLayoutRef.value?.resetFields(...args),
clearValidate: (...args) => formLayoutRef.value?.clearValidate(...args),
scrollToField: (...args) => formLayoutRef.value?.scrollToField(...args),
});
</script>
<style scoped lang="scss">
.form-section {
flex-shrink: 0;
margin-bottom: 1rem;
}
</style>

View File

@@ -0,0 +1,136 @@
<template>
<el-input
:model-value="displayValue"
:placeholder="placeholder"
:disabled="disabled"
:clearable="clearable"
@input="handleInput"
@blur="handleBlur"
@change="handleChange"
>
<template v-if="suffix" #suffix>{{ suffix }}</template>
</el-input>
</template>
<script setup>
import { ref, computed, watch } from 'vue';
const props = defineProps({
modelValue: [Number, String],
placeholder: String,
disabled: Boolean,
clearable: {
type: Boolean,
default: true,
},
suffix: String,
precision: Number, // 小数位数
min: Number,
max: Number,
});
const emit = defineEmits(['update:modelValue', 'blur', 'change']);
const displayValue = computed(() => {
if (props.modelValue === null || props.modelValue === undefined || props.modelValue === '') {
return '';
}
return String(props.modelValue);
});
const handleInput = (value) => {
// 只允许数字、小数点和负号
let newValue = value.replace(/[^\d.-]/g, '');
// 只允许一个小数点
const parts = newValue.split('.');
if (parts.length > 2) {
newValue = parts[0] + '.' + parts.slice(1).join('');
}
// 只允许一个负号,且必须在开头
if (newValue.indexOf('-') > 0) {
newValue = newValue.replace(/-/g, '');
}
if (newValue.startsWith('-') && newValue.split('-').length > 2) {
newValue = '-' + newValue.replace(/-/g, '');
}
// 如果为空,直接返回空字符串
if (newValue === '' || newValue === '-') {
emit('update:modelValue', '');
return;
}
// 转换为数字
const numValue = parseFloat(newValue);
if (isNaN(numValue)) {
emit('update:modelValue', '');
return;
}
// 限制最小值
if (props.min !== undefined && numValue < props.min) {
newValue = String(props.min);
}
// 限制最大值
if (props.max !== undefined && numValue > props.max) {
newValue = String(props.max);
}
// 处理精度
if (props.precision !== undefined && newValue.includes('.')) {
const parts = newValue.split('.');
if (parts[1] && parts[1].length > props.precision) {
parts[1] = parts[1].substring(0, props.precision);
newValue = parts.join('.');
}
}
emit('update:modelValue', newValue);
};
const handleBlur = (event) => {
const value = event.target.value;
if (value === '' || value === '-') {
emit('update:modelValue', '');
emit('blur', event);
return;
}
const numValue = parseFloat(value);
if (isNaN(numValue)) {
emit('update:modelValue', '');
emit('blur', event);
return;
}
// 应用精度
let finalValue = numValue;
if (props.precision !== undefined) {
finalValue = parseFloat(numValue.toFixed(props.precision));
}
// 限制范围
if (props.min !== undefined && finalValue < props.min) {
finalValue = props.min;
}
if (props.max !== undefined && finalValue > props.max) {
finalValue = props.max;
}
emit('update:modelValue', String(finalValue));
emit('blur', event);
};
const handleChange = (value) => {
emit('change', value);
};
</script>
<style scoped lang="scss">
:deep(.el-input__inner) {
text-align: left;
}
</style>

View File

@@ -0,0 +1,29 @@
<template>
<Layout>
<template #default>
<div class="page-wrapper">
<slot />
</div>
</template>
<template v-if="$slots.footer" #footer>
<slot name="footer" />
</template>
</Layout>
</template>
<script setup>
import Layout from '@/components/Layout/index.vue';
defineOptions({
name: 'PageLayout',
});
</script>
<style scoped lang="scss">
.page-wrapper {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,20 @@
<template>
<div class="page-wrapper">
<slot />
</div>
</template>
<script setup>
defineOptions({
name: 'PageWrapper',
});
</script>
<style scoped lang="scss">
.page-wrapper {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,171 @@
<template>
<div class="quick-date-range">
<el-select v-model="quickType" class="quick-select" @change="handleQuickChange">
<el-option label="自定义时间段" value="custom" />
<el-option label="今天" value="today" />
<el-option label="昨天" value="yesterday" />
<el-option label="本周" value="thisWeek" />
<el-option label="上周" value="lastWeek" />
<el-option label="最近30日" value="last30Days" />
</el-select>
<el-date-picker
v-model="innerValue"
type="daterange"
range-separator="-"
:start-placeholder="startPlaceholder || '开始日期'"
:end-placeholder="endPlaceholder || '结束日期'"
:value-format="valueFormat"
:clearable="clearable"
:style="datePickerStyle"
v-bind="attrs"
@change="handleDateChange"
/>
</div>
</template>
<script setup lang="ts">
import { ref, watch, computed } from 'vue';
import type { QuickDateRangeProps } from '../types/QuickDateRange.d';
defineOptions({
name: 'QuickDateRange'
});
const props = withDefaults(defineProps<QuickDateRangeProps>(), {
modelValue: () => [],
startPlaceholder: '',
endPlaceholder: '',
valueFormat: 'YYYY-MM-DD',
clearable: true,
datePickerStyle: () => ({}),
attrs: () => ({}),
});
const emit = defineEmits<{
'update:modelValue': [value: string[]];
change: [value: string[]];
}>();
const innerValue = ref<string[]>(props.modelValue && props.modelValue.length ? [...props.modelValue] : []);
const quickType = ref<string>('custom');
watch(
() => props.modelValue,
(val) => {
if (!val || !val.length) {
innerValue.value = [];
quickType.value = 'custom';
} else {
innerValue.value = [...val];
}
},
{ deep: true }
);
const datePickerStyle = computed(() => {
return Object.assign({ width: '300px' }, props.datePickerStyle || {});
});
function formatDate(date) {
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
return `${y}-${m}-${d}`;
}
function getToday() {
const today = new Date();
const d = new Date(today.getFullYear(), today.getMonth(), today.getDate());
const s = formatDate(d);
return [s, s];
}
function getYesterday() {
const today = new Date();
const d = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 1);
const s = formatDate(d);
return [s, s];
}
function getThisWeek() {
const today = new Date();
const day = today.getDay() || 7; // 周日返回 7
const monday = new Date(today);
monday.setDate(today.getDate() - day + 1);
const sunday = new Date(monday);
sunday.setDate(monday.getDate() + 6);
return [formatDate(monday), formatDate(sunday)];
}
function getLastWeek() {
const today = new Date();
const day = today.getDay() || 7;
const lastMonday = new Date(today);
lastMonday.setDate(today.getDate() - day - 6);
const lastSunday = new Date(lastMonday);
lastSunday.setDate(lastMonday.getDate() + 6);
return [formatDate(lastMonday), formatDate(lastSunday)];
}
function getLast30Days() {
const today = new Date();
const end = new Date(today.getFullYear(), today.getMonth(), today.getDate());
const start = new Date(end);
start.setDate(end.getDate() - 29);
return [formatDate(start), formatDate(end)];
}
function handleQuickChange(val: string) {
if (val === 'custom') {
// 自定义时间段,清空日期值
innerValue.value = [];
emit('update:modelValue', []);
emit('change', []);
return;
}
let range: string[] = [];
switch (val) {
case 'today':
range = getToday();
break;
case 'yesterday':
range = getYesterday();
break;
case 'thisWeek':
range = getThisWeek();
break;
case 'lastWeek':
range = getLastWeek();
break;
case 'last30Days':
range = getLast30Days();
break;
default:
range = [];
}
innerValue.value = range;
emit('update:modelValue', range);
emit('change', range);
}
function handleDateChange(val: string[] | null) {
// 用户手动选择时间段时,将预设切换为自定义
quickType.value = 'custom';
innerValue.value = val || [];
emit('update:modelValue', innerValue.value);
emit('change', innerValue.value);
}
</script>
<style scoped lang="scss">
.quick-date-range {
display: inline-flex;
align-items: center;
gap: 8px;
.quick-select {
width: 130px;
}
}
</style>

View File

@@ -0,0 +1,373 @@
<template>
<div class="table-container">
<div ref="tableWrapperRef" class="table-wrapper">
<el-table
ref="tableRef"
v-loading="loading"
:data="computedTableData"
:border="border"
:stripe="stripe"
:size="size"
:height="computedTableHeight"
:row-key="rowKey"
:highlight-current-row="highlightCurrentRow"
@row-click="handleRowClick"
@selection-change="handleSelectionChange"
@sort-change="handleSortChange"
style="width: 100%; height: 100%"
>
<!-- 通过配置数组生成的列 -->
<template v-for="column in tableColumns" :key="column.prop || column.type">
<el-table-column
v-if="column.type && column.type !== 'expand'"
:type="column.type"
:width="column.width"
:min-width="column.minWidth"
:align="column.align || 'center'"
:fixed="
column.type === 'selection'
? column.fixed !== undefined
? column.fixed
: 'left'
: column.fixed
"
:selectable="column.selectable"
/>
<!-- 展开列支持自定义插槽内容 -->
<el-table-column
v-else-if="column.type === 'expand'"
type="expand"
:width="column.width"
:min-width="column.minWidth"
:fixed="column.fixed"
>
<template #default="scope">
<slot :name="column.slot || 'expand'" :row="scope.row" :scope="scope" />
</template>
</el-table-column>
<!-- 普通数据列 -->
<el-table-column
v-else
:prop="column.prop"
:label="column.label"
:width="column.width"
:min-width="column.minWidth"
:align="column.align || 'left'"
:fixed="column.fixed"
:show-overflow-tooltip="column.showOverflowTooltip !== false"
>
<template v-if="column.slot" #default="scope">
<slot :name="column.slot" :row="scope.row" :scope="scope" />
</template>
<template v-else-if="column.formatter" #default="scope">
{{
column.formatter(
scope.row,
scope.column,
column.prop ? scope.row[column.prop] : undefined,
scope.$index
)
}}
</template>
</el-table-column>
</template>
<!-- 通过插槽自定义的列 -->
<slot name="table" />
</el-table>
</div>
<div v-if="showPagination" ref="paginationWrapperRef" class="pagination-wrapper">
<div
class="pagination-content"
:class="{ 'has-left-content': paginationLeftText || $slots.paginationLeft }"
>
<div v-if="paginationLeftText || $slots.paginationLeft" class="pagination-left">
<slot name="paginationLeft">
{{ paginationLeftText }}
</slot>
</div>
<pagination
v-show="computedTotal > 0"
:total="computedTotal"
:page="computedPageNo"
:limit="computedPageSize"
v-bind="paginationProps"
@pagination="handlePagination"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue';
import Pagination from '@/components/Pagination/index.vue';
import type { TableProps } from '../types/Table.d';
defineOptions({
name: 'Table',
});
const props = withDefaults(defineProps<TableProps>(), {
tableData: () => [],
loading: false,
border: true,
stripe: false,
size: 'default',
highlightCurrentRow: false,
tableColumns: () => [],
showPagination: false,
total: 0,
pageNo: 1,
pageSize: 20,
isAllData: false,
paginationLeftText: '',
paginationProps: () => ({}),
});
const emit = defineEmits<{
'row-click': [row: Record<string, any>, column: any, event: Event];
'selection-change': [selection: Record<string, any>[]];
'sort-change': [sortInfo: { column: any; prop: string; order: string }];
pagination: [pagination: { page: number; limit: number }];
}>();
const internalPageNo = ref(props.pageNo);
const internalPageSize = ref(props.pageSize);
watch(
() => [props.pageNo, props.pageSize],
([newPageNo, newPageSize]) => {
if (!props.isAllData) {
internalPageNo.value = newPageNo;
internalPageSize.value = newPageSize;
}
}
);
watch(
() => props.isAllData,
(isAllData) => {
if (isAllData) {
internalPageNo.value = props.pageNo;
internalPageSize.value = props.pageSize;
}
}
);
const computedPageNo = computed(() => {
return props.isAllData ? internalPageNo.value : props.pageNo;
});
const computedPageSize = computed(() => {
return props.isAllData ? internalPageSize.value : props.pageSize;
});
const computedTotal = computed(() => {
return props.isAllData ? props.tableData.length : props.total;
});
const computedTableData = computed(() => {
if (!props.isAllData) {
return props.tableData;
}
const start = (computedPageNo.value - 1) * computedPageSize.value;
const end = start + computedPageSize.value;
return props.tableData.slice(start, end);
});
const handlePagination = (pagination: { page: number; limit: number }) => {
if (props.isAllData) {
internalPageNo.value = pagination.page;
internalPageSize.value = pagination.limit;
} else {
emit('pagination', pagination);
}
nextTick(() => {
calculateTableHeight();
});
};
const tableRef = ref<InstanceType<typeof import('element-plus').ElTable> | null>(null);
const tableWrapperRef = ref<HTMLDivElement | null>(null);
const paginationWrapperRef = ref<HTMLDivElement | null>(null);
const dynamicTableHeight = ref<number | null>(null);
const paginationHeight = ref<number>(0);
const computedTableHeight = computed(() => {
if (props.tableHeight) {
return props.tableHeight;
}
if (props.maxHeight) {
return props.maxHeight;
}
if (dynamicTableHeight.value) {
const height = dynamicTableHeight.value - paginationHeight.value;
return height > 0 ? height : dynamicTableHeight.value;
}
return null;
});
const calculateTableHeight = () => {
nextTick(() => {
if (tableWrapperRef.value) {
const tableContainer = tableWrapperRef.value.parentElement;
if (tableContainer) {
const containerRect = tableContainer.getBoundingClientRect();
let height = containerRect.height;
if (props.showPagination && paginationWrapperRef.value && computedTotal.value > 0) {
const paginationRect = paginationWrapperRef.value.getBoundingClientRect();
paginationHeight.value = paginationRect.height;
height -= paginationRect.height;
} else {
paginationHeight.value = 0;
}
if (height > 0) {
dynamicTableHeight.value = height;
}
}
}
});
};
let resizeObserver: ResizeObserver | null = null;
let paginationObserver: ResizeObserver | null = null;
onMounted(() => {
calculateTableHeight();
const tableContainer = tableWrapperRef.value?.parentElement;
if (tableContainer && window.ResizeObserver) {
resizeObserver = new ResizeObserver(() => {
calculateTableHeight();
});
resizeObserver.observe(tableContainer);
} else {
window.addEventListener('resize', calculateTableHeight);
}
});
watch(
() => props.showPagination && computedTotal.value > 0 && paginationWrapperRef.value,
(shouldObserve) => {
if (shouldObserve && paginationWrapperRef.value && window.ResizeObserver) {
if (!paginationObserver) {
paginationObserver = new ResizeObserver(() => {
calculateTableHeight();
});
}
paginationObserver.observe(paginationWrapperRef.value);
} else if (paginationObserver && paginationWrapperRef.value) {
paginationObserver.unobserve(paginationWrapperRef.value);
}
},
{ immediate: true }
);
onUnmounted(() => {
if (resizeObserver) {
resizeObserver.disconnect();
}
if (paginationObserver) {
paginationObserver.disconnect();
}
if (!resizeObserver) {
window.removeEventListener('resize', calculateTableHeight);
}
});
watch(
() => props.tableData,
() => {
calculateTableHeight();
if (props.isAllData && internalPageNo.value !== 1) {
internalPageNo.value = 1;
}
},
{ deep: true }
);
watch(
() => [props.showPagination, computedTotal.value],
() => {
calculateTableHeight();
}
);
const handleRowClick = (row: Record<string, any>, column: any, event: Event) => {
emit('row-click', row, column, event);
};
const handleSelectionChange = (selection: Record<string, any>[]) => {
emit('selection-change', selection);
};
const handleSortChange = ({
column,
prop,
order,
}: {
column: any;
prop: string;
order: string;
}) => {
emit('sort-change', { column, prop, order });
};
defineExpose({
tableRef,
tableWrapperRef,
});
</script>
<style scoped lang="scss">
.table-container {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
}
.table-wrapper {
flex: 1;
min-height: 0;
overflow: hidden;
position: relative;
}
.pagination-wrapper {
flex-shrink: 0;
margin-top: 8px;
padding-bottom: 0;
overflow: visible;
}
.pagination-content {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 16px;
&.has-left-content {
justify-content: space-between;
}
}
.pagination-left {
flex-shrink: 0;
display: flex;
justify-content: flex-start;
align-items: center;
color: var(--el-text-color-regular);
font-size: 14px;
}
.pagination-content :deep(.pagination-container) {
.el-pagination {
margin-right: 16px;
display: flex;
align-items: center;
justify-content: flex-end;
}
}
</style>

View File

@@ -0,0 +1,27 @@
<template>
<div class="table-section">
<slot />
</div>
</template>
<script setup>
defineOptions({
name: 'TableSection',
});
</script>
<style scoped lang="scss">
.table-section {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
:deep(.editable-table) {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
}
</style>

View File

@@ -0,0 +1,411 @@
<template>
<div class="table-layout-container">
<div class="card-content-wrapper">
<div
v-if="showSideQuery"
class="side-query-wrapper"
:class="{ collapsed: sideQueryCollapsed }"
>
<div v-if="!sideQueryCollapsed" class="side-query-header">
<el-input v-model="sideSearchKeyword" placeholder="搜索树节点" clearable size="small">
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
</div>
<div v-if="!sideQueryCollapsed" class="side-query-content">
<el-tree
ref="treeRef"
:data="treeDataWithAll"
:props="defaultProps"
:node-key="treeNodeKey"
:expand-on-click-node="false"
default-expand-all
highlight-current
@node-click="handleNodeClick"
@current-change="handleCurrentChange"
></el-tree>
</div>
</div>
<div v-if="showSideQuery" class="collapse-divider">
<el-button
circle
size="small"
class="collapse-btn"
@click="sideQueryCollapsed = !sideQueryCollapsed"
>
<el-icon>
<ArrowRight v-if="sideQueryCollapsed" />
<ArrowLeft v-else />
</el-icon>
</el-button>
</div>
<!-- 主内容区域 -->
<div
class="main-content-wrapper"
:class="{ 'with-side-query': showSideQuery && !sideQueryCollapsed }"
>
<Filter
v-if="showTopQuery"
ref="queryFormComponentRef"
:query-params="queryParams"
:form-items="formItems"
:show-default-buttons="showDefaultButtons"
@query="handleQuery"
@reset="resetQuery"
>
<template
v-for="item in customFormItems"
:key="item.prop"
v-slot:[item.slotName]="slotProps"
>
<slot :name="item.slotName" :item="slotProps.item" :queryParams="props.queryParams" />
</template>
<template #default="{ queryParams, handleQuery, resetQuery }">
<slot
name="topQuery"
:queryParams="queryParams"
:handleQuery="handleQuery"
:resetQuery="resetQuery"
/>
</template>
</Filter>
<!-- 操作按钮区域 -->
<div class="table-operation-bar">
<slot name="operations" />
</div>
<!-- 表格区域 -->
<Table
:table-data="tableData"
:loading="loading"
:border="border"
:stripe="stripe"
:size="size"
:table-height="tableHeight"
:max-height="maxHeight"
:row-key="rowKey"
:highlight-current-row="highlightCurrentRow"
:table-columns="tableColumns"
:show-pagination="showPagination"
:total="total"
:page-no="props.queryParams.pageNo"
:page-size="props.queryParams.pageSize"
@row-click="handleRowClick"
@selection-change="handleSelectionChange"
@sort-change="handleSortChange"
@pagination="handlePagination"
>
<template v-for="(_, slotName) in $slots" :key="slotName" #[slotName]="slotProps">
<slot :name="slotName" v-bind="slotProps" />
</template>
</Table>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import Filter from './Filter.vue';
import Table from './Table.vue';
import type { TableLayoutProps, TreeNodeData } from '../types/TableLayout.d';
defineOptions({
name: 'TableLayout',
});
const props = withDefaults(defineProps<TableLayoutProps>(), {
tableData: () => [],
loading: false,
total: 0,
queryParams: () => ({
pageNo: 1,
pageSize: 20,
}),
sideQueryParams: () => ({}),
formItems: () => [],
showTopQuery: true,
showSideQuery: false,
showPagination: true,
showDefaultButtons: true,
sideWidth: 6,
border: true,
stripe: false,
size: 'default',
highlightCurrentRow: false,
siderData: () => [],
treeNodeKey: 'id',
tableColumns: () => [],
});
const emit = defineEmits<{
query: [queryParams: Record<string, any>];
reset: [];
pagination: [pagination: { page: number; limit: number }];
'row-click': [row: Record<string, any>, column: any, event: Event];
'selection-change': [selection: Record<string, any>[]];
'sort-change': [sortInfo: { column: any; prop: string; order: string }];
'side-query': [node: TreeNodeData];
'reset-side-query': [];
}>();
const queryFormRef = ref<InstanceType<typeof import('element-plus').ElForm> | null>(null);
import type { FilterExpose } from '../types/Filter.d';
const queryFormComponentRef = ref<FilterExpose | null>(null);
const sideSearchKeyword = ref<string>('');
const treeRef = ref<InstanceType<typeof import('element-plus').ElTree> | null>(null);
const currentTreeNode = ref<TreeNodeData | null>(null);
const sideQueryCollapsed = ref<boolean>(false);
const customFormItems = computed(() => {
return props.formItems
.filter((item) => item.type === 'custom')
.map((item) => ({
...item,
slotName: item.slot || item.prop,
}));
});
const defaultProps = {
children: 'children',
label: 'label',
};
const filteredSiderData = computed(() => {
if (!sideSearchKeyword.value || !props.siderData || props.siderData.length === 0) {
return props.siderData;
}
const keyword = sideSearchKeyword.value.toLowerCase();
const filterTree = (nodes: TreeNodeData[]): TreeNodeData[] => {
if (!nodes || nodes.length === 0) return [];
return nodes
.map((node: TreeNodeData) => {
const label = (node[defaultProps.label] || '').toLowerCase();
const match = label.includes(keyword);
const children = node[defaultProps.children];
let filteredChildren: TreeNodeData[] | null = null;
if (children && children.length > 0) {
filteredChildren = filterTree(children);
}
if (match || (filteredChildren && filteredChildren.length > 0)) {
return {
...node,
[defaultProps.children]: filteredChildren,
};
}
return null;
})
.filter(Boolean) as TreeNodeData[];
};
return filterTree(props.siderData);
});
const treeDataWithAll = computed(() => {
const children = filteredSiderData.value || [];
return [
{
[props.treeNodeKey]: '__ALL__',
[defaultProps.label]: '全部',
[defaultProps.children]: children || [],
},
];
});
const handleQuery = () => {
props.queryParams.pageNo = 1;
emit('query', props.queryParams);
if (currentTreeNode.value) {
emit('side-query', currentTreeNode.value);
}
};
const handleNodeClick = (data: TreeNodeData, node: any) => {
currentTreeNode.value = data;
if (treeRef.value && data && data[props.treeNodeKey]) {
treeRef.value.setCurrentKey(data[props.treeNodeKey]);
}
handleQuery();
};
const handleCurrentChange = (data: TreeNodeData, node: any) => {
currentTreeNode.value = data;
};
const resetQuery = () => {
if (queryFormComponentRef.value?.queryFormRef) {
queryFormComponentRef.value.queryFormRef.resetFields();
}
if (props.queryParams) {
Object.keys(props.queryParams).forEach((key) => {
if (key !== 'pageNo' && key !== 'pageSize') {
if (Array.isArray(props.queryParams[key])) {
props.queryParams[key] = [];
} else if (typeof props.queryParams[key] === 'object' && props.queryParams[key] !== null) {
props.queryParams[key] = null;
} else {
props.queryParams[key] = '';
}
}
});
if (Object.prototype.hasOwnProperty.call(props.queryParams, 'pageNo')) {
props.queryParams.pageNo = 1;
}
}
emit('reset');
handleQuery();
};
const handlePagination = (pagination) => {
if (props.queryParams) {
props.queryParams.pageNo = pagination.page;
props.queryParams.pageSize = pagination.limit;
}
emit('pagination', pagination);
emit('query', props.queryParams);
if (currentTreeNode.value) {
emit('side-query', currentTreeNode.value);
}
};
const handleRowClick = (row, column, event) => {
emit('row-click', row, column, event);
};
const handleSelectionChange = (selection) => {
emit('selection-change', selection);
};
const handleSortChange = ({ column, prop, order }) => {
emit('sort-change', { column, prop, order });
};
defineExpose({
queryFormRef: computed(() => queryFormComponentRef.value?.queryFormRef),
handleQuery,
resetQuery,
});
</script>
<style scoped lang="scss">
.table-layout-container {
height: 100%;
padding: 8px;
display: flex;
flex-direction: column;
overflow: hidden;
box-sizing: border-box;
.main-content-card {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
min-height: 0;
overflow: visible;
:deep(.el-card__body) {
flex: 1;
display: flex;
flex-direction: column;
padding: 16px 16px 8px 16px;
min-height: 0;
overflow: visible;
}
}
.card-content-wrapper {
flex: 1;
display: flex;
gap: 0;
min-height: 0;
position: relative;
}
.collapse-divider {
flex-shrink: 0;
width: 1px;
background-color: #ebeef5;
position: relative;
display: flex;
align-items: flex-start;
justify-content: center;
margin: 0 12px;
.collapse-btn {
position: absolute;
left: 50%;
top: 18px;
transform: translateX(-50%);
background-color: #fff;
border: 1px solid #ebeef5;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
z-index: 10;
width: 24px;
height: 24px;
&:hover {
background-color: #409eff;
color: #fff;
border-color: #409eff;
}
}
}
.side-query-wrapper {
flex-shrink: 0;
display: flex;
flex-direction: column;
transition: width 0.3s, opacity 0.3s;
overflow: hidden;
&.collapsed {
width: 0;
opacity: 0;
padding: 0;
border: none;
}
.side-query-header {
flex-shrink: 0;
margin-bottom: 12px;
display: flex;
align-items: center;
}
.side-query-content {
flex: 1;
min-height: 0;
overflow-y: auto;
:deep(.el-tree--highlight-current) {
background-color: #fff !important;
}
}
}
.main-content-wrapper {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
min-width: 0;
overflow: hidden;
}
.table-operation-bar {
flex-shrink: 0;
margin-bottom: 8px;
}
}
</style>

View File

@@ -1,188 +1,125 @@
<!--
* @Author: sjjh
* @Date: 2025-04-07 20:42:45
* @Description:住院患者信息给医生用带折叠
-->
<template>
<div class="inPatientBarDoctorFold-container">
<div class="basic_info">
<div class="patient-header white-bg">
<div class="select_wrapper_div">
<b class="bedNumber" style="margin-left: 12px">{{ patientInfo?.bedName }}</b>
<label class="content-text-color" style="margin-left: 12px; color: #a15209">
{{ patientInfo?.patientName }}
<span class="sex-age"> {{ patientInfo?.sexName }}/{{ patientInfo?.age }} </span>
<b class="bedNumber">{{ patientInfo?.bedName || '未分床' }}</b>
<label class="patient-name">
{{ patientInfo?.patientName || '-' }}
<span class="sex-age">
{{ formatSexAge(patientInfo?.sexName, patientInfo?.age) }}
</span>
</label>
<div style="display: flex; margin-left: 8px">
<!-- 状态展示// TODO 后端给状态,前段 -->
<div class="tag-list" v-if="patientInfo?.list && patientInfo.list.length > 0">
<ball-tag
style="margin-right: 4px"
v-for="item in patientInfo?.list"
v-for="item in patientInfo.list"
:key="item"
:tagId="item"
class="tag-item"
></ball-tag>
</div>
<div
class="gray-border"
v-show="patientInfo?.feeTypeName && patientInfo?.feeTypeName !== ''"
>
{{ patientInfo?.feeTypeName }}
<div class="gray-border" v-if="patientInfo?.feeTypeName">
{{ patientInfo.feeTypeName }}
</div>
<label style="margin-left: 24px">
<label class="info-label">
<span class="label-text-color">住院</span>
<span class="content-text-color">{{ patientInfo?.inHospitalDays + '天' }}</span>
<span class="content-text-color">{{ formatDays(patientInfo?.inHospitalDays) }}</span>
</label>
<label style="margin-left: 24px">
<label class="info-label" v-if="patientInfo?.inOrgTime">
<span class="label-text-color">入科</span>
<span class="content-text-color">{{ patientInfo?.inDeptDate }}</span>
<span class="content-text-color">{{ patientInfo.inOrgTime }}</span>
</label>
<label style="margin-left: 24px">
<label class="info-label" v-if="patientInfo?.inHospitalTime">
<span class="label-text-color">入院时间</span>
<span class="content-text-color">{{ patientInfo?.inHospitalTime }}</span>
<span class="content-text-color">{{ patientInfo.inHospitalTime }}</span>
</label>
<label style="margin-left: 24px">
<span class="label-text-color">住院号:{{ patientInfo?.busNo }}</span>
<label class="info-label" v-if="patientInfo?.busNo">
<span class="label-text-color">住院号</span>
<span class="content-text-color">{{ patientInfo.busNo }}</span>
</label>
<svg-icon icon-class="hipCopy" height="20px" width="20px" class="copy-svg" />
<label style="margin-left: 30px">
<label class="info-label diagnosis-label" v-if="patientInfo?.regDiagnosisName">
<span class="label-text-color">诊断</span>
<span class="content-text-color">{{ patientInfo?.regDiagnosisName }}</span>
<span class="content-text-color">{{ patientInfo.regDiagnosisName }}</span>
</label>
<label class="info-label">
<span class="label-text-color">费用</span>
<span class="content-text-color">{{ formatMoney(patientInfo?.totalAmount) }}</span>
</label>
<label class="info-label">
<span class="label-text-color">余额</span>
<span class="content-text-color">{{ formatMoney(patientInfo?.balanceAmount) }}</span>
</label>
<!-- <div style="margin-left: auto">
<el-icon v-if="expand" @click="toggleExpand"><ArrowUpBold /></el-icon>
<el-icon v-else @click="toggleExpand"><ArrowDownBold /></el-icon>
</div> -->
</div>
</div>
</div>
<div v-if="expand" class="expand_more">
<div style="background-color: #ffffff">
<div style="margin-top: -10px">
<label style="font-size: 14px">
<div class="expand-content">
<div class="expand-section">
<label class="expand-label">
<span class="primary-text">过敏</span>
<span class="primary-text">{{ patientInfo?.allergies || '无过敏史' }}</span>
</label>
<label style="font-size: 14px; margin-left: 32px" v-show="patientInfo?.insuplcAdmdvsName">
<span class="primary-text">医保统筹区:</span>
<span class="primary-text">{{ patientInfo?.insuplcAdmdvsName }}</span>
<label class="expand-label" v-if="patientInfo?.insuplcAdmdvsName">
<span class="primary-text">医保统筹区</span>
<span class="primary-text">{{ patientInfo.insuplcAdmdvsName }}</span>
</label>
<label style="font-size: 14px; margin-left: 32px" v-show="patientInfo?.ciType">
<label class="expand-label" v-if="patientInfo?.ciType">
<span class="primary-text">商保信息</span>
<span class="primary-text">{{ patientInfo?.ciType }}</span>
<span class="primary-text">{{ patientInfo.ciType }}</span>
</label>
<div style="display: flex; flex-wrap: nowrap; margin-top: 8px; white-space: nowrap">
<div
class="blue-bg"
style="background-color: #f1faff; flex-shrink: 0; min-width: fit-content"
>
<div class="info-tags">
<div class="blue-bg">
<span class="content-text-color">
{{
patientInfo?.height && patientInfo?.weight
? `${patientInfo?.height}cm/${patientInfo?.weight}kg`
: '身高/体重'
}}
{{ formatHeightWeight(patientInfo?.height, patientInfo?.weight) }}
</span>
</div>
<div
class="blue-bg"
style="
margin-left: 24px;
background-color: #f1faff;
flex-shrink: 0;
min-width: fit-content;
"
v-show="patientInfo?.postoperativeDays"
>
<span class="content-text-color">术后{{ patientInfo?.postoperativeDays }}</span>
<div class="blue-bg" v-if="patientInfo?.postoperativeDays">
<span class="content-text-color">术后{{ patientInfo.postoperativeDays }}</span>
</div>
<div
class="blue-bg"
style="
margin-left: 16px;
background-color: #f1faff;
flex-shrink: 0;
min-width: fit-content;
"
v-show="patientInfo?.poorTypeName"
>
<span class="label-text-color">贫困类型:</span>
<span class="content-text-color" style="margin-left: 4px">{{
patientInfo?.poorTypeName
}}</span>
<div class="blue-bg" v-if="patientInfo?.poorTypeName">
<span class="label-text-color">贫困类型</span>
<span class="content-text-color">{{ patientInfo.poorTypeName }}</span>
</div>
<div
class="blue-bg"
style="
margin-left: 16px;
background-color: #f1faff;
flex-shrink: 0;
min-width: fit-content;
"
v-show="patientInfo?.pathwayName"
>
<span class="label-text-color">路径情况:</span>
<span class="content-text-color" style="margin-left: 4px">{{
patientInfo?.pathwayName
}}</span>
<div class="blue-bg" v-if="patientInfo?.pathwayName">
<span class="label-text-color">路径情况</span>
<span class="content-text-color">{{ patientInfo.pathwayName }}</span>
</div>
</div>
</div>
</div>
<div style="background-color: #ffffff">
<div style="margin-top: -10px">
<div class="expand-content">
<div class="expand-section">
<div class="patient-board">
<div class="item-center">
<div class="line-block">
<div class="line-block-top">
<span class="label-text-color">科室</span>
<span class="content-text-color">{{ patientInfo?.admissionDeptName }}</span>
<span class="content-text-color">{{
patientInfo?.admissionDeptName || '-'
}}</span>
</div>
<div class="line-block-bottom">
<span class="label-text-color">病区</span>
<span class="content-text-color">{{ patientInfo?.deptNurseName }}</span>
<span class="content-text-color">{{ patientInfo?.deptNurseName || '-' }}</span>
</div>
</div>
<div class="line-block">
<div class="line-block-top">
<span class="label-text-color">主治医生</span>
<span class="content-text-color">{{ patientInfo?.masterDoctorName }}</span>
<span class="content-text-color">{{ patientInfo?.masterDoctorName || '-' }}</span>
</div>
<div class="line-block-bottom">
<span class="label-text-color">责任护士</span>
<span class="content-text-color">{{ patientInfo?.masterNurseName }}</span>
</div>
</div>
<div class="line-blockMoney">
<div class="line-blockMoney-top">
<span class="label-text-color">费用</span>
</div>
<div class="line-blockMoney-bottom">
<b class="money-content size-15">{{
patientInfo?.totalAmount ? patientInfo?.totalAmount : 0
}}</b>
<span class="content-text-color">{{ patientInfo?.masterNurseName || '-' }}</span>
</div>
</div>
<div class="line-blockMoney">
<div class="line-blockMoney-top">
<span class="label-text-color">预交金</span>
</div>
<div class="line-blockMoney-bottom">
<b class="money-content size-15">{{
patientInfo?.prepayAmount ? patientInfo?.prepayAmount : 0
}}</b>
</div>
</div>
<div class="line-blockMoney">
<div class="line-blockMoney-top">
<span class="label-text-color">余额</span>
</div>
<div class="line-blockMoney-bottom">
<b class="money-content size-15">{{
patientInfo?.balance ? patientInfo?.balance : 0
}}</b>
<b class="money-content size-15">{{ formatMoney(patientInfo?.prepayAmount) }}</b>
</div>
</div>
</div>
@@ -192,45 +129,57 @@
</div>
</div>
</template>
<script setup>
import { computed, onMounted, ref, watch } from 'vue';
<script setup lang="ts">
import { ref, watch } from 'vue';
import BallTag from './components/BallTag.vue';
import { patientInfo } from '@/views/inpatientDoctor/home/store/patient.js';
// import { ElMessage } from 'element-plus'
const expand = ref(false);
const showDividers = ref(true);
// 示例方法:切换显示状态
const toggleDividers = () => {
showDividers.value = !showDividers.value;
};
const iconClass = ref('hipBarDown');
// 切换展开状态的方法
function toggleExpand() {
expand.value = !expand.value;
iconClass.value = expand.value ? 'hipBarUp' : 'hipBarDown';
toggleDividers();
interface Props {
visitCode?: string;
}
const fetchPatientInfoById = async (patientId) => {
// 查询患者信息
console.log(patientId);
};
const props = defineProps({
const props = withDefaults(defineProps<Props>(), {
visitCode: '',
});
watch(
() => props.visitCode,
(val) => {
if (val !== null && val !== '') {
fetchPatientInfoById(val);
}
const expand = ref<boolean>(false);
// 格式化性别和年龄
const formatSexAge = (sexName?: string, age?: number | string): string => {
const sex = sexName || '';
const ageStr = age !== undefined && age !== null ? String(age) : '';
if (sex && ageStr) {
return `${sex}/${ageStr}`;
} else if (sex) {
return sex;
} else if (ageStr) {
return ageStr;
}
);
return '-';
};
// 格式化天数
const formatDays = (days?: number | string): string => {
if (days === undefined || days === null || days === '') {
return '-';
}
return `${days}`;
};
// 格式化身高体重
const formatHeightWeight = (height?: number | string, weight?: number | string): string => {
if (height && weight) {
return `${height}cm/${weight}kg`;
}
return '身高/体重';
};
// 格式化金额
const formatMoney = (amount?: number | string): number => {
if (amount === undefined || amount === null || amount === '') {
return 0;
}
return Number(amount) || 0;
};
defineOptions({
name: 'NurserDoctorPatientBarminimal',
@@ -240,27 +189,77 @@ defineOptions({
<style lang="scss" scoped>
.inPatientBarDoctorFold-container {
border-bottom: 1px solid #ebeef5;
background-color: #ffffff;
align-items: center;
.basic_info {
height: 43px;
min-height: 44px;
padding: 0 8px;
display: flex;
align-items: center;
}
/* expand_more */
.expand_more {
width: 100%;
height: 56px;
display: flex;
justify-content: space-between;
flex-direction: column;
padding: 0 8px;
}
.expand-content {
background-color: #ffffff;
}
.expand-section {
margin-top: -10px;
padding: 8px 0;
}
.patient-header {
width: 100%;
padding: 6px 0;
font-size: 13px;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
.select_wrapper_div {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
width: 100%;
}
.bedNumber {
font-weight: bold;
font-size: 18px;
margin-left: 12px;
color: #1f2933;
}
.patient-name {
margin-left: 12px;
color: #a15209;
font-weight: 500;
}
.sex-age {
margin-left: 20px;
margin-left: 8px;
color: var(--hip-color-text-description);
font-size: 14px;
font-weight: normal;
}
.tag-list {
display: flex;
align-items: center;
margin-left: 8px;
gap: 4px;
}
.tag-item {
margin-right: 4px;
}
.gray-border {
@@ -270,43 +269,66 @@ defineOptions({
align-items: center;
border: 1px solid var(--hip-color-text-description);
border-radius: 20px;
padding: 0px 8px;
padding: 0 8px;
font-size: 12px;
}
.info-label {
margin-left: 24px;
white-space: nowrap;
&:first-of-type {
margin-left: 0;
}
}
.diagnosis-label {
margin-left: 30px;
}
.copy-svg {
fill: var(--hip-color-primary);
cursor: pointer;
margin-left: 4px;
transition: opacity 0.2s;
&:hover {
opacity: 0.8;
}
}
}
.expand-label {
font-size: 14px;
margin-right: 32px;
white-space: nowrap;
&:first-child {
margin-right: 32px;
}
}
.info-tags {
display: flex;
flex-wrap: nowrap;
margin-top: 8px;
white-space: nowrap;
gap: 16px;
}
.size-15 {
font-size: 15px;
}
.bedNumber {
font-weight: bold;
font-size: 18px;
}
.primary-text {
color: var(--hip-color-primary);
}
.flex-between {
display: flex;
justify-content: space-between;
}
.item-center {
display: flex;
align-items: center;
}
.flex-row {
display: flex;
}
.patient-board {
margin-left: 20px;
@@ -330,10 +352,8 @@ defineOptions({
margin-bottom: 8px;
}
.money-content {
color: #ff8616;
font-size: 20px;
font-weight: 500;
&-bottom {
margin-top: 4px;
}
}
@@ -354,7 +374,7 @@ defineOptions({
}
&-top {
margin-bottom: 0px;
margin-bottom: 0;
}
.money-content {
@@ -378,13 +398,24 @@ defineOptions({
justify-content: center;
align-items: center;
background-color: #f1faff;
padding-left: 8px;
padding-right: 8px;
border-radius: 4px; /*圆角*/
padding: 4px 8px;
border-radius: 4px;
flex-shrink: 0;
min-width: fit-content;
white-space: nowrap;
.content-text-color {
margin-left: 0;
}
.label-text-color {
margin-right: 4px;
}
}
.label-text-color {
font-size: 14px;
color: #666666;
}
.content-text-color {

View File

@@ -0,0 +1,42 @@
/**
* Dialog 尺寸类型
*/
export type DialogSize = 'small' | 'medium' | 'large';
/**
* Dialog 组件的 Props 类型
*/
export interface DialogProps {
/** 对话框标题 */
title?: string;
/** 对话框尺寸 */
size?: DialogSize;
/** 自定义宽度 */
width?: string | number;
/** 自定义高度 */
height?: string | number;
/** 是否显示对话框 */
modelValue?: boolean;
/** 是否在关闭时销毁子元素 */
destroyOnClose?: boolean;
/** 是否将对话框追加到 body 上 */
appendToBody?: boolean;
/** 是否可以通过点击遮罩层关闭对话框 */
closeOnClickModal?: boolean;
/** 是否可以通过按下 ESC 关闭对话框 */
closeOnPressEscape?: boolean;
/** 是否显示关闭按钮 */
showClose?: boolean;
/** 是否在对话框出现时将 body 滚动锁定 */
lockScroll?: boolean;
/** 自定义类名 */
customClass?: string;
/** 是否可拖拽 */
draggable?: boolean;
/** 是否全屏 */
fullscreen?: boolean;
/** 是否显示加载状态 */
loading?: boolean;
/** 对话框打开前的回调 */
beforeClose?: (done: () => void) => void;
}

View File

@@ -0,0 +1,99 @@
import type { FormItemOption } from './FormItem.d';
/**
* 可编辑表格列配置类型
*/
export interface EditableTableColumn {
/** 列字段名 */
prop: string;
/** 列标签 */
label: string;
/** 列宽度 */
width?: string | number;
/** 最小宽度 */
minWidth?: string | number;
/** 是否固定列 */
fixed?: boolean | 'left' | 'right';
/** 对齐方式 */
align?: 'left' | 'center' | 'right';
/** 列类型 */
type?: 'input' | 'number' | 'select' | 'date' | 'slot';
/** 占位符 */
placeholder?: string;
/** 是否禁用 */
disabled?: boolean | ((row: Record<string, any>, index: number) => boolean);
/** 是否可清空 */
clearable?: boolean;
/** 是否可搜索select 类型) */
filterable?: boolean;
/** 是否多选select 类型) */
multiple?: boolean;
/** 最小值number 类型) */
min?: number;
/** 最大值number 类型) */
max?: number;
/** 精度number 类型) */
precision?: number;
/** 日期类型date 类型) */
dateType?: 'date' | 'datetime' | 'daterange';
/** 日期格式 */
valueFormat?: string;
/** 选项列表select 类型) */
options?: FormItemOption[] | ((row: Record<string, any>, index: number) => FormItemOption[]);
/** 自定义插槽名称slot 类型) */
slot?: string;
/** 格式化函数 */
formatter?: (row: Record<string, any>, column: any, cellValue: any) => string;
/** 验证规则 */
rules?: any;
/** 后缀文本input 类型) */
suffix?: string;
/** 条件显示 */
vIf?: () => boolean;
/** 失焦回调 */
onBlur?: (row: Record<string, any>, index: number) => void;
/** 输入回调 */
onInput?: (row: Record<string, any>, index: number) => void;
/** 变更回调 */
onChange?: (row: Record<string, any>, index: number, value?: any) => void;
}
/**
* EditableTable 组件的 Props 类型
*/
export interface EditableTableProps {
/** 表格数据 */
modelValue: Record<string, any>[];
/** 列配置 */
columns: EditableTableColumn[];
/** 表单验证规则 */
rules?: Record<string, any>;
/** 默认行数据 */
defaultRow?: Record<string, any>;
/** 是否显示边框 */
border?: boolean;
/** 是否显示斑马纹 */
stripe?: boolean;
/** 最大高度 */
maxHeight?: string | number;
/** 最小高度 */
minHeight?: string | number;
/** 是否开启虚拟滚动(不传则根据数据量自动开启) */
virtualized?: boolean;
/** 自动开启虚拟滚动的行数阈值,默认 100 */
virtualizedThreshold?: number;
/** 是否显示选择列 */
showSelection?: boolean;
/** 是否显示新增按钮 */
showAddButton?: boolean;
/** 是否显示删除按钮 */
showDeleteButton?: boolean;
/** 是否显示行级增删按钮 */
showRowActions?: boolean;
/** 是否显示行级“增加”按钮 */
showRowAddButton?: boolean;
/** 是否显示行级“删除”按钮 */
showRowDeleteButton?: boolean;
/** 搜索字段列表(用于筛选,不为空时自动显示搜索框) */
searchFields?: string[];
}

View File

@@ -0,0 +1,30 @@
import type { FormItemConfig } from './FormItem.d';
import type { DefineComponent } from 'vue';
/**
* Filter 组件的 Props 类型
*/
export interface FilterProps {
/** 查询参数对象 */
queryParams: Record<string, any>;
/** 表单项配置数组 */
formItems: FormItemConfig[];
/** 是否显示 */
show?: boolean;
/** 是否显示默认按钮 */
showDefaultButtons?: boolean;
/** 标签宽度 */
labelWidth?: string;
/** 标签后是否添加冒号 */
showLabelColon?: boolean;
}
/**
* Filter 组件暴露的方法
*/
export interface FilterExpose {
queryFormRef: InstanceType<typeof import('element-plus').ElForm> | null;
handleQuery: () => void;
resetQuery: () => void;
}

View File

@@ -0,0 +1,21 @@
import type { FormItemConfig } from './FormItem.d';
/**
* Form 组件的 Props 类型
*/
export interface FormProps {
/** 表单数据对象 */
model: Record<string, any>;
/** 表单项配置数组 */
formItems: FormItemConfig[];
/** 表单验证规则 */
rules?: Record<string, any>;
/** 标签宽度 */
labelWidth?: string;
/** 是否内联表单 */
inline?: boolean;
/** 标签位置 */
labelPosition?: 'left' | 'right' | 'top';
/** 标签后是否添加冒号 */
showLabelColon?: boolean;
}

View File

@@ -0,0 +1,82 @@
import type { CSSProperties } from 'vue';
/**
* 表单项选项类型
*/
export interface FormItemOption {
/** 选项标签 */
label: string;
/** 选项值 */
value: string | number | boolean | null | undefined;
/** 是否禁用该选项 */
disabled?: boolean;
}
/**
* onChange 回调函数类型
* @param value - 新的值
* @returns 返回 false 时阻止更新,返回其他值或 undefined 时允许更新
*/
export type OnChangeCallback = (
value: string | number | Array<string | number> | Date | null | undefined
) => Promise<boolean | void> | boolean | void;
/**
* 表单项配置类型
*/
export interface FormItemConfig {
/** 表单项类型 */
type: 'input' | 'select' | 'radio' | 'date' | 'daterange' | 'custom' | 'text';
/** 表单项标签 */
label: string;
/** 表单项字段名(对应 queryParams 的 key */
prop: string;
/** 占位符文本 */
placeholder?: string;
/** 开始日期占位符(仅 daterange 类型) */
startPlaceholder?: string;
/** 结束日期占位符(仅 daterange 类型) */
endPlaceholder?: string;
/** 是否可清空 */
clearable?: boolean;
/** 自定义样式对象或字符串 */
style?: CSSProperties | string | { width?: string; [key: string]: any };
/** 表单项宽度(字符串,如 '200px' */
width?: string;
/** 额外的属性(会通过 v-bind 传递给组件) */
extraprops?: Record<string, any>;
/** 是否多选(仅 select 类型) */
multiple?: boolean;
/** 是否可搜索(仅 select 类型) */
filterable?: boolean;
/** 多选时是否折叠标签(仅 select 类型) */
collapseTags?: boolean;
/** 选项列表select 和 radio 类型) */
options?: FormItemOption[];
/** 日期格式date 和 daterange 类型) */
valueFormat?: string;
/** 自定义插槽名称custom 类型) */
slot?: string;
/** 是否必填 */
required?: boolean;
/** 标签后缀 */
labelSuffix?: string;
/** 值变更时的回调函数 */
onChange?: OnChangeCallback;
/** 是否在变更前进行检查(仅 select 类型,为 true 时会执行 onChange 回调) */
checkBeforeChange?: boolean;
/** 格式化函数text 类型,用于格式化显示的文本) */
formatter?: (value: any) => string;
}
/**
* FormItem 组件的 Props 类型
*/
export interface FormItemProps {
/** 表单项配置对象 */
item: FormItemConfig;
/** 表单项的值 */
modelValue?: string | number | Array<string | number> | Date | null;
/** 回车键按下时的回调函数 */
onEnter?: () => void;
}

View File

@@ -0,0 +1,21 @@
import type { FormItemConfig } from './FormItem.d';
/**
* FormLayout 组件的 Props 类型
*/
export interface FormLayoutProps {
/** 表单数据对象 */
model: Record<string, any>;
/** 表单项配置数组 */
formItems: FormItemConfig[];
/** 表单验证规则 */
rules?: Record<string, any>;
/** 标签宽度 */
labelWidth?: string;
/** 标签位置 */
labelPosition?: 'left' | 'right' | 'top';
/** 标签后是否添加冒号 */
showLabelColon?: boolean;
/** 列数0 表示不限制) */
columns?: number;
}

View File

@@ -0,0 +1,21 @@
import type { DefineComponent } from 'vue';
/**
* Pagination 组件的 Props 类型
*/
export interface PaginationProps {
/** 总记录数 */
total: number;
/** 当前页码 */
page: number;
/** 每页条数 */
limit: number;
}
/**
* 为 Pagination.vue 组件提供类型声明
*/
declare module '@/components/Pagination/index.vue' {
const component: DefineComponent<PaginationProps>;
export default component;
}

View File

@@ -0,0 +1,21 @@
import type { DefineComponent } from 'vue';
/**
* QuickDateRange 组件的 Props 类型
*/
export interface QuickDateRangeProps {
/** 日期范围值(字符串数组,如 ['2024-01-01', '2024-01-31'] */
modelValue?: string[];
/** 开始日期占位符 */
startPlaceholder?: string;
/** 结束日期占位符 */
endPlaceholder?: string;
/** 日期格式 */
valueFormat?: string;
/** 是否可清空 */
clearable?: boolean;
/** 日期选择器的样式 */
datePickerStyle?: Record<string, any>;
/** 透传给日期选择器的其他属性 */
attrs?: Record<string, any>;
}

View File

@@ -0,0 +1,48 @@
import type { TableColumn } from './TableLayout.d';
/**
* Table 组件的 Props 类型
*/
export interface TableProps {
/** 表格数据 */
tableData: Record<string, any>[];
/** 加载状态 */
loading?: boolean;
/** 是否显示边框 */
border?: boolean;
/** 是否显示斑马纹 */
stripe?: boolean;
/** 表格尺寸 */
size?: 'large' | 'default' | 'small';
/** 表格高度 */
tableHeight?: string | number;
/** 最大高度 */
maxHeight?: string | number;
/** 行键 */
rowKey?: string | ((row: Record<string, any>) => string);
/** 是否高亮当前行 */
highlightCurrentRow?: boolean;
/** 表格列配置 */
tableColumns?: TableColumn[];
/** 是否显示分页 */
showPagination?: boolean;
/** 总记录数 */
total?: number;
/** 当前页码 */
pageNo?: number;
/** 每页条数 */
pageSize?: number;
/** 是否所有数据都在客户端,如果是则在组件内部进行分页 */
isAllData?: boolean;
/** 分页左侧自定义文案 */
paginationLeftText?: string;
/** 分页组件自定义属性 */
paginationProps?: {
pageSizes?: number[];
pagerCount?: number;
layout?: string;
background?: boolean;
autoScroll?: boolean;
hidden?: boolean;
};
}

View File

@@ -0,0 +1,89 @@
import type { FormItemConfig } from './FormItem.d';
/**
* 表格列配置类型
*/
export interface TableColumn {
/** 列类型 */
type?: 'selection' | 'index' | 'expand';
/** 列字段名 */
prop?: string;
/** 列标签 */
label?: string;
/** 列宽度 */
width?: string | number;
/** 最小宽度 */
minWidth?: string | number;
/** 是否固定列 */
fixed?: boolean | 'left' | 'right';
/** 对齐方式 */
align?: 'left' | 'center' | 'right';
/** 是否显示溢出提示 */
showOverflowTooltip?: boolean;
/** 自定义插槽名称 */
slot?: string;
/** 格式化函数 */
formatter?: (row: Record<string, any>, column: any, cellValue: any, index?: number) => string;
/** 是否可选selection 类型) */
selectable?: (row: Record<string, any>, index: number) => boolean;
}
/**
* 树节点数据类型
*/
export interface TreeNodeData {
[key: string]: any;
children?: TreeNodeData[];
}
/**
* TableLayout 组件的 Props 类型
*/
export interface TableLayoutProps {
/** 表格数据 */
tableData: Record<string, any>[];
/** 加载状态 */
loading?: boolean;
/** 总记录数 */
total?: number;
/** 查询参数对象 */
queryParams: {
pageNo: number;
pageSize: number;
[key: string]: any;
};
/** 侧边查询参数 */
sideQueryParams?: Record<string, any>;
/** 表单项配置数组 */
formItems?: FormItemConfig[];
/** 是否显示顶部查询 */
showTopQuery?: boolean;
/** 是否显示侧边查询 */
showSideQuery?: boolean;
/** 是否显示分页 */
showPagination?: boolean;
/** 是否显示默认按钮 */
showDefaultButtons?: boolean;
/** 侧边栏宽度 */
sideWidth?: number;
/** 是否显示边框 */
border?: boolean;
/** 是否显示斑马纹 */
stripe?: boolean;
/** 表格尺寸 */
size?: 'large' | 'default' | 'small';
/** 表格高度 */
tableHeight?: string | number;
/** 最大高度 */
maxHeight?: string | number;
/** 行键 */
rowKey?: string | ((row: Record<string, any>) => string);
/** 是否高亮当前行 */
highlightCurrentRow?: boolean;
/** 侧边栏数据 */
siderData?: TreeNodeData[];
/** 树节点键名 */
treeNodeKey?: string;
/** 表格列配置 */
tableColumns?: TableColumn[];
}

View File

@@ -0,0 +1,13 @@
/**
* TableLayout 组件相关的类型定义
*/
export * from './FormItem.d';
export * from './QuickDateRange.d';
export * from './EditableTable.d';
export * from './Filter.d';
export * from './FormLayout.d';
export * from './Form.d';
export * from './Table.d';
export * from './TableLayout.d';
export * from './Dialog.d';

View File

@@ -1,51 +1,70 @@
<template>
<div class="navbar">
<!-- <hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" /> -->
<!-- <breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!settingsStore.topNav" /> -->
<!-- <top-nav id="topmenu-container" class="topmenu-container" style="border: 1px solid blue;"/> -->
<div class="right-menu">
<template v-if="appStore.device !== 'mobile'">
<header-search id="header-search" class="right-menu-item" />
<!-- <el-tooltip content="源码地址" effect="dark" placement="bottom">
<ruo-yi-git id="openhis-git" class="right-menu-item hover-effect" />
</el-tooltip> -->
<!-- <el-tooltip content="文档地址" effect="dark" placement="bottom">
<ruo-yi-doc id="openhis-doc" class="right-menu-item hover-effect" />
</el-tooltip> -->
<!-- <screenfull id="screenfull" class="right-menu-item hover-effect" /> -->
<el-tooltip content="布局大小" effect="dark" placement="bottom">
<size-select id="size-select" class="right-menu-item hover-effect" />
</el-tooltip>
</template>
<div class="avatar-container">
<el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
<div class="avatar-wrapper">
<img :src="userStore.avatar" class="user-avatar" />
<span class="nick-name">{{ userStore.nickName }}</span>
<!-- <el-icon><caret-bottom /></el-icon> -->
</div>
<template #dropdown>
<el-dropdown-menu>
<router-link to="/user/profile">
<el-dropdown-item>个人中心</el-dropdown-item>
</router-link>
<el-dropdown-item command="switch">
<span>切换科室</span>
</el-dropdown-item>
<el-dropdown-item command="setLayout" v-if="settingsStore.showSettings">
<span>布局设置</span>
</el-dropdown-item>
<el-dropdown-item divided command="logout">
<span>退出登录</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<div class="avatar-wrapper">
<el-dropdown
@command="handleCommand"
class="user-info-dropdown hover-effect"
trigger="click"
teleported
popper-class="navbar-dropdown"
:popper-options="{
modifiers: [
{
name: 'offset',
options: {
offset: [0, 12],
},
},
],
}"
>
<div class="user-info">
<img :src="userStore.avatar" class="user-avatar" />
<span class="nick-name">{{ userStore.nickName }}</span>
</div>
<template #dropdown>
<el-dropdown-menu>
<router-link to="/user/profile">
<el-dropdown-item>个人中心</el-dropdown-item>
</router-link>
<!-- <el-dropdown-item command="setLayout" v-if="settingsStore.showSettings">
<span>布局设置</span>
</el-dropdown-item> -->
<el-dropdown-item divided command="logout">
<span>退出登录</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<span class="divider">|</span>
<el-dropdown
@command="handleOrgSwitch"
trigger="click"
teleported
popper-class="navbar-dropdown"
:placement="'bottom-start'"
>
<span class="org-name">{{ userStore.orgName }}</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="item in orgOptions"
:key="item.orgId"
:command="item.orgId"
:class="{ 'is-active': item.orgId === userStore.orgId }"
>
{{ item.orgName }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<span class="divider" />
</div>
</div>
</div>
<el-dialog title="切换科室" v-model="showDialog" width="400px" append-to-body destroy-on-close>
@@ -68,6 +87,7 @@
</template>
<script setup>
import { onMounted } from 'vue';
import { ElMessageBox } from 'element-plus';
import Breadcrumb from '@/components/Breadcrumb';
import TopNav from '@/components/TopNav';
@@ -86,8 +106,39 @@ const settingsStore = useSettingsStore();
const orgOptions = ref([]);
const showDialog = ref(false);
const orgId = ref('');
function toggleSideBar() {
appStore.toggleSideBar();
function loadOrgList() {
getOrg().then((res) => {
orgOptions.value = res.data;
});
}
onMounted(() => {
loadOrgList();
});
function handleOrgSwitch(selectedOrgId) {
if (selectedOrgId === userStore.orgId) {
return;
}
const selectedOrg = orgOptions.value.find((item) => item.orgId === selectedOrgId);
const orgName = selectedOrg ? selectedOrg.orgName : '该科室';
ElMessageBox.confirm(`确定要切换到科室"${orgName}"吗?`, '切换科室', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
orgId.value = selectedOrgId;
switchOrg(selectedOrgId).then((res) => {
if (res.code === 200) {
userStore.logOut().then(() => {
location.href = '/index';
});
}
});
});
}
function handleCommand(command) {
@@ -98,13 +149,6 @@ function handleCommand(command) {
case 'logout':
logout();
break;
case 'switch':
orgId.value = userStore.orgId;
getOrg().then((res) => {
orgOptions.value = res.data;
showDialog.value = true;
});
break;
default:
break;
}
@@ -143,29 +187,35 @@ function setLayout() {
<style lang='scss' scoped>
.navbar {
height: 50px;
overflow: hidden;
overflow: visible;
position: relative;
background-color: transparent;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 10px;
flex-shrink: 0;
min-width: 200px;
z-index: 1002;
.right-menu {
display: flex;
align-items: center;
flex-shrink: 0;
white-space: nowrap;
&:focus {
outline: none;
}
.right-menu-item {
display: inline-block;
display: flex;
align-items: center;
padding: 0 6px;
height: 100%;
font-size: 16px;
color: #606266;
vertical-align: text-bottom;
flex-shrink: 0;
&.hover-effect {
cursor: pointer;
@@ -180,25 +230,70 @@ function setLayout() {
.avatar-container {
margin-right: 0;
margin-left: 5px;
flex-shrink: 0;
position: relative;
z-index: 1003;
display: flex;
align-items: center;
.avatar-wrapper {
display: flex;
align-items: center;
margin-top: 5px;
gap: 8px;
position: relative;
.user-info-dropdown {
display: flex;
align-items: center;
cursor: pointer;
}
.user-info {
display: flex;
align-items: center;
gap: 10px;
}
.user-avatar {
cursor: pointer;
width: 30px;
height: 30px;
border-radius: 6px;
flex-shrink: 0;
}
.nick-name {
font-weight: 500;
color: #606266;
font-size: 14px;
width: 80px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100px;
flex-shrink: 0;
}
.divider {
color: #dcdfe6;
font-size: 14px;
margin: 0 8px;
flex-shrink: 0;
}
.org-name {
font-weight: 500;
color: #606266;
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 120px;
flex-shrink: 0;
cursor: pointer;
transition: color 0.3s;
&:hover {
color: #409eff;
}
}
.el-icon {
cursor: pointer;
position: absolute;
@@ -210,5 +305,99 @@ function setLayout() {
}
}
}
@media (max-width: 768px) {
min-width: 150px;
padding-right: 5px;
.right-menu {
.avatar-container {
.avatar-wrapper {
gap: 4px;
.nick-name {
max-width: 60px;
font-size: 12px;
}
.org-name {
max-width: 80px;
font-size: 12px;
}
.divider {
margin: 0 4px;
font-size: 12px;
}
.user-avatar {
width: 24px;
height: 24px;
}
}
}
}
}
@media (max-width: 480px) {
min-width: 120px;
padding-right: 5px;
.right-menu {
.avatar-container {
.avatar-wrapper {
.nick-name {
display: none;
}
.divider {
display: none;
}
.org-name {
max-width: 80px;
font-size: 11px;
}
}
}
}
}
}
</style>
<style lang="scss">
.navbar-dropdown {
margin-bottom: 8px !important;
z-index: 10010 !important;
.el-dropdown-menu {
margin-bottom: 0 !important;
z-index: 10010 !important;
max-height: 300px !important;
overflow-y: auto !important;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.2);
border-radius: 3px;
&:hover {
background: rgba(0, 0, 0, 0.3);
}
}
&::-webkit-scrollbar-track {
background: transparent;
}
.el-dropdown-menu__item.is-active {
color: #409eff !important;
font-weight: 500 !important;
background-color: #ecf5ff !important;
}
}
}
</style>

View File

@@ -1,33 +1,36 @@
<template>
<div
:class="{ 'has-logo': showLogo }"
class="sidebar-wrapper"
:style="{
backgroundColor:
sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground,
}"
>
<!-- <logo v-if="showLogo" :collapse="isCollapse" /> -->
<!-- <el-scrollbar :class="sideTheme" wrap-class="scrollbar-wrapper"> -->
<el-menu
:default-active="activeMenu"
:collapse="isCollapse"
:background-color="
sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground
"
:text-color="sideTheme === 'theme-dark' ? variables.menuColor : variables.menuLightColor"
:unique-opened="true"
:active-text-color="theme"
:collapse-transition="false"
mode="horizontal"
>
<sidebar-item
v-for="(route, index) in sidebarRouters"
:key="route.path + index"
:item="route"
:base-path="route.path"
/>
</el-menu>
<!-- </el-scrollbar> -->
<div class="menu-container">
<div class="menu-scrollbar">
<el-menu
:default-active="activeMenu"
:collapse="isCollapse"
:background-color="
sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground
"
:text-color="sideTheme === 'theme-dark' ? variables.menuColor : variables.menuLightColor"
:unique-opened="true"
:active-text-color="theme"
:collapse-transition="false"
mode="horizontal"
>
<sidebar-item
v-for="(route, index) in sidebarRouters"
:key="route.path + index"
:item="route"
:base-path="route.path"
/>
</el-menu>
</div>
</div>
<navbar @setLayout="setLayout" class="navbar-container" />
<settings ref="settingRef" />
</div>
@@ -71,17 +74,61 @@ function setLayout() {
</script>
<style lang="scss" scoped>
.scrollbar-wrapper {
overflow-x: auto !important;
.sidebar-wrapper {
display: flex;
flex-direction: row;
align-items: center;
height: 50px;
padding: 0;
width: 100%;
overflow: hidden;
position: relative;
}
.menu-container {
flex: 1;
height: 50px;
min-width: 0;
overflow: hidden;
position: relative;
display: flex;
align-items: center;
}
.menu-scrollbar {
height: 50px;
width: 100%;
overflow-x: auto;
overflow-y: hidden;
display: flex;
align-items: center;
&::-webkit-scrollbar {
height: 4px;
}
&::-webkit-scrollbar:vertical {
display: none;
}
&::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 2px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
}
.el-menu--horizontal {
display: flex !important;
align-items: center !important;
border-bottom: none !important;
background-color: transparent !important;
min-width: auto;
flex-wrap: nowrap;
height: 50px;
& > .el-menu-item,
& > .el-sub-menu {
@@ -91,7 +138,10 @@ function setLayout() {
padding: 0 20px !important;
font-size: 14px;
min-width: 120px !important;
border: 1px solid red !important;
flex-shrink: 0;
white-space: nowrap;
display: flex;
align-items: center;
}
:deep(.svg-icon) {
@@ -101,6 +151,9 @@ function setLayout() {
:deep(.el-sub-menu__title) {
padding-right: 25px !important;
display: flex;
align-items: center;
height: 50px;
}
:deep(.el-sub-menu__icon-arrow) {
@@ -112,20 +165,36 @@ function setLayout() {
}
}
/* 水平布局,并与 Navbar 正确配合 */
div {
&.has-logo {
display: flex;
flex-direction: row;
align-items: center;
height: 50px;
padding: 0;
width: 100%;
.navbar-container {
flex-shrink: 0;
height: 50px;
z-index: 1002;
}
& > .el-scrollbar {
flex: 1;
height: 50px;
min-width: 0;
/* 响应式处理 */
@media (max-width: 768px) {
.menu-container {
flex: 1;
min-width: 0;
}
.el-menu--horizontal {
& > .el-menu-item,
& > .el-sub-menu {
padding: 0 12px !important;
min-width: 80px !important;
font-size: 12px;
}
}
}
@media (max-width: 480px) {
.el-menu--horizontal {
& > .el-menu-item,
& > .el-sub-menu {
padding: 0 8px !important;
min-width: 60px !important;
font-size: 11px;
}
}
}

View File

@@ -105,6 +105,7 @@ function setLayout() {
left: 0;
z-index: 1001;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.35);
overflow: visible;
}
.sidebar-container {

View File

@@ -24,12 +24,17 @@
<div class="single-row-layout">
<el-form-item label="姓名" prop="patientName" class="row-item">
<div class="input-with-unit">
<el-input v-model="formData.patientName" type="text" placeholder="请输入" />
<el-input
disabled
v-model="formData.patientName"
type="text"
placeholder="请输入"
/>
</div>
</el-form-item>
<el-form-item label="年龄" prop="age" class="row-item">
<div class="input-with-unit">
<el-input v-model="formData.age" type="text" placeholder="请输入" />
<el-input disabled v-model="formData.age" type="text" placeholder="请输入" />
</div>
</el-form-item>
<el-form-item label="性别" prop="gender" class="row-item">
@@ -40,7 +45,7 @@
<el-form-item label="住院号" prop="busNo" class="row-item">
<div class="input-with-unit">
<el-input v-model="formData.busNo" type="text" placeholder="请输入" />
<el-input disabled v-model="formData.busNo" type="text" placeholder="请输入" />
</div>
</el-form-item>
<el-form-item label="职业" prop="temperature" class="row-item">
@@ -68,7 +73,7 @@
</el-form-item>
<el-form-item label="住院天数" prop="hospitalDays" class="row-item">
<div class="input-with-unit">
<el-input v-model="formData.hospitalDays" type="number" placeholder="请输入" />
<el-input disabled v-model="formData.hospitalDays" placeholder="请输入" />
</div>
</el-form-item>
</div>
@@ -81,7 +86,7 @@
v-model="formData.DischargeDiagnosis"
type="textarea"
placeholder="请输入出院诊断"
:rows="4"
:autosize="{ minRows: 1 }"
style="resize: none; padding-right: 10px"
/>
</el-form-item>
@@ -95,7 +100,7 @@
v-model="formData.SummaryAndDiagnosisAndTreatmentProcess"
type="textarea"
placeholder="请输入出院病情摘要及诊疗经过"
:rows="4"
:autosize="{ minRows: 1 }"
style="resize: none; padding-right: 10px"
/>
</el-form-item>
@@ -109,7 +114,7 @@
v-model="formData.RequirementsAndPrecautionsAfterDischarge"
type="textarea"
placeholder="请输入出院后要求及注意事项"
:rows="4"
:autosize="{ minRows: 1 }"
style="resize: none; padding-right: 10px"
/>
</el-form-item>
@@ -123,19 +128,21 @@
v-model="formData.TraditionalChineseMedicineNursing"
type="textarea"
placeholder="请输入中医调护"
:rows="4"
:autosize="{ minRows: 1 }"
style="resize: none; padding-right: 10px"
/>
</el-form-item>
</el-form>
</div>
</div>
<DisDiagnMedicalRecord v-if="isShowprintDom" ref="recordPrintRef"></DisDiagnMedicalRecord>
</template>
<script setup>
import { reactive, ref, onMounted, watch } from 'vue';
import { reactive, ref, onMounted, watch, nextTick } from 'vue';
import { ElMessage } from 'element-plus';
import { patientInfo } from '../views/doctorstation/components/store/patient';
import { previewPrint } from '../utils/printUtils';
import DisDiagnMedicalRecord from '../views/hospitalRecord/components/disDiagnMedicalRecord.vue';
defineOptions({
name: 'DischargeDiagnosisCertificate',
@@ -149,6 +156,9 @@ const props = defineProps({
},
});
const recordPrintRef = ref();
const isShowprintDom = ref(false);
const patient = props.patientInfo;
// const props = defineProps({});
@@ -220,6 +230,10 @@ onMounted(() => {
if (!formData.admissionDate) {
formData.admissionDate = patient?.inHospitalTime || '';
}
if (!formData.hospitalDays) {
formData.hospitalDays = patient?.inHospitalDays;
}
console.log('patientInfo========>', JSON.stringify(props.patientInfo));
});
// 监听患者信息变化,实现联动显示和自动填充
@@ -246,14 +260,27 @@ const fillPatientInfo = (patientData) => {
formData.age = patientData.age || '';
formData.gender = patientData.genderEnum_enumText || '';
formData.busNo = patientData.busNo || '';
formData.hospitalDays = patientData.inHospitalDays || '';
} catch (error) {
console.error('填充患者信息时发生错误:', error);
ElMessage.error('自动填充患者信息失败,请检查数据源格式');
}
};
// 打印方法
const printFun = () => {
isShowprintDom.value = true;
nextTick(() => {
recordPrintRef?.value.setData(formData);
nextTick(() => {
previewPrint(recordPrintRef?.value.getDom());
isShowprintDom.value = false;
});
});
};
// 暴露接口
defineExpose({ formData, submit, setFormData, fillPatientInfo });
defineExpose({ formData, submit, setFormData, fillPatientInfo, printFun });
</script>
<style scoped>

View File

@@ -14,7 +14,7 @@
@onCaseSecond="updateCaseFirstDatas"
></medicalRecordSecond>
</el-tab-pane>
<el-tab-pane label="病案附页()" name="third">
<el-tab-pane label="病案附页()" name="third">
<medicalRecordThird
:formData="formData"
@onCaseThird="updateCaseFirstDatas"
@@ -23,9 +23,13 @@
</el-tabs>
<div class="form-footer">
<button @click="printForm" class="print-btn">打印表单</button>
<!-- <button @click="printForm" class="print-btn">打印表单</button> -->
<button @click="resetForm" class="reset-btn">重置表单</button>
</div>
<medicalRecordPrint v-if="isShowprintDom" ref="recordPrintRef"></medicalRecordPrint>
<!-- <el-drawer v-model="drawer" size="100%">
<medicalRecordPrint ref="recordPrintRef"></medicalRecordPrint>
</el-drawer> -->
</div>
</template>
@@ -33,7 +37,7 @@
defineOptions({
name: 'HospitalRecordForm',
});
import { ref, reactive } from 'vue';
import { ref, reactive, nextTick } from 'vue';
import { ElMessage } from 'element-plus';
// import medicalRecordFirst from './components/medicalRecordFirst.vue';
import medicalRecordFirst from '@/views/hospitalRecord/components/medicalRecordFirst.vue';
@@ -43,10 +47,17 @@ import medicalRecordFirstPrint from '@/views/hospitalRecord/components/medicalRe
import medicalRecordSecondPrint from '@/views/hospitalRecord/components/medicalRecordSecondPrint.json';
import medicalRecordThirdPrint from '@/views/hospitalRecord/components/medicalRecordThirdPrint.json';
import formDataJs from '../views/doctorstation/components/store/medicalpage';
import medicalRecordPrint from '../views/hospitalRecord/components/medicalRecordPrint.vue';
import { previewPrint } from '../utils/printUtils';
import {
getEncounterDiagnosis,
getTcmDiagnosis,
} from '../views/inpatientDoctor/home/components/api';
import { cloneDeep } from 'lodash';
const firstRef = ref();
const commpoentType = 'medicalRecord';
const emit = defineEmits(['submitOk']);
const drawer = ref(false);
// 表单数据
const formData = reactive({
//医院信息
@@ -304,29 +315,40 @@ const formData = reactive({
tableData_top: [],
});
// Props与事件
const props = defineProps({
patientInfo: {
type: Object,
required: true,
},
});
const activeName = ref('first');
const recordPrintRef = ref();
const isShowprintDom = ref(false);
// 打印表单
const printForm = () => {
// 创建一个新的打印窗口
const printWindow = window.open('', '_blank');
let printContent;
// 获取模板字符串并替换转义的插值标记
if (activeName.value == 'first') {
printContent = medicalRecordFirstPrint.printContent;
} else if (activeName.value == 'second') {
printContent = medicalRecordSecondPrint.printContent;
} else {
printContent = medicalRecordThirdPrint.printContent;
}
// 这里可以进行实际的数据替换操作
printContent = printContent.replace(/\$\{([^}]+)\}/g, (match, expr) => {
// 简单示例实际应根据expr内容进行数据提取
return eval(expr); // 注意实际使用中应避免eval这里仅为示例
});
// 将内容写入打印窗口并打印
printWindow.document.write(printContent);
printWindow.document.close();
drawer.value = true;
// // 创建一个新的打印窗口
// const printWindow = window.open('', '_blank');
// let printContent;
// // 获取模板字符串并替换转义的插值标记
// if (activeName.value == 'first') {
// printContent = medicalRecordFirstPrint.printContent;
// } else if (activeName.value == 'second') {
// printContent = medicalRecordSecondPrint.printContent;
// } else {
// printContent = medicalRecordThirdPrint.printContent;
// }
// // 这里可以进行实际的数据替换操作
// printContent = printContent.replace(/\$\{([^}]+)\}/g, (match, expr) => {
// // 简单示例实际应根据expr内容进行数据提取
// return eval(expr); // 注意实际使用中应避免eval这里仅为示例
// });
// // 将内容写入打印窗口并打印
// printWindow.document.write(printContent);
// printWindow.document.close();
};
function handleClick() {
@@ -345,8 +367,16 @@ const resetFun = (data) => {
// 重置表单
const resetForm = () => {
if (activeName.value == 'first') {
resetFun(firstRef.value.formData.hospitalInfo);
resetFun(firstRef.value.formData.patientInfo);
// resetFun(firstRef.value.formData.hospitalInfo);
// resetFun(firstRef.value.formData.patientInfo);
firstRef.value.formData.patientInfo.napl = ''; //籍贯
firstRef.value.formData.patientInfo.certno = ''; //身份证号
firstRef.value.formData.patientInfo.resd_addr = ''; //户口地址
firstRef.value.formData.patientInfo.empr_addr = ''; //工作单位
firstRef.value.formData.patientInfo.coner_name = ''; //联系人
firstRef.value.formData.patientInfo.coner_rlts_code = ''; //关系
firstRef.value.formData.patientInfo.coner_addr = ''; //联系人地址
firstRef.value.formData.patientInfo.coner_tel = ''; //联系人电话
resetFun(firstRef.value.formData.admission);
resetFun(firstRef.value.formData.diagnosis);
resetFun(firstRef.value.formData.medicalInfo);
@@ -373,9 +403,37 @@ const resetForm = () => {
const updateCaseFirstDatas = (newDatas) => {
Object.assign(formData, newDatas);
};
const getList = () => {
getEncounterDiagnosis(props.patientInfo.encounterId).then((res) => {
if (res.code == 200) {
console.log('getEncounterDiagnosis=======>', JSON.stringify(res.data));
formDataJs.diagnosisList = res.data;
}
});
getTcmDiagnosis({ encounterId: props.patientInfo.encounterId }).then((res) => {
if (res.code == 200) {
// if (res.data.illness.length > 0) {
// diagnosisNetDatas.value = res.data.illness;
// res.data.illness.forEach((item, index) => {
// form.value.diagnosisList.push({
// name: item.name + '-' + res.data.symptom[index].name,
// ybNo: item.ybNo,
// medTypeCode: item.medTypeCode,
// });
// });
// }
}
});
};
// 点击历史数据回传布局
const setFormData = (data) => {
Object.assign(firstRef.value.formData, data);
if (Object.keys(data).length > 0) {
Object.assign(firstRef.value.formData, data);
} else {
resetForm();
}
getList();
};
//保存数据方法
const submit = () => {
@@ -581,11 +639,21 @@ const verifyMethod = (data) => {
}
return true;
};
// 打印方法
const printFun = () => {
isShowprintDom.value = true;
nextTick(() => {
recordPrintRef?.value.setData();
previewPrint(recordPrintRef?.value.getDom());
isShowprintDom.value = false;
});
};
defineExpose({
submit,
commpoentType,
setFormData,
printFun,
});
</script>

View File

@@ -7,177 +7,133 @@
<span class="hospital-text">长春市朝阳区中医院</span>
</h1>
</div>
<!-- 页面标题 -->
<h2 class="form-title">住院病人风险评估表</h2>
<!-- 表单卡片 -->
<el-card class="form-card">
<el-form :model="formData" label-width="100px">
<!-- 第一行科室床号住院号 -->
<el-row :gutter="16">
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="科室">
<el-input
v-model="formData.department"
readonly="true"
type="textarea"
:autosize="{ minRows: 1 }"
class="auto-resize-input"
></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="床号">
<el-input
v-model="formData.bedNo"
readonly="true"
type="textarea"
:autosize="{ minRows: 1 }"
class="auto-resize-input"
></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="住院号">
<el-input
v-model="formData.busNo"
readonly="true"
type="textarea"
:autosize="{ minRows: 1 }"
class="auto-resize-input"
></el-input>
</el-form-item>
</el-col>
</el-row>
<!-- 第二行姓名性别年龄 -->
<el-row :gutter="16">
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="姓名">
<el-input
v-model="formData.patientName"
readonly="true"
type="textarea"
:autosize="{ minRows: 1 }"
class="auto-resize-input"
></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="性别">
<el-input
v-model="formData.gender"
readonly="true"
type="textarea"
:autosize="{ minRows: 1 }"
class="auto-resize-input"
></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="年龄">
<el-input
v-model="formData.age"
readonly="true"
type="textarea"
:autosize="{ minRows: 1 }"
class="auto-resize-input"
></el-input>
</el-form-item>
</el-col>
</el-row>
<!-- 病情简介 -->
<el-form-item label="病情简介">
<el-input
type="textarea"
v-model="formData.adm_cond"
:autosize="{ minRows: 4, maxRows: 10 }"
class="full-width-textarea"
></el-input>
</el-form-item>
<!-- 不良后果及预后 -->
<el-form-item label="可能发生的不良后果及预后">
<el-input
type="textarea"
v-model="formData.effectless"
:autosize="{ minRows: 1, maxRows: 5 }"
class="full-width-textarea"
></el-input>
</el-form-item>
<!-- 评估等级 -->
<el-form-item label="评估等级">
<el-radio-group v-model="formData.evalLevel">
<el-radio label="一般">一般</el-radio>
<el-radio label="病重">病重</el-radio>
<el-radio label="病危">病危</el-radio>
</el-radio-group>
</el-form-item>
<!-- 护理等级 -->
<el-form-item label="护理等级">
<el-radio-group v-model="formData.nurseLevel">
<el-radio label="特级护理">特级护理</el-radio>
<el-radio label="一级护理">一级护理</el-radio>
<el-radio label="二级护理">二级护理</el-radio>
<el-radio label="三级护理">三级护理</el-radio>
</el-radio-group>
</el-form-item>
<!-- 收集资料时间 -->
<el-form-item label="收集资料时间">
<span class="date-display">{{ currentDate }}</span>
</el-form-item>
<!-- 医师签名 -->
<el-row :gutter="16">
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="评估医师签名">
<el-input
v-model="formData.sign_doc"
type="textarea"
:autosize="{ minRows: 1 }"
class="auto-resize-input"
></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="主治医师签名">
<el-input
v-model="formData.sign_maindoc"
type="textarea"
:autosize="{ minRows: 1 }"
class="auto-resize-input"
></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="科主任签名">
<el-input
v-model="formData.sign_leader"
type="textarea"
:autosize="{ minRows: 1 }"
class="auto-resize-input"
></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<el-form :model="formData" label-width="100px">
<el-row>
<el-col :span="8">
<el-form-item label="科室" label-position="top">
<el-input v-model="formData.department" readonly="true"></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="床号" label-position="top" class="comment-padding">
<el-input v-model="formData.bedNo" readonly="true"></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="住院号" label-position="top" class="comment-padding">
<el-input v-model="formData.busNo" readonly="true"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="姓名" label-position="top">
<el-input
v-model="formData.patientName"
readonly="true"
class="auto-resize-input"
></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="性别" label-position="top" class="comment-padding">
<el-input
v-model="formData.gender"
readonly="true"
class="auto-resize-input"
></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="年龄" label-position="top" class="comment-padding">
<el-input v-model="formData.age" readonly="true" class="auto-resize-input"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="病情简介" label-position="top">
<el-input
type="textarea"
v-model="formData.adm_cond"
:autosize="{ minRows: 1, maxRows: 100 }"
class="full-width-textarea"
></el-input>
</el-form-item>
<el-form-item label="可能发生的不良后果及预后" label-position="top">
<el-input
type="textarea"
v-model="formData.effectless"
:autosize="{ minRows: 1, maxRows: 100 }"
class="full-width-textarea"
></el-input>
</el-form-item>
<el-form-item label="评估等级" label-position="top">
<el-radio-group v-model="formData.evalLevel">
<el-radio label="一般">一般</el-radio>
<el-radio label="病重">病重</el-radio>
<el-radio label="病危">病危</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="护理等级" label-position="top">
<el-radio-group v-model="formData.nurseLevel">
<el-radio label="特级护理">特级护理</el-radio>
<el-radio label="一级护理">一级护理</el-radio>
<el-radio label="二级护理">二级护理</el-radio>
<el-radio label="三级护理">三级护理</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="收集资料时间:">
<span class="date-display">{{ currentDate }}</span>
</el-form-item>
<el-row>
<el-col :span="8">
<el-form-item label="评估医师签名:">
<el-input
disabled
v-model="formData.sign_doc"
:autosize="{ minRows: 1 }"
class="auto-resize-input"
></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="主治医师签名:" class="comment-padding">
<el-input
v-model="formData.sign_maindoc"
:autosize="{ minRows: 1 }"
class="auto-resize-input"
></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="科主任签名:" class="comment-padding">
<el-input
v-model="formData.sign_leader"
:autosize="{ minRows: 1 }"
class="auto-resize-input"
></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</div>
<inAssessmentForm v-if="isShowprintDom" ref="recordPrintRef"></inAssessmentForm>
</template>
<script setup>
import { computed, onMounted, nextTick, reactive, ref } from 'vue';
import inAssessmentForm from '../views/hospitalRecord/components/inAssessmentForm.vue';
import useUserStore from '@/store/modules/user';
import { previewPrint } from '../utils/printUtils';
defineOptions({
name: 'InHospitalCaseForm',
});
const isShowprintDom = ref(false);
const recordPrintRef = ref();
const props = defineProps({
patientInfo: {
type: Object,
@@ -186,20 +142,21 @@ const props = defineProps({
});
const patient = props.patientInfo;
const userStore = useUserStore();
// 表单数据 - 修复:将 formData 定义移到 patient 之后
const formData = reactive({
department: patient?.department || '',
bedNo: patient?.bedNo || '',
department: patient?.inHospitalOrgName || '',
bedNo: patient?.bedName || '',
busNo: patient?.busNo || '',
patientName: patient?.patientName || '',
gender: patient?.sex || '',
gender: patient?.genderEnum_enumText || '',
age: patient?.age || '',
adm_cond: '',
effectless: '',
evalLevel: '',
nurseLevel: '',
sign_doc: '',
sign_doc: userStore.nickName || '',
sign_maindoc: '',
sign_leader: '',
});
@@ -220,9 +177,9 @@ const formRef = ref(null);
const submit = () => {
// 如果需要表单验证,可以使用以下代码
// formRef.value.validate((valid) => {
// if (valid) {
// emits('submitOk', formData);
// }
// if (valid) {
// emits('submitOk', formData);
// }
// });
// 简化版本:
emits('submitOk', formData);
@@ -243,28 +200,36 @@ onMounted(() => {
setTimeout(() => {
window.dispatchEvent(new Event('resize'));
}, 100);
console.log('@@@@@=======>', JSON.stringify(props.patientInfo));
});
// 打印方法
const printFun = () => {
console.log('入院记录打印');
isShowprintDom.value = true;
nextTick(() => {
recordPrintRef?.value.setData(formData);
nextTick(() => {
previewPrint(recordPrintRef?.value.getDom());
isShowprintDom.value = false;
});
});
};
//暴露接口
defineExpose({ formData, submit, setFormData });
defineExpose({ formData, submit, setFormData, printFun });
</script>
<style scoped>
/* ===== 页面容器与背景 ===== */
.comment-padding {
padding-left: 10px;
}
.assessment-page {
font-family: 'Microsoft YaHei', 宋体, sans-serif;
background-color: #f3f4f6;
padding: 1.25rem;
min-height: 100vh;
width: 100%;
}
.page-container {
max-width: 48rem;
margin: 0 auto;
background-color: #ffffff;
padding: 1.5rem;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
border-radius: 4px;
width: 100%;
}
/* ===== 医院头部 ===== */
@@ -272,8 +237,7 @@ defineExpose({ formData, submit, setFormData });
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
margin-bottom: 1rem;
margin-top: 40px;
}
.hospital-name {
@@ -292,6 +256,7 @@ defineExpose({ formData, submit, setFormData });
/* ===== 表单标题与操作 ===== */
.form-title {
margin-top: 10px;
font-size: 1.25rem;
font-weight: 600;
text-align: center;
@@ -305,22 +270,6 @@ defineExpose({ formData, submit, setFormData });
border-radius: 4px;
}
:deep(.el-card.form-card) {
box-shadow: none !important;
}
/* ===== 自动调整大小的输入框样式 ===== */
.auto-resize-input {
width: 100%;
}
:deep(.auto-resize-input .el-textarea__inner) {
overflow: hidden;
resize: none;
min-height: 32px !important;
padding: 5px 12px;
}
/* ===== Textarea 自动扩展样式 ===== */
.full-width-textarea {
width: 100%;
@@ -337,15 +286,4 @@ defineExpose({ formData, submit, setFormData });
font-size: 0.95rem;
color: #666;
}
/* ===== 响应式调整 ===== */
@media (max-width: 768px) {
.page-container {
padding: 1rem;
}
.hospital-name {
font-size: 1.25rem;
}
}
</style>
</style>

View File

@@ -1,7 +1,9 @@
<template>
<div class="medical-form">
<h2 style="text-align: center;">{{ userStore.hospitalName || '长春市朝阳区中医院' }} -入院记录</h2>
<h2 style="text-align: center">
{{ userStore.hospitalName || '长春市朝阳区中医院' }} -入院记录
</h2>
<!-- 滚动内容区域 -->
<div class="form-scroll-container">
<el-form
@@ -16,90 +18,67 @@
<h4 class="section-title">基础信息</h4>
<div class="adaptive-grid form-section">
<el-form-item label="姓名" prop="patientName" class="grid-item required">
<el-input
v-model="formData.patientName"
placeholder="请输入姓名"
clearable
/>
<el-input v-model="formData.patientName" placeholder="请输入姓名" clearable />
</el-form-item>
<el-form-item label="住院号" prop="hospitalNo" class="grid-item required">
<el-input
v-model="formData.hospitalNo"
placeholder="请输入住院号"
clearable
/>
<el-input v-model="formData.hospitalNo" placeholder="请输入住院号" clearable />
</el-form-item>
<el-form-item label="性别" prop="gender" class="grid-item required">
<el-select v-model="formData.gender" placeholder="请选择" style="width: 100%;">
<el-select v-model="formData.gender" placeholder="请选择" style="width: 100%">
<el-option label="男" value="男"></el-option>
<el-option label="女" value="女"></el-option>
</el-select>
</el-form-item>
<el-form-item label="年龄" prop="age" class="grid-item required">
<div class="input-with-unit">
<el-input
v-model.number="formData.age"
placeholder="请输入年龄"
clearable
/>
<el-input v-model.number="formData.age" placeholder="请输入年龄" clearable />
<span class="unit"></span>
</div>
</el-form-item>
<el-form-item label="民族" prop="nation" class="grid-item">
<el-input
v-model="formData.nation"
placeholder="请输入民族"
clearable
/>
<el-input v-model="formData.nation" placeholder="请输入民族" clearable />
</el-form-item>
<el-form-item label="职业" prop="occupation" class="grid-item">
<el-input
v-model="formData.occupation"
placeholder="请输入职业"
clearable
/>
<el-input v-model="formData.occupation" placeholder="请输入职业" clearable />
</el-form-item>
<el-form-item label="婚姻状况" prop="marriage" class="grid-item">
<el-select v-model="formData.marriage" placeholder="请选择" clearable style="width: 100%;">
<el-select
v-model="formData.marriage"
placeholder="请选择"
clearable
style="width: 100%"
>
<el-option label="已婚" value="已婚"></el-option>
<el-option label="未婚" value="未婚"></el-option>
<el-option label="离异" value="离异"></el-option>
</el-select>
</el-form-item>
<el-form-item label="出生地" prop="birthplace" class="grid-item">
<el-input
v-model="formData.birthplace"
placeholder="请输入出生地"
clearable
/>
<el-input v-model="formData.birthplace" placeholder="请输入出生地" clearable />
</el-form-item>
<el-form-item label="入院时间" prop="admissionTime" class="grid-item required">
<el-date-picker
v-model="formData.admissionTime"
type="datetime"
placeholder="选择入院时间"
<el-date-picker
v-model="formData.admissionTime"
type="datetime"
placeholder="选择入院时间"
value-format="YYYY-MM-DD HH:mm"
style="width: 100%;"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="记录时间" prop="recordTime" class="grid-item required">
<el-date-picker
v-model="formData.recordTime"
type="datetime"
placeholder="选择记录时间"
<el-date-picker
v-model="formData.recordTime"
type="datetime"
placeholder="选择记录时间"
value-format="YYYY-MM-DD HH:mm"
style="width: 100%;"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="病史陈述者" prop="historyReporter" class="grid-item">
<el-input
v-model="formData.historyReporter"
placeholder="请输入陈述者"
clearable
/>
<el-input v-model="formData.historyReporter" placeholder="请输入陈述者" clearable />
</el-form-item>
<el-form-item label="可靠程度" prop="reliability" class="grid-item">
<el-select v-model="formData.reliability" placeholder="请选择" style="width: 100%;">
<el-select v-model="formData.reliability" placeholder="请选择" style="width: 100%">
<el-option label="可靠" value="可靠"></el-option>
<el-option label="基本可靠" value="基本可靠"></el-option>
<el-option label="不可靠" value="不可靠"></el-option>
@@ -111,76 +90,76 @@
<h4 class="section-title">病史信息</h4>
<div class="form-section">
<el-form-item label="主诉" prop="complaint" class="history-item required">
<el-input
v-model="formData.complaint"
type="textarea"
placeholder="请输入主诉"
<el-input
v-model="formData.complaint"
type="textarea"
placeholder="请输入主诉"
autosize
maxlength="200"
show-word-limit
/>
</el-form-item>
<el-form-item label="现病史" prop="presentIllness" class="history-item">
<el-input
v-model="formData.presentIllness"
type="textarea"
placeholder="请详细描述现病史"
<el-input
v-model="formData.presentIllness"
type="textarea"
placeholder="请详细描述现病史"
autosize
maxlength="1000"
show-word-limit
/>
</el-form-item>
<el-form-item label="既往史" prop="pastHistory" class="history-item">
<el-input
v-model="formData.pastHistory"
type="textarea"
placeholder="请输入既往史"
<el-input
v-model="formData.pastHistory"
type="textarea"
placeholder="请输入既往史"
autosize
maxlength="800"
show-word-limit
/>
</el-form-item>
<el-form-item label="个人史" prop="personalHistory" class="history-item">
<el-input
v-model="formData.personalHistory"
type="textarea"
placeholder="请输入个人史"
<el-input
v-model="formData.personalHistory"
type="textarea"
placeholder="请输入个人史"
autosize
maxlength="500"
show-word-limit
/>
</el-form-item>
<el-form-item label="婚育史" prop="maritalHistory" class="history-item">
<el-input
v-model="formData.maritalHistory"
type="textarea"
placeholder="请输入婚育史"
<el-input
v-model="formData.maritalHistory"
type="textarea"
placeholder="请输入婚育史"
autosize
maxlength="500"
show-word-limit
/>
</el-form-item>
<el-form-item label="月经史" prop="menstrualHistory" class="history-item">
<el-input
v-model="formData.menstrualHistory"
type="textarea"
placeholder="请输入月经史"
<el-input
v-model="formData.menstrualHistory"
type="textarea"
placeholder="请输入月经史"
autosize
maxlength="500"
show-word-limit
/>
</el-form-item>
<el-form-item label="家族史" prop="familyHistory" class="history-item">
<el-input
v-model="formData.familyHistory"
type="textarea"
placeholder="请输入家族史"
<el-input
v-model="formData.familyHistory"
type="textarea"
placeholder="请输入家族史"
autosize
maxlength="500"
show-word-limit
@@ -192,10 +171,10 @@
<h4 class="section-title">中医望闻问切</h4>
<div class="form-section">
<el-form-item label="望闻问切" prop="tcmInfo" class="history-item">
<el-input
v-model="formData.tcmInfo"
type="textarea"
placeholder="请输入中医望闻问切结果"
<el-input
v-model="formData.tcmInfo"
type="textarea"
placeholder="请输入中医望闻问切结果"
autosize
maxlength="600"
show-word-limit
@@ -209,138 +188,134 @@
<div class="adaptive-grid">
<el-form-item label="体温" prop="temp" class="grid-item">
<div class="input-with-unit">
<el-input
v-model.number="formData.temp"
type="number"
step="0.1"
placeholder="如36.0"
<el-input
v-model.number="formData.temp"
type="number"
step="0.1"
placeholder="如36.0"
clearable
/>
<span class="unit"></span>
</div>
</el-form-item>
<el-form-item label="脉搏" prop="pulse" class="grid-item">
<div class="input-with-unit">
<el-input
v-model.number="formData.pulse"
type="number"
placeholder="如76"
<el-input
v-model.number="formData.pulse"
type="number"
placeholder="如76"
clearable
/>
<span class="unit">/</span>
</div>
</el-form-item>
<el-form-item label="呼吸" prop="respiration" class="grid-item">
<div class="input-with-unit">
<el-input
v-model.number="formData.respiration"
type="number"
placeholder="如16"
<el-input
v-model.number="formData.respiration"
type="number"
placeholder="如16"
clearable
/>
<span class="unit">/</span>
</div>
</el-form-item>
<el-form-item label="血压" prop="bp" class="grid-item">
<div class="input-with-unit">
<el-input
v-model="formData.bp"
placeholder="如188/94"
<el-input
v-model="formData.bp"
placeholder="如188/94"
clearable
@blur="validateBloodPressure"
/>
<span class="unit">mmHg</span>
</div>
</el-form-item>
<el-form-item label="身高" prop="height" class="grid-item">
<div class="input-with-unit">
<el-input
v-model.number="formData.height"
type="number"
placeholder="如165"
<el-input
v-model.number="formData.height"
type="number"
placeholder="如165"
clearable
/>
<span class="unit">cm</span>
</div>
</el-form-item>
<el-form-item label="体重" prop="weight" class="grid-item">
<div class="input-with-unit">
<el-input
v-model.number="formData.weight"
type="number"
placeholder="如79"
<el-input
v-model.number="formData.weight"
type="number"
placeholder="如79"
clearable
/>
<span class="unit">kg</span>
</div>
</el-form-item>
<el-form-item label="BMI" prop="bmi" class="grid-item">
<div class="input-with-unit">
<el-input
v-model="formData.bmi"
placeholder="如29.02"
readonly
/>
<el-input v-model="formData.bmi" placeholder="如29.02" readonly />
<span class="unit">kg/</span>
</div>
</el-form-item>
</div>
<el-form-item label="一般情况" prop="general" class="history-item">
<el-input
v-model="formData.general"
type="textarea"
placeholder="请输入一般情况"
<el-input
v-model="formData.general"
type="textarea"
placeholder="请输入一般情况"
autosize
maxlength="300"
show-word-limit
/>
</el-form-item>
<el-form-item label="皮肤粘膜" prop="skin" class="history-item">
<el-input
v-model="formData.skin"
type="textarea"
placeholder="请输入皮肤粘膜情况"
<el-input
v-model="formData.skin"
type="textarea"
placeholder="请输入皮肤粘膜情况"
autosize
maxlength="300"
show-word-limit
/>
</el-form-item>
<el-form-item label="胸部(心、肺)" prop="chest" class="history-item">
<el-input
v-model="formData.chest"
type="textarea"
placeholder="请输入胸部检查结果"
<el-input
v-model="formData.chest"
type="textarea"
placeholder="请输入胸部检查结果"
autosize
maxlength="500"
show-word-limit
/>
</el-form-item>
<el-form-item label="腹部" prop="abdomen" class="history-item">
<el-input
v-model="formData.abdomen"
type="textarea"
placeholder="请输入腹部检查结果"
<el-input
v-model="formData.abdomen"
type="textarea"
placeholder="请输入腹部检查结果"
autosize
maxlength="500"
show-word-limit
/>
</el-form-item>
<el-form-item label="四肢/神经系统" prop="limbsNervous" class="history-item">
<el-input
v-model="formData.limbsNervous"
type="textarea"
placeholder="请输入四肢及神经系统检查结果"
<el-input
v-model="formData.limbsNervous"
type="textarea"
placeholder="请输入四肢及神经系统检查结果"
autosize
maxlength="500"
show-word-limit
@@ -352,10 +327,10 @@
<h4 class="section-title">辅助检查</h4>
<div class="form-section">
<el-form-item label="检查结果" prop="auxExam" class="history-item">
<el-input
v-model="formData.auxExam"
type="textarea"
placeholder="请输入辅助检查结果"
<el-input
v-model="formData.auxExam"
type="textarea"
placeholder="请输入辅助检查结果"
autosize
maxlength="1000"
show-word-limit
@@ -367,21 +342,21 @@
<h4 class="section-title">初步诊断</h4>
<div class="form-section">
<el-form-item label="中医诊断" prop="tcmDiagnosis" class="history-item">
<el-input
v-model="formData.tcmDiagnosis"
type="textarea"
placeholder="如:胸痹心痛(气阴两虚证)"
<el-input
v-model="formData.tcmDiagnosis"
type="textarea"
placeholder="如:胸痹心痛(气阴两虚证)"
autosize
maxlength="500"
show-word-limit
/>
</el-form-item>
<el-form-item label="西医诊断" prop="westernDiagnosis" class="history-item">
<el-input
v-model="formData.westernDiagnosis"
type="textarea"
placeholder="如1.冠状动脉粥样硬化性心脏病"
<el-input
v-model="formData.westernDiagnosis"
type="textarea"
placeholder="如1.冠状动脉粥样硬化性心脏病"
autosize
maxlength="800"
show-word-limit
@@ -391,30 +366,22 @@
<!-- 7. 签名信息三列布局 -->
<h4 class="section-title">签名信息</h4>
<div class="adaptive-grid form-section" style="grid-template-columns: repeat(3, 1fr);">
<div class="adaptive-grid form-section" style="grid-template-columns: repeat(3, 1fr)">
<el-form-item label="医师签名" prop="doctorSign" class="grid-item">
<el-input
v-model="formData.doctorSign"
placeholder="请签名"
clearable
/>
<el-input v-model="formData.doctorSign" placeholder="请签名" clearable />
</el-form-item>
<el-form-item label="上级医师签名" prop="superiorSign" class="grid-item">
<el-input
v-model="formData.superiorSign"
placeholder="请签名"
clearable
/>
<el-input v-model="formData.superiorSign" placeholder="请签名" clearable />
</el-form-item>
<el-form-item label="记录日期" prop="signDate" class="grid-item">
<el-date-picker
v-model="formData.signDate"
type="datetime"
placeholder="选择日期"
<el-date-picker
v-model="formData.signDate"
type="datetime"
placeholder="选择日期"
value-format="YYYY-MM-DD HH:mm"
style="width: 100%;"
style="width: 100%"
/>
</el-form-item>
</div>
@@ -426,16 +393,30 @@
</el-form>
</div>
</div>
<admissionRecord v-if="isShowprintDom" ref="recordPrintRef"></admissionRecord>
</template>
<script setup>
import { ref, reactive, watch, onMounted } from 'vue';
import { ElInput, ElSelect, ElOption, ElDatePicker, ElButton, ElMessage, ElMessageBox, ElForm, ElFormItem } from 'element-plus';
import { previewPrint } from '../utils/printUtils';
import admissionRecord from '../views/hospitalRecord/components/admissionRecord.vue';
import {
ElInput,
ElSelect,
ElOption,
ElDatePicker,
ElButton,
ElMessage,
ElMessageBox,
ElForm,
ElFormItem,
} from 'element-plus';
import useUserStore from '../store/modules/user';
const isShowprintDom = ref(false);
const recordPrintRef = ref();
defineOptions({
name: 'InHospitalRecord',
components: { ElInput, ElSelect, ElOption, ElDatePicker, ElButton, ElForm, ElFormItem }
components: { ElInput, ElSelect, ElOption, ElDatePicker, ElButton, ElForm, ElFormItem },
});
// Props与事件
@@ -461,26 +442,26 @@ const formData = reactive({
gender: patient?.genderEnum_enumText || '',
age: patient?.age || '',
nation: '',
occupation: '',// 职业
marriage: '',// 婚姻状况
birthplace: '',// 出生地
admissionTime: '',// 入院时间
recordTime: '',// 记录时间
historyReporter: '',// 病史陈述者
reliability: '可靠',// 可靠程度
occupation: '', // 职业
marriage: '', // 婚姻状况
birthplace: '', // 出生地
admissionTime: '', // 入院时间
recordTime: '', // 记录时间
historyReporter: '', // 病史陈述者
reliability: '可靠', // 可靠程度
// 病史信息
complaint: '',// 主诉
presentIllness: '',// 现病史
pastIllness: '',// 既往史
personalHistory: '',// 个人史
allergyHistory: '',// 过敏史
pastHistory: '',// 既往史
familyHistory: '',// 家族史
maritalHistory: '',// 婚姻史
menstrualHistory: '',// 月经史
complaint: '', // 主诉
presentIllness: '', // 现病史
pastIllness: '', // 既往史
personalHistory: '', // 个人史
allergyHistory: '', // 过敏史
pastHistory: '', // 既往史
familyHistory: '', // 家族史
maritalHistory: '', // 婚姻史
menstrualHistory: '', // 月经史
// 中医信息
tcmInfo: '',
// 体格检查
temp: '',
pulse: '',
@@ -494,44 +475,38 @@ const formData = reactive({
chest: '',
abdomen: '',
limbsNervous: '',
// 辅助检查
auxExam: '',
// 诊断信息
tcmDiagnosis: '',
westernDiagnosis: '',
// 签名信息
doctorSign: '',
superiorSign: '',
signDate: ''
signDate: '',
});
// 表单校验规则
const rules = reactive({
name: [
{ required: true, message: '请填写姓名', trigger: ['blur', 'submit'] }
],
hospitalNo: [
{ required: true, message: '请填写住院号', trigger: ['blur', 'submit'] }
],
gender: [
{ required: true, message: '请选择性别', trigger: ['change', 'submit'] }
],
name: [{ required: true, message: '请填写姓名', trigger: ['blur', 'submit'] }],
hospitalNo: [{ required: true, message: '请填写住院号', trigger: ['blur', 'submit'] }],
gender: [{ required: true, message: '请选择性别', trigger: ['change', 'submit'] }],
age: [
{ required: true, message: '请填写年龄', trigger: ['blur', 'submit'] },
{ type: 'number', min: 1, max: 120, message: '年龄需在1-120岁之间', trigger: ['blur', 'submit'] }
{
type: 'number',
min: 1,
max: 120,
message: '年龄需在1-120岁之间',
trigger: ['blur', 'submit'],
},
],
admissionTime: [
{ required: true, message: '请选择入院时间', trigger: ['change', 'submit'] }
],
recordTime: [
{ required: true, message: '请选择记录时间', trigger: ['change', 'submit'] }
],
chiefComplaint: [
{ required: true, message: '请填写主诉', trigger: ['blur', 'submit'] }
]
admissionTime: [{ required: true, message: '请选择入院时间', trigger: ['change', 'submit'] }],
recordTime: [{ required: true, message: '请选择记录时间', trigger: ['change', 'submit'] }],
chiefComplaint: [{ required: true, message: '请填写主诉', trigger: ['blur', 'submit'] }],
});
// 生命周期
@@ -548,11 +523,11 @@ onMounted(() => {
}
if (!formData.patientName) {
formData.patientName = patient?.patientName || '';
}
if (!formData.gender) {
}
if (!formData.gender) {
formData.gender = patient?.genderEnum_enumText || '';
}
if (!formData.age) {
}
if (!formData.age) {
formData.age = patient?.age || '';
}
if (!formData.hospitalNo) {
@@ -571,21 +546,22 @@ watch([() => formData.height, () => formData.weight], ([newHeight, newWeight]) =
});
// 入院时间变化处理
watch(() => formData.admissionTime, (val) => {
if (val && !formData.recordTime) {
ElMessageBox.confirm(
'是否将记录时间同步为入院时间?',
'时间同步提示',
{
watch(
() => formData.admissionTime,
(val) => {
if (val && !formData.recordTime) {
ElMessageBox.confirm('是否将记录时间同步为入院时间?', '时间同步提示', {
confirmButtonText: '同步',
cancelButtonText: '手动设置',
type: 'info'
}
).then(() => {
formData.recordTime = val;
}).catch(() => {});
type: 'info',
})
.then(() => {
formData.recordTime = val;
})
.catch(() => {});
}
}
});
);
// 血压格式校验
const validateBloodPressure = () => {
@@ -607,7 +583,7 @@ const submit = () => {
validateBloodPressure();
if (!formData.bp) return; // 格式错误时终止提交
}
emits('submitOk', formData);
ElMessage.success('记录保存成功!');
}
@@ -616,35 +592,33 @@ const submit = () => {
// 新增:重置表单方法(带确认提示)
const handleReset = () => {
ElMessageBox.confirm(
'确定要重置表单吗?所有已填写内容将被清空,且不可恢复',
'重置确认',
{
confirmButtonText: '确认重置',
cancelButtonText: '取消',
type: 'warning',
center: true
}
).then(() => {
// 执行表单重置
formRef.value.resetFields();
// 保留患者基础信息和默认值(避免清空关键基础数据)
formData.patientName = patient?.name || '';
formData.hospitalNo = patient?.busNo || '';
formData.gender = patient?.genderEnum_enumText || '';
formData.age = patient?.age || '';
formData.reliability = '可靠';
// 重置时间为当前时间
const today = new Date();
formData.admissionTime = formatDateTime(today);
formData.recordTime = formatDateTime(today);
formData.signDate = formatDateTime(today);
// 重置成功提示
ElMessage.success('表单已成功重置');
}).catch(() => {
// 取消重置提示
ElMessage.info('已取消表单重置');
});
ElMessageBox.confirm('确定要重置表单吗?所有已填写内容将被清空,且不可恢复', '重置确认', {
confirmButtonText: '确认重置',
cancelButtonText: '取消',
type: 'warning',
center: true,
})
.then(() => {
// 执行表单重置
formRef.value.resetFields();
// 保留患者基础信息和默认值(避免清空关键基础数据)
formData.patientName = patient?.name || '';
formData.hospitalNo = patient?.busNo || '';
formData.gender = patient?.genderEnum_enumText || '';
formData.age = patient?.age || '';
formData.reliability = '可靠';
// 重置时间为当前时间
const today = new Date();
formData.admissionTime = formatDateTime(today);
formData.recordTime = formatDateTime(today);
formData.signDate = formatDateTime(today);
// 重置成功提示
ElMessage.success('表单已成功重置');
})
.catch(() => {
// 取消重置提示
ElMessage.info('已取消表单重置');
});
};
// 表单数据赋值
@@ -664,8 +638,21 @@ const formatDateTime = (date) => {
return `${year}-${month}-${day} ${hour}:${minute}`;
};
// 打印方法
const printFun = () => {
console.log('入院记录打印');
isShowprintDom.value = true;
nextTick(() => {
recordPrintRef?.value.setData(formData);
nextTick(() => {
previewPrint(recordPrintRef?.value.getDom());
isShowprintDom.value = false;
});
});
};
// 暴露接口
defineExpose({ formData, submit, setFormData, handleReset });
defineExpose({ formData, submit, setFormData, handleReset, printFun });
</script>
<style scoped>
@@ -673,8 +660,7 @@ defineExpose({ formData, submit, setFormData, handleReset });
.medical-form {
max-width: 1200px;
width: 100%;
height: 700px;
margin: 15px auto;
height: 28000px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 8px;
@@ -810,7 +796,8 @@ defineExpose({ formData, submit, setFormData, handleReset });
.adaptive-grid {
grid-template-columns: 1fr; /* 小屏幕下单列显示 */
}
.grid-item, .history-item {
.grid-item,
.history-item {
margin-bottom: 10px;
}
/* 小屏幕按钮居中 */
@@ -825,4 +812,4 @@ defineExpose({ formData, submit, setFormData, handleReset });
grid-template-columns: repeat(2, 1fr); /* 中等屏幕下两列显示 */
}
}
</style>
</style>

View File

@@ -231,10 +231,12 @@
<el-button type="warning" @click="handleReset">重置表单</el-button>
</div>
</div>
<intOperRecordSheet v-if="isShowprintDom" ref="recordPrintRef"></intOperRecordSheet>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import intOperRecordSheet from '../views/hospitalRecord/components/intOperRecordSheet.vue';
import {
ElMessage,
ElMessageBox,
@@ -246,6 +248,9 @@ import {
ElDatePicker,
ElButton,
} from 'element-plus';
import { previewPrint } from '../utils/printUtils';
const isShowprintDom = ref(false);
const recordPrintRef = ref();
// 医院名称
const hospitalName = '长春市朝阳区中医院';
defineOptions({
@@ -374,6 +379,9 @@ onMounted(() => {
if (!formData.bedNo) {
formData.bedNo = patient?.houseName + '-' + patient?.bedName;
}
if (!formData.busNo) {
formData.busNo = patient?.busNo || '';
}
});
const emits = defineEmits(['submitOk']);
@@ -393,6 +401,9 @@ const submit = () => {
const setFormData = (data) => {
if (data) {
Object.assign(formData, data);
if (!formData.busNo) {
formData.busNo = patient?.busNo || '';
}
}
};
@@ -439,18 +450,31 @@ const formatDateTime = (date) => {
const minute = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hour}:${minute}`;
};
defineExpose({ submit, setFormData });
const printFun = () => {
console.log('入院记录打印');
isShowprintDom.value = true;
nextTick(() => {
recordPrintRef?.value.setData(formData);
nextTick(() => {
previewPrint(recordPrintRef?.value.getDom());
isShowprintDom.value = false;
});
});
};
defineExpose({ submit, setFormData, printFun });
</script>
<style scoped>
/* 样式与原代码保持一致,无需修改 */
/* 核心容器PC端限制合理最大宽度避免超宽屏内容过散 */
.medical-document {
max-width: 1200px;
max-width: 1440px; /* PC端最优宽度兼顾大屏和常规屏 */
width: 98%; /* 占满父容器98%,保留少量边距 */
margin: 20px auto;
padding: 30px;
background: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
font-family: 'SimSun', '宋体', serif;
box-sizing: border-box; /* 确保内边距不撑大容器 */
}
.doc-header {
@@ -489,11 +513,14 @@ defineExpose({ submit, setFormData });
font-weight: bold;
}
/* 自适应网格PC端优先展示多列优化列宽比例 */
.adaptive-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
/* PC端最小列宽220px保证每列内容不拥挤自动适配列数 */
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 15px 20px;
margin-bottom: 15px;
width: 100%;
}
.grid-item {
@@ -547,13 +574,15 @@ defineExpose({ submit, setFormData });
margin-right: 4px;
}
/* 仅针对小屏设备做基础适配优先保证PC端体验 */
@media (max-width: 768px) {
.medical-document {
max-width: 100%;
padding: 15px;
}
.adaptive-grid {
grid-template-columns: 1fr;
grid-template-columns: 1fr; /* 移动端强制单列 */
}
.doc-title {
@@ -565,6 +594,18 @@ defineExpose({ submit, setFormData });
}
}
/* 超宽屏≥1920px优化适度增大间距提升视觉体验 */
@media (min-width: 1920px) {
.medical-document {
max-width: 1600px;
padding: 40px;
}
.adaptive-grid {
gap: 20px 25px;
}
}
/* 打印样式保留 */
@media print {
.btn-group {
display: none;
@@ -574,6 +615,7 @@ defineExpose({ submit, setFormData });
box-shadow: none;
margin: 0;
padding: 0;
max-width: 100%;
}
.el-input__inner,

View File

@@ -163,6 +163,7 @@ export function getPrinterList() {
}
import useUserStore from '@/store/modules/user';
import { ElMessage } from 'element-plus';
/**
* 获取当前登录用户ID
@@ -315,6 +316,22 @@ export async function selectPrinterAndPrint(
}
}
// 预览打印
export function previewPrint(elementDom) {
if (elementDom) {
//初始化
window.hiprint.init();
const hiprintTemplate = new window.hiprint.PrintTemplate();
// printByHtml为预览打印
hiprintTemplate.printByHtml(elementDom, {});
} else {
ElMessage({
type: 'error',
message: '加载模版失败',
});
}
}
// 默认导出简化的打印方法
export default {
print: simplePrint,

View File

@@ -1,10 +1,10 @@
/**
* 判断url是否是http或https
* 判断url是否是http或https
* @param {string} path
* @returns {Boolean}
*/
export function isHttp(url) {
return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1
export function isHttp(url) {
return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1;
}
/**
@@ -12,8 +12,8 @@
* @param {string} path
* @returns {Boolean}
*/
export function isExternal(path) {
return /^(https?:|mailto:|tel:)/.test(path)
export function isExternal(path) {
return /^(https?:|mailto:|tel:)/.test(path);
}
/**
@@ -21,8 +21,8 @@
* @returns {Boolean}
*/
export function validUsername(str) {
const valid_map = ['admin', 'editor']
return valid_map.indexOf(str.trim()) >= 0
const valid_map = ['admin', 'editor'];
return valid_map.indexOf(str.trim()) >= 0;
}
/**
@@ -30,8 +30,9 @@ export function validUsername(str) {
* @returns {Boolean}
*/
export function validURL(url) {
const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
return reg.test(url)
const reg =
/^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/;
return reg.test(url);
}
/**
@@ -39,8 +40,8 @@ export function validURL(url) {
* @returns {Boolean}
*/
export function validLowerCase(str) {
const reg = /^[a-z]+$/
return reg.test(str)
const reg = /^[a-z]+$/;
return reg.test(str);
}
/**
@@ -48,8 +49,8 @@ export function validLowerCase(str) {
* @returns {Boolean}
*/
export function validUpperCase(str) {
const reg = /^[A-Z]+$/
return reg.test(str)
const reg = /^[A-Z]+$/;
return reg.test(str);
}
/**
@@ -57,8 +58,8 @@ export function validUpperCase(str) {
* @returns {Boolean}
*/
export function validAlphabets(str) {
const reg = /^[A-Za-z]+$/
return reg.test(str)
const reg = /^[A-Za-z]+$/;
return reg.test(str);
}
/**
@@ -66,8 +67,9 @@ export function validAlphabets(str) {
* @returns {Boolean}
*/
export function validEmail(email) {
const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
return reg.test(email)
const reg =
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return reg.test(email);
}
/**
@@ -76,9 +78,9 @@ export function validEmail(email) {
*/
export function isString(str) {
if (typeof str === 'string' || str instanceof String) {
return true
return true;
}
return false
return false;
}
/**
@@ -87,7 +89,39 @@ export function isString(str) {
*/
export function isArray(arg) {
if (typeof Array.isArray === 'undefined') {
return Object.prototype.toString.call(arg) === '[object Array]'
return Object.prototype.toString.call(arg) === '[object Array]';
}
return Array.isArray(arg)
return Array.isArray(arg);
}
// 手机号正则
export function isValidCNPhoneNumber(phone) {
const regex = /^1[3-9]\d{9}$/;
return regex.test(phone);
}
// 身份证号正则
export function isValidCNidCardNumber(idCard) {
const regex = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
return regex.test(idCard);
}
// 根据身份证号获取性别和年龄
export function getGenderAndAge(idCard) {
// 确保身份证号码是18位
if (idCard.length !== 18) {
return { error: '身份证号码必须是18位' };
}
// 提取出生年月日
const birthDate = idCard.substr(6, 8); // YYYYMMDD
const year = birthDate.substr(0, 4);
const month = birthDate.substr(4, 2);
const day = birthDate.substr(6, 2);
const dateOfBirth = new Date(`${year}-${month}-${day}`);
// 计算年龄
const age = new Date().getFullYear() - dateOfBirth.getFullYear();
const m = new Date().getMonth() - dateOfBirth.getMonth();
if (m < 0 || (m === 0 && new Date().getDate() < dateOfBirth.getDate())) {
age--;
}
// 提取性别
const gender = idCard.charAt(16) % 2 === 0 ? 1 : 0;
return { age, gender };
}

View File

@@ -32,7 +32,7 @@
<PopoverList @search="handleSearch" :width="800" :modelValue="scope.row.name">
<template #popover-content="{}">
<DeviceList
v-if="scope.row.type == '2' || props.tab == 2 "
v-if="scope.row.type == '2' || props.tab == 2"
@selectRow="(row) => selectRow(row, scope.$index)"
:searchKey="searchKey"
/>
@@ -171,7 +171,7 @@
</el-form>
</div>
</template>
<script setup>
import { reactive, watch } from 'vue';
import { bind, deleteBind, init } from './api';
@@ -345,6 +345,5 @@ function selectRow(row, index) {
}
}
</script>
<style scoped>
</style>
<style scoped></style>

View File

@@ -79,8 +79,12 @@ import { getActivityList, getBindList, getRegistrationfeeList } from './componen
import ConsumablesList from './components/consumablesList.vue';
const activityList = ref([]);
const queryParams = ref({});
const queryParamsRegistration = ref({});
const queryParams = ref({
statusEnum: 2,
});
const queryParamsRegistration = ref({
activeFlag: 1,
});
const bindList = ref([]);
const bindInfo = ref({});
const activeTab = ref(1);
@@ -91,19 +95,18 @@ const { proxy } = getCurrentInstance();
const { method_code } = proxy.useDict('method_code');
getList();
getRegistrationList()
getRegistrationList();
function getList() {
// queryParams.value.typeEnum = activeTab.value;
getActivityList(queryParams.value).then((res) => {
activityList.value = res.data.records;
});
}
function getRegistrationList() {
getRegistrationfeeList(queryParamsRegistration.value).then(res => {
getRegistrationfeeList(queryParamsRegistration.value).then((res) => {
RegistrationfeeList.value = res.data.records;
})
});
}
// 点击诊疗列表 获取绑定的耗材
@@ -119,5 +122,4 @@ function clickRow(row) {
}
</script>
<style scoped>
</style>
<style scoped></style>

View File

@@ -5,7 +5,7 @@
<span style="vertical-align: middle">病区</span>
</template>
<div style="width: 100%">
<el-button type="primary" @click="open = true" class="mb8"> 新增 </el-button>
<el-button type="primary" @click="onIncrease" class="mb8"> 新增 </el-button>
<el-button type="success" plain @click="handleEnableBatch('wardRef')" class="mb8">
批量启用
</el-button>
@@ -16,7 +16,7 @@
<el-table
max-height="630"
:data="wardList"
@cell-click="(row) => clickRow(row, 10)"
@cell-click="(row) => clickRow(row, 10, 0)"
highlight-current-row
ref="wardRef"
>
@@ -95,7 +95,7 @@
<el-table
height="280"
:data="houseList"
@cell-click="(row) => clickRow(row, 8)"
@cell-click="(row) => clickRow(row, 8, 0, 1)"
highlight-current-row
v-loading="loading"
ref="hourseRef"
@@ -134,7 +134,7 @@
@click.stop="
() => {
handleUnable(scope.row).then(() => {
clickRow(houseRow, 10);
getHouseList();
});
}
"
@@ -153,7 +153,7 @@
@click.stop="
() => {
handleEnable(scope.row).then(() => {
clickRow(houseRow, 10);
getHouseList();
});
}
"
@@ -206,7 +206,7 @@
@click.stop="
() => {
handleUnable(scope.row, 10).then(() => {
clickRow(bedRow, 8);
getBedList();
});
}
"
@@ -225,7 +225,7 @@
@click.stop="
() => {
handleEnable(scope.row, 10).then(() => {
clickRow(bedRow, 8);
getBedList();
});
}
"
@@ -255,7 +255,7 @@
</el-radio-group>
</el-form-item>
<el-form-item :label="type + '名称'" prop="name">
<el-input v-model="form.name" placeholder="请输入科室名称" />
<el-input v-model="form.name" :placeholder="'请输入' + type + '名称'" />
</el-form-item>
<el-col>
<el-form-item :label="upLabel" prop="busNoParent">
@@ -304,8 +304,9 @@
</el-dialog>
</div>
</template>
<script setup name="Ward">
import { onMounted, ref } from 'vue';
import {
getList,
addLocation,
@@ -315,33 +316,65 @@ import {
unableLocation,
enableLocation,
} from './components/api';
import { ElMessage } from 'element-plus';
const { proxy } = getCurrentInstance();
// 病区参数
const queryParams = ref({
pageNum: 1,
pageSize: 50,
formEnum: 4,
formEnum: 4, //4 病区 10 病房 8床位
// locationFormList: [4],
});
// 病房参数
const queryHouseParams = ref({
pageNum: 1,
pageSize: 50,
formEnum: 10, //4 病区 10 病房 8床位
// locationFormList: [4],
});
// 病床参数
const querybedParams = ref({
pageNum: 1,
pageSize: 50,
formEnum: 8, //4 病区 10 病房 8床位
// locationFormList: [4],
});
// 病区、病房、病床类型
const type = ref('病区');
// 病区列表
const wardList = ref([]);
// 床位列表
const bedList = ref([]);
// 病房列表
const houseList = ref([]);
const wardListOption = ref([]);
const organization = ref([]);
const loading = ref(false);
const isEdit = ref(false);
const open = ref(false);
// 病区row
const wardRow = ref({});
// 床位row
const bedRow = ref({});
// 病房row
const houseRow = ref({});
// 记录点击的是启用
const clickType = ref(0);
const orgRef = ref();
// 新增数据参数
const form = reactive({
formEnum: 4,
busNoParent: '',
organizationId: '',
name: '',
busNo: '',
});
const upLabel = ref('关联科室');
const title = ref('新增');
const rules = ref({
name: [
{ required: true, message: '请输入科室名称', trigger: 'blur' },
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' },
{ required: true, message: '请输入名称', trigger: 'blur' },
{ required: true, min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' },
],
busNoParent: [
{
@@ -351,23 +384,76 @@ const rules = ref({
},
],
});
/**
* 病区列表
*/
function getWardList() {
queryParams.value.formEnum = 4;
getList(queryParams.value).then((res) => {
wardList.value = res.data.records;
});
}
// 获取科室下啦树
function init() {
getOrgList().then((res) => {
organization.value = res.data.records;
});
}
/**
* 病区列表
*/
function getWardList() {
houseList.value = [];
bedList.value = [];
getList(queryParams.value).then((res) => {
wardList.value = res.data.records;
});
}
// 获取病房列表
const getHouseList = () => {
// 4病区 10病房 8床位
loading.value = true;
// 病区号
queryHouseParams.value.busNo = wardRow.value.busNo;
bedList.value = [];
getList(queryHouseParams.value).then((res) => {
houseList.value = res.data.records;
loading.value = false;
});
};
// 获取床位列表
const getBedList = () => {
// 4病区 10病房 8床位
loading.value = true;
querybedParams.value.busNo = houseRow.value.busNo;
bedList.value = [];
getList(querybedParams.value).then((res) => {
bedList.value = res.data.records;
loading.value = false;
});
};
// 点击新增按钮
const onIncrease = () => {
open.value = true;
};
// 查询病房和病区的下拉选
const getHomeOrBed = (formEnum) => {
const params = {
formEnum,
pageNum: 1,
pageSize: 50,
};
getList(params).then((res) => {
if (formEnum == 10) {
const datas = res.data?.records || [];
const optionsList = datas.map((item) => {
let obj = {
...item,
};
obj.name = item.parentName + '-' + item.name;
return obj;
});
wardListOption.value = optionsList;
} else {
wardListOption.value = res.data?.records || [];
}
});
};
function handleRadioChange(val) {
let formEnum = 4;
if (val == 4) {
type.value = '病区';
upLabel.value = '关联科室';
@@ -375,38 +461,56 @@ function handleRadioChange(val) {
} else if (val == 10) {
type.value = '病房';
upLabel.value = '所属病区';
queryParams.value.formEnum = 4;
formEnum = 4;
// queryParams.value.formEnum = 4;
} else {
type.value = '床位';
upLabel.value = '所属病房';
queryParams.value.formEnum = 10;
formEnum = 10;
// queryParams.value.formEnum = 10;
}
getList(queryParams.value).then((res) => {
wardListOption.value = res.data.records;
});
form.organizationId = '';
form.busNo = '';
form.busNoParent = '';
form.name = '';
getHomeOrBed(formEnum);
}
/**
* 点击患者列表行 获取处方列表
*/
function clickRow(row, val) {
loading.value = true;
queryParams.value.formEnum = val;
queryParams.value.busNo = row.busNo;
bedList.value = [];
getList(queryParams.value).then((res) => {
if (val == 10) {
houseList.value = res.data.records;
houseRow.value = row;
} else if (val == 8) {
bedRow.value = row;
bedList.value = res.data.records;
}
setTimeout(() => {
queryParams.value.busNo = undefined;
loading.value = false;
}, 100);
});
function clickRow(row, val, type) {
// 1点击了启用
clickType.value = type;
console.log('val=====>', JSON.stringify(row));
console.log('type=====>', JSON.stringify(type));
console.log('val=====>', JSON.stringify(val));
// if (type !== 1) {
// if (val == 10) {
// wardRow.value = row;
// getHouseList();
// } else if (val == 8) {
// houseRow.value = row;
// getBedList();
// }
// } else {
// if (val == 10) {
// houseRow.value = row;
// getHouseList();
// } else if (val == 8) {
// bedRow.value = row;
// getBedList();
// }
// }
if (val == 10) {
wardRow.value = row;
getHouseList();
} else if (val == 8) {
houseRow.value = row;
console.log('houseRow=====>', houseRow.value);
getBedList();
}
}
function checkSelectable(row, index) {
@@ -459,38 +563,113 @@ function handleUnableBatch(tableRef) {
});
}
// 新增病床拆分"busNo": "LOC055.LOC056",
const splitBusNo = (busNo) => {
const busNoArr = busNo.split('.') || [];
let busNoParent = '';
if (busNoArr.length > 1) {
busNoParent = busNoArr[0];
}
return busNoParent;
};
function submitForm() {
if (form.busNoParent) {
if (form.formEnum == 4) {
form.organizationId = form.busNoParent;
} else {
form.busNo = form.busNoParent;
if (!orgRef) return;
const params = {
...form,
};
console.log('form========>', JSON.stringify(form));
console.log('params11========>', JSON.stringify(params));
orgRef.value.validate((valid) => {
if (valid) {
console.log('99999999');
if (form.busNoParent) {
if (form.formEnum == 4) {
params.organizationId = form.busNoParent;
} else {
if (!isEdit.value) {
params.busNo = form.busNoParent;
// params.busNoParent = splitBusNo(form.busNoParent);
}
}
} else {
if (form.formEnum == 4) {
if (!(params.organizationId && params.organizationId.length > 0)) {
ElMessage({
type: 'error',
message: '请选择关联科室!',
});
return;
}
} else if (form.formEnum == 10) {
if (!(params.busNo && params.busNo.length > 0)) {
ElMessage({
type: 'error',
message: '请选择所属病区!',
});
return;
}
} else {
if (!(params.busNo && params.busNo.length > 8)) {
ElMessage({
type: 'error',
message: '请选择所属病房!',
});
return;
}
}
}
// console.log('params========>', JSON.stringify(form));
console.log('params========>', JSON.stringify(params));
// console.log('params========>', isEdit.value);
if (!isEdit.value) {
addLocation(params).then((res) => {
if (res.code == 200) {
proxy.$modal.msgSuccess('操作成功');
if (params.formEnum == 4) {
cancel();
getWardList();
} else if (params.formEnum == 10) {
getHouseList();
open.value = false;
} else if (params.formEnum == 8) {
getBedList();
open.value = false;
}
}
});
} else {
editLocation(params).then((res) => {
if (res.code == 200) {
proxy.$modal.msgSuccess('操作成功');
if (params.formEnum == 4) {
cancel();
getWardList();
} else if (params.formEnum == 10) {
getHouseList();
open.value = false;
} else if (params.formEnum == 8) {
getBedList();
open.value = false;
}
}
});
}
}
}
console.log(form);
if (!isEdit.value) {
addLocation(form).then((res) => {
if (res.code == 200) {
proxy.$modal.msgSuccess('操作成功');
cancel();
getWardList();
}
});
} else {
editLocation(form).then((res) => {
if (res.code == 200) {
proxy.$modal.msgSuccess('操作成功');
cancel();
getWardList();
}
});
}
});
}
function handleDelete(row) {
deleteLocation(row.busNo).then((res) => {
if (res.code == 200) {
proxy.$modal.msgSuccess('删除成功');
if (row.formEnum == 4) {
getWardList();
} else if (row.formEnum == 10) {
getHouseList();
} else {
getBedList();
}
}
});
}
@@ -501,24 +680,27 @@ function getLastPartOfString(str) {
}
function handleEdit(row, val) {
console.log('editRow=========>', JSON.stringify(row));
form.id = row.id;
form.name = row.name;
form.formEnum = row.formEnum;
form.busNo = row.busNo;
if (row.organizationId) {
form.busNoParent = row.organizationId;
form.organizationId = row.organizationId;
} else {
form.busNoParent = row.busNo.split('.').slice(0, -1).join('.');
}
isEdit.value = true;
title.value = '编辑';
if (val) {
queryParams.value.formEnum = val;
getList(queryParams.value).then((res) => {
wardListOption.value = res.data.records;
});
}
open.value = true;
console.log('editRow1=========>', JSON.stringify(form));
if (val == 4) {
getHomeOrBed(4);
} else if (val == 10) {
getHomeOrBed(10);
}
}
function cancel() {
@@ -532,10 +714,13 @@ function cancel() {
isEdit.value = false;
title.value = '新增';
}
init();
getWardList();
// 页面挂在成功
onMounted(() => {
// 获取所有科室
init();
// 获取病区列表
getWardList();
});
</script>
<style scoped>
</style>
<style scoped></style>

View File

@@ -98,6 +98,13 @@
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="6">
<el-form-item label="69码" prop="drug69Code">
<el-input v-model="form.drug69Code" placeholder="" />
</el-form-item>
</el-col>
</el-row>
<div class="title">临床信息</div>
<el-row :gutter="24">
<el-col :span="6">
@@ -610,14 +617,15 @@
<el-row :gutter="24">
<el-col :span="8">
<el-form-item label="DDD值" prop="dddCode">
<el-select v-model="antibioticForm.dddCode" clearable>
<!-- <el-select v-model="antibioticForm.dddCode" clearable>
<el-option
v-for="category in ddd_code"
:key="category.value"
:label="category.label"
:value="category.value"
/>
</el-select>
</el-select> -->
<el-input v-model="antibioticForm.dddCode" placeholder="请输入dddCode"></el-input>
</el-form-item>
</el-col>
<el-col :span="8">

View File

@@ -219,6 +219,13 @@
prop="ybMatchFlag_enumText"
:show-overflow-tooltip="true"
/>
<el-table-column
label="69"
align="center"
key="drug69Code"
prop="drug69Code"
:show-overflow-tooltip="true"
/>
<!-- <el-table-column
label="医保上传"
align="center"
@@ -592,11 +599,10 @@ getMedicationCategoryList();
getList();
// 药品名称按照第一个汉字首字母排序
function getPinyinFirstLetter(row){
function getPinyinFirstLetter(row) {
const firstChar = row.merchandisePyStr.charAt(0);
return firstChar.toUpperCase();
};
}
</script>
<style scoped>
.el-form--inline .el-form-item {

View File

@@ -31,6 +31,11 @@
<template v-for="(item, index) in formData.selfPay" :key="index">
<div v-show="item.payEnum != 220500" class="payment-item">
<span>支付方式</span>
<img
v-if="item.payEnum == 220100 || item.payEnum == 220200"
:src="imgs[item.payEnum == 220100 ? 0 : 1]"
style="width: 20px; height: 20px"
/>
<el-select
v-model="item.payEnum"
placeholder="选择支付方式"
@@ -164,7 +169,9 @@ import { hiprint } from 'vue-plugin-hiprint';
import templateJson from './template.json';
import { pa } from 'element-plus/es/locales.mjs';
import printUtils, { PRINT_TEMPLATE } from '@/utils/printUtils';
import image1 from '../../../../assets/images/weixinzhifu.png';
import image2 from '../../../../assets/images/zhifubaozhifu.png';
const imgs = ref([image1, image2]);
const props = defineProps({
open: {
type: Boolean,

View File

@@ -241,7 +241,7 @@ import {
changeStudentPayTosStudentSelf,
changeStudentSelfToStudentPay,
} from './components/api';
import { invokeYbPlugin } from '@/api/public';
import { invokeYbPlugin5000, invokeYbPlugin5001 } from '@/api/public';
import ChargeDialog from './components/chargeDialog.vue';
import { formatDateStr } from '@/utils';
import useUserStore from '@/store/modules/user';
@@ -446,7 +446,7 @@ async function handleReadCard(value) {
switch (value) {
case '01': // 电子凭证
// readCardLoading.value = true;
await invokeYbPlugin({
await invokeYbPlugin5000({
FunctionId: 3,
url: 'http://10.47.0.67:8089/localcfc/api/hsecfc/localQrCodeQuery',
orgId: 'H22010200672',
@@ -468,7 +468,7 @@ async function handleReadCard(value) {
readCardLoading.value = false;
});
cardInfo = JSON.parse(JSON.stringify(jsonResult));
let message = JSON.parse(cardInfo.message);
let message = JSON.parse(cardInfo.data);
userMessage = {
certType: '02', // 证件类型
certNo: message.data.idNo, // 身份证号
@@ -488,7 +488,7 @@ async function handleReadCard(value) {
case '03': // 社保卡
readCardLoading.value = true;
loadingText.value = '正在读取...';
await invokeYbPlugin(
await invokeYbPlugin5001(
JSON.stringify({
FunctionId: 1,
IP: 'ddjk.jlhs.gov.cn',

View File

@@ -360,6 +360,8 @@ async function printReceipt(param) {
// 收费时间
chargeTime: new Date().toLocaleString(),
//电子收据二维码
pictureUrl: param.pictureUrl || 'https://chinaebill.com/img/xiaochengxu.png',
},
],
};

View File

@@ -71,6 +71,15 @@ export function getHealthcareMetadata(query) {
});
}
// 更新患者手机号
export function updatePatientPhone(data) {
return request({
url: '/patient-manage/information/update-patient-phone',
method: 'post',
data: data,
});
}
// 门诊挂号查询
export function getOutpatientRegistrationCurrent(query) {
return request({

View File

@@ -22,7 +22,7 @@
</el-radio-group>
</el-form-item>
</el-col>
<!-- <el-col :span="8">
<el-col :span="8">
<el-form-item label="活动标识" prop="tempFlag">
<el-radio-group v-model="form.tempFlag" :disabled="isViewMode">
<el-radio v-for="dict in patient_temp_flag" :key="dict.value" :label="dict.value">
@@ -30,15 +30,33 @@
</el-radio>
</el-radio-group>
</el-form-item>
</el-col> -->
</el-col>
<el-col :span="8">
<el-form-item label="联系方式" prop="phone">
<el-input v-model="form.phone" clearable :disabled="isViewMode" />
</el-form-item>
</el-col>
<!-- <el-col :span="8">
<el-form-item label="证件类型" prop="typeCode">
<el-select
v-model="form.typeCode"
placeholder="证件类型"
clearable
:disabled="isViewMode"
@change="typeChange"
>
<el-option
v-for="dict in sys_idtype"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col> -->
<el-col :span="8">
<el-form-item label="证号码" prop="idCard">
<el-input v-model="form.idCard" clearable :disabled="isViewMode" />
<el-form-item label="身份证号码" prop="idCard">
<el-input v-model="form.idCard" clearable :disabled="isViewMode" @blur="onBlur" />
</el-form-item>
</el-col>
<el-col :span="8">
@@ -53,33 +71,16 @@
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="卡类别" prop="typeCode">
<el-select
v-model="form.typeCode"
placeholder="卡类别"
clearable
:disabled="isViewMode"
>
<el-option
v-for="dict in sys_idtype"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="就诊卡号" prop="identifierNo">
<el-input v-model="form.identifierNo" clearable :disabled="isViewMode" />
</el-form-item>
</el-col>
<el-col :span="8">
<!-- <el-col :span="8">
<el-form-item label="国家编码" prop="countryCode">
<el-input v-model="form.countryCode" clearable :disabled="isViewMode" />
</el-form-item>
</el-col>
</el-col> -->
</el-row>
<!-- <el-col :span="6">
<el-form-item label="年龄" prop="age">
@@ -243,7 +244,13 @@
import { watch } from 'vue';
import pcas from 'china-division/dist/pcas-code.json';
import { addPatient, patientlLists, getOutpatientRegistrationList } from './outpatientregistration';
import {
isValidCNPhoneNumber,
isValidCNidCardNumber,
getGenderAndAge,
} from '../../../../utils/validate';
import { fromPairs } from 'lodash';
import { ElMessage } from 'element-plus';
const router = useRouter();
const { proxy } = getCurrentInstance();
@@ -289,13 +296,16 @@ const emits = defineEmits(['submit']); // 声明自定义事件
const data = reactive({
isViewMode: false,
form: {
typeCode: '08',
typeCode: '01',
tempFlag: '1',
// genderEnum: 0,
},
rules: {
name: [{ required: true, message: '姓名不能为空', trigger: 'change' }],
genderEnum: [{ required: true, message: '请选择性别', trigger: 'change' }],
age: [{ required: true, message: '年龄不能为空', trigger: 'change' }],
phone: [{ required: true, message: '联系方式不能为空', trigger: 'change' }],
idCard: [{ required: true, message: '证件号不能为空', trigger: 'change' }],
},
});
@@ -308,30 +318,30 @@ const props = defineProps({
},
});
watch(
() => form.value.idCard,
(newIdCard) => {
if (newIdCard && newIdCard.length === 18) {
const birthYear = parseInt(newIdCard.substring(6, 10));
const birthMonth = parseInt(newIdCard.substring(10, 12));
const birthDay = parseInt(newIdCard.substring(12, 14));
// watch(
// () => form.value.idCard,
// (newIdCard) => {
// if (newIdCard && newIdCard.length === 18) {
// const birthYear = parseInt(newIdCard.substring(6, 10));
// const birthMonth = parseInt(newIdCard.substring(10, 12));
// const birthDay = parseInt(newIdCard.substring(12, 14));
const today = new Date();
const currentYear = today.getFullYear();
const currentMonth = today.getMonth() + 1;
const currentDay = today.getDate();
// const today = new Date();
// const currentYear = today.getFullYear();
// const currentMonth = today.getMonth() + 1;
// const currentDay = today.getDate();
let age = currentYear - birthYear;
// let age = currentYear - birthYear;
// 如果当前月份小于出生月份或者月份相同但当前日期小于出生日期则年龄减1
if (currentMonth < birthMonth || (currentMonth === birthMonth && currentDay < birthDay)) {
age--;
}
// // 如果当前月份小于出生月份或者月份相同但当前日期小于出生日期则年龄减1
// if (currentMonth < birthMonth || (currentMonth === birthMonth && currentDay < birthDay)) {
// age--;
// }
form.value.age = age;
}
}
);
// form.value.age = age;
// }
// }
// );
/** 查询菜单列表 */
function getList() {
patientlLists().then((response) => {
@@ -342,6 +352,7 @@ function getList() {
bloodtypearhList.value = response.data.bloodTypeRH;
familyrelationshiptypeList.value = response.data.familyRelationshipType;
maritalstatusList.value = response.data.maritalStatus;
console.log('administrativegenderList======>', JSON.stringify(administrativegenderList.value));
});
}
@@ -399,7 +410,7 @@ function reset() {
menuName: undefined,
age: undefined,
genderEnum: undefined,
typeCode: '08',
typeCode: '01',
idCard: undefined,
phone: undefined,
prfsEnum: undefined,
@@ -426,14 +437,45 @@ function reset() {
}
/** 提交按钮 */
function submitForm() {
if (!isValidCNPhoneNumber(form.value.phone)) {
ElMessage({
type: 'error',
message: '手机号有误,请重新输入',
});
return;
}
if (form.value.typeCode === '01') {
if (!isValidCNidCardNumber(form.value.idCard)) {
ElMessage({
type: 'error',
message: '身份证号有误,请重新输入',
});
return;
}
}
// 活动标识 2 启用 3 停用
if (form.value.tempFlag == '1') {
form.value.activeFlag = 2;
} else {
form.value.activeFlag = 3;
}
form.value.patientIdInfoList = [
{
typeCode: form.value.typeCode,
},
];
if (form.value.idCard) {
form.value.birthDate =
form.value.idCard.toString().substring(6, 10) +
'-' +
form.value.idCard.toString().substring(10, 12) +
'-' +
form.value.idCard.toString().substring(12, 14);
console.log(form.value.birthDate, 123);
if (form.value.typeCode === '01') {
form.value.birthDate =
form.value.idCard.toString().substring(6, 10) +
'-' +
form.value.idCard.toString().substring(10, 12) +
'-' +
form.value.idCard.toString().substring(12, 14);
console.log(form.value.birthDate, 123);
} else {
form.value.birthDate = undefined;
}
}
proxy.$refs['patientRef'].validate((valid) => {
if (valid) {
@@ -471,6 +513,22 @@ function cancel() {
visible.value = false;
reset();
}
// 身份证号失去焦点获取年龄和性别
const onBlur = () => {
if (form.value.typeCode === '01') {
const info = getGenderAndAge(form.value.idCard || '');
form.value.age = info.age;
form.value.genderEnum = info.gender;
}
};
//切换证件类型
const typeChange = () => {
if (form.value.typeCode === '01') {
const info = getGenderAndAge(form.value.idCard || '');
form.value.age = info.age;
form.value.genderEnum = info.gender;
}
};
defineExpose({
show,
});

View File

@@ -1,6 +1,6 @@
<template>
<el-dialog
title="确认退费"
:title="eventType == '1' ? '确认退费' : '挂号详情'"
v-model="props.open"
width="700px"
append-to-body
@@ -50,6 +50,7 @@
v-model="reason"
placeholder="退费原因"
class="reason-textarea"
:disabled="eventType == '1' ? false : true"
/>
</div>
<!-- 添加退费方式功能暂时注释 -->
@@ -102,7 +103,7 @@
</div>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submit"> </el-button>
<el-button v-if="eventType == 1" type="primary" @click="submit"> </el-button>
<el-button @click="close"> </el-button>
</div>
</template>
@@ -137,6 +138,10 @@ const props = defineProps({
type: [],
default: [],
},
eventType: {
type: String,
default: 1,
},
});
const { proxy } = getCurrentInstance();

View File

@@ -44,13 +44,7 @@
<el-button type="primary" plain @click="handleReadCard('01')" style="width: 65px">
电子凭证
</el-button>
<el-button
type="primary"
plain
@click="handleReadCard('02')"
style="width: 65px"
:disabled="true"
>
<el-button type="primary" plain @click="handleReadCard('02')" style="width: 65px">
身份证
</el-button>
<el-button type="primary" plain @click="handleReadCard('03')" style="width: 65px">
@@ -547,7 +541,7 @@
<span>{{ parseTime(scope.row.registerTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" key="registerTime" prop="registerTime">
<el-table-column label="操作" align="center" key="registerTime" prop="registerTime" do>
<template #default="scope">
<!-- <el-tooltip
:content="
@@ -560,14 +554,19 @@
placement="top"
:disabled="scope.row.statusEnum != 6"
> -->
<el-button
link
type="primary"
@click="handleReturn(scope.row)"
:disabled="scope.row.statusEnum == 6"
>
退号
</el-button>
<div style="display: flex">
<el-button
link
type="primary"
@click="handleReturn(scope.row, 1)"
:disabled="scope.row.statusEnum == 6"
>
退号
</el-button>
<el-button link type="primary" @click="handleReturn(scope.row, 0)">
详情
</el-button>
</div>
<!-- </el-tooltip> -->
</template>
</el-table-column>
@@ -616,6 +615,7 @@
:patientInfo="patientInfo"
:paymentId="paymentId"
:chargeItemIds="chargeItemIdList"
:eventType="eventType"
/>
</div>
</template>
@@ -635,8 +635,9 @@ import {
precharge,
cancelRegister,
gerPreInfo,
updatePatientPhone,
} from './components/outpatientregistration';
import { invokeYbPlugin } from '@/api/public';
import { invokeYbPlugin5000, invokeYbPlugin5001 } from '@/api/public';
import patientInfoDialog from './components/patientInfoDialog';
import PatientAddDialog from './components/patientAddDialog';
import patientList from './components/patientList';
@@ -646,9 +647,11 @@ import RefundDialog from './components/refundDialog.vue';
import { handleColor } from '@/utils/his';
import useUserStore from '@/store/modules/user';
import { formatDate, formatDateStr } from '@/utils/index';
import { isValidCNPhoneNumber } from '../../../utils/validate';
import { ElMessage } from 'element-plus';
const patientInfo = ref({});
const eventType = ref(0);
const router = useRouter();
const { proxy } = getCurrentInstance();
const { sys_normal_disable, sys_user_sex, med_chrgitm_type } = proxy.useDict(
@@ -730,11 +733,10 @@ const data = reactive({
serviceTypeId: [{ required: true, message: '挂号类型不能为空', trigger: 'blur' }],
organizationId: [{ required: true, message: '优先级不能为空', trigger: 'blur' }],
orgId: [{ required: true, message: '就诊科室不能为空', trigger: 'blur' }],
practitionerId: [
{ required: true, message: "医生不能为空", trigger: "blur" },
],
practitionerId: [{ required: true, message: '医生不能为空', trigger: 'blur' }],
typeCode: [{ required: true, message: '账户类型不能为空', trigger: 'blur' }],
definitionId: [{ required: true, message: '费用定价不能为空', trigger: 'blur' }],
phone: [{ required: true, message: '联系电话不能为空', trigger: 'blur' }],
// totalPrice: [{ required: true, message: "总价不能为空", trigger: "blur" }],
},
});
@@ -852,10 +854,10 @@ async function handleReadCard(value) {
// .getInfoByQrCodeAsync(
// )
await invokeYbPlugin({
await invokeYbPlugin5000({
FunctionId: 3,
url: 'http://10.47.0.67:8089/localcfc/api/hsecfc/localQrCodeQuery',
orgId: 'H22010200672',
orgId: 'H22010402403',
businessType: '01101',
operatorId: userStore.id.toString(),
operatorName: userStore.name,
@@ -872,7 +874,7 @@ async function handleReadCard(value) {
readCardLoading.value = false;
});
cardInfo = JSON.parse(JSON.stringify(jsonResult));
let message = JSON.parse(cardInfo.message);
let message = JSON.parse(cardInfo.data);
userMessage = {
certType: '02', // 证件类型
certNo: message.data.idNo, // 身份证号
@@ -892,7 +894,7 @@ async function handleReadCard(value) {
case '03': // 社保卡
readCardLoading.value = true;
loadingText.value = '正在读取...';
await invokeYbPlugin(
await invokeYbPlugin5001(
JSON.stringify({
FunctionId: 1,
IP: 'ddjk.jlhs.gov.cn',
@@ -1143,6 +1145,8 @@ function reset() {
/** 新增按钮操作 */
function handleAdd() {
console.log('isValidCNPhoneNumber=======>', isValidCNPhoneNumber(form.value.phone));
transformedData.value = transformFormData(form.value);
console.log(transformedData, 'transformedData门诊挂号');
chargeItemIdList.value = [];
@@ -1165,6 +1169,13 @@ function handleAdd() {
};
proxy.$refs['outpatientRegistrationRef'].validate((valid) => {
if (valid) {
if (!isValidCNPhoneNumber(patientInfo.value.phone)) {
ElMessage({
type: 'error',
message: '手机号格式不正确,请重新输入!',
});
return;
}
readCardLoading.value = true;
transformedData.value.busiCardInfo = userCardInfo.busiCardInfo;
transformedData.value.certType = userCardInfo.certType;
@@ -1231,13 +1242,14 @@ function handleSearchPatient(value) {
patientSearchKey.value = value;
}
function handleReturn(row) {
function handleReturn(row, type = '1') {
openRefundDialog.value = true;
patientInfo.value.patientId = row.patientId;
patientInfo.value.encounterId = row.encounterId;
totalAmount.value = row.totalPrice;
chargeItemIdList.value = row.chargeItemIds.split(',');
paymentId.value = row.paymentId;
eventType.value = type;
console.log(paymentId.value);
}
@@ -1298,13 +1310,31 @@ function transformFormData(form) {
},
};
}
// 更新患者手机号
async function updatePhone() {
const params = {
id: patientInfo.value.patientId,
phone: patientInfo.value.phone,
};
console.log('params========>', JSON.stringify(params));
try {
await updatePatientPhone(params);
getList();
reset();
} catch (error) {
console.log(error);
}
}
function handleClose(value) {
openDialog.value = false;
if (value == 'success') {
proxy.$modal.msgSuccess('操作成功');
getList();
reset();
// 更新患者手机号
updatePhone();
// getList();
// reset();
// addOutpatientRegistration(transformedData.value).then((response) => {
// reset();
// proxy.$modal.msgSuccess('新增成功');

View File

@@ -34,7 +34,7 @@
"top": 22.5,
"height": 12,
"width": 420,
"title": "长春市朝阳区中医院医院",
"title": "长春市朝阳区中医院",
"coordinateSync": false,
"widthHeightSync": false,
"fontSize": 13.5,

View File

@@ -44,6 +44,7 @@
<el-table-column prop="patientName" label="姓名" align="center" />
<el-table-column prop="genderEnum_enumText" label="性别" align="center" />
<el-table-column prop="age" label="年龄" align="center" />
<el-table-column prop="receptionTime" label="挂号时间" align="center" />
</el-table>
<pagination
v-show="total > 0"
@@ -97,13 +98,7 @@
>
打印处置单
</el-button>
<el-button
type="primary"
plain
@click.stop="getEnPrescription()"
>
处方单
</el-button>
<el-button type="primary" plain @click.stop="getEnPrescription()"> 处方单 </el-button>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<el-form
@@ -290,16 +285,16 @@
</template>
</el-table-column>
<el-table-column align="center" header-align="center" prop="unitPrice" label="单价">
<template #default="scope">
<template #default="{ row }">
<span>
{{ scope.unitPrice ? scope.unitPrice.toFixed(2) : '0.00' + ' 元' }}
{{ row.unitPrice ? row.unitPrice.toFixed(2) : '0.00' + ' 元' }}
</span>
</template>
</el-table-column>
<el-table-column align="center" header-align="center" prop="totalPrice" label="总价">
<template #default="scope">
<template #default="{ row }">
<span>
{{ scope.totalPrice ? scope.totalPrice.toFixed(2) : '0.00' + ' 元' }}
{{ row.totalPrice ? row.totalPrice.toFixed(2) : '0.00' + ' 元' }}
</span>
</template>
</el-table-column>
@@ -309,7 +304,7 @@
</el-col>
</el-row>
<PerformRecordDialog :open="openDialog" :recordList="recordList" @close="openDialog = false" />
<PrescriptionInfo
<PrescriptionInfo
:open="openPrescriptionDialog"
:precriptionInfo="prescriptionInfo"
@close="openPrescriptionDialog = false"
@@ -429,6 +424,7 @@ function handleServiceCategoryChange(value) {
deviceList.value = res.data.records.filter((item) => {
return item.requestTable == 'wor_device_request';
});
activityList.value = res.data.records.filter((item) => {
return (
item.requestTable == 'wor_service_request' ||
@@ -461,6 +457,7 @@ function getAllList(row) {
deviceList.value = res.data.records.filter((item) => {
return item.requestTable == 'wor_device_request';
});
console.log('耗材列表=====>', JSON.stringify(deviceList.value));
activityList.value = res.data.records.filter((item) => {
return (
item.requestTable == 'wor_service_request' || item.requestTable == 'med_medication_request'

View File

@@ -451,6 +451,8 @@ const templateRef = ref(null);
const handleTemplateClick = (data) => {
newEmr();
editForm.value = data;
editForm.value.id = '';
editForm.value.recordTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
nextTick(() => {
emrComponentRef.value?.setFormData(JSON.parse(editForm.value.contextJson));
});

View File

@@ -868,6 +868,8 @@ watch(
watch(
() => prescriptionList.value,
(newValue) => {
// 每次进来重置总合计,存在医嘱会重新计算金额
totalAmount.value = 0;
if (newValue && newValue.length > 0) {
handleTotalAmount();
}

View File

@@ -2,16 +2,16 @@ const formData = reactive({
//医院信息
hospitalInfo: {
//组织机构代码
orgCode: '',
medins_orgcode: '',
//医疗付款方式
medicalPaymentCode: '',
medfee_paymtd_code: '',
},
//患者信息
patientInfo: {
// 健康卡号
healthCardNo: '',
// 患者姓名
patientName: '',
patient_name: '',
// 患者性别
gend: '',
// 出生日期
@@ -19,11 +19,11 @@ const formData = reactive({
// 年龄
age: '',
// 国籍
nationality: '中国',
ntly: '中国',
// 籍贯
napl: '',
// 民族
naty: '',
naty: '1',
// 身份证号
certno: '',
// 户口住址
@@ -50,9 +50,9 @@ const formData = reactive({
// 入院途径
adm_way_code: '',
// 入院时间
admitTime: '',
adm_time: '',
// 入院科室
adm_caty: '',
adm_dept_name: '',
// 病房
adm_ward: '',
// 确诊日期
@@ -64,7 +64,7 @@ const formData = reactive({
// 病房
dscg_ward: '',
// 实际住院天数
ipt_days: '',
act_ipt_days: '',
},
// 诊断信息
diagnosis: {
@@ -73,6 +73,8 @@ const formData = reactive({
// 其他诊断
otherDiagnosis: '',
},
// 诊断信息
diagnosisList: [],
// 医疗信息
medicalInfo: {
// 是否输血
@@ -116,7 +118,7 @@ const formData = reactive({
// 31天是否计划出院
dscg_31days_rinp_flag: '',
// 目的
purpose: '',
dscg_31days_rinp_pup: '',
//昏迷时间---入院前
brn_damg_bfadm_coma_dura: '',
//昏迷时间---入院后
@@ -132,7 +134,7 @@ const formData = reactive({
// 判断依据
judgmentBase: '',
// 分化程度
degreeDifferentiation: '',
bkup_deg_code: '',
// 临床路径
enterPath: '',
// 变异
@@ -148,7 +150,7 @@ const formData = reactive({
// 3级护理
nursingLevel_3: '',
// 呼吸机使用
ventilatorUse: '',
use_vent_flag: '',
// 有创呼吸机使用小时
vent_used_dura: '',
// 手术表

View File

@@ -98,7 +98,7 @@
' / ' +
patientInfo.genderEnum_enumText +
' / ' +
patientInfo.contractName +
(patientInfo?.contractName ? patientInfo.contractName : '') +
'/' +
patientInfo.phone +
'/' +

View File

@@ -35,7 +35,7 @@
/>
</el-select>
</el-form-item>
<el-form-item label="就诊日期">
<el-form-item label="入院日期">
<el-date-picker
v-model="dateRange"
type="daterange"
@@ -60,14 +60,15 @@
highlight-current-row
@row-click="handleCurrentChange"
>
<el-table-column prop="encounterNo" label="住院号" width="150" align="center" />
<el-table-column prop="patientName" label="姓名" width="130" align="center" />
<el-table-column prop="genderEnum_enumText" label="性别" width="80" align="center" />
<el-table-column prop="age" label="年龄" width="80" align="center" />
<el-table-column prop="receptionTime" label="就诊日期" align="center">
<!-- <el-table-column prop="receptionTime" label="就诊日期" align="center">
<template #default="scope">
{{ scope.row.receptionTime ? formatDate(scope.row.receptionTime) : '-' }}
</template>
</el-table-column>
</el-table-column> -->
</el-table>
<pagination
v-show="total > 0"
@@ -392,8 +393,8 @@ function submit(value) {
}
function getList() {
queryParams.value.receptionTimeSTime = dateRange.value[0] + ' 00:00:00';
queryParams.value.receptionTimeETime = dateRange.value[1] + ' 23:59:59';
queryParams.value.startTimeSTime = dateRange.value[0] + ' 00:00:00';
queryParams.value.startTimeETime = dateRange.value[1] + ' 23:59:59';
if (projectTypeCode.value == 2) {
listPatient(queryParams.value).then((response) => {
patientList.value = response.data.records;
@@ -504,11 +505,11 @@ function resetQuery() {
/** 搜索按钮操作 */
function handleQuery() {
if (dateRange.value) {
queryParams.value.receptionTimeSTime = dateRange.value[0] + ' 00:00:00';
queryParams.value.receptionTimeETime = dateRange.value[1] + ' 23:59:59';
queryParams.value.startTimeSTime = dateRange.value[0] + ' 00:00:00';
queryParams.value.startTimeETime = dateRange.value[1] + ' 23:59:59';
} else {
queryParams.value.receptionTimeSTime = null;
queryParams.value.receptionTimeETime = null;
queryParams.value.startTimeSTime = null;
queryParams.value.startTimeETime = null;
}
queryParams.value.pageNo = 1;
getList();

View File

@@ -15,7 +15,7 @@
<el-select
placeholder="发放状态"
style="width: 250px; margin-left: 10px"
v-model="queryParams.therapyEnum"
v-model="queryParams.statusEnum"
@change="getSummaryList"
>
<el-option
@@ -135,10 +135,16 @@
<script setup>
import { ref } from 'vue';
import { totalSendDrug, getFromSummaryList, getFromSummaryDetails } from './api.js';
import {
totalSendDrug,
getFromSummaryList,
getFromSummaryDetails,
getFromSummaryInit,
} from './api.js';
import { getCurrentInstance } from 'vue';
const { proxy } = getCurrentInstance();
const statusEnumOptions = ref([]);
const summaryList = ref([]);
const queryParams = ref({
applyTime: [
@@ -206,6 +212,15 @@ function handleSend(row) {
});
}
// 获取发药状态
const getStatusOption = async () => {
try {
const res = await getFromSummaryInit();
statusEnumOptions.value = res.data.dispenseStatusOptions;
} catch (error) {}
};
getStatusOption();
// 定义暴露给父组件的数据和方法
defineExpose({
selectedRows,

View File

@@ -217,8 +217,8 @@ const ypName = ref('');
const { proxy } = getCurrentInstance();
getEncounterList();
function getEncounterList() {
queryParams.value.receptionTimeSTime = dateRange.value[0] + ' 00:00:00';
queryParams.value.receptionTimeETime = dateRange.value[1] + ' 23:59:59';
queryParams.value.startTimeSTime = dateRange.value[0] + ' 00:00:00';
queryParams.value.startTimeETime = dateRange.value[1] + ' 23:59:59';
getList(queryParams.value).then((res) => {
encounterList.value = res.data.records;
});
@@ -478,4 +478,4 @@ function handelSpanMethod({ row, column, rowIndex, columnIndex }) {
:deep(.no-hover-table) .el-table__body tr:hover > td {
background: inherit !important;
}
</style>
</style>

View File

@@ -18,6 +18,15 @@ export function getFromSummaryList(queryParams) {
});
}
// 发药汇总发放状态
export function getFromSummaryInit(queryParams) {
return request({
url: '/nurse-station/medicine-summary/summary-init',
method: 'get',
params: queryParams,
});
}
// 汇总发药单详情
export function getFromSummaryDetails(queryParams) {
return request({
@@ -111,7 +120,7 @@ export function adviceCancel(data) {
/**
* 明细发药
*
*
*/
export function totalSendDrug(data) {
return request({
@@ -131,69 +140,67 @@ export function totalReturnDrug(data) {
});
}
// -----------------------------------门诊发药接口------------------------------------------------------------
export function listPatient(query) {
return request({
url: '/pharmacy-manage/western-medicine-dispense/encounter-list',
method: 'get',
params: query
})
params: query,
});
}
export function devicePatientList(query) {
return request({
url: '/pharmacy-manage/device-dispense/encounter-list',
method: 'get',
params: query
})
params: query,
});
}
export function listInit(query) {
return request({
url: '/pharmacy-manage/western-medicine-dispense/init',
method: 'get',
params: query
})
params: query,
});
}
export function listWesternmedicine(query) {
return request({
url: '/pharmacy-manage/western-medicine-dispense/medicine-order',
method: 'get',
params: query
})
params: query,
});
}
export function updateMedicion(prescriptionList) {
return request({
url: '/pharmacy-manage/western-medicine-dispense/medicine-dispense',
method: 'put',
data: prescriptionList
})
}
return request({
url: '/pharmacy-manage/western-medicine-dispense/medicine-dispense',
method: 'put',
data: prescriptionList,
});
}
export function prepareMedicion(data) {
return request({
url: '/pharmacy-manage/western-medicine-dispense/prepare',
method: 'put',
data: data
})
}
return request({
url: '/pharmacy-manage/western-medicine-dispense/prepare',
method: 'put',
data: data,
});
}
export function backMedicion(data) {
return request({
url: '/pharmacy-manage/western-medicine-dispense/medicine-cancel',
method: 'put',
data: data
})
data: data,
});
}
//扫码枪返回追溯码筛选
export function itemTraceNo(params) {
return request({
url: '/app-common/item-trace-no?traceNoList=' + params,
method: 'get',
})
});
}
/**
@@ -203,8 +210,8 @@ export function getReportRegisterInit(query) {
return request({
url: '/pharmacy-manage/device-dispense/device-order',
method: 'get',
params: query
})
params: query,
});
}
/**
@@ -214,8 +221,8 @@ export function deviceDispense(params) {
return request({
url: '/pharmacy-manage/device-dispense/device-dispense',
method: 'put',
data: params
})
data: params,
});
}
/**
@@ -225,8 +232,8 @@ export function deviceInvalid(data) {
return request({
url: '/pharmacy-manage/device-dispense/device-cancel',
method: 'put',
data: data
})
data: data,
});
}
/**
@@ -236,8 +243,8 @@ export function medicineMatch(data) {
return request({
url: '/pharmacy-manage/western-medicine-dispense/medicine-match',
method: 'get',
params: data
})
params: data,
});
}
// ----------------------------------------------门诊退药接口----------------------------------------------------------------------
@@ -248,8 +255,8 @@ export function getList(queryParams) {
return request({
url: '/pharmacy-manage/return-medicine/return-patient-page',
method: 'get',
params: queryParams
})
params: queryParams,
});
}
/**
@@ -259,8 +266,8 @@ export function getReturnDrugList(params) {
return request({
url: '/pharmacy-manage/return-medicine/medicine-return-list',
method: 'get',
params: params
})
params: params,
});
}
/**
@@ -270,8 +277,8 @@ export function returnDrug(data) {
return request({
url: '/pharmacy-manage/return-medicine/medicine-return',
method: 'put',
data: data
})
data: data,
});
}
/**
@@ -281,7 +288,7 @@ export function init() {
return request({
url: '/pharmacy-manage/return-medicine/init',
method: 'get',
})
});
}
// //扫码枪返回追溯码筛选
// export function itemTraceNo(params) {

View File

@@ -0,0 +1,725 @@
<template>
<div style="padding: 20px; max-width: 1200px; margin: 0 auto" ref="bodyRef">
<!-- 标题区域 - 居中加粗增加层次感 -->
<div style="text-align: center; margin-bottom: 30px">
<div style="font-size: 22px; color: #333; letter-spacing: 1px">长春市朝阳区中医院</div>
<div
style="
font-size: 32px;
font-weight: 700;
color: #222;
padding: 12px 0;
border-bottom: 2px solid #333;
display: inline-block;
"
>
入院记录
</div>
<div style="margin-top: 8px; font-size: 14px; color: #666"> 1 1 </div>
</div>
<!-- 基本信息模块 - 卡片式布局优化间距和对齐 -->
<div
style="
border: 1px solid #ddd;
border-radius: 8px;
padding: 25px;
margin-bottom: 25px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.03);
"
>
<div
style="
font-size: 18px;
font-weight: 600;
color: #333;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
"
>
基本信息
</div>
<div
style="
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 18px;
"
>
<!-- 第一列 -->
<div>
<div style="display: flex; align-items: flex-start; margin-bottom: 16px">
<span style="font-weight: 500; color: #333; min-width: 80px; font-size: 15px"
>姓名</span
>
<span style="font-size: 14px; color: #666; line-height: 1.5">{{
formData.patientName
}}</span>
</div>
<div style="display: flex; align-items: flex-start; margin-bottom: 16px">
<span style="font-weight: 500; color: #333; min-width: 80px; font-size: 15px"
>性别</span
>
<span style="font-size: 14px; color: #666; line-height: 1.5">{{
formData.gender
}}</span>
</div>
<div style="display: flex; align-items: flex-start; margin-bottom: 16px">
<span style="font-weight: 500; color: #333; min-width: 80px; font-size: 15px"
>民族</span
>
<span style="font-size: 14px; color: #666; line-height: 1.5">{{
formData.nation || '汉族'
}}</span>
</div>
<div style="display: flex; align-items: flex-start; margin-bottom: 16px">
<span style="font-weight: 500; color: #333; min-width: 80px; font-size: 15px"
>婚姻状况</span
>
<span style="font-size: 14px; color: #666; line-height: 1.5">{{
formData.marriage || ''
}}</span>
</div>
<div style="display: flex; align-items: flex-start; margin-bottom: 16px">
<span style="font-weight: 500; color: #333; min-width: 80px; font-size: 15px"
>入院时间</span
>
<span style="font-size: 14px; color: #666; line-height: 1.5">{{
formData.admissionTime || ''
}}</span>
</div>
<div style="display: flex; align-items: flex-start">
<span style="font-weight: 500; color: #333; min-width: 80px; font-size: 15px"
>病史陈述</span
>
<span style="font-size: 14px; color: #666; line-height: 1.5">{{
formData.historyReporter || ''
}}</span>
</div>
</div>
<!-- 第二列 -->
<div>
<div style="display: flex; align-items: flex-start; margin-bottom: 16px">
<span style="font-weight: 500; color: #333; min-width: 80px; font-size: 15px"
>住院号</span
>
<span style="font-size: 14px; color: #666; line-height: 1.5">{{
formData.hospitalNo || '123456'
}}</span>
</div>
<div style="display: flex; align-items: flex-start; margin-bottom: 16px">
<span style="font-weight: 500; color: #333; min-width: 80px; font-size: 15px"
>年龄</span
>
<span style="font-size: 14px; color: #666; line-height: 1.5">{{
formData.age || '16 岁'
}}</span>
</div>
<div style="display: flex; align-items: flex-start; margin-bottom: 16px">
<span style="font-weight: 500; color: #333; min-width: 80px; font-size: 15px"
>职业</span
>
<span style="font-size: 14px; color: #666; line-height: 1.5">{{
formData.occupation || '中医'
}}</span>
</div>
<div style="display: flex; align-items: flex-start; margin-bottom: 16px">
<span style="font-weight: 500; color: #333; min-width: 80px; font-size: 15px"
>出生地</span
>
<span style="font-size: 14px; color: #666; line-height: 1.5">{{
formData.birthplace || '阿拉善'
}}</span>
</div>
<div style="display: flex; align-items: flex-start; margin-bottom: 16px">
<span style="font-weight: 500; color: #333; min-width: 80px; font-size: 15px"
>记录时间</span
>
<span style="font-size: 14px; color: #666; line-height: 1.5">{{
formData.recordTime || '2025-12-18 14:04:00'
}}</span>
</div>
<div style="display: flex; align-items: flex-start">
<span style="font-weight: 500; color: #333; min-width: 80px; font-size: 15px"
>可靠程度</span
>
<span style="font-size: 14px; color: #666; line-height: 1.5">{{
formData.reliability
}}</span>
</div>
</div>
</div>
</div>
<!-- 病史信息模块 - 优化布局内容可换行 -->
<div
style="
border: 1px solid #ddd;
border-radius: 8px;
padding: 25px;
margin-bottom: 25px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.03);
"
>
<div
style="
font-size: 18px;
font-weight: 600;
color: #333;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
"
>
病史信息
</div>
<div
style="
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 18px;
"
>
<!-- 第一列 -->
<div>
<div style="display: flex; align-items: flex-start; margin-bottom: 16px">
<span style="font-weight: 500; color: #333; min-width: 80px; font-size: 15px"
>家族史</span
>
<span style="font-size: 14px; color: #666; line-height: 1.6">{{
formData.familyHistory || ''
}}</span>
</div>
<div style="display: flex; align-items: flex-start; margin-bottom: 16px">
<span style="font-weight: 500; color: #333; min-width: 80px; font-size: 15px"
>现病史</span
>
<span style="font-size: 14px; color: #666; line-height: 1.6">{{
formData.presentIllness || ''
}}</span>
</div>
<div style="display: flex; align-items: flex-start; margin-bottom: 16px">
<span style="font-weight: 500; color: #333; min-width: 80px; font-size: 15px"
>既往史</span
>
<span style="font-size: 14px; color: #666; line-height: 1.6">{{
formData.pastIllness || formData.pastHistory || ''
}}</span>
</div>
<div style="display: flex; align-items: flex-start">
<span style="font-weight: 500; color: #333; min-width: 80px; font-size: 15px"
>主诉</span
>
<span style="font-size: 14px; color: #666; line-height: 1.6">{{
formData.complaint || ''
}}</span>
</div>
</div>
<!-- 第二列 -->
<div>
<div style="display: flex; align-items: flex-start; margin-bottom: 16px">
<span style="font-weight: 500; color: #333; min-width: 80px; font-size: 15px"
>婚育史</span
>
<span style="font-size: 14px; color: #666; line-height: 1.6">{{
formData.maritalHistory || ''
}}</span>
</div>
<div style="display: flex; align-items: flex-start; margin-bottom: 16px">
<span style="font-weight: 500; color: #333; min-width: 80px; font-size: 15px"
>月经史</span
>
<span style="font-size: 14px; color: #666; line-height: 1.6">{{
formData.menstrualHistory || ''
}}</span>
</div>
<div style="display: flex; align-items: flex-start">
<span style="font-weight: 500; color: #333; min-width: 80px; font-size: 15px"
>个人史</span
>
<span style="font-size: 14px; color: #666; line-height: 1.6">{{
formData.personalHistory || ''
}}</span>
</div>
</div>
</div>
</div>
<!-- 中医望闻问切模块 -->
<div
style="
border: 1px solid #ddd;
border-radius: 8px;
padding: 25px;
margin-bottom: 25px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.03);
"
>
<div
style="
font-size: 18px;
font-weight: 600;
color: #333;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
"
>
中医望闻问切
</div>
<div style="display: flex; align-items: flex-start">
<span style="font-weight: 500; color: #333; min-width: 120px; font-size: 15px"
>详细记录</span
>
<span style="font-size: 14px; color: #666; line-height: 1.8">{{
formData.tcmInfo || ''
}}</span>
</div>
</div>
<!-- 体格检查模块 - 优化两列布局信息对齐 -->
<div
style="
border: 1px solid #ddd;
border-radius: 8px;
padding: 25px;
margin-bottom: 25px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.03);
"
>
<div
style="
font-size: 18px;
font-weight: 600;
color: #333;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
"
>
体格检查
</div>
<div
style="
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 25px;
"
>
<!-- 第一列生命体征 -->
<div>
<div
style="
font-size: 16px;
font-weight: 500;
color: #333;
margin-bottom: 15px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
"
>
生命体征
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px">
<div style="padding: 10px; background: #f8f8f8; border-radius: 4px; text-align: center">
<div style="font-size: 13px; color: #666; margin-bottom: 4px">体温</div>
<div style="font-size: 14px; color: #333; font-weight: 500">
{{ formData.temp || '' }}
</div>
</div>
<div style="padding: 10px; background: #f8f8f8; border-radius: 4px; text-align: center">
<div style="font-size: 13px; color: #666; margin-bottom: 4px">脉搏</div>
<div style="font-size: 14px; color: #333; font-weight: 500">
{{ formData.pulse || '' }}
</div>
</div>
<div style="padding: 10px; background: #f8f8f8; border-radius: 4px; text-align: center">
<div style="font-size: 13px; color: #666; margin-bottom: 4px">呼吸</div>
<div style="font-size: 14px; color: #333; font-weight: 500">
{{ formData.respiration || '' }}
</div>
</div>
<div style="padding: 10px; background: #f8f8f8; border-radius: 4px; text-align: center">
<div style="font-size: 13px; color: #666; margin-bottom: 4px">血压</div>
<div style="font-size: 14px; color: #333; font-weight: 500">
{{ formData.bp || '' }}
</div>
</div>
<div style="padding: 10px; background: #f8f8f8; border-radius: 4px; text-align: center">
<div style="font-size: 13px; color: #666; margin-bottom: 4px">身高</div>
<div style="font-size: 14px; color: #333; font-weight: 500">
{{ formData.height || '' }}
</div>
</div>
<div style="padding: 10px; background: #f8f8f8; border-radius: 4px; text-align: center">
<div style="font-size: 13px; color: #666; margin-bottom: 4px">体重</div>
<div style="font-size: 14px; color: #333; font-weight: 500">
{{ formData.weight || '' }}
</div>
</div>
<div
style="
padding: 10px;
background: #f8f8f8;
border-radius: 4px;
text-align: center;
grid-column: 1/3;
"
>
<div style="font-size: 13px; color: #666; margin-bottom: 4px">BMI</div>
<div style="font-size: 14px; color: #333; font-weight: 500">
{{ formData.bmi || '' }}
</div>
</div>
</div>
</div>
<!-- 第二列检查详情 -->
<div>
<div
style="
font-size: 16px;
font-weight: 500;
color: #333;
margin-bottom: 15px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
"
>
检查详情
</div>
<div style="display: flex; align-items: flex-start; margin-bottom: 14px">
<span style="font-weight: 500; color: #333; min-width: 100px; font-size: 14px"
>皮肤粘膜</span
>
<span style="font-size: 13px; color: #666; line-height: 1.5">{{
formData.skin || ''
}}</span>
</div>
<div style="display: flex; align-items: flex-start; margin-bottom: 14px">
<span style="font-weight: 500; color: #333; min-width: 100px; font-size: 14px"
>胸部</span
>
<span style="font-size: 13px; color: #666; line-height: 1.5">{{
formData.chest || ''
}}</span>
</div>
<div style="display: flex; align-items: flex-start; margin-bottom: 14px">
<span style="font-weight: 500; color: #333; min-width: 100px; font-size: 14px"
>腹部</span
>
<span style="font-size: 13px; color: #666; line-height: 1.5">{{
formData.abdomen || ''
}}</span>
</div>
<div style="display: flex; align-items: flex-start; margin-bottom: 14px">
<span style="font-weight: 500; color: #333; min-width: 100px; font-size: 14px"
>一般情况</span
>
<span style="font-size: 13px; color: #666; line-height: 1.5">{{
formData.general || ''
}}</span>
</div>
<div style="display: flex; align-items: flex-start">
<span style="font-weight: 500; color: #333; min-width: 100px; font-size: 14px"
>四肢/神经</span
>
<span style="font-size: 13px; color: #666; line-height: 1.5">{{
formData.limbsNervous || ''
}}</span>
</div>
</div>
</div>
</div>
<!-- 辅助检查模块 - 合并重复模块优化显示 -->
<div
style="
border: 1px solid #ddd;
border-radius: 8px;
padding: 25px;
margin-bottom: 25px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.03);
"
>
<div
style="
font-size: 18px;
font-weight: 600;
color: #333;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
"
>
辅助检查
</div>
<div
style="
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 20px;
"
>
<div style="padding: 15px; background: #f8f8f8; border-radius: 6px">
<div style="font-weight: 500; color: #333; font-size: 15px; margin-bottom: 8px">
检查结果
</div>
<div style="font-size: 14px; color: #666; line-height: 1.6">
{{ formData.auxExam || '' }}
</div>
</div>
</div>
</div>
<!-- 诊断信息模块 - 修正分类优化布局 -->
<div
style="
border: 1px solid #ddd;
border-radius: 8px;
padding: 25px;
margin-bottom: 25px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.03);
"
>
<div
style="
font-size: 18px;
font-weight: 600;
color: #333;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
"
>
诊断信息
</div>
<div
style="
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 20px;
"
>
<div style="padding: 18px; background: #f8f8f8; border-radius: 6px">
<div style="font-weight: 500; color: #333; font-size: 15px; margin-bottom: 10px">
中医诊断
</div>
<div style="font-size: 14px; color: #666; line-height: 1.8">
{{ formData.tcmDiagnosis || '' }}
</div>
</div>
<div style="padding: 18px; background: #f8f8f8; border-radius: 6px">
<div style="font-weight: 500; color: #333; font-size: 15px; margin-bottom: 10px">
西医诊断
</div>
<div style="font-size: 14px; color: #666; line-height: 1.8">
{{ formData.westernDiagnosis || '' }}
</div>
</div>
</div>
</div>
<!-- 签名信息模块 -->
<div
style="
border: 1px solid #ddd;
border-radius: 8px;
padding: 25px;
margin-bottom: 30px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.03);
"
>
<div
style="
font-size: 18px;
font-weight: 600;
color: #333;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
"
>
签名信息
</div>
<div
style="
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 30px;
max-width: 800px;
margin: 0 auto;
"
>
<div style="text-align: center">
<div
style="
font-weight: 500;
color: #333;
font-size: 15px;
margin-bottom: 40px;
border-bottom: 1px solid #333;
padding-bottom: 2px;
display: inline-block;
"
>
医师签名
</div>
<div style="font-size: 14px; color: #666">{{ formData.doctorSign || '' }}</div>
</div>
<div style="text-align: center">
<div
style="
font-weight: 500;
color: #333;
font-size: 15px;
margin-bottom: 40px;
border-bottom: 1px solid #333;
padding-bottom: 2px;
display: inline-block;
"
>
上级医师
</div>
<div style="font-size: 14px; color: #666">{{ formData.superiorSign || '' }}</div>
</div>
<div style="text-align: center">
<div
style="
font-weight: 500;
color: #333;
font-size: 15px;
margin-bottom: 40px;
border-bottom: 1px solid #333;
padding-bottom: 2px;
display: inline-block;
"
>
记录日期
</div>
<div style="font-size: 14px; color: #666">{{ formData.signDate || '' }}</div>
</div>
</div>
</div>
<!-- 底部备注 -->
<div
style="
text-align: center;
font-size: 13px;
color: #888;
margin-bottom: 20px;
padding-top: 10px;
border-top: 1px solid #eee;
"
>
本记录由长春市朝阳区中医院医师根据患者病情如实记录仅供临床诊疗参考 |
地址长春市朝阳区XX街XX号 | 联系电话0431-XXXXXXX
</div>
<!-- 打印按钮 - 优化样式居中显示 -->
<!-- <div style="text-align: center; margin-bottom: 30px">
<el-button type="primary" @click="onPrint" style="padding: 10px 30px; font-size: 15px"
>打印预览</el-button
>
</div> -->
</div>
</template>
<script setup>
import { ref, reactive } from 'vue';
import { previewPrint } from '../../../utils/printUtils';
const bodyRef = ref();
// 响应式表单数据
const formData = reactive({
// 基础信息
patientName: '', // 原patient?.name
hospitalNo: '', // 原patient?.busNo
gender: '', // 原patient?.genderEnum_enumText
age: '',
nation: '',
occupation: '', // 职业
marriage: '', // 婚姻状况
birthplace: '', // 出生地
admissionTime: '', // 入院时间
recordTime: '', // 记录时间
historyReporter: '', // 病史陈述者
reliability: '可靠', // 可靠程度
// 病史信息
complaint: '', // 主诉
presentIllness: '', // 现病史
pastIllness: '', // 既往史
personalHistory: '', // 个人史
allergyHistory: '', // 过敏史
pastHistory: '', // 既往史(重复字段,保留兼容)
familyHistory: '', // 家族史
maritalHistory: '', // 婚姻史
menstrualHistory: '', // 月经史
// 中医信息
tcmInfo: '',
// 体格检查
temp: '',
pulse: '',
respiration: '',
bp: '',
height: '',
weight: '',
bmi: '',
general: '',
skin: '',
chest: '',
abdomen: '',
limbsNervous: '',
// 辅助检查
auxExam: '',
// 诊断信息
tcmDiagnosis: '',
westernDiagnosis: '',
// 签名信息
doctorSign: '',
superiorSign: '',
signDate: '',
});
// 打印方法
const onPrint = () => {
// previewPrint(bodyRef.value);
};
const getDom = () => {
return bodyRef.value;
};
const setData = (data) => {
console.log('设置数据=========>', JSON.stringify(data));
Object.assign(formData, data);
};
defineExpose({
setData,
getDom,
});
</script>
<style scoped>
/* 打印样式优化,隐藏不必要元素 */
@media print {
.el-button {
display: none !important;
}
body {
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
}
div[style*='box-shadow'] {
box-shadow: none !important;
}
}
</style>

View File

@@ -0,0 +1,543 @@
<template>
<div
ref="bodyRef"
style="
padding: 30px 20px;
max-width: 1200px;
margin: 0 auto;
min-height: 100vh;
background: #f8f9fa;
font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif;
"
>
<!-- 标题区域 - 强化正式感与层次感 -->
<div style="text-align: center; margin-bottom: 35px; padding: 20px 0">
<div
style="
font-size: 24px;
color: #2c3e50;
letter-spacing: 2px;
font-weight: 500;
margin-bottom: 8px;
"
>
长春市朝阳区中医院
</div>
<div
style="
font-size: 32px;
font-weight: 700;
color: #1a2b48;
padding: 12px 0;
border-bottom: 2px solid #2c3e50;
display: inline-block;
letter-spacing: 1px;
"
>
出院诊断病历
</div>
</div>
<!-- 基础信息卡片 -->
<div
style="
border: 1px solid #e5e8eb;
border-radius: 12px;
padding: 25px;
margin-bottom: 25px;
background: #ffffff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
"
>
<div
style="
font-size: 18px;
font-weight: 600;
color: #2c3e50;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #f0f2f5;
position: relative;
"
>
基础信息
</div>
<div
style="
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 18px;
"
>
<div
style="
display: flex;
align-items: flex-start;
margin-bottom: 16px;
line-height: 1.6;
word-wrap: break-word;
"
>
<span
style="
font-weight: 500;
color: #34495e;
min-width: 80px;
font-size: 15px;
flex-shrink: 0;
word-wrap: break-word;
"
>姓名</span
>
<span
style="
font-size: 14px;
color: #606266;
flex: 1;
word-wrap: break-word;
word-break: break-all;
white-space: normal;
"
>{{ formData.patientName }}</span
>
</div>
<div
style="
display: flex;
align-items: flex-start;
margin-bottom: 16px;
line-height: 1.6;
word-wrap: break-word;
"
>
<span
style="
font-weight: 500;
color: #34495e;
min-width: 80px;
font-size: 15px;
flex-shrink: 0;
word-wrap: break-word;
"
>性别</span
>
<span
style="
font-size: 14px;
color: #606266;
flex: 1;
word-wrap: break-word;
word-break: break-all;
white-space: normal;
"
>{{ formData.gender }}</span
>
</div>
<div
style="
display: flex;
align-items: flex-start;
margin-bottom: 16px;
line-height: 1.6;
word-wrap: break-word;
"
>
<span
style="
font-weight: 500;
color: #34495e;
min-width: 80px;
font-size: 15px;
flex-shrink: 0;
word-wrap: break-word;
"
>年龄</span
>
<span
style="
font-size: 14px;
color: #606266;
flex: 1;
word-wrap: break-word;
word-break: break-all;
white-space: normal;
"
>{{ formData.age }}</span
>
</div>
<div
style="
display: flex;
align-items: flex-start;
margin-bottom: 16px;
line-height: 1.6;
word-wrap: break-word;
"
>
<span
style="
font-weight: 500;
color: #34495e;
min-width: 80px;
font-size: 15px;
flex-shrink: 0;
word-wrap: break-word;
"
>住院号</span
>
<span
style="
font-size: 14px;
color: #606266;
flex: 1;
word-wrap: break-word;
word-break: break-all;
white-space: normal;
"
>{{ formData.busNo }}</span
>
</div>
<div
style="
display: flex;
align-items: flex-start;
margin-bottom: 16px;
line-height: 1.6;
word-wrap: break-word;
"
>
<span
style="
font-weight: 500;
color: #34495e;
min-width: 80px;
font-size: 15px;
flex-shrink: 0;
word-wrap: break-word;
"
>职业</span
>
<span
style="
font-size: 14px;
color: #606266;
flex: 1;
word-wrap: break-word;
word-break: break-all;
white-space: normal;
"
>{{ formData.temperature }}</span
>
</div>
<div
style="
display: flex;
align-items: flex-start;
margin-bottom: 16px;
line-height: 1.6;
word-wrap: break-word;
"
>
<span
style="
font-weight: 500;
color: #34495e;
min-width: 80px;
font-size: 15px;
flex-shrink: 0;
word-wrap: break-word;
"
>入院时间</span
>
<span
style="
font-size: 14px;
color: #606266;
flex: 1;
word-wrap: break-word;
word-break: break-all;
white-space: normal;
"
>{{ formData.admissionDate }}</span
>
</div>
<div
style="
display: flex;
align-items: flex-start;
margin-bottom: 16px;
line-height: 1.6;
word-wrap: break-word;
"
>
<span
style="
font-weight: 500;
color: #34495e;
min-width: 80px;
font-size: 15px;
flex-shrink: 0;
word-wrap: break-word;
"
>出院时间</span
>
<span
style="
font-size: 14px;
color: #606266;
flex: 1;
word-wrap: break-word;
word-break: break-all;
white-space: normal;
"
>{{ formData.dischargeDate }}</span
>
</div>
<div
style="
display: flex;
align-items: flex-start;
margin-bottom: 16px;
line-height: 1.6;
word-wrap: break-word;
"
>
<span
style="
font-weight: 500;
color: #34495e;
min-width: 80px;
font-size: 15px;
flex-shrink: 0;
word-wrap: break-word;
"
>住院天数</span
>
<span
style="
font-size: 14px;
color: #606266;
flex: 1;
word-wrap: break-word;
word-break: break-all;
white-space: normal;
"
>{{ formData.hospitalDays }}</span
>
</div>
</div>
</div>
<!-- 诊断信息卡片 -->
<div
style="
border: 1px solid #e5e8eb;
border-radius: 12px;
padding: 25px;
margin-bottom: 25px;
background: #ffffff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
"
>
<div
style="
font-size: 18px;
font-weight: 600;
color: #2c3e50;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #f0f2f5;
position: relative;
"
>
诊断
</div>
<div
style="
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 18px;
"
>
<div
style="
display: flex;
align-items: flex-start;
margin-bottom: 16px;
line-height: 1.6;
word-wrap: break-word;
"
>
<span
style="
font-weight: 500;
color: #34495e;
min-width: 80px;
font-size: 15px;
flex-shrink: 0;
word-wrap: break-word;
"
>出诊诊断</span
>
<span
style="
font-size: 14px;
color: #606266;
flex: 1;
word-wrap: break-word;
word-break: break-all;
white-space: normal;
"
>{{ formData.DischargeDiagnosis }}</span
>
</div>
<div
style="
display: flex;
align-items: flex-start;
margin-bottom: 16px;
line-height: 1.6;
word-wrap: break-word;
grid-column: 1 / -1;
padding: 4px 0;
"
>
<span
style="
font-weight: 500;
color: #34495e;
min-width: 80px;
font-size: 15px;
flex-shrink: 0;
word-wrap: break-word;
"
>出院病情摘要</span
>
<span
style="
font-size: 14px;
color: #606266;
flex: 1;
word-wrap: break-word;
word-break: break-all;
white-space: normal;
"
>{{ formData.SummaryAndDiagnosisAndTreatmentProcess }}</span
>
</div>
<div
style="
display: flex;
align-items: flex-start;
margin-bottom: 16px;
line-height: 1.6;
word-wrap: break-word;
grid-column: 1 / -1;
padding: 4px 0;
"
>
<span
style="
font-weight: 500;
color: #34495e;
min-width: 80px;
font-size: 15px;
flex-shrink: 0;
word-wrap: break-word;
"
>出院后要求及注意事项</span
>
<span
style="
font-size: 14px;
color: #606266;
flex: 1;
word-wrap: break-word;
word-break: break-all;
white-space: normal;
"
>{{ formData.RequirementsAndPrecautionsAfterDischarge }}</span
>
</div>
<div
style="
display: flex;
align-items: flex-start;
margin-bottom: 16px;
line-height: 1.6;
word-wrap: break-word;
grid-column: 1 / -1;
padding: 4px 0;
"
>
<span
style="
font-weight: 500;
color: #34495e;
min-width: 80px;
font-size: 15px;
flex-shrink: 0;
word-wrap: break-word;
"
>中医调护</span
>
<span
style="
font-size: 14px;
color: #606266;
flex: 1;
word-wrap: break-word;
word-break: break-all;
white-space: normal;
"
>{{ formData.TraditionalChineseMedicineNursing }}</span
>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'; // 补充缺失的reactive导入
const bodyRef = ref(null);
const showPrintPreview = ref(false); // 控制弹窗显隐
// 表单数据
const formData = reactive({
patientName: '', // 姓名
age: '', // 年龄
gender: '', // 性别
busNo: '', // 住院号
admissionDate: '', // 入院日期
dischargeDate: '', // 出院日期
hospitalDays: '', // 住院天数
DischargeDiagnosis: '', // 出院诊断
SummaryAndDiagnosisAndTreatmentProcess: '', // 出院病情摘要及诊疗经过
RequirementsAndPrecautionsAfterDischarge: '', // 出院后要求及注意事项
TraditionalChineseMedicineNursing: '', // 中医调护
});
const getDom = () => {
return bodyRef.value;
};
const setData = (data) => {
console.log('设置数据=========>', JSON.stringify(data));
Object.assign(formData, data);
};
defineExpose({
setData,
getDom,
});
</script>
<style scoped>
/* 以下是内联样式无法实现的样式(保留说明) */
/* 1. 伪元素样式card-title::after内联样式不支持伪元素 */
/* 2. hover效果内联样式不支持:hover伪类 */
/* 3. 媒体查询响应式:内联样式无法编写@media规则 */
/* 4. 打印预览相关样式:若需要打印功能,需单独处理 */
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,347 @@
<template>
<div style="padding: 20px; background: #f8f9fa; min-height: 100vh;" ref="bodyRef">
<!-- 标题区域 - 强化正式感 -->
<div
style="text-align: center; margin-bottom: 30px; padding: 20px; background: #fff; border-radius: 12px; box-shadow: 0 2px 6px rgba(0,0,0,0.03);">
<div style="font-size: 22px; color: #2d3748; letter-spacing: 1px;">长春市朝阳区中医院</div>
<div
style="font-size: 28px; font-weight: 700; margin: 12px 0; padding: 8px 0; border-bottom: 2px solid #e8f4f8; display: inline-block;">
住院患者入院沟通记录单</div>
<div style="display: flex; justify-content: center; align-items: center; gap: 10px; font-size: 15px; ">
<span style="font-weight: 500;">住院号</span>
<span style="font-weight: 600; text-decoration: underline;">20210001</span>
</div>
</div>
<!-- 基本信息模块 -->
<div class="card-container">
<div class="card-header">
<span class="card-title">基本信息</span>
</div>
<div class="card-content grid-2col">
<div class="info-item">
<span class="info-label">姓名</span>
<span class="info-value">张三</span>
</div>
<div class="info-item">
<span class="info-label">性别</span>
<span class="info-value"></span>
</div>
<div class="info-item">
<span class="info-label">年龄</span>
<span class="info-value">34 </span>
</div>
<div class="info-item">
<span class="info-label">科室/病区</span>
<span class="info-value">中医科</span>
</div>
<div class="info-item">
<span class="info-label">病房/床号</span>
<span class="info-value">305-2</span>
</div>
<div class="info-item">
<span class="info-label">入院日期</span>
<span class="info-value">2025-02-14</span>
</div>
</div>
</div>
<!-- 团队信息模块 -->
<div class="card-container">
<div class="card-header">
<span class="card-title">团队信息</span>
</div>
<div class="card-content grid-2col">
<div class="info-item">
<span class="info-label">经治医生</span>
<span class="info-value">华佗</span>
</div>
<div class="info-item">
<span class="info-label">主治医生</span>
<span class="info-value">王海明</span>
</div>
<div class="info-item">
<span class="info-label">科主任</span>
<span class="info-value">特斯拉</span>
</div>
</div>
</div>
<!-- 病情与诊断模块 -->
<div class="card-container">
<div class="card-header">
<span class="card-title">病情与诊断</span>
</div>
<div class="card-content grid-2col">
<div class="info-item">
<span class="info-label">病情状况</span>
<div class="info-value multi-line">
1111111111111111111111111111111111111111111111111111111111111111111111111111</div>
</div>
<div class="info-item">
<span class="info-label">中医诊断</span>
<div class="info-value multi-line">
1111111111111111111111111111111111111111111111111111111111111111111111111111</div>
</div>
<div class="info-item">
<span class="info-label">西医诊断</span>
<div class="info-value multi-line">
1111111111111111111111111111111111111111111111111111111111111111111111111111</div>
</div>
</div>
</div>
<!-- 治疗与检查计划模块 -->
<div class="card-container">
<div class="card-header">
<span class="card-title">治疗与检查计划</span>
</div>
<div class="card-content grid-2col">
<div class="info-item">
<span class="info-label">治疗方案</span>
<div class="info-value multi-line">
1111111111111111111111111111111111111111111111111111111111111111111111111111</div>
</div>
<div class="info-item">
<span class="info-label">进一步检查项目</span>
<div class="info-value multi-line">血常规肝肾功能腹部B超心电图中医辨证分型检查肿瘤标志物筛查</div>
</div>
</div>
</div>
<!-- 风险告知模块 -->
<div class="card-container">
<div class="card-header">
<span class="card-title">风险告知</span>
</div>
<div class="card-content">
<div class="info-item full-width">
<span class="info-label">告知内容</span>
<div class="info-value multi-line">
1. 治疗过程中可能出现药物不良反应如过敏胃肠道不适等若出现不适需及时告知医护人员<br />
2. 检查项目存在一定的操作风险如穿刺出血感染等医护人员将严格按照规范操作<br />
3. 病情可能因个体差异出现变化需根据实际情况调整治疗方案<br />
4. 若患者存在隐瞒病史不配合治疗等情况可能影响治疗效果相关风险由患者自行承担
</div>
</div>
</div>
</div>
<!-- 签署确认模块 -->
<div class="card-container">
<div class="card-header">
<span class="card-title">签署确认</span>
</div>
<div class="card-content grid-2col">
<div class="info-item">
<span class="info-label">患者或家属</span>
<span class="info-value">张三</span>
</div>
<div class="info-item">
<span class="info-label">与患者关系</span>
<span class="info-value">妻子</span>
</div>
<div class="info-item">
<span class="info-label">签字日期</span>
<span class="info-value">2025-02-14</span>
</div>
<div class="info-item">
<span class="info-label">沟通医师签字</span>
<span class="info-value">华佗</span>
</div>
<div class="info-item">
<span class="info-label">沟通日期</span>
<span class="info-value">2025-02-14</span>
</div>
</div>
</div>
</div>
</template>
<style scoped>
/* 全局卡片容器统一样式 */
.card-container {
border: 1px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
margin-bottom: 20px;
background: #fff;
transition: box-shadow 0.3s ease, transform 0.2s ease;
}
/* 卡片hover效果 */
.card-container:hover {
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.08);
transform: translateY(-1px);
}
/* 卡片头部样式 */
.card-header {
padding: 14px 20px;
border-bottom: 1px solid #f0f0f0;
background-color: #f5fafe;
display: flex;
align-items: center;
}
/* 卡片标题样式 */
.card-title {
font-size: 18px;
font-weight: 600;
letter-spacing: 0.5px;
}
/* 卡片内容区统一样式 */
.card-content {
padding: 22px;
gap: 20px;
}
/* 2列网格布局桌面端 */
.grid-2col {
display: grid;
grid-template-columns: repeat(2, 1fr);
}
/* 信息项统一样式 */
.info-item {
display: flex;
align-items: flex-start;
/* 改为顶端对齐,适配多行文本 */
padding: 9px 0;
gap: 8px;
}
/* 信息标签样式 */
.info-label {
font-weight: 500;
color: #333;
min-width: 110px;
font-size: 15px;
padding-top: 5px;
/* 对齐多行文本的顶部 */
}
/* 信息值样式(单行) */
.info-value {
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
min-height: 28px;
/* 确保单行和多行高度一致 */
display: flex;
align-items: center;
}
/* 多行文本样式(适配长内容) */
.info-value.multi-line {
align-items: flex-start;
padding: 10px 12px;
line-height: 1.6;
min-height: 80px;
/* 最小高度,避免内容过少时显得空旷 */
white-space: pre-line;
/* 支持换行符和空格 */
}
/* 全屏宽度信息项(如风险告知) */
.info-item.full-width {
width: 100%;
}
/* 信息值hover效果 */
.info-value:hover {
background: #f2f2f2;
border-color: #e0e0e0;
}
/* 响应式适配移动端1列布局 */
@media (max-width: 768px) {
.grid-2col {
grid-template-columns: 1fr;
}
.card-content {
padding: 16px;
gap: 16px;
}
.card-header {
padding: 12px 16px;
}
.card-title {
font-size: 16px;
}
.info-item {
padding: 10px 0;
}
.info-label {
min-width: 90px;
font-size: 14px;
padding-top: 4px;
}
.info-value {
font-size: 14px;
padding: 4px 10px;
min-height: 24px;
}
.info-value.multi-line {
padding: 8px 10px;
min-height: 60px;
line-height: 1.5;
}
/* 标题区域适配移动端 */
div[style*="font-size: 28px"] {
font-size: 24px !important;
}
div[style*="font-size: 22px"] {
font-size: 20px !important;
}
}
/* 打印样式优化 */
@media print {
body {
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
background: #fff !important;
}
.card-container {
box-shadow: none !important;
border: 1px solid #ddd !important;
margin-bottom: 15px !important;
transform: none !important;
}
.card-header {
background: #f8f8f8 !important;
border-bottom: 1px solid #ddd !important;
}
.info-value {
background: #fff !important;
border: 1px dashed #eee !important;
}
.card-container:hover {
box-shadow: none !important;
}
}
</style>
<script setup>
</script>

View File

@@ -0,0 +1,877 @@
<template>
<div style="padding: 20px; background: #f8f9fa; min-height: 100vh" ref="bodyRef">
<!-- 标题区域 - 强化正式感 -->
<div
style="
text-align: center;
margin-bottom: 30px;
padding: 20px;
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.03);
"
>
<div style="font-size: 22px; color: #2d3748; letter-spacing: 1px">长春市朝阳区中医院</div>
<div
style="
font-size: 28px;
font-weight: 700;
margin: 12px 0;
padding: 8px 0;
border-bottom: 2px solid #e8f4f8;
display: inline-block;
"
>
患者与手术基础信息
</div>
<div
style="
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
font-size: 15px;
color: #4a5568;
"
>
<span style="font-weight: 500">住院号</span>
<span style="font-weight: 600; text-decoration: underline">{{ formData.busNo }}</span>
</div>
</div>
<!-- 基本信息模块统一为card样式 -->
<div
style="
border: 1px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
margin-bottom: 20px;
background: #fff;
transition: box-shadow 0.3s ease, transform 0.2s ease;
"
>
<div
style="
padding: 14px 20px;
border-bottom: 1px solid #f0f0f0;
display: flex;
align-items: center;
"
>
<span style="font-size: 18px; font-weight: 600; letter-spacing: 0.5px">基本信息</span>
</div>
<div style="padding: 22px; gap: 20px; display: grid; grid-template-columns: repeat(2, 1fr)">
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>姓名</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.patientName }}</span
>
</div>
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>性别</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.gender }}</span
>
</div>
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>年龄</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.age }}</span
>
</div>
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>科室</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.department }}</span
>
</div>
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>病房/床号</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.bedNo }}</span
>
</div>
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>手术日期</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.operationDateTime }}</span
>
</div>
</div>
</div>
<!-- 手术团队信息模块 -->
<div
style="
border: 1px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
margin-bottom: 20px;
background: #fff;
transition: box-shadow 0.3s ease, transform 0.2s ease;
"
>
<div
style="
padding: 14px 20px;
border-bottom: 1px solid #f0f0f0;
display: flex;
align-items: center;
"
>
<span style="font-size: 18px; font-weight: 600; letter-spacing: 0.5px">手术团队信息</span>
</div>
<div style="padding: 22px; gap: 20px; display: grid; grid-template-columns: repeat(2, 1fr)">
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>手术者</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.surgeon }}</span
>
</div>
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>第一助手</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.firstAssistant }}</span
>
</div>
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>第二助手</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.secondAssistant }}</span
>
</div>
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>器械护士</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.scrubNurse }}</span
>
</div>
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>麻醉医师</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.anesthesiologist }}</span
>
</div>
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>巡逻护士</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.circulatingNurse }}</span
>
</div>
</div>
</div>
<!-- 手术详情模块 -->
<div
style="
border: 1px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
margin-bottom: 20px;
background: #fff;
transition: box-shadow 0.3s ease, transform 0.2s ease;
"
>
<div
style="
padding: 14px 20px;
border-bottom: 1px solid #f0f0f0;
display: flex;
align-items: center;
"
>
<span style="font-size: 18px; font-weight: 600; letter-spacing: 0.5px">手术详情</span>
</div>
<div style="padding: 22px; gap: 20px; display: grid; grid-template-columns: repeat(2, 1fr)">
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>手术名称</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.operationName }}</span
>
</div>
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>手术方式</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.operationMethod }}</span
>
</div>
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>手术入路</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.surgicalApproach }}</span
>
</div>
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>术中发现</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.intraoperativeFindings }}</span
>
</div>
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>手术过程</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.operationProcess }}</span
>
</div>
</div>
</div>
<!-- 术后情况模块 -->
<div
style="
border: 1px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
margin-bottom: 20px;
background: #fff;
transition: box-shadow 0.3s ease, transform 0.2s ease;
"
>
<div
style="
padding: 14px 20px;
border-bottom: 1px solid #f0f0f0;
display: flex;
align-items: center;
"
>
<span style="font-size: 18px; font-weight: 600; letter-spacing: 0.5px">术后情况</span>
</div>
<div style="padding: 22px; gap: 20px; display: grid; grid-template-columns: repeat(2, 1fr)">
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>术中出血量</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.bloodLoss }}ml</span
>
</div>
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>输血情况</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.bloodTransfusion }}</span
>
</div>
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>引流管放置</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.drainageTube }}</span
>
</div>
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>标本处理</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.specimenDisposal }}</span
>
</div>
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>手术结束时间</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.operationEndTime }}</span
>
</div>
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>患者去向</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.patientDestination }}</span
>
</div>
</div>
</div>
<!-- 签署确认模块 -->
<div
style="
border: 1px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
margin-bottom: 20px;
background: #fff;
transition: box-shadow 0.3s ease, transform 0.2s ease;
"
>
<div
style="
padding: 14px 20px;
border-bottom: 1px solid #f0f0f0;
display: flex;
align-items: center;
"
>
<span style="font-size: 18px; font-weight: 600; letter-spacing: 0.5px">签署确认</span>
</div>
<div style="padding: 22px; gap: 20px; display: grid; grid-template-columns: repeat(2, 1fr)">
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>手术者签名</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.surgeonSignature }}</span
>
</div>
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>记录者签名</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.recorderSignature }}</span
>
</div>
<div style="display: flex; align-items: center; padding: 9px 0">
<span
style="
font-weight: 500;
color: #333;
min-width: 100px;
font-size: 15px;
margin-right: 10px;
"
>记录日期</span
>
<span
style="
font-size: 15px;
color: #222;
padding: 5px 12px;
background: #f9f9f9;
border-radius: 4px;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
"
>{{ formData.recordDate }}</span
>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, defineExpose } from 'vue';
const bodyRef = ref();
// 响应式表单数据
const formData = reactive({
// 患者与手术基础信息
busNo: '',
patientName: '',
gender: '',
age: '',
department: '',
bedNo: '',
operationDateTime: '', // 手术日期时间
// 手术团队信息
surgeon: '', // 主刀医师
firstAssistant: '', // 第一助手
secondAssistant: '', // 第二助手
anesthesiologist: '', // 麻醉医师
circulatingNurse: '', // 巡回护士
scrubNurse: '', // 器械护士
// 手术详情
operationName: '', // 规范手术名称
operationMethod: '', // 手术方式
surgicalApproach: '', // 手术入路
intraoperativeFindings: '', // 术中发现
operationProcess: '', // 手术过程
// 术后情况
bloodLoss: '', // 术中出血量
bloodTransfusion: '', // 输血情况
drainageTube: '', // 引流管放置
specimenDisposal: '', // 标本处理
operationEndTime: '', // 手术结束时间
patientDestination: '', // 患者去向
// 签署信息
surgeonSignature: '', // 手术者签名
recorderSignature: '', // 记录者签名
recordDate: '', // 记录日期
});
const getDom = () => {
return bodyRef.value;
};
const setData = (data) => {
console.log('设置数据=========>', JSON.stringify(data));
Object.assign(formData, data);
};
defineExpose({
setData,
getDom,
});
</script>

View File

@@ -12,13 +12,13 @@
<label>组织机构代码:</label>
<el-input
type="text"
v-model="formData.hospitalInfo.orgCode"
v-model="formData.hospitalInfo.medins_orgcode"
placeholder="请填写组织机构代码"
/>
</div>
<div class="form-item">
<label>医疗付费方式:</label>
<el-select placeholder="请选择" v-model="formData.hospitalInfo.medicalPaymentCode">
<el-select placeholder="请选择" v-model="formData.hospitalInfo.medfee_paymtd_code">
<el-option
v-for="item in medicalSelectOptions"
:key="item.id"
@@ -60,6 +60,7 @@
:label="item.label"
></el-option>
</el-select>
<!-- <el-input v-model="formData.patientInfo.gend" placeholder="请输入"></el-input> -->
</div>
<div class="form-item">
<label>出生日期:</label>
@@ -67,21 +68,11 @@
</div>
<div class="form-item">
<label>年龄:</label>
<el-input
type="number"
max="120"
min="0"
v-model="formData.patientInfo.age"
placeholder="请填写年龄"
/>
<el-input max="120" min="0" v-model="formData.patientInfo.age" placeholder="请填写年龄" />
</div>
<div class="form-item">
<label>国籍:</label>
<el-input
type="text"
v-model="formData.patientInfo.nationality"
placeholder="请填写国籍"
/>
<el-input type="text" v-model="formData.patientInfo.ntly" placeholder="请填写国籍" />
</div>
</div>
@@ -211,13 +202,13 @@
</div>
<div class="form-item">
<label>入院时间:</label>
<el-input type="date" v-model="formData.admission.admitTime" />
<el-input type="date" v-model="formData.admission.adm_time" />
</div>
<div class="form-item">
<label>入院科室:</label>
<el-input
type="text"
v-model="formData.admission.adm_caty"
v-model="formData.admission.adm_dept_name"
placeholder="请填写入院科室"
/>
</div>
@@ -252,7 +243,7 @@
<label>实际住院天数:</label>
<el-input
type="number"
v-model="formData.admission.ipt_days"
v-model="formData.admission.act_ipt_days"
placeholder="请填写实际住院天数"
/>
</div>
@@ -262,26 +253,99 @@
<!-- 诊断信息 -->
<div class="section">
<div class="section-title">诊断信息</div>
<div class="form-row">
<div class="form-item full-width">
<label>主要诊断:</label>
<el-input
type="text"
v-model="formData.diagnosis.mainDiagnosis"
placeholder="腰椎间盘突出症(L4-5)"
/>
</div>
</div>
<div class="form-row">
<div class="form-item full-width">
<label>其他诊断:</label>
<el-input
type="textarea"
v-model="formData.diagnosis.otherDiagnosis"
placeholder="腰椎管狭窄(L4-5)\n右下肢不全瘫"
></el-input>
</div>
</div>
<el-col :span="23" :xs="24">
<el-form disabled :model="formData" :rules="rules" ref="formRef">
<el-table ref="diagnosisTableRef" :data="formData?.diagnosisList" width="100px">
<el-table-column label="序号" type="index" width="50" />
<el-table-column label="诊断排序" align="center" prop="diagSrtNo">
<template #default="scope">
<el-input-number
v-model="scope.row.diagSrtNo"
controls-position="right"
:controls="false"
/>
</template>
</el-table-column>
<el-table-column label="诊断类别" align="center" prop="diagSrtNo">
<template #default="scope">
<el-form-item
:prop="`diagnosisList.${scope.$index}.medTypeCode`"
:rules="rules.medTypeCode"
>
<el-select
v-model="scope.row.medTypeCode"
placeholder=" "
style="margin-top: 15px"
>
<el-option
v-for="item in med_type"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="诊断名称" align="center" prop="name">
<template #default="scope">
<el-form-item :prop="`diagnosisList.${scope.$index}.name`" :rules="rules.name">
<el-popover
:popper-style="{ padding: '0' }"
placement="bottom-start"
:visible="scope.row.showPopover"
trigger="manual"
>
<diagnosislist
:diagnosisSearchkey="diagnosisSearchkey"
@selectDiagnosis="handleSelsectDiagnosis"
/>
<template #reference>
<el-input
v-model="scope.row.name"
placeholder="请选择诊断"
@input="handleChange"
@focus="handleFocus(scope.row, scope.$index)"
@blur="handleBlur(scope.row)"
style="margin-top: 15px"
/>
</template>
</el-popover>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="医保码" align="center" prop="ybNo" />
<el-table-column label="诊断类型" align="center" prop="maindiseFlag">
<template #default="scope">
<div style="display: flex">
<el-checkbox
label="主诊断"
:trueLabel="1"
:falseLabel="0"
v-model="scope.row.maindiseFlag"
border
size="small"
@change="(value) => handleMaindise(value, scope.$index)"
/>
<el-select
v-model="scope.row.verificationStatusEnum"
placeholder=" "
style="padding-bottom: 5px; padding-left: 10px"
size="small"
>
<el-option
v-for="item in diagnosisOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
</template>
</el-table-column>
</el-table>
</el-form>
</el-col>
</div>
<!-- 医疗信息 -->
@@ -433,11 +497,14 @@
</template>
<script setup>
import { reactive, watch } from 'vue';
import { reactive, watch, ref } from 'vue';
import formDataJs from '../../doctorstation/components/store/medicalpage';
import { patientInfo } from '../../inpatientDoctor/home/store/patient';
import diagnosislist from '../../inpatientDoctor/home/components/diagnosis/diagnosislist.vue';
import { diagnosisInit } from '../../doctorstation/components/api';
import useUserStore from '@/store/modules/user';
const { proxy } = getCurrentInstance();
const { med_type } = proxy.useDict('med_type');
const {
medfee_paymtd_code,
blotype_abo,
@@ -461,9 +528,13 @@ const {
'naty',
'patn_rlts'
);
const formRef = ref();
const diagnosisTableRef = ref();
// 诊断类型下拉选
const diagnosisOptions = ref([]);
const userStore = useUserStore();
const fixmedinsCode = userStore.fixmedinsCode;
formDataJs.hospitalInfo.orgCode = fixmedinsCode;
formDataJs.hospitalInfo.medins_orgcode = fixmedinsCode;
// 医疗付费方式
const medicalSelectOptions = medfee_paymtd_code;
// 性别
@@ -490,23 +561,48 @@ const formData = reactive(formDataJs);
defineExpose({
formData,
});
// 映射性别
const mapSex = (data = '') => {
let code = '0';
if (data.indexOf('男') !== -1) {
code = '1';
}
if (data.indexOf('女') !== -1) {
code = '2';
}
return code;
};
watch(
patientInfo,
(newValue) => {
if (newValue) {
console.log('21321212132321=====>', newValue.age);
const birthDate = newValue.birthDate.split(' ');
formData.patientInfo.patientName = newValue.patientName ?? '';
formData.patientInfo.healthCardNo = newValue.busNo ?? '';
// formData.patientInfo.gender = newValue.genderEnum ?? '';
formData.patientInfo.gend = mapSex(newValue.genderEnum_enumText) ?? '';
formData.patientInfo.brdy = birthDate.length > 0 ? birthDate[0] : '';
formData.patientInfo.age = newValue.age ?? '';
formData.admission.patn_ipt_cnt = newValue.inHospitalDays ?? '';
formData.admission.dscg_ward = newValue.houseName ?? '';
formData.admission.adm_ward = newValue.houseName ?? '';
formData.admission.ipt_no = newValue.contractNo ?? '';
}
},
{ immediate: true }
);
init();
function init() {
diagnosisInit().then((res) => {
if (res.code == 200) {
diagnosisOptions.value = res.data.verificationStatusOptions;
}
});
}
const rules = ref({
name: [{ required: true, message: '请选择诊断', trigger: 'change' }],
medTypeCode: [{ required: true, message: '请选择诊断类型', trigger: 'change' }],
diagSrtNo: [{ required: true, message: '请输入诊断序号', trigger: 'change' }],
});
</script>
<style scoped>

File diff suppressed because it is too large Load Diff

View File

@@ -158,7 +158,10 @@
</div>
<div class="form-item">
<label>目的:</label>
<el-input v-model="formData.medicalSecond.purpose" placeholder="请填写目的"></el-input>
<el-input
v-model="formData.medicalSecond.dscg_31days_rinp_pup"
placeholder="请填写目的"
></el-input>
</div>
</div>
<div class="form-row">
@@ -223,21 +226,30 @@
</div>
<div class="form-item">
<label>分化程度:</label>
<el-input type="date" v-model="formData.medicalSecond.degreeDifferentiation" />
<!-- <el-input type="date" v-model="formData.medicalSecond.bkup_deg" /> -->
<el-select v-model="formData.medicalSecond.bkup_deg_code" placeholder="请选择分化程度">
<el-option
v-for="item in bkup_deg_codeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label>临床路径-进入路径</label>
<el-select v-model="formData.medicalSecond.enterPath">
<!-- <el-select v-model="formData.medicalSecond.enterPath">
<el-option
v-for="item in enterPathOptions"
:key="item.id"
:value="item.value"
:label="item.label"
></el-option>
</el-select>
</el-select> -->
<el-input v-model="formData.medicalSecond.enterPath" placeholder="请输入"></el-input>
</div>
<div class="form-item">
<label>变异:</label>
@@ -254,14 +266,15 @@
<div class="form-row">
<div class="form-item">
<label>退出路径</label>
<el-select v-model="formData.medicalSecond.outPath">
<!-- <el-select v-model="formData.medicalSecond.outPath">
<el-option
v-for="item in outPathOptions"
:key="item.id"
:value="item.value"
:label="item.label"
></el-option>
</el-select>
</el-select> -->
<el-input v-model="formData.medicalSecond.outPath" placeholder="请输入"></el-input>
</div>
<div class="form-item"></div>
</div>
@@ -311,7 +324,7 @@
<div class="form-row">
<div class="form-item">
<label>呼吸机使用:</label>
<el-select v-model="formData.medicalSecond.ventilatorUse">
<el-select v-model="formData.medicalSecond.use_vent_flag">
<el-option
v-for="item in ventilatorUseOptions"
:key="item.id"
@@ -356,6 +369,7 @@ const {
sys_yes_no,
sys_exit_path,
oprn_patn_type_code,
bkup_deg_code,
} = proxy.useDict(
'dscg_way',
'days_rinp_flag_31',
@@ -369,7 +383,8 @@ const {
'sys_entry_path',
'sys_yes_no',
'sys_exit_path',
'oprn_patn_type_code'
'oprn_patn_type_code',
'bkup_deg_code'
);
// 手术类型
@@ -398,6 +413,8 @@ const cutLevelOptions = sinc_heal_lv_code;
const anesthesiaTypeOptions = anst_mtd_code;
// 麻醉分级
const anesthesiaLevelOptions = anst_lv_code;
// 分化程度
const bkup_deg_codeOptions = bkup_deg_code;
watch(
() => formData.medicalSecond.surgery_tableData,
(newValue) => {

View File

@@ -153,7 +153,7 @@ function viewPatient(patient) {
console.log('View patient:', patient);
selectedPatient.value = patient; // 选中患者
emits('patientSelected', selectedPatient.value); // 发送选中的患者数据
emits('triggerSearch', patient.busNo);// 触发预交金查询事件
emits('triggerSearch', patient.busNo); // 触发预交金查询事件
drawerVisible.value = false;
reset(); // 重置筛选条件
// 可以在这里做一些操作比如高亮行或者如果“查看”是选择并关闭则触发confirm
@@ -185,18 +185,20 @@ function show() {
console.log('show', props);
wardListOptions.value = props.wardListOptions;
drawerVisible.value = props.drawerVisible;
getWardList().then((res) => {
if (res.length > 0) {
wardListOptions.value = res.map(ward => ({
label: ward.name,
value: ward.id
}));
}
getList();
}).catch((error) => {
console.error('获取病区列表失败:', error);
getList();
});
getWardList()
.then((res) => {
if (res.length > 0) {
wardListOptions.value = res.map((ward) => ({
label: ward.name,
value: ward.id,
}));
}
getList();
})
.catch((error) => {
console.error('获取病区列表失败:', error);
getList();
});
}
/**
@@ -205,7 +207,7 @@ function show() {
function getList() {
console.log('queryParams', queryParams.value);
getDepositInfo(queryParams.value).then((res) => {
patientData.value = res.data.records;
patientData.value = res.data?.records || [];
total.value = res.data.total;
});
}

View File

@@ -31,6 +31,11 @@
<template v-for="(item, index) in formData.selfPay" :key="index">
<div v-show="item.payEnum != 220500" class="payment-item">
<span>支付方式</span>
<img
v-if="item.payEnum == 220100 || item.payEnum == 220200"
:src="imgs[item.payEnum == 220100 ? 0 : 1]"
style="width: 20px; height: 20px"
/>
<el-select
v-model="item.payEnum"
placeholder="选择支付方式"
@@ -163,7 +168,9 @@ import useUserStore from '@/store/modules/user';
import { hiprint } from 'vue-plugin-hiprint';
import templateJson from './template.json';
import { pa } from 'element-plus/es/locales.mjs';
import image1 from '../../../../../assets/images/weixinzhifu.png';
import image2 from '../../../../../assets/images/zhifubaozhifu.png';
const imgs = ref([image1, image2]);
const props = defineProps({
open: {
type: Boolean,
@@ -206,6 +213,14 @@ const props = defineProps({
type: Array,
default: () => [],
},
newId: {
type: String,
default: '',
},
oldId: {
type: String,
default: '',
},
});
const { proxy } = getCurrentInstance();
@@ -216,7 +231,6 @@ const userStore = useUserStore();
const discountRadio = ref();
const discountAmount = ref(0);
const txtCode = ref('');
const formData = reactive({
totalAmount: 0,
selfPay: [{ payEnum: 220100, amount: 0.0, payLevelEnum: 2 }],
@@ -398,6 +412,7 @@ async function submit() {
return;
}
dialogLoading.value = true;
console.log(props.newId, props.oldId);
savePayment({
chargeItemIds: props.chargeItemIds,
encounterId: props.patientInfo.encounterId,
@@ -405,6 +420,8 @@ async function submit() {
paymentDetails: formData.selfPay,
ybMdtrtCertType: props.userCardInfo.psnCertType,
busiCardInfo: props.userCardInfo.busiCardInfo,
newId: props.newId,
oldId: props.oldId,
})
.then((res) => {
if (res.code == 200) {

View File

@@ -58,7 +58,7 @@
@cell-click="clickRow"
highlight-current-row
>
<el-table-column label="住院号" align="center" prop="patientBusNo" />
<el-table-column label="住院号" align="center" prop="encounterBusNo" />
<!-- <el-table-column label="床号" align="center" prop="bedNo" /> -->
<el-table-column label="姓名" align="center" prop="patientName" />
<el-table-column label="账户余额" align="center" prop="balanceAmount" />
@@ -86,15 +86,30 @@
<!-- <el-descriptions-item label="科室:">
{{ patientInfo.organizationName }}
</el-descriptions-item> -->
<el-descriptions-item label="床号:">
<!-- <el-descriptions-item label="床号:">
{{ patientInfo.bedNo }}
</el-descriptions-item>
<el-descriptions-item label="就诊时间:">
{{ formatDateStr(patientInfo.receptionTime, 'YYYY-MM-DD HH:mm:ss') }}
</el-descriptions-item>
</el-descriptions-item> -->
</el-descriptions>
</el-card>
<el-card style="min-width: 1100px">
<el-card>
<el-date-picker
v-model="costSearchTime"
type="daterange"
range-separator="~"
start-placeholder="开始时间"
end-placeholder="结束时间"
placement="bottom"
value-format="YYYY-MM-DD"
style="width: 40%; margin-bottom: 10px; margin-right: 10px"
@change="getPatientList"
/>
<el-button type="primary" style="margin-bottom: 10px" @click="costSearch"> 搜索 </el-button>
</el-card>
<el-card style="min-width: 1100px; margin-top: 20px">
<template #header>
<span style="vertical-align: middle">收费项目</span>
</template>
@@ -156,7 +171,7 @@
<el-table
ref="chargeListRef"
height="530"
:data="chargeList"
:data="chargeFilterList"
row-key="id"
@selection-change="handleSelectionChange"
v-loading="chargeLoading"
@@ -174,17 +189,14 @@
<el-table-column label="费用性质" align="center" prop="contractName" />
<el-table-column label="结算状态" align="center" prop="statusEnum_enumText" width="150">
<template #default="scope">
<el-tag v-if="scope.row.encounterStatus === 1" disable-transitions>
<!-- 1代收费 2代结算 5已收费 8已退费 -->
<el-tag v-if="scope.row.statusEnum === 1" disable-transitions>
{{ scope.row.statusEnum_enumText }}
</el-tag>
<el-tag
v-else-if="scope.row.encounterStatus === 5"
type="success"
disable-transitions
>
<el-tag v-else-if="scope.row.statusEnum === 5" type="success" disable-transitions>
{{ scope.row.statusEnum_enumText }}
</el-tag>
<el-tag v-else-if="scope.row.encounterStatus === 8" type="danger" disable-transitions>
<el-tag v-else-if="scope.row.statusEnum === 8" type="danger" disable-transitions>
{{ scope.row.statusEnum_enumText }}
</el-tag>
<el-tag v-else type="warning" disable-transitions>
@@ -231,7 +243,8 @@
</el-table-column>
<el-table-column label="折扣率" align="right" prop="discountRate" header-align="center">
<template #default="scope">
{{ scope.row.discountRate.toFixed(2) + '%' || '0.00' + '%' }}
<!-- discountRate是一个字符串类型的 -->
{{ Number(scope.row.discountRate).toFixed(2) + '%' || '0.00' + '%' }}
</template>
</el-table-column>
<el-table-column
@@ -270,6 +283,8 @@
:details="details"
:chargedItems="chargedItems"
@refresh="getPatientList"
:newId="newId"
:oldId="oldId"
/>
</div>
</template>
@@ -286,11 +301,13 @@ import {
changeStudentPayTosStudentSelf,
changeStudentSelfToStudentPay,
} from './components/api';
import { invokeYbPlugin } from '@/api/public';
import { invokeYbPlugin5001 } from '@/api/public';
import ChargeDialog from './components/chargeDialog.vue';
import { formatDateStr } from '@/utils';
import useUserStore from '@/store/modules/user';
import Decimal from 'decimal.js';
import moment from 'moment';
import { onMounted } from 'vue';
const { proxy } = getCurrentInstance();
const userStore = useUserStore();
@@ -302,12 +319,17 @@ const queryParams = ref({
const totalAmounts = ref(0);
const selectedRows = ref([]);
const patientList = ref([]);
// 收费项目列表
const chargeList = ref([]);
// 根据时间过滤的收费项目列表
const chargeFilterList = ref([]);
const chargeItemIdList = ref([]);
const chrgBchnoList = ref([]);
const chargeLoading = ref(false);
const encounterId = ref('');
const paymentId = ref('');
const newId = ref('');
const oldId = ref('');
const patientInfo = ref({});
const openDialog = ref(false);
const totalAmount = ref(0);
@@ -318,6 +340,40 @@ const receptionTime = ref([
formatDateStr(new Date(), 'YYYY-MM-DD'),
formatDateStr(new Date(), 'YYYY-MM-DD'),
]);
// 计算当前时间+6天
const accumulateDay = () => {
// 获取当前时间
let now = moment();
// 在当前时间上加一天
let tomorrow = now.add(6, 'days');
// 格式化为年-月-日的格式
let formattedDate = tomorrow.format('YYYY-MM-DD');
return formattedDate;
};
// 收费项目时间段搜索
const costSearchTime = ref([formatDateStr(new Date(), 'YYYY-MM-DD'), accumulateDay()]);
// 测试数据
const items = [
{ id: 1, name: 'Item 1', date: '2025-12-15' },
{ id: 2, name: 'Item 2', date: '2025-12-18' },
{ id: 3, name: 'Item 3', date: '2025-12-20' },
];
// 根据时间短搜索
const costSearch = () => {
console.log(costSearchTime.value === null);
if (costSearchTime.value === null) {
chargeFilterList.value = chargeList.value;
} else {
const startTime = moment(costSearchTime.value[0], 'YYYY-MM-DD');
const endTime = moment(costSearchTime.value[1], 'YYYY-MM-DD');
const filterData = chargeList.value.filter((item) => {
const itemDate = moment(item.billDate || '', 'YYYY-MM-DD'); // 将数据项的日期也格式化为moment对象
return itemDate.isBetween(startTime, endTime, null, '[]'); // 使用isBetween方法进行范围判断'[]'表示包含边界值
});
chargeFilterList.value = filterData;
}
};
const buttonDisabled = computed(() => {
return Object.keys(patientInfo.value).length === 0;
});
@@ -339,9 +395,7 @@ watch(
() => chargeList.value,
(newVlaue) => {
if (newVlaue && newVlaue.length > 0) {
handleTotalAmount();
} else {
totalAmounts.value = 0;
chargeFilterList.value = newVlaue;
}
},
{ immediate: true }
@@ -350,15 +404,9 @@ function handleSelectionChange(selection) {
selectedRows.value = selection;
}
function handleTotalAmount() {
if (selectedRows.value.length == 0) {
totalAmounts.value = chargeList.value.reduce((accumulator, currentRow) => {
return new Decimal(accumulator).add(currentRow.totalPrice.toFixed(2) || 0);
}, new Decimal(0));
} else {
totalAmounts.value = selectedRows.value.reduce((accumulator, currentRow) => {
return new Decimal(accumulator).add(currentRow.totalPrice.toFixed(2) || 0);
}, 0);
}
totalAmounts.value = selectedRows.value.reduce((accumulator, currentRow) => {
return new Decimal(accumulator).add(currentRow.totalPrice.toFixed(2) || 0);
}, 0);
}
getPatientList();
initOption();
@@ -375,7 +423,7 @@ function getPatientList() {
}
getList1(queryParams.value).then((res) => {
console.log('患者列表', res);
patientList.value = res.data.data.records;
patientList.value = res.data.records;
});
}
@@ -389,7 +437,7 @@ function initOption() {
function checkSelectable(row, index) {
// 已结算时禁用选择框
return row.encounterStatus === 1;
return row.statusEnum == 1 || row.statusEnum == 2;
}
/**
@@ -433,6 +481,7 @@ function confirmCharge() {
chargeItemIdList.value = selectRows.map((item) => {
return item.id;
});
//wor_device_request 器材
consumablesIdList.value = selectRows
.filter((item) => {
return item.serviceTable == 'wor_device_request';
@@ -451,11 +500,15 @@ function confirmCharge() {
chargeItemIds: chargeItemIdList.value,
}).then((res) => {
if (res.code == 200) {
// totalAmount.value = res.data.psnCashPay;
paymentId.value = res.data.paymentId;
const item = res.data.paymentRecDetailDtoList.find((i) => {
return i.payEnum == 220000;
});
totalAmount.value = item?.amount;
paymentId.value = res.data.id;
newId.value = res.data.newId;
oldId.value = res.data.oldId;
chrgBchnoList.value = res.data.chrgBchnoList;
totalAmount.value = res.data.details.find((item) => item.payEnum == 220000).amount;
details.value = res.data.details.filter((item) => {
details.value = res.data.paymentRecDetailDtoList?.filter((item) => {
return item.amount > 0;
});
openDialog.value = true;
@@ -491,7 +544,7 @@ async function handleReadCard(value) {
switch (value) {
case '01': // 电子凭证
// readCardLoading.value = true;
await invokeYbPlugin({
await invokeYbPlugin5001({
FunctionId: 3,
url: 'http://10.47.0.67:8089/localcfc/api/hsecfc/localQrCodeQuery',
orgId: 'H22010200672',
@@ -533,7 +586,7 @@ async function handleReadCard(value) {
case '03': // 社保卡
readCardLoading.value = true;
loadingText.value = '正在读取...';
await invokeYbPlugin(
await invokeYbPlugin5001(
JSON.stringify({
FunctionId: 1,
IP: 'ddjk.jlhs.gov.cn',
@@ -605,16 +658,19 @@ async function handleReadCard(value) {
busiCardInfo: userCardInfo.busiCardInfo,
}).then((res) => {
if (res.code == 200) {
// totalAmount.value = res.data.psnCashPay;
paymentId.value = res.data.paymentId;
totalAmount.value = res.data.details.find((item) => item.payEnum == 220000).amount;
details.value = res.data.details;
paymentId.value = res.data.id;
totalAmount.value = res.data.paymentRecDetailDtoList.find(
(item) => item.payEnum == 220000
).amount;
details.value = res.data.paymentRecDetailDtoList;
// chrgBchnoList.value = res.data.chrgBchnoList;
chargeItemIdList.value = selectRows.map((item) => {
return item.id;
});
// 打印项目赋值
chargedItems.value = selectRows;
newId.value = res.data.newId;
oldId.value = res.data.oldId;
consumablesIdList.value = selectRows
.filter((item) => {
return item.serviceTable == 'wor_device_request';
@@ -622,6 +678,7 @@ async function handleReadCard(value) {
.map((item) => {
return item.id;
});
openDialog.value = true;
} else {
proxy.$modal.msgError(res.msg);

View File

@@ -23,8 +23,14 @@
</el-col>
<el-col :span="8">
<el-form-item label="入院日期" prop="admissionDate">
<el-date-picker v-model="formData.admissionDate" type="date" format="yyyy-MM-dd" value-format="yyyy-MM-dd"
style="width: 100%" placeholder="请选择日期" />
<el-date-picker
v-model="formData.admissionDate"
type="date"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd"
style="width: 100%"
placeholder="请选择日期"
/>
</el-form-item>
</el-col>
</el-row>
@@ -46,7 +52,12 @@
<el-row>
<el-col :span="8">
<el-form-item label="重复住院">
<el-switch inline-prompt v-model="formData.isRepeatHospitalization" active-text="是" inactive-text="否" />
<el-switch
inline-prompt
v-model="formData.isRepeatHospitalization"
active-text=""
inactive-text=""
/>
</el-form-item>
</el-col>
<el-col :span="8">
@@ -75,7 +86,12 @@
</el-col>
<el-col :span="8">
<el-form-item label="急诊标志">
<el-switch inline-prompt v-model="formData.isEmergency" active-text="是" inactive-text="否" />
<el-switch
inline-prompt
v-model="formData.isEmergency"
active-text=""
inactive-text=""
/>
</el-form-item>
</el-col>
<el-col :span="8">
@@ -97,9 +113,11 @@
</div>
</template>
<script setup >
<script setup>
import { ref } from 'vue';
defineOptions({
name: 'YbregisterEdit',
});
// interface FormData {
// medicalType: string;
// diseaseName: string;
@@ -141,7 +159,6 @@ import { ref } from 'vue';
const handleSubmit = () => {
// 处理表单提交
};
</script>

View File

@@ -79,9 +79,12 @@
@cancelAct="cancelAct"
/>
</template>
<script setup >
<script setup>
import PatientRegister from './patientRegister.vue';
import { getAdmissionPage, getPatientBasicInfo, getInHospitalInfo } from './api';
import { getContractList } from './api';
const { proxy } = getCurrentInstance();
const { admit_source_code } = proxy.useDict('admit_source_code');
//const { proxy } = getCurrentInstance();
const emits = defineEmits([]);
// const props = defineProps({});
@@ -105,6 +108,7 @@ const patientRegisterVisible = ref(false);
const noFile = ref(false);
const registrationType = ref(true);
const patient = ref({});
const priceTypeList = ref({});
const doEdit = (row) => {
getPatientBasicInfo(row.patientId).then((res) => {
patient.value = res.data;
@@ -116,6 +120,7 @@ const doEdit = (row) => {
});
};
onBeforeMount(() => {});
getContract();
onMounted(() => {
getList();
});
@@ -149,9 +154,47 @@ function resetQuery() {
getList();
}
// 入院登记
const mapHospitalization = (admitSourceCode = '') => {
console.log('admit_source_code=======>', admitSourceCode);
const findObj = admit_source_code.value.find((item) => {
return item.value == admitSourceCode;
});
return findObj?.label;
};
/** 查询费用性质 */
function getContract() {
getContractList().then((response) => {
priceTypeList.value = response.data;
});
}
// 映射费用性质
const priceTypeDic = (contractNo) => {
const findObj = priceTypeList.value.find((item) => {
return item.busNo == contractNo;
});
return findObj?.contractName;
};
const getList = () => {
getAdmissionPage(queryParams.value).then((res) => {
treatHospitalizedData.value = res.data.records;
console.log('priceTypeList=======>', JSON.stringify(priceTypeList.value));
let dataList = [];
for (let index = 0; index < (res.data.records || []).length; index++) {
const obj = (res.data.records || [])[index];
const newObj = {
...obj,
};
newObj.admitSourceCode = mapHospitalization(obj.admitSourceCode);
dataList.push(newObj);
}
for (let index = 0; index < dataList.length; index++) {
const obj = dataList[index];
obj.contractNo = priceTypeDic(obj.contractNo);
}
treatHospitalizedData.value = dataList;
// treatHospitalizedData.value = res.data.records;
total.value = res.data.total;
});
};

View File

@@ -124,10 +124,12 @@ const patientYbRegisterVisible = ref(false);
const patientRegisterOK = () => {
patientRegisterVisible.value = false;
queryParams.value.searchKey = '';
getList();
emits('okList');
};
const cancelAct = () => {
getList();
patientRegisterVisible.value = false;
};

View File

@@ -166,7 +166,7 @@
import { ref, reactive, watch, onMounted } from 'vue';
import PatientInfoForm from './patientInfoForm.vue';
import { patientlLists, getOrgList, gerPreInfo } from './api';
import { invokeYbPlugin } from '@/api/public';
import { invokeYbPlugin5001 } from '@/api/public';
import useUserStore from '@/store/modules/user';
import { useRouter } from 'vue-router';
@@ -212,7 +212,7 @@ const props = defineProps({
required: false,
},
});
const emits = defineEmits(['onChangFeeType']);
const emits = defineEmits(['onChangFeeType', 'carReading']);
const registerRef = ref();
const typeCode = ref('01');
const organization = ref([]);
@@ -282,9 +282,6 @@ const getDictLabel = (dictList, value) => {
return item ? item.label : value;
};
async function handleReadCard(value) {
// if (window.CefSharp === undefined) {
// alert('请在医保版本中调用读卡功能!');
// } else {
try {
// await CefSharp.BindObjectAsync('boundAsync');
// string url,
@@ -312,7 +309,7 @@ async function handleReadCard(value) {
// .getInfoByQrCodeAsync(
// )
await invokeYbPlugin({
await invokeYbPlugin5001({
FunctionId: 3,
url: 'http://10.47.0.67:8089/localcfc/api/hsecfc/localQrCodeQuery',
orgId: 'H22010200672',
@@ -327,38 +324,41 @@ async function handleReadCard(value) {
loadingText.value = '正在读取...';
console.log(res);
jsonResult = res.data;
cardInfo = JSON.parse(JSON.stringify(jsonResult));
let message = JSON.parse(cardInfo.message);
console.log('patientInfo中的encounterId:', props.patientInfo);
const encounterId = props.inHospitalInfo.encounterId || '1993854019030441985';
console.log('1111111111111111111准备使用的encounterId:', encounterId);
userMessage = {
certType: '02', // 证件类型
certNo: message.data.idNo, // 身份证号
psnCertType: '02', // 居民身份证
encounterId: encounterId || '1993854019030441985',
};
userCardInfo = {
certType: '01', // 证件类型
certNo: message.data.idNo, // 身份证号
psnCertType: '01', // 居民身份证
busiCardInfo: message.data.ecToken, // 令牌
encounterId: encounterId || '1993854019030441985',
};
BusiCardInfo.value = message.data.ecToken;
console.log(BusiCardInfo.value);
emits('carReading', jsonResult);
})
.catch(() => {
readCardLoading.value = false;
});
cardInfo = JSON.parse(JSON.stringify(jsonResult));
let message = JSON.parse(cardInfo.message);
console.log('patientInfo中的encounterId:', props.patientInfo);
const encounterId = props.inHospitalInfo.encounterId || '1993854019030441985';
console.log('1111111111111111111准备使用的encounterId:', encounterId);
userMessage = {
certType: '02', // 证件类型
certNo: message.data.idNo, // 身份证号
psnCertType: '02', // 居民身份证
encounterId: encounterId || '1993854019030441985',
};
userCardInfo = {
certType: '01', // 证件类型
certNo: message.data.idNo, // 身份证号
psnCertType: '01', // 居民身份证
busiCardInfo: message.data.ecToken, // 令牌
encounterId: encounterId || '1993854019030441985',
};
BusiCardInfo.value = message.data.ecToken;
console.log(BusiCardInfo.value);
break;
case '02':
break;
case '03': // 社保卡
readCardLoading.value = true;
loadingText.value = '正在读取...';
await invokeYbPlugin(
await invokeYbPlugin5001(
JSON.stringify({
FunctionId: 1,
IP: 'ddjk.jlhs.gov.cn',
@@ -369,32 +369,34 @@ async function handleReadCard(value) {
)
.then((res) => {
jsonResult = JSON.stringify(res.data);
let message1 = JSON.parse(jsonResult);
// 从patientInfo中获取encounterId如果没有则尝试从住院号中获取
// const encounterId =
// props.patientInfo?.encounterId || props.patientInfo?.visitNo || props.patientInfo?.busNo;
// console.log('准备使用的encounterId:', encounterId);
userMessage = {
certType: '02', // 证件类型
certNo: message1.SocialSecurityNumber, // 身份证号
psnCertType: '02', // 居民身份证
encounterId: encounterId || '1993854019030441985',
};
userCardInfo = {
certType: '02', // 证件类型
certNo: message1.SocialSecurityNumber, // 身份证号
psnCertType: '02', // 居民身份证
busiCardInfo: message1.BusiCardInfo, //卡号
encounterId: encounterId || '1993854019030441985',
};
BusiCardInfo.value = message1.BusiCardInfo;
console.log(message1.BusiCardInfo);
emits('carReading', res.data);
})
.finally(() => {
readCardLoading.value = false;
});
let message1 = JSON.parse(jsonResult);
// 从patientInfo中获取encounterId如果没有则尝试从住院号中获取
// const encounterId =
// props.patientInfo?.encounterId || props.patientInfo?.visitNo || props.patientInfo?.busNo;
// console.log('准备使用的encounterId:', encounterId);
userMessage = {
certType: '02', // 证件类型
certNo: message1.SocialSecurityNumber, // 身份证号
psnCertType: '02', // 居民身份证
encounterId: encounterId || '1993854019030441985',
};
userCardInfo = {
certType: '02', // 证件类型
certNo: message1.SocialSecurityNumber, // 身份证号
psnCertType: '02', // 居民身份证
busiCardInfo: message1.BusiCardInfo, //卡号
encounterId: encounterId || '1993854019030441985',
};
BusiCardInfo.value = message1.BusiCardInfo;
console.log(message1.BusiCardInfo);
break;
case '99':
break;
@@ -448,7 +450,12 @@ async function handleReadCard(value) {
const changFeeType = () => {
emits('onChangFeeType');
};
defineExpose({ submitForm, form, isEditing });
// 无档登记收集信息
const getPatientForm = () => {
return patientInfoFormRef?.value?.form;
};
defineExpose({ submitForm, form, isEditing, getPatientForm });
</script>
<style lang="scss" scoped>

View File

@@ -262,6 +262,7 @@ const data = reactive({
isViewMode: false,
form: {
typeCode: '01',
genderEnum: 0,
},
rules: {
name: [{ required: true, message: '姓名不能为空', trigger: 'change' }],

View File

@@ -19,6 +19,7 @@
ref="patientInfoRef"
:is-registered="props.isRegistered"
@onChangFeeType="onChangFeeType"
@carReading="onCarRead"
/>
<!-- <PatientRelationList
class="relationList"
@@ -39,6 +40,19 @@
</el-scrollbar>
<template v-slot:footer>
<div class="advance-container">
<div v-if="currentFeeType !== 'hipCash'" class="payment-item">
<span>{{ payType() }}支付</span>
<el-input
ref="txtCodeRef"
v-model="txtCode"
style="width: 300px; margin-left: 10px"
:placeholder="payType() + '支付码'"
/>
<el-button link type="primary" @click="handleWxPay()" style="margin-left: 10px"
>扫码支付</el-button
>
<el-button link type="primary" @click="getWxPayResult()">查看结果</el-button>
</div>
<el-space>
<div>缴费预交金</div>
<el-input
@@ -86,9 +100,12 @@ const { proxy } = getCurrentInstance();
import { ElMessageBox } from 'element-plus';
import PatientInfoComp from './patientInfo.vue';
import RegisterForm from './registerForm.vue';
import { noFilesRegister, registerInHospital, getInit, getProvincesAndCities } from './api';
import { noFilesRegister, registerInHospital, getProvincesAndCities } from './api';
import { getInit } from '../../../../doctorstation/components/api';
import { useRouter } from 'vue-router';
import { wxPay, WxPayResult } from '../../../../charge/cliniccharge/components/api';
import printUtils from '@/utils/printUtils';
const txtCode = ref('');
const router = useRouter();
const emits = defineEmits(['okAct', 'cancelAct']);
@@ -165,12 +182,58 @@ const handleSubmit = () => {
};
params.inHospitalInfo.payEnum = payEnum.value;
params.payEnum = payEnum.value;
debugger;
console.log('params==========>', JSON.stringify(patientInfoRef?.value.getPatientForm()));
if (props.noFile) {
const paramsDic = patientInfoRef?.value.getPatientForm();
const paramsDic1 = RegisterFormRef.value.submitForm;
if (!paramsDic?.name) {
ElMessage({
type: 'error',
message: '请输入患者姓名',
});
return;
} else if (!paramsDic?.phone) {
ElMessage({
type: 'error',
message: '请输入联系方式',
});
return;
} else if (!paramsDic?.age) {
ElMessage({
type: 'error',
message: '请输入年龄',
});
return;
} else if (!paramsDic1?.inHospitalOrgId) {
ElMessage({
type: 'error',
message: '请选择入院科室',
});
return;
} else if (!paramsDic1?.wardLocationId) {
ElMessage({
type: 'error',
message: '请选择入院病区',
});
return;
} else if (!paramsDic1?.diagnosisDefinitionId) {
ElMessage({
type: 'error',
message: '请选择入院诊断',
});
return;
}
// else if (!paramsDic1?.diagnosisDesc) {
// ElMessage({
// type: 'error',
// message: '请输入诊断描述',
// });
// return;
// }
RegisterFormRef.value.validateData(async () => {
params.inHospitalInfo = RegisterFormRef.value.submitForm;
params.inHospitalInfo.payEnum = payEnum.value;
params.patientInformation = patientInfoRef.value.getPatientForm();
params.patientInformation = patientInfoRef?.value.getPatientForm();
if (params.patientInformation.idCard) {
// 验证身份证号长度是否为18位
const idCard = params.patientInformation.idCard.toString();
@@ -184,7 +247,6 @@ const handleSubmit = () => {
}
}
console.log('params', params);
debugger;
params.inHospitalInfo.balanceAmount = advance.value;
const performRegistration = () => {
noFilesRegister(params).then((res) => {
@@ -194,47 +256,48 @@ const handleSubmit = () => {
ElMessage.success(res.msg);
// 打印预交金收据
printDepositReceipt(props.patientInfo, params.inHospitalInfo);
cancelAct();
// 询问是否需要医保登记
ElMessageBox.confirm('是否需要进行医保登记?', '医保登记确认', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'info',
})
.then(() => {
// 准备传递的数据
const cardData = {
patientInfo: params.patientInformation,
inHospitalInfo: params.inHospitalInfo,
encounterId: params.inHospitalInfo.encounterId,
};
// ElMessageBox.confirm('是否需要进行医保登记?', '医保登记确认', {
// confirmButtonText: '确认',
// cancelButtonText: '取消',
// type: 'info',
// })
// .then(() => {
// // 准备传递的数据
// const cardData = {
// patientInfo: params.patientInformation,
// inHospitalInfo: params.inHospitalInfo,
// encounterId: params.inHospitalInfo.encounterId,
// };
// 跳转到医保登记页面
try {
router
.push({
path: '/ybmanagement/ybInhospital/ybregisterEdit',
query: {
encounterId: props.patientInfo.encounterId,
cardData: encodeURIComponent(JSON.stringify(cardData)),
cardType: 'inHospital',
operationType: 'HospitalizationRegistration',
},
})
.then(() => {
console.log('路由跳转成功');
})
.catch((error) => {
console.error('路由跳转失败:', error);
ElMessage.error('跳转到医保登记页面失败');
});
} catch (error) {
console.error('跳转异常:', error);
}
})
.catch(() => {
// 用户取消医保登记,关闭当前弹窗
emits('okAct');
});
// // 跳转到医保登记页面
// try {
// router
// .push({
// path: '/ybmanagement/ybInhospital/ybregisterEdit',
// query: {
// encounterId: props.patientInfo.encounterId,
// cardData: encodeURIComponent(JSON.stringify(cardData)),
// cardType: 'inHospital',
// operationType: 'HospitalizationRegistration',
// },
// })
// .then(() => {
// console.log('路由跳转成功');
// })
// .catch((error) => {
// console.error('路由跳转失败:', error);
// ElMessage.error('跳转到医保登记页面失败');
// });
// } catch (error) {
// console.error('跳转异常:', error);
// }
// })
// .catch(() => {
// // 用户取消医保登记,关闭当前弹窗
// emits('okAct');
// });
} else {
ElMessage.error(res.msg);
}
@@ -259,7 +322,6 @@ const handleSubmit = () => {
params.patientId = props.patientInfo.patientId;
RegisterFormRef.value.validateData(async () => {
params = { ...params, ...RegisterFormRef.value.submitForm };
console.log('params', params);
const performRegistration = () => {
console.log('params', params);
registerInHospital(params).then((res) => {
@@ -273,48 +335,51 @@ const handleSubmit = () => {
params,
RegisterFormRef.value.medicalInsuranceTitle
);
// 询问是否需要医保登记
ElMessageBox.confirm('是否需要进行医保登记', '医保登记确认', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'info',
})
.then(() => {
// 准备传递的数据
const cardData = {
patientInfo: props.patientInfo,
inHospitalInfo: params,
};
// 跳转到医保登记页面
try {
router
.push({
path: '/ybmanagement/ybInhospital/ybregisterEdit',
query: {
encounterId: props.patientInfo.encounterId,
cardData: encodeURIComponent(JSON.stringify(cardData)),
cardType: 'inHospital',
operationType: 'HospitalizationRegistration',
certType: props.patientInfo.certType,
},
})
.then(() => {
console.log('路由跳转成功');
})
.catch((error) => {
console.error('路由跳转失败:', error);
ElMessage.error('跳转到医保登记页面失败');
});
} catch (error) {
console.error('跳转异常:', error);
}
// 自费不需要弹医保
if (params.contractNo != '0000') {
// 询问是否需要医保登记
ElMessageBox.confirm('是否需要进行医保登记?', '医保登记确认', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'info',
})
.catch(() => {
// 用户取消医保登记,关闭当前弹窗
emits('okAct');
});
.then(() => {
// 准备传递的数据
const cardData = {
patientInfo: props.patientInfo,
inHospitalInfo: params,
};
// 跳转到医保登记页面
try {
router
.push({
path: '/ybmanagement/ybInhospital/ybregisterEdit',
query: {
encounterId: props.patientInfo.encounterId,
cardData: encodeURIComponent(JSON.stringify(cardData)),
cardType: 'inHospital',
operationType: 'HospitalizationRegistration',
certType: props.patientInfo.certType,
},
})
.then(() => {
console.log('路由跳转成功');
})
.catch((error) => {
console.error('路由跳转失败:', error);
ElMessage.error('跳转到医保登记页面失败');
});
} catch (error) {
console.error('跳转异常:', error);
}
})
.catch(() => {
// 用户取消医保登记,关闭当前弹窗
emits('okAct');
});
}
cancelAct();
} else {
ElMessage.error(res.msg);
}
@@ -355,6 +420,8 @@ const closedAct = () => {
onMounted(() => {
getInit().then((res) => {
console.log('getInit=========>', JSON.stringify(res.data));
initOptions.value = res.data;
});
});
@@ -522,6 +589,55 @@ const convertToChineseNumber = (amount) => {
/* 导入用户信息 */
import useUserStore from '@/store/modules/user';
const userStore = useUserStore();
function handleWxPay() {
wxPay({
// 支付码
txtCode: txtCode.value,
// 收费项id 住院怎么给
chargeItemIds: props.chargeItemIds,
encounterId: props.patientInfo.encounterId,
// 支付id 住院怎么给
id: props.paymentId,
// 支付详情 住院怎么给 格式[{ payEnum: 220100, amount: 0.0, payLevelEnum: 2 }]
paymentDetails: formData.selfPay,
// 读卡的时候获取的
ybMdtrtCertType: props.userCardInfo.psnCertType,
// 读卡获取
busiCardInfo: props.userCardInfo.busiCardInfo,
});
}
function getWxPayResult() {
WxPayResult({
txtCode: txtCode.value,
chargeItemIds: props.chargeItemIds,
encounterId: props.patientInfo.encounterId,
id: props.paymentId,
paymentDetails: formData.selfPay,
ybMdtrtCertType: props.userCardInfo.psnCertType,
busiCardInfo: props.userCardInfo.busiCardInfo,
});
}
// 根据不同支付方式,显示不同的支付方式详情
const payType = () => {
switch (currentFeeType.value) {
case 'hipCash':
return '现金';
case 'hipAlipay':
return '支付宝';
case 'wechat':
return '微信卡';
case 'hipPayCard':
return '银行卡';
default:
return '';
}
};
// 读卡操作
const onCarRead = (a) => {
console.log('读卡操作:', a);
};
</script>
<style lang="scss" scoped>
.patientRegister-container {
@@ -532,7 +648,12 @@ const userStore = useUserStore();
.advance-container {
width: 660px;
display: flex;
flex-direction: column;
.payment-item {
display: flex;
align-items: center;
margin-bottom: 12px;
}
.feeType {
display: flex;
align-items: center;

View File

@@ -134,9 +134,9 @@
<el-form-item label="患者病情">
<el-select v-model="submitForm.priorityEnum" :disabled="props.isRegistered">
<el-option
v-for="item in props.initOptions.priorityEnumList"
v-for="item in props.initOptions.priorityLevelOptionOptions"
:key="item.value"
:label="item.info"
:label="item.label"
:value="item.value"
/>
</el-select>
@@ -280,13 +280,13 @@ const rules = reactive({
trigger: ['blur', 'change'],
},
],
diagnosisDesc: [
{
required: true,
message: '诊断描述未填写',
trigger: ['blur', 'change'],
},
],
// diagnosisDesc: [
// {
// required: true,
// message: '诊断描述未填写',
// trigger: ['blur', 'change'],
// },
// ],
});
//获取省市医保字符串
@@ -298,11 +298,13 @@ const medicalInsuranceTitle = ref('');
// });
const getProvincesAndCitiesInfo = async () => {
try {
const res = await getProvincesAndCities(props.inHospitalInfo.encounterId);
// console.log('获取省市医保字符串', res);
if (res && res.code == 200) {
// 确保有数据时才更新
medicalInsuranceTitle.value = res.data?.insutype || res.data || '';
if (inHospitalInfo.encounterId) {
const res = await getProvincesAndCities(props.inHospitalInfo.encounterId);
// console.log('获取省市医保字符串', res);
if (res && res.code == 200) {
// 确保有数据时才更新
medicalInsuranceTitle.value = res.data?.insutype || res.data || '';
}
}
} catch (error) {
// 静默处理错误,确保页面不会显示错误信息

View File

@@ -22,7 +22,6 @@ const props = defineProps({});
const state = reactive({});
defineExpose({ state });
const activeName = ref('first');
</script>
<style lang="scss" scoped>
.sds {

View File

@@ -1,72 +1,89 @@
<template>
<div @keyup="handleKeyDown" tabindex="0" ref="tableWrapper">
<el-table
<div @keyup="handleKeyDown" tabindex="0" ref="tableWrapper" class="advice-base-list-wrapper">
<Table
ref="adviceBaseRef"
height="400"
:data="adviceBaseList"
highlight-current-row
@current-change="handleCurrentChange"
:table-data="adviceBaseList"
:table-columns="tableColumns"
:highlight-current-row="true"
:table-height="400"
:max-height="400"
:loading="loading"
row-key="patientId"
@cell-click="clickRow"
@row-click="handleRowClick"
>
<el-table-column label="名称" align="center" prop="adviceName" />
<el-table-column label="类型" align="center" prop="activityType_enumText" />
<el-table-column label="包装单位" align="center" prop="unitCode_dictText" />
<el-table-column label="最小单位" align="center" prop="minUnitCode_dictText" />
<el-table-column label="规格" align="center" prop="volume" />
<el-table-column label="用法" align="center" prop="methodCode_dictText" />
<el-table-column label="库存数量" align="center">
<template #default="scope">{{ handleQuantity(scope.row) }}</template>
</el-table-column>
<el-table-column label="频次" align="center" prop="rateCode_dictText" />
<!-- <el-table-column label="单次剂量" align="center" prop="dose" /> -->
<!-- <el-table-column label="剂量单位" align="center" prop="doseUnitCode_dictText" /> -->
<el-table-column label="注射药品" align="center" prop="injectFlag_enumText" />
<el-table-column label="皮试" align="center" prop="skinTestFlag_enumText" />
<el-table-column label="医保码" align="center" prop="ybNo" />
<!-- <el-table-column label="限制使用标志" align="center" prop="useLimitFlag" /> -->
<el-table-column
label="限制使用范围"
align="center"
:show-overflow-tooltip="true"
prop="useScope"
>
<template #default="scope">
<span v-if="scope.row.useLimitFlag === 1">{{ scope.row.useScope }}</span>
<span v-else>{{ '-' }}</span>
</template>
</el-table-column>
</el-table>
<template #quantity="{ row }">
{{ handleQuantity(row) }}
</template>
<template #useScope="{ row }">
<span v-if="row.useLimitFlag === 1">{{ row.useScope }}</span>
<span v-else>{{ '-' }}</span>
</template>
</Table>
</div>
</template>
<script setup>
import { nextTick } from 'vue';
import { getAdviceBaseInfo } from './api';
<script setup lang="ts">
import { ref, nextTick, watch, computed } from 'vue';
import { throttle } from 'lodash-es';
import Table from '@/components/TableLayout/Table.vue';
import { getAdviceBaseInfo } from './api';
import type { TableColumn } from '@/components/types/TableLayout.d';
const props = defineProps({
adviceQueryParams: {
type: Object,
default: '',
},
interface Props {
adviceQueryParams?: {
searchKey?: string;
adviceType?: string;
};
patientInfo: {
type: Object,
required: true,
},
inHospitalOrgId?: string;
[key: string]: any;
};
}
const props = withDefaults(defineProps<Props>(), {
adviceQueryParams: () => ({}),
});
const emit = defineEmits(['selectAdviceBase']);
const total = ref(0);
const adviceBaseRef = ref();
const tableWrapper = ref();
const currentIndex = ref(0); // 当前选中行索引
const currentSelectRow = ref({});
const emit = defineEmits<{
selectAdviceBase: [row: any];
}>();
const total = ref<number>(0);
const loading = ref<boolean>(false);
const adviceBaseRef = ref<InstanceType<typeof Table> | null>(null);
const tableWrapper = ref<HTMLDivElement | null>(null);
const currentIndex = ref<number>(0);
const currentSelectRow = ref<any>({});
const queryParams = ref({
pageSize: 100,
pageNum: 1,
adviceTypes: '1,3'
adviceTypes: '1,3',
searchKey: '',
organizationId: '',
});
const adviceBaseList = ref([]);
const adviceBaseList = ref<any[]>([]);
// 表格列配置
const tableColumns = computed<TableColumn[]>(() => [
{ label: '名称', prop: 'adviceName', align: 'center', width: 200 },
{ label: '类型', prop: 'activityType_enumText', align: 'center' },
{ label: '包装单位', prop: 'unitCode_dictText', align: 'center' },
{ label: '最小单位', prop: 'minUnitCode_dictText', align: 'center' },
{ label: '规格', prop: 'volume', align: 'center' },
{ label: '用法', prop: 'methodCode_dictText', align: 'center' },
{ label: '库存数量', prop: 'quantity', align: 'center', slot: 'quantity' },
{ label: '频次', prop: 'rateCode_dictText', align: 'center' },
{ label: '注射药品', prop: 'injectFlag_enumText', align: 'center' },
{ label: '皮试', prop: 'skinTestFlag_enumText', align: 'center' },
{ label: '医保码', prop: 'ybNo', align: 'center' },
{
label: '限制使用范围',
prop: 'useScope',
align: 'center',
showOverflowTooltip: true,
slot: 'useScope',
},
]);
// 节流函数
const throttledGetList = throttle(
() => {
@@ -88,39 +105,52 @@ watch(
getList();
function getList() {
loading.value = true;
queryParams.value.organizationId = props.patientInfo.inHospitalOrgId;
getAdviceBaseInfo(queryParams.value).then((res) => {
if (res.data.records.length > 0) {
adviceBaseList.value = res.data.records.filter((item) => {
if (item.adviceType == 1 || item.adviceType == 2) {
return handleQuantity(item) != 0;
} else {
return true;
}
});
total.value = res.data.total;
nextTick(() => {
currentIndex.value = 0;
adviceBaseRef.value.setCurrentRow(adviceBaseList.value[0]);
});
}
});
getAdviceBaseInfo(queryParams.value)
.then((res) => {
console.log(res.data.records);
if (res.data.records.length > 0) {
adviceBaseList.value = res.data.records.filter((item) => {
if (item.adviceType == 1 || item.adviceType == 2) {
return handleQuantity(item) != 0;
} else {
return true;
}
});
total.value = res.data.total;
nextTick(() => {
if (adviceBaseList.value.length > 0) {
currentIndex.value = 0;
setCurrentRow(adviceBaseList.value[0]);
}
});
} else {
adviceBaseList.value = [];
}
})
.catch(() => {
adviceBaseList.value = [];
})
.finally(() => {
loading.value = false;
});
}
// 处理键盘事件
const handleKeyDown = (event) => {
const handleKeyDown = (event: KeyboardEvent): void => {
const key = event.key;
const data = adviceBaseList.value;
switch (key) {
case 'ArrowUp': // 上箭头
event.preventDefault(); // 阻止默认滚动行为
event.preventDefault();
if (currentIndex.value > 0) {
currentIndex.value--;
setCurrentRow(data[currentIndex.value]);
}
break;
case 'ArrowDown': // 下箭头`
case 'ArrowDown': // 下箭头
event.preventDefault();
if (currentIndex.value < data.length - 1) {
currentIndex.value++;
@@ -128,52 +158,84 @@ const handleKeyDown = (event) => {
}
break;
case 'Enter': // 回车键
// const currentRow = adviceBaseRef.value.getSelectionRows();
event.preventDefault();
if (currentSelectRow.value) {
// 这里可以触发自定义逻辑,如弹窗、跳转等
if (currentSelectRow.value && Object.keys(currentSelectRow.value).length > 0) {
emit('selectAdviceBase', currentSelectRow.value);
}
break;
}
};
function handleQuantity(row) {
function handleQuantity(row: any): string {
if (row.inventoryList && row.inventoryList.length > 0) {
const totalQuantity = row.inventoryList.reduce((sum, item) => sum + (item.quantity || 0), 0);
return totalQuantity.toString() + row.minUnitCode_dictText;
const totalQuantity = row.inventoryList.reduce(
(sum: number, item: any) => sum + (item.quantity || 0),
0
);
return totalQuantity.toString() + (row.minUnitCode_dictText || '');
}
return 0;
return '0';
}
// 设置选中行(带滚动)
const setCurrentRow = (row) => {
adviceBaseRef.value.setCurrentRow(row);
// 滚动到选中行
const tableBody = adviceBaseRef.value.$el.querySelector('.el-table__body-wrapper');
const currentRowEl = adviceBaseRef.value.$el.querySelector('.current-row');
if (tableBody && currentRowEl) {
currentRowEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
const setCurrentRow = (row: any) => {
if (adviceBaseRef.value?.tableRef) {
adviceBaseRef.value.tableRef.setCurrentRow(row);
// 滚动到选中行
nextTick(() => {
const tableEl = adviceBaseRef.value?.tableRef?.$el;
if (tableEl) {
const tableBody = tableEl.querySelector('.el-table__body-wrapper');
const currentRowEl = tableEl.querySelector('.current-row');
if (tableBody && currentRowEl) {
currentRowEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
}
});
}
};
// 当前行变化时更新索引
const handleCurrentChange = (currentRow) => {
currentIndex.value = adviceBaseList.value.findIndex((item) => item === currentRow);
currentSelectRow.value = currentRow;
// 行点击事件
const handleRowClick = (row: any): void => {
currentIndex.value = adviceBaseList.value.findIndex((item) => item === row);
currentSelectRow.value = row;
emit('selectAdviceBase', row);
};
function clickRow(row) {
emit('selectAdviceBase', row);
}
// 监听表格当前行变化(通过 el-table 的 current-change 事件)
watch(
() => adviceBaseRef.value?.tableRef,
(tableRef) => {
if (tableRef) {
// 通过 $el 访问原生 el-table 并监听 current-change
const elTable = tableRef.$el?.querySelector('.el-table');
if (elTable) {
// 使用 MutationObserver 或直接监听 DOM 变化来检测当前行变化
// 或者通过 watch 监听 currentSelectRow 的变化
}
}
},
{ immediate: true }
);
defineExpose({
handleKeyDown,
});
</script>
<style scoped>
.popover-table-wrapper:focus {
outline: 2px solid #409eff; /* 聚焦时的高亮效果 */
<style scoped lang="scss">
.advice-base-list-wrapper {
height: 400px;
overflow: hidden;
display: flex;
flex-direction: column;
&:focus {
outline: 2px solid #409eff;
}
}
</style>
.popover-table-wrapper:focus {
outline: 2px solid #409eff;
}
</style>

View File

@@ -110,6 +110,7 @@ import { getCurrentInstance, ref, watch, computed } from 'vue';
import { Refresh } from '@element-plus/icons-vue';
import { patientInfo } from '../../store/patient.js';
import { getBloodTransfusion } from './api';
import { getOrgList } from '../../../../doctorstation/components/api.js';
const { proxy } = getCurrentInstance();
@@ -118,6 +119,7 @@ const loading = ref(false);
const detailDialogVisible = ref(false);
const currentDetail = ref(null);
const descJsonData = ref(null);
const orgOptions = ref([]);
const fetchData = async () => {
if (!patientInfo.value?.encounterId) {
@@ -172,12 +174,40 @@ const hasMatchedFields = computed(() => {
return Object.keys(descJsonData.value).some((key) => isFieldMatched(key));
});
/** 查询科室 */
const getLocationInfo = () => {
getOrgList().then((res) => {
orgOptions.value = res.data.records;
});
};
const recursionFun = (targetDepartment) => {
let name = '';
for (let index = 0; index < orgOptions.value.length; index++) {
const obj = orgOptions.value[index];
if (obj.id == targetDepartment) {
name = obj.name;
}
const subObjArray = obj['children'];
for (let index = 0; index < subObjArray.length; index++) {
const item = subObjArray[index];
if (item.id == targetDepartment) {
name = item.name;
}
}
}
return name;
};
const handleViewDetail = (row) => {
currentDetail.value = row;
// 解析 descJson
if (row.descJson) {
try {
descJsonData.value = JSON.parse(row.descJson);
// descJsonData.value = JSON.parse(row.descJson);
const obj = JSON.parse(row.descJson);
obj.targetDepartment = recursionFun(obj.targetDepartment);
descJsonData.value = obj;
} catch (e) {
console.error('解析 descJson 失败:', e);
descJsonData.value = null;
@@ -193,6 +223,7 @@ watch(
(val) => {
if (val) {
fetchData();
getLocationInfo();
} else {
tableData.value = [];
}

View File

@@ -110,6 +110,7 @@ import { getCurrentInstance, ref, watch, computed } from 'vue';
import { Refresh } from '@element-plus/icons-vue';
import { patientInfo } from '../../store/patient.js';
import { getCheck } from './api';
import { getOrgList } from '../../../../doctorstation/components/api.js';
const { proxy } = getCurrentInstance();
@@ -118,6 +119,7 @@ const loading = ref(false);
const detailDialogVisible = ref(false);
const currentDetail = ref(null);
const descJsonData = ref(null);
const orgOptions = ref([]);
const fetchData = async () => {
if (!patientInfo.value?.encounterId) {
@@ -172,12 +174,41 @@ const hasMatchedFields = computed(() => {
return Object.keys(descJsonData.value).some((key) => isFieldMatched(key));
});
/** 查询科室 */
const getLocationInfo = () => {
getOrgList().then((res) => {
orgOptions.value = res.data.records;
});
};
const recursionFun = (targetDepartment) => {
let name = '';
for (let index = 0; index < orgOptions.value.length; index++) {
const obj = orgOptions.value[index];
if (obj.id == targetDepartment) {
name = obj.name;
}
const subObjArray = obj['children'];
for (let index = 0; index < subObjArray.length; index++) {
const item = subObjArray[index];
if (item.id == targetDepartment) {
name = item.name;
}
}
}
return name;
};
const handleViewDetail = (row) => {
console.log('targetDepartment========>', JSON.stringify(row));
currentDetail.value = row;
// 解析 descJson
if (row.descJson) {
try {
descJsonData.value = JSON.parse(row.descJson);
const obj = JSON.parse(row.descJson);
obj.targetDepartment = recursionFun(obj.targetDepartment);
descJsonData.value = obj;
} catch (e) {
console.error('解析 descJson 失败:', e);
descJsonData.value = null;
@@ -193,6 +224,7 @@ watch(
(val) => {
if (val) {
fetchData();
getLocationInfo();
} else {
tableData.value = [];
}

View File

@@ -110,6 +110,7 @@ import { getCurrentInstance, ref, watch, computed } from 'vue';
import { Refresh } from '@element-plus/icons-vue';
import { patientInfo } from '../../store/patient.js';
import { getSurgery } from './api';
import { getOrgList } from '../../../../doctorstation/components/api.js';
const { proxy } = getCurrentInstance();
@@ -118,6 +119,7 @@ const loading = ref(false);
const detailDialogVisible = ref(false);
const currentDetail = ref(null);
const descJsonData = ref(null);
const orgOptions = ref([]);
const fetchData = async () => {
if (!patientInfo.value?.encounterId) {
@@ -172,12 +174,40 @@ const hasMatchedFields = computed(() => {
return Object.keys(descJsonData.value).some((key) => isFieldMatched(key));
});
/** 查询科室 */
const getLocationInfo = () => {
getOrgList().then((res) => {
orgOptions.value = res.data.records;
});
};
const recursionFun = (targetDepartment) => {
let name = '';
for (let index = 0; index < orgOptions.value.length; index++) {
const obj = orgOptions.value[index];
if (obj.id == targetDepartment) {
name = obj.name;
}
const subObjArray = obj['children'];
for (let index = 0; index < subObjArray.length; index++) {
const item = subObjArray[index];
if (item.id == targetDepartment) {
name = item.name;
}
}
}
return name;
};
const handleViewDetail = (row) => {
currentDetail.value = row;
// 解析 descJson
if (row.descJson) {
try {
descJsonData.value = JSON.parse(row.descJson);
// descJsonData.value = JSON.parse(row.descJson);
const obj = JSON.parse(row.descJson);
obj.targetDepartment = recursionFun(obj.targetDepartment);
descJsonData.value = obj;
} catch (e) {
console.error('解析 descJson 失败:', e);
descJsonData.value = null;
@@ -193,6 +223,7 @@ watch(
(val) => {
if (val) {
fetchData();
getLocationInfo();
} else {
tableData.value = [];
}

View File

@@ -110,6 +110,7 @@ import { getCurrentInstance, ref, watch, computed } from 'vue';
import { Refresh } from '@element-plus/icons-vue';
import { patientInfo } from '../../store/patient.js';
import { getInspection } from './api';
import { getOrgList } from '../../../../doctorstation/components/api.js';
const { proxy } = getCurrentInstance();
@@ -118,6 +119,7 @@ const loading = ref(false);
const detailDialogVisible = ref(false);
const currentDetail = ref(null);
const descJsonData = ref(null);
const orgOptions = ref([]);
const fetchData = async () => {
if (!patientInfo.value?.encounterId) {
@@ -172,12 +174,40 @@ const hasMatchedFields = computed(() => {
return Object.keys(descJsonData.value).some((key) => isFieldMatched(key));
});
/** 查询科室 */
const getLocationInfo = () => {
getOrgList().then((res) => {
orgOptions.value = res.data.records;
});
};
const recursionFun = (targetDepartment) => {
let name = '';
for (let index = 0; index < orgOptions.value.length; index++) {
const obj = orgOptions.value[index];
if (obj.id == targetDepartment) {
name = obj.name;
}
const subObjArray = obj['children'];
for (let index = 0; index < subObjArray.length; index++) {
const item = subObjArray[index];
if (item.id == targetDepartment) {
name = item.name;
}
}
}
return name;
};
const handleViewDetail = (row) => {
currentDetail.value = row;
// 解析 descJson
if (row.descJson) {
try {
descJsonData.value = JSON.parse(row.descJson);
const obj = JSON.parse(row.descJson);
obj.targetDepartment = recursionFun(obj.targetDepartment);
descJsonData.value = obj;
// descJsonData.value = JSON.parse(row.descJson);
} catch (e) {
console.error('解析 descJson 失败:', e);
descJsonData.value = null;
@@ -193,6 +223,7 @@ watch(
(val) => {
if (val) {
fetchData();
getLocationInfo();
} else {
tableData.value = [];
}

View File

@@ -259,7 +259,17 @@ watch(
function getList() {
getEncounterDiagnosis(patientInfo.value.encounterId).then((res) => {
if (res.code == 200) {
form.value.diagnosisList = res.data;
const datas = (res.data || []).map((item) => {
let obj = {
...item,
};
if (obj.diagSrtNo == null) {
obj.diagSrtNo = '1';
}
return obj;
});
form.value.diagnosisList = datas;
// form.value.diagnosisList = res.data;
emits('diagnosisSave', false);
}
});

Some files were not shown because too many files have changed in this diff Show More