| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772 |
- <template>
- <div class="bed-card-container">
- <!-- 床头卡主体 -->
- <div class="bed-card">
- <!-- 头部信息 -->
- <div class="card-header">
- <img src="../../assets/imgs/logo_card.png" alt="颐年医养" class="header-logo" />
- <div style="display: flex; align-items: center">
- <h2 class="header-title">长者床头卡</h2>
- </div>
- <div style="font-size: 16px; color: #fff; margin-right: 10px"
- >更新时间:{{ formatToDate(patientData.updateTime) || '' }}</div
- >
- <!-- <div class="header-room">{{ patientData.roomName || '' }}</div>-->
- </div>
- <div class="card-main">
- <!-- 左侧床号信息 -->
- <div class="left-section">
- <div class="bed-info">
- <div class="bed-letter" v-for="(item, index) in bedList" :key="index">
- <span>{{ item || '' }}</span>
- <span v-if="index == (bedList || []).length - 1 && !item.includes('床')" class="bed-text"
- >床</span
- >
- </div>
- </div>
- <!-- 二维码 -->
- <div class="qrcode-container">
- <Qrcode :text="qrCodeContent" :width="120" :logo="logoUrl" tag="canvas" />
- <div class="qrcode-text">长者二维码</div>
- </div>
- </div>
- <!-- 右侧信息表格 -->
- <div class="right-section">
- <table class="info-table">
- <!-- 基本信息行 -->
- <tr class="row-basic">
- <td class="table-label">姓名</td>
- <td class="table-data">{{ patientData.elderName || '--' }}</td>
- <td class="table-label">性别</td>
- <td class="table-data">{{ patientData.elderSex || '--' }}</td>
- <td class="table-label">生日</td>
- <td class="table-data">{{ patientData.birthday || '--' }}</td>
- <td class="table-label">入住时间</td>
- <td class="table-data">{{ patientData.checkInTime || '--' }}</td>
- </tr>
- <!-- 护理等级 / 照护要点 / 过敏史 -->
- <tr class="row-care">
- <td class="table-label">护理等级</td>
- <td class="table-data">{{ patientData.careLevel || '--' }}</td>
- <td class="table-label">照护要点</td>
- <td class="table-data" style="font-size: 14px" colspan="3">{{
- patientData.carePoints || '--'
- }}</td>
- <td class="table-label">过敏史</td>
- <td class="table-data" style="font-size: 14px">{{
- patientData.allergiesText || '--'
- }}</td>
- </tr>
- <!-- 既往病史 / 用药史 -->
- <tr class="row-medical">
- <td class="table-label">既往病史</td>
- <td class="table-data" style="font-size: 14px" colspan="3">{{
- patientData.medicalHistory || '--'
- }}</td>
- <td class="table-label">用药史</td>
- <td class="table-data" style="font-size: 14px" colspan="3">{{
- patientData.medicationHistory || '--'
- }}</td>
- </tr>
- <!-- 饮食要点 -->
- <tr class="row-diet">
- <td class="table-label">饮食要点</td>
- <td class="table-data diet-cell" colspan="7">
- <div v-if="hasDietGuidelines" class="diet-list">
- <div
- v-for="item in dietTypes"
- :key="item.value"
- class="diet-item"
- :class="{ active: isDietActive(item.label) }"
- >
- <el-icon v-if="isDietActive(item.label)" style="margin-right: 2px" size="22"
- ><Check
- /></el-icon>
- <!-- <span v-if="isDietActive(item.label)" class="diet-check">√</span>-->
- <span class="diet-name" style="font-size: 16px">{{ item.label }}</span>
- </div>
- </div>
- <div v-else class="diet-empty">--</div>
- </td>
- </tr>
- <!-- 九防注意事项 -->
- <tr class="row-prevention">
- <td class="table-label prevention-title">
- <div class="vertical-text">九防注意事项</div>
- </td>
- <td class="table-data prevention-cell" colspan="7">
- <div class="prevention-grid">
- <div
- v-for="(item, index) in preventionItems"
- :key="item.key"
- class="prevention-item"
- :class="[`prevention-pos-${index + 1}`]"
- >
- <!-- <div class="risk-badge" v-if="patientData[item.field]">
- <span class="risk-star">★</span>
- <span class="risk-text">{{ getRiskText(patientData[item.field]) }}</span>
- </div> -->
- <img
- v-if="
- patientData[item.field] &&
- getPreventionImage(item.field, patientData[item.field])
- "
- :src="getPreventionImage(item.field, patientData[item.field])"
- :alt="item.name"
- class="placeholder-image"
- />
- <div
- v-else
- class="prevention-image placeholder-image"
- style="display: flex; flex-direction: column"
- >
- <div style="font-size: 14px; color: #666666">{{ item.name }}</div>
- <div style="font-size: 14px; color: #666666">(无风险)</div>
- </div>
- <!-- <div class="prevention-name">{{ item.name }}</div>-->
- </div>
- </div>
- </td>
- </tr>
- <!-- 责任人员 -->
- <tr class="row-staff">
- <td class="table-label"><div>责任</div><div>护理员</div></td>
- <td class="table-data" colspan="2">{{
- patientData.responsibleStaff?.nurseAide || '--'
- }}</td>
- <td class="table-label">责任护士</td>
- <td class="table-data" colspan="2">{{
- patientData.responsibleStaff?.nurse || '--'
- }}</td>
- <td class="table-label">主管医生</td>
- <td class="table-data">{{ patientData.responsibleStaff?.doctor || '--' }}</td>
- </tr>
- </table>
- </div>
- </div>
- <!-- 底部装饰条 -->
- <div class="card-footer"></div>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, reactive, onMounted, computed } from 'vue'
- // 定义全局window接口扩展
- declare global {
- interface Window {
- __GlobalZipManager?: any
- }
- }
- import { ElMessage } from 'element-plus'
- import html2canvas from 'html2canvas'
- import { Qrcode } from '@/components/Qrcode'
- import JSZip from 'jszip'
- import { Check } from '@element-plus/icons-vue'
- import { formatToDate } from '@/utils/dateUtil'
- // Props定义
- const props = defineProps<{
- roomNumber?: string
- bedLetter?: string
- showLogo?: boolean
- qrcodeUrl?: string
- }>()
- // 默认值
- const qrCodeContent = ref('')
- const logoUrl = ref('/logo.png')
- // 饮食类型
- const dietTypes = [
- { value: '1', label: '普食' },
- { value: '2', label: '软食' },
- { value: '3', label: '半流质' },
- { value: '4', label: '流质' },
- { value: '5', label: '鼻饲' }
- ]
- // 九防项目配置
- const preventionItems = [
- { key: 'asphyxiation', name: '防噎食', field: 'asphyxiationByChoking' },
- { key: 'pressureSores', name: '防压疮', field: 'pressureSores' },
- { key: 'bedFall', name: '防坠床', field: 'fallPreventionMeasures' },
- { key: 'selfHarm', name: '防他伤和自伤', field: 'commitSuicide' },
- { key: 'foodDrug', name: '防误食', field: 'assessSimpleMentalState' },
- { key: 'scald', name: '防烫伤', field: 'empyrosis' },
- { key: 'fall', name: '防跌倒', field: 'fallDown' },
- { key: 'wandering', name: '防走失', field: 'wanderAway' },
- { key: 'entertainment', name: '防文娱活动意外', field: 'antiEntertainment' }
- ]
- // 判断饮食类型是否激活
- const hasDietGuidelines = computed(() => {
- return !!(patientData.dietaryGuidelines && patientData.dietaryGuidelines.trim())
- })
- const isDietActive = (label: string) => {
- const value = patientData.dietaryGuidelines
- // 1. 如果是空值,直接返回 false
- if (!value) return false
- // 2. 如果是字符串(例如 '半流质'),直接做全等判断
- if (typeof value === 'string') {
- return value === label
- }
- return false
- }
- // 风险等级文本
- const getRiskText = (value: string) => {
- const map: Record<string, string> = {
- high: '高风险',
- medium: '中风险',
- low: '低风险',
- none: '无风险'
- }
- return map[value] || value || ''
- }
- // 根据风险等级获取九防图片地址
- const getPreventionImage = (field: string, level: string) => {
- const levelMap: Record<string, string> = {
- 低: '01',
- 中: '02',
- 高: '03'
- }
- const suffix = levelMap[level] || '00'
- if (suffix === '00') {
- return false
- } else {
- return `/jiufang/${field}-${suffix}.png`
- }
- }
- // 患者数据
- const patientData = reactive({
- roomName: '',
- bedName: '',
- elderName: '',
- elderSex: '',
- birthday: '',
- checkInTime: '',
- careLevel: '',
- carePoints: '',
- allergiesText: '',
- medicalHistory: '',
- medicationHistory: '',
- dietaryGuidelines: '',
- responsibleStaff: {
- nurseAide: '',
- nurse: '',
- doctor: ''
- },
- asphyxiationByChoking: '',
- pressureSores: '',
- fallPreventionMeasures: '',
- assessSimpleMentalState: '',
- empyrosis: '',
- fallDown: '',
- wanderAway: '',
- antiEntertainment: '',
- commitSuicide: '',
- qr: {
- eId: '',
- tId: ''
- }
- })
- // 使用全局变量方式实现真正的全局单例管理
- if (!(window as any).__GlobalZipManager) {
- ;(window as any).__GlobalZipManager = {
- instance: null as JSZip | null,
- count: 0,
- getZip: function (): JSZip {
- if (!this.instance) {
- this.instance = new JSZip()
- this.count = 0
- console.log('创建了新的全局JSZip单例实例')
- }
- return this.instance
- },
- incrementCount: function (): void {
- this.count++
- },
- getCount: function (): number {
- return this.count
- },
- reset: function (): void {
- this.instance = null
- this.count = 0
- console.log('已重置全局JSZip单例实例')
- },
- getFileList: function (): string[] {
- if (!this.instance) return []
- return Object.keys(this.instance.files)
- }
- }
- }
- const GlobalZipManager = (window as any).__GlobalZipManager
- const fileCount = ref(0)
- onMounted(() => {})
- // 导出床头卡
- const exportAsImage = async (type: number) => {
- try {
- const element = document.querySelector('.bed-card')
- if (!element) return
- const canvas = await html2canvas(element as HTMLElement, {
- scale: 2,
- useCORS: true,
- backgroundColor: '#ffffff',
- logging: false,
- allowTaint: true,
- foreignObjectRendering: false,
- imageTimeout: 0,
- removeContainer: true,
- scrollX: 0,
- scrollY: 0,
- width: element.scrollWidth,
- height: element.scrollHeight
- })
- const fileName = `${patientData.elderName}${patientData.bedName}床头卡.png`
- const imageBase64 = canvas.toDataURL('image/png', 1.0)
- const base64Data = imageBase64.split(',')[1]
- if (type === 2) {
- const zipInstance = GlobalZipManager.getZip()
- zipInstance.file(fileName, base64Data, { base64: true })
- GlobalZipManager.incrementCount()
- fileCount.value = GlobalZipManager.getCount()
- } else if (type === 3) {
- const zipInstance = GlobalZipManager.getZip()
- zipInstance.file(fileName, base64Data, { base64: true })
- GlobalZipManager.incrementCount()
- fileCount.value = GlobalZipManager.getCount()
- const fileNames = GlobalZipManager.getFileList()
- const content = await zipInstance.generateAsync({ type: 'blob' })
- const link = document.createElement('a')
- link.href = URL.createObjectURL(content)
- link.download = `床头卡压缩包_${new Date().getTime()}.zip`
- link.click()
- GlobalZipManager.reset()
- fileCount.value = 0
- ElMessage.success(`床头卡压缩包导出成功,共包含${fileNames.length}个文件`)
- } else {
- const link = document.createElement('a')
- link.href = imageBase64
- link.download = fileName
- link.click()
- ElMessage.success('床头卡导出成功')
- }
- } catch (error) {
- console.error('导出失败:', error)
- ElMessage.error('导出失败,请稍后重试')
- if (type === 2 || type === 3) {
- GlobalZipManager.reset()
- fileCount.value = 0
- }
- }
- }
- const bedList = ref<string[]>()
- // 更新患者数据的方法
- const updatePatientData = (data: any) => {
- qrCodeContent.value =
- 'https://callback.ynims.com/qrcode/elderlyId?=' + data.qr?.eId + '&tenantId=' + data.qr?.tId
- Object.assign(patientData, data)
- console.log('收到数据:', data)
- try {
- bedList.value = patientData.bedName.toString().split('-')
- //console.log(bedList.value)
- } catch (e) {}
- }
- // 重置临时zip数据
- const resetTempZip = () => {
- GlobalZipManager.reset()
- fileCount.value = 0
- console.log('已重置临时zip数据')
- }
- // 暴露方法给父组件
- defineExpose({
- updatePatientData,
- exportAsImage,
- resetTempZip
- })
- </script>
- <style scoped>
- .bed-card-container {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 12px;
- }
- .bed-card {
- width: 980px;
- height: 680px;
- background-color: white;
- border: 1px solid #e0e0e0;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- position: relative;
- overflow: hidden;
- display: flex;
- flex-direction: column;
- }
- .card-header {
- background-color: #e78d35;
- color: white;
- padding: 8px 10px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- height: 76px;
- box-sizing: border-box;
- }
- .header-title {
- margin: 0;
- font-size: 22px;
- font-weight: bold;
- }
- .header-room {
- font-size: 28px;
- font-weight: bold;
- text-align: center;
- flex: 1;
- }
- .header-logo {
- height: 60px;
- text-align: right;
- object-fit: contain;
- }
- .card-main {
- display: flex;
- flex: 1;
- min-height: 0;
- overflow: hidden;
- }
- .left-section {
- width: 150px;
- padding: 20px 10px;
- text-align: center;
- border-right: 1px solid #e0e0e0;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: space-between;
- flex-shrink: 0;
- }
- .room-number {
- font-size: 60px;
- font-weight: bold;
- color: #ff6b00;
- margin-bottom: 10px;
- }
- .bed-info {
- font-size: 32px;
- font-weight: bold;
- color: #ff6b00;
- margin-bottom: 20px;
- }
- .bed-letter {
- padding-top: 10px;
- }
- .qrcode-container {
- margin-top: 10px;
- }
- .qrcode-text {
- margin-top: 8px;
- font-size: 14px;
- color: #666;
- }
- .right-section {
- flex: 1;
- min-width: 0;
- padding: 0;
- height: 100%;
- }
- .info-table {
- width: 100%;
- height: 100%;
- border-collapse: collapse;
- font-size: 18px;
- table-layout: fixed;
- }
- .info-table tr {
- height: 86px;
- }
- .info-table .row-basic {
- height: 50px;
- }
- .info-table .row-care {
- height: 50px;
- }
- .info-table .row-medical {
- height: 70px;
- }
- .info-table .row-diet {
- height: 50px;
- }
- .info-table .row-prevention {
- height: 200px;
- }
- .info-table .row-staff {
- height: 60px;
- }
- .info-table td {
- border: 1px solid #999;
- padding: 10px;
- text-align: center;
- vertical-align: middle;
- overflow: hidden;
- }
- .table-label {
- font-weight: bold;
- background-color: #f5f5f5;
- width: 90px;
- white-space: nowrap;
- }
- .table-data {
- text-align: left;
- color: #333;
- overflow: hidden;
- white-space: wrap;
- }
- .diet-cell {
- padding: 0 !important;
- }
- .diet-list {
- display: flex;
- height: 100%;
- }
- .diet-item {
- flex: 1;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 10px;
- border-right: 1px solid #999;
- position: relative;
- min-height: 44px;
- }
- .diet-item:last-child {
- border-right: none;
- }
- .diet-item.active {
- background-color: #ffffff;
- color: #ff6b00;
- }
- .diet-name {
- font-size: 14px;
- }
- .diet-check {
- margin-right: 6px;
- color: #e78d35;
- font-weight: bold;
- font-size: 16px;
- }
- .diet-empty {
- padding: 10px;
- color: #999;
- text-align: left;
- min-height: 44px;
- display: flex;
- align-items: center;
- }
- .prevention-title {
- width: 40px;
- padding: 0 !important;
- }
- .vertical-text {
- writing-mode: vertical-rl;
- text-orientation: upright;
- letter-spacing: 4px;
- font-size: 18px;
- font-weight: bold;
- margin: 0 auto;
- }
- .prevention-cell {
- padding: 0 !important;
- }
- .prevention-grid {
- display: grid;
- grid-template-columns: repeat(5, 1fr);
- grid-template-rows: repeat(2, 1fr);
- height: 100%;
- }
- .prevention-item {
- border-right: 1px solid #999;
- border-bottom: 1px solid #999;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 6px;
- position: relative;
- }
- .prevention-item:nth-child(9) {
- border-right: none;
- border-bottom: none;
- }
- .prevention-item:nth-child(n + 6):nth-child(-n + 8) {
- border-bottom: none;
- }
- .prevention-pos-1 {
- grid-column: 1;
- grid-row: 1;
- }
- .prevention-pos-2 {
- grid-column: 2;
- grid-row: 1;
- }
- .prevention-pos-3 {
- grid-column: 3;
- grid-row: 1;
- }
- .prevention-pos-4 {
- grid-column: 4;
- grid-row: 1;
- }
- .prevention-pos-5 {
- grid-column: 1;
- grid-row: 2;
- }
- .prevention-pos-6 {
- grid-column: 2;
- grid-row: 2;
- }
- .prevention-pos-7 {
- grid-column: 3;
- grid-row: 2;
- }
- .prevention-pos-8 {
- grid-column: 4;
- grid-row: 2;
- }
- .prevention-pos-9 {
- grid-column: 5;
- grid-row: 1 / 3;
- background-color: #fff;
- }
- .risk-badge {
- position: absolute;
- top: 4px;
- left: 4px;
- display: flex;
- align-items: center;
- font-size: 11px;
- color: #e78d35;
- font-weight: bold;
- }
- .risk-star {
- margin-right: 2px;
- }
- .placeholder-image {
- width: 90px;
- object-fit: contain;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .placeholder-image span {
- font-size: 11px;
- color: #999;
- text-align: center;
- }
- .prevention-name {
- font-size: 12px;
- color: #333;
- text-align: center;
- line-height: 1.2;
- }
- .card-footer {
- height: 8px;
- flex-shrink: 0;
- background: linear-gradient(90deg, #e78d35 0%, #4caf50 100%);
- clear: both;
- }
- .export-btn {
- margin-top: 20px;
- padding: 10px 30px;
- background-color: #4caf50;
- color: white;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- font-size: 16px;
- transition: background-color 0.3s;
- }
- .export-btn:hover {
- background-color: #45a049;
- }
- </style>
|