Commit 3b4e440d authored by songrui's avatar songrui

随访详情

parent 8e6ad985
...@@ -78,7 +78,9 @@ ...@@ -78,7 +78,9 @@
// 字体大小 // 字体大小
.text-16 { font-size: 16px; } .text-16 { font-size: 16px; }
.text-12 { font-size: 12px; } .text-12 { font-size: 12px; }
.text-start { text-align: start; }
.text-center { text-align: center; } .text-center { text-align: center; }
.text-end { text-align: end; }
.text-black { color: #000; } .text-black { color: #000; }
.text-green { color: #52C41A; } .text-green { color: #52C41A; }
.text-red { color: #FF4D4F; } .text-red { color: #FF4D4F; }
......
<svg width="10" height="6" viewBox="0 0 10 6" xmlns="http://www.w3.org/2000/svg">
<path d="M4.99999 3.78145L8.29999 0.481445L9.24266 1.42411L4.99999 5.66678L0.757324 1.42411L1.69999 0.481445L4.99999 3.78145Z"/>
</svg>
<svg width="8" height="9" viewBox="0 0 8 9" xmlns="http://www.w3.org/2000/svg">
<rect x="0.158203" y="0.900391" width="2.68262" height="7.70361" rx="1"/>
<rect x="5.1582" y="0.900391" width="2.68262" height="7.70361" rx="1"/>
</svg>
\ No newline at end of file
<svg width="8" height="9" viewBox="0 0 8 9" xmlns="http://www.w3.org/2000/svg">
<path d="M6.6432 3.90404C7.26987 4.29571 7.26987 5.20837 6.6432 5.60004L1.53 8.79579C0.863951 9.21207 0 8.73323 0 7.94779V1.55629C0 0.770851 0.863951 0.292008 1.53 0.708288L6.6432 3.90404Z"/>
</svg>
<template>
<div class="doc-image">
<div v-if="isPdf" class="p-2 flex items-center view-pdf">
<doc-icon type="doc-PDF" style="font-size: .48rem" class="shrink-0"/>
<span class="grow px-4 text-ellipsis">{{name}}</span>
<span class="close-btn" @click.stop="removeBtn" v-if="remove">
<doc-icon type="close-circle" />
</span>
</div>
<div class="view-img" v-if="status==='success' && !isPdf">
<img :src="src" :alt="name"
:loading="loading" >
</div>
<template v-if="!isPdf">
<div class="doc-image-wrapper" v-if="status==='loading'">
<slot name="placeholder">
<div class="doc-image-default">加载中...</div>
</slot>
</div>
<div class="doc-image-wrapper" v-if="status==='fail'">
<slot name="fail">
<div class="doc-image-default">加载失败</div>
</slot>
</div>
<span class="close-btn" @click.stop="removeBtn" v-if="remove">
<doc-icon type="close-circle" />
</span>
</template>
</div>
</template>
<script>
export default {
name: 'doc-image',
props: {
src: String,
// 浏览器应当如何加载该图像
// img标签中的属性 需要浏览器支持
// https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/img
// eager 立即加载图像
// lazy 延迟加载图像,直到它和视口接近到一个计算得到的距离
loading: String,
// 开启删除功能
remove: Boolean,
// 处理pdf预览
isPdf: Boolean,
name: String
},
emits: ['removeBtn'],
data() {
return {
// 图片加载状态 loading | fail | success
status: 'loading',
imgStyle: {},
// viewer显示
visible: false,
// 按键界面显示
btVisible: false
}
},
methods: {
loadImage() {
if (this.isPdf) {
this.status = 'success'
return
}
const image = new Image()
image.onload = () => {
this.show = true
this.status = 'success'
}
image.onerror = (err) => {
console.warn('doc-image', err)
this.show = false
this.status = 'fail'
}
this.status = 'loading'
image.src = this.src
},
removeBtn() {
this.btVisible = false
this.$emit('onRemove', this.src)
}
},
watch: {
src: {
handler(val) {
if (val) {
this.loadImage()
}
},
immediate: true
}
}
}
</script>
<style lang="less" scoped>
.doc-image {
display: inline-block;
width: 100%;
height: 100%;
position: relative;
.view-img {
width: 100%;
height: 100%;
min-height: 90px;
// border: 1px dashed #d9d9d9;
// background: #fafafa;
background: #EFF2F7;
img {
width: 100%;
height: 90px;
object-fit: contain;
}
}
.view-pdf {
width: 100%;
min-height: 48px;
overflow-y: hidden;
border-radius: 8px;
border: 1px solid #EEEEEE;
.close-btn {
top: 50%;
right: 8px;
transform: translateY(-50%);
}
}
.close-btn {
position: absolute;
font-size: 16px;
top: -8px;
right: -8px;
}
}
.doc-image-wrapper {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.doc-image-default {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 14px;
background: #f5faf7;
color: #a8abb2;
vertical-align: middle;
user-select: none;
}
</style>
<template>
<div class="px-3 py-3 flex items-center doc-nav-bar">
<div class="shrink-0 left">
<slot name="left">
<span class="back-btn" @click="goBack" v-if="!hideBack">
<doc-icon type="doc-left" style="color: #262626"/>
</span>
</slot>
</div>
<div class="grow text-center font-semibold title">
<slot>
{{title}}
</slot>
</div>
<div class="shrink-0 right">
<slot name="right"></slot>
</div>
</div>
</template>
<script>
import { backHome } from '@/utils/common.js'
export default {
name: 'DocNavBar',
props: {
title: String,
// 是否首页
home: Boolean,
// 替换返回函数
backFunc: Function,
// 隐藏返回按键
hideBack: Boolean
},
methods: {
goBack() {
if (this.backFunc) {
this.backFunc()
return
}
if (this.home) {
backHome()
return
}
this.$router.back()
}
}
}
</script>
<style lang="less" scoped>
.doc-nav-bar {
background: transparent;
border-bottom: 1px solid #00000019;
.left {
min-width: 24px;
}
.title {
font-size: 16px;
}
}
</style>
<template>
<div>
<div class='list gap-x-2.5 gap-y-1 flex items-center flex-wrap'>
<div v-for="(url, index) in imgList" :key="index" @click='toPreview(index)'
class="flex gap-x-2.5">
<!-- <img :style="{width: imgSize.width, height: imgSize.height}" :src="url.trueDownloadUrl" /> -->
<DocImage :style="{width: imgSize.width, height: imgSize.height}" :src="url.trueDownloadUrl"/>
</div>
</div>
<van-overlay :show='imgShow' @click='imgShow = false'>
<div class='wrapper'>
<van-swipe class='block' :initial-swipe='initSwipe'>
<van-swipe-item v-for='image in imgList' :key='image'>
<img :src='image.trueDownloadUrl' style='width: 100%;height: 100%'/>
</van-swipe-item>
</van-swipe>
</div>
</van-overlay>
</div>
</template>
<script>
import DocImage from '../docImage/DocImage.vue'
export default {
name: 'ImagePreview',
components: {
DocImage
},
props: {
imgList: Array,
imgSize: {
default: () => {
return {
width: '.97rem',
height: '.97rem'
}
}}
},
data() {
return {
imgShow: false,
initSwipe: 0
}
},
methods: {
//图片预览
toPreview(index) {
this.initSwipe = index
this.imgShow = true
},
}
}
</script>
<style scoped lang='less'>
.wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
.block {
width: 100%;
}
}
</style>
\ No newline at end of file
<template>
<div class="p-1 flex flex-col mp3">
<div v-if="file.annexName" class="text-12 mb-1 text-ellipsis">{{ file.annexName }}</div>
<div class="flex items-center justify-between gap-x-2.5">
<div class="shrink-0 play-bt" @click.stop="start(file)">
<doc-icon type="doc-play" v-if="!player.playing"/>
<doc-icon type="doc-pause" v-else/>
</div>
<span class="shrink-0 time">{{timeFormat(player.duration)}}</span>
<div class="grow progress">
<van-slider v-model="player.currentTime" :max="sliderMax" :bar-height="6" :button-size="0"
@change="onProgress" />
<!-- <div :style="`width: ${progress}%`"></div> -->
</div>
<span class="shrink-0 time" :style="`opacity: ${player.currentTime ? 1 : 0}`">
{{ timeFormat(player.currentTime) }}</span>
<span class="text-16 shrink-0 close-btn" @click.stop="removeBtn" v-if="remove">
<doc-icon type="close-circle" />
</span>
</div>
<audio ref="audio" type="audio/mpeg" crossOrigin="anonymous" preload="metadata" style="display: none"></audio>
</div>
</template>
<script>
import { musicPlayer } from './mp3.js'
import { showToast } from 'vant'
import { useStore } from '@/tumour/store/index.js'
export default {
props: {
file: { default: () => ({}) },
activeMediaUrl: { default: '' },
remove: Boolean
},
emits: ['play', 'onRemove'],
data() {
return {
store: useStore(),
player: {},
// 正在播放的items
activeAudio: {},
}
},
computed: {
sliderMax() {
return this.player.duration ? Math.floor(this.player.duration) : 100
},
progress() {
if (!this.player.currentTime) return 0
const temp = this.player.currentTime / this.player.duration
return Math.round(temp * 1000) / 10
}
},
mounted() {
this.init()
},
methods: {
init() {
this.player = new musicPlayer(this.$refs.audio)
this.player.init()
this.player.setSrc(this.file.annexUrl)
this.player.audioEl.onended = () => {
console.log('播放结束')
this.stop()
}
},
start(item) {
if (!item || !item.annexUrl) {
showToast('文件获取失败')
return
}
if (!this.player.audioCtx) {
this.player.init()
this.player.setSrc(item.annexUrl)
}
if (this.player.playing) {
this.stop()
} else {
this.player.audioEl.play()
this.activeAudio = item
this.player.playing = true
this.$emit('play', item)
console.log('this.player.duration', this.player.audioEl.duration)
}
console.log('this.player', this.player)
},
stop() {
this.player.audioEl.pause()
this.player.playing = false
},
timeFormat(value) {
if (!value || value == Infinity) {
return '00:00'
}
let date = Math.ceil(parseFloat(value))
let minutes = Math.floor(date / 60)
let seconds = date % 60
let format = `${minutes >= 10 ? minutes : '0' + minutes.toString()}:${seconds >= 10 ? seconds : '0' + seconds.toString()}`
return format
},
onProgress(value) {
this.player.progressChange(value)
this.player.playing = true
},
removeBtn() {
this.$emit('onRemove', this.file)
}
},
watch: {
activeMediaUrl(val) {
if (val !== this.file.annexUrl) {
this.stop()
}
},
'store.documentHidden': {
handler(val) {
if (val && this.activeMediaUrl == this.file.annexUrl) {
this.stop()
}
}
}
}
}
</script>
<style lang="less" scoped>
.mp3 {
background-color: #fff;
.play-bt {
display: inline-flex;
width: 24px;
height: 24px;
align-items: center;
justify-content: center;
border-radius: 50%;
background-color: #E4F2F0;
font-size: 10px;
padding-left: 2px;
color: var(--van-primary-color);
}
.progress {
// height: 6px;
// border-radius: 4px;
// background: #EFF2F7;
// overflow: hidden;
// text-align: left;
// >div {
// height: 100%;
// background: #54CCBD;
// }
}
}
</style>
<template>
<div class="mp4">
<div class="flex flex-wrap justify-between video-list">
<div v-for="item in files" :key="item.annexId" @click.stop="start(item)">
<div class="item">
<div class="shrink-0 play-bt" >
<doc-icon type="doc-play" />
</div>
<span class="close-btn" @click.stop="removeBtn(item)" v-if="remove">
<doc-icon type="close-circle" />
</span>
</div>
<div v-if="item.annexName" class="text-12 my-1 text-ellipsis">{{ item.annexName }}</div>
</div>
</div>
<!-- <van-popup v-model:show="visible" :close-on-click-overlay="false" closeable
close-icon-position="top-right"
close-icon="clear">
<video controls v-if="visible" style="width: calc(100vw - var(--van-padding-md) * 2)">
<source :src="activeVideo.annexUrl" type="video/mp4" />
播放失败!
</video>
</van-popup> -->
<van-overlay :show="visible">
<div class="h-full flex items-center justify-center wrapper" @click.stop>
<video controls v-if="visible" ref="video">
<source :src="activeVideo.annexUrl" type="video/mp4" />
播放失败!
</video>
<van-icon name="close" class="close-icon" @click="visible = false"/>
</div>
</van-overlay>
</div>
</template>
<script>
import { useStore } from '@/tumour/store/index.js'
export default {
props: {
files: { default: () => [] },
activeMediaUrl: { default: '' },
remove: Boolean
},
emits: ['play', 'onRemove'],
data() {
return {
visible: false,
activeVideo: {},
store: useStore()
}
},
methods: {
start(item) {
this.activeVideo = item
this.visible = true
this.$emit('play', item)
},
removeBtn(item) {
this.$emit('onRemove', item)
}
},
watch: {
activeMediaUrl(val) {
if (val !== this.activeVideo.annexUrl) {
const dom = this.$refs.video
dom && dom.pause()
}
},
'store.documentHidden': {
handler(val) {
if (val && this.activeMediaUrl == this.activeVideo.annexUrl) {
const dom = this.$refs.video
dom && dom.pause()
}
}
}
}
}
</script>
<style lang="less" scoped>
.video-list {
>div {
width: calc(50% - 5px);
.item {
position: relative;
background: url('@/assets/image/video-default.png') no-repeat;
background-size: 100%;
height: .84rem;
display: flex;
align-items: center;
justify-content: center;
}
}
.play-bt {
display: inline-flex;
width: 36px;
height: 36px;
align-items: center;
justify-content: center;
border-radius: 50%;
background-color: #E4F2F0;
font-size: 15px;
padding-left: 2px;
color: var(--van-primary-color);
}
.close-btn {
position: absolute;
font-size: 16px;
top: -8px;
right: -8px;
}
}
.wrapper {
position: relative;
.close-icon {
position: absolute;
top: 16px;
right: 16px;
color: #ccc;
font-size: 24px;
}
video {
width: calc(100vw - var(--van-padding-md) * 2);
background-color: #fff;
}
}
</style>
export class musicPlayer {
constructor(audioEl) {
this.audioEl = audioEl
// 音频上下文
this.audioCtx = null
// 音源
this.audioSource = null
// 时长
this.duration = 0
// 当前播放时间
this.currentTime = 0
// 播放进度
this.progress = 0
// 声音
this.volume = 20
// AnalyserNode 接口表示了一个可以提供实时频域和时域分析信息的节点
this.analyser = null
// 音量节点
this.gainNode = null
// 缓冲进度
this.timeRange = null
// 是否播放中
this.playing = false
}
init() {
// 音频上下文
this.audioCtx = new (window.AudioContext || window.webkitAudioContext)()
if (!this.audioCtx) {
throw new Error('audioCtx is null')
}
// 获取音频数据的节点
this.analyser = this.audioCtx.createAnalyser()
// 音量节点
this.gainNode = this.audioCtx.createGain()
this.gainNode.gain.value = 1
this.audioEl.volume = this.volume / 100
// 从<audio>或<video>元素生成的音频源
this.audioSource = this.audioCtx.createMediaElementSource(this.audioEl)
this.audioEl.ontimeupdate = () => {
this.currentTime = this.audioEl.currentTime
if (this.progress >= this.duration) return
this.progress = this.currentTime
}
this.audioEl.onloadedmetadata = () => {
// 时长
this.duration = this.audioEl.duration
console.log('onloadedmetadata', this.duration)
}
this.audioEl.onprogress = () => {
// 当浏览器正在下载音频/视频时
this.timeRange = this.audioEl.buffered
if (this.timeRange && this.timeRange.length > 0) {
console.log(
'buffered',
this.audioEl.buffered.start(0),
this.audioEl.buffered.end(0)
)
}
}
this.audioEl.oncanplay = () => {
console.log('可播放')
setTimeout(() => {
this.getAudioSource()
}, 100)
}
this.audioEl.onerror = (e) => {
console.error('加载出现错误', e)
this.onerror(e)
}
}
onerror() {}
onended() {}
setSrc(src) {
this.progress = 0
this.audioEl.src = src
this.audioEl.load()
}
// 调整进度
progressChange(value) {
this.audioEl.currentTime = value
}
// 调整音量
volumeChange(value) {
this.volume = value
this.audioEl.volume = this.volume / 100
}
// 音频波形处理
getAudioSource() {
// 节点链接到音源
this.audioSource.connect(this.analyser)
// 链接音量节点
this.analyser.connect(this.gainNode)
this.gainNode.connect(this.audioCtx.destination)
return
// 使用快速傅立叶变换(Fast Fourier Transform (FFT) )来捕获音频数据
// this.analyser.fftSize = 2048
this.analyser.fftSize = 256
let bufferLength = this.analyser.frequencyBinCount
let dataArray = new Uint8Array(bufferLength)
let c = this.boardEl
let canvasWidth = c.width
let canvasHeight = c.height
let ctx = c.getContext('2d')
let left = this
ctx.strokeStyle = 'rgba(81,167,255, .5)'
drawBar()
function drawBar() {
// 波形绘制
ctx.clearRect(0, 0, canvasWidth, canvasHeight)
let barWidth = (canvasWidth / bufferLength) * 1
let barHeight = 0
let x = 0
left.analyser.getByteFrequencyData(dataArray)
for (let i = 0; i < bufferLength; i++) {
barHeight = dataArray[i] / 2
let tempColor = barHeight * 3 > 255 ? 255 : barHeight * 3
ctx.fillStyle = 'rgba(' + tempColor + ', 160, 255, .5)'
ctx.fillRect(x, canvasHeight - barHeight / 2, barWidth, barHeight)
x += barWidth + 0.1
}
requestAnimationFrame(drawBar)
}
}
}
...@@ -14,4 +14,30 @@ dayjs.locale('cn') ...@@ -14,4 +14,30 @@ dayjs.locale('cn')
export function registe(app) { export function registe(app) {
// 自定义组件 // 自定义组件
app.use(DocIcon) app.use(DocIcon)
app.config.globalProperties.$idCardHide = idCardHide
app.config.globalProperties.$phoneHide = phoneHide
app.config.globalProperties.$addrJoin = addrJoin
}
// idCard 脱敏
function idCardHide(idCard) {
if (!idCard || idCard.length < 18) {
return idCard
}
return idCard.substring(0, 6) + '******' + idCard.substring(14)
}
// phone 脱敏
function phoneHide(phone) {
if (!phone || phone.length < 11) {
return phone
}
return phone.substring(0, 3) + '******' + phone.substring(9)
}
// 地址拼接显示
function addrJoin(str1 = '', str2 = '') {
if (!str1 && !str2) return '-'
return (str1 ?? '') + (str2 ?? '')
} }
...@@ -9,7 +9,12 @@ const routes = [ ...@@ -9,7 +9,12 @@ const routes = [
{ {
path: 'screening/simple/form', path: 'screening/simple/form',
name: 'tumour-screening-simple-form', name: 'tumour-screening-simple-form',
component: () => import(/* webpackChunkName: "page-tumour" */ '@/tumour/screening/simpleV2/form/Index.vue') component: () => import(/* webpackChunkName: "page-tumour-screening" */ '@/tumour/screening/simpleV2/form/Index.vue')
},
{
path: 'visit/detail',
name: 'tumour-visit-detail',
component: () => import(/* webpackChunkName: "page-tumour-visit" */ '@/tumour/visit/detail/Detail.vue')
} }
] ]
} }
......
...@@ -15,6 +15,8 @@ import { useStore } from './store/index.js' ...@@ -15,6 +15,8 @@ import { useStore } from './store/index.js'
export default { export default {
created() { created() {
this.init() this.init()
// 监听页面是否隐藏
document.addEventListener('visibilitychange', this.visibilitychange)
}, },
setup() { setup() {
const store = useStore() const store = useStore()
...@@ -25,7 +27,17 @@ export default { ...@@ -25,7 +27,17 @@ export default {
getDict().then(res => { getDict().then(res => {
this.store.$patch({ dict: res.data }) this.store.$patch({ dict: res.data })
}) })
},
visibilitychange() {
if (document.hidden) {
this.store.onDocumentHidden(true)
} else {
this.store.onDocumentHidden(false)
}
} }
},
beforeUnmount() {
document.removeEventListener('visibilitychange', this.visibilitychange)
} }
} }
</script> </script>
......
import { fetchBase } from '@/utils/fetch.js'
// 简易筛查 新增
export function getVisitById(id, loading = true) {
return fetchBase({ url: `/tumour-admin/v1/h5-app/visit-details/${id}`, loading })
}
...@@ -194,7 +194,7 @@ import { useStore } from '@/tumour/store/index.js' ...@@ -194,7 +194,7 @@ import { useStore } from '@/tumour/store/index.js'
import { getResidentInfo } from '@/tumour/api/base.js' import { getResidentInfo } from '@/tumour/api/base.js'
import { getDictValue } from '@/tumour/utils/dictionaries.js' import { getDictValue } from '@/tumour/utils/dictionaries.js'
import { showToast } from 'vant' import { showToast } from 'vant'
import DocAddress from '@/tumour/utils/docAddress/DocAddress.vue' import DocAddress from '@/components/docAddress/DocAddress.vue'
const defaultForm = (info = {}) => { const defaultForm = (info = {}) => {
const form = { const form = {
......
...@@ -4,7 +4,9 @@ export const useStore = defineStore('tumour', { ...@@ -4,7 +4,9 @@ export const useStore = defineStore('tumour', {
state: () => { state: () => {
return { return {
// 字典 // 字典
dict: [] dict: [],
// 页面是否处于隐藏状态
documentHidden: false
} }
}, },
getters: {}, getters: {},
...@@ -12,6 +14,9 @@ export const useStore = defineStore('tumour', { ...@@ -12,6 +14,9 @@ export const useStore = defineStore('tumour', {
getDict(val) { getDict(val) {
if (!val) return [] if (!val) return []
return this.dict[val] || [] return this.dict[val] || []
},
onDocumentHidden(value) {
this.documentHidden = value
} }
} }
}) })
<template>
<div class="h-full flex flex-col visit-detail">
<DocNavBar home v-if="embed !== 'wx'">
随访详情
</DocNavBar>
<div class="px-4 py-3 flex shrink-0 base-info">
<div class="flex w-full">
<div class="grow flex flex-col justify-between">
<div class="flex justify-between">
<span class="name">{{ baseInfo.residentName || '-' }}</span>
</div>
<div class='top-label'>
<div class='mt-3 flex'>
<div><span>随访方式:</span><span class='color-b'>{{ info.visitModeTrans }}</span></div>
<div class='ml-4'>随访日期:<span class='color-b'>{{ info.visitDate }}</span></div>
</div>
<div><span>下次随访日期:</span>
<span v-if="info.nextVisitCustomDate" class='color-b'> {{ info.nextVisitCustomDate }}</span>
<span v-else class='color-b'>{{ info.nextVisitDictTrans }}</span>
</div>
</div>
</div>
</div>
</div>
<div class='p-3 grow cont-box'>
<div class='p-3 h-full cont-inner'>
<div class='flex justify-between collapse-head mt-2'>
<span class='text-16 font-semibold'>全部内容</span>
<span @click='toggleAll'>
<span v-if='!collapseAll'>展开全部</span>
<span v-else>收起全部</span>
<span :class="['ml-2 icon-down', { 'icon-down-expanded': collapseAll }]">
<doc-icon type='doc-down' />
</span>
</span>
</div>
<van-collapse :model-value="activeCollapse" ref='collapse' class='doc-collapse'
@change='collapseChange'>
<van-collapse-item v-for="item in collapseList" :key='item.name'
:title='item.title' :name='item.name'>
<template #right-icon>
<doc-icon type='doc-down' />
</template>
<!-- 基本信息 -->
<div v-if="item.name === '1'">
<div v-for='item in columnsBase' :key='item.key' class="info-item">
<span class='shrink-0 mr-2'>{{ item.title }}</span>
<span v-if="item.key === 'idCard'">{{ $idCardHide(baseInfo.idCard) || '-'
}}</span>
<span class='text-end' v-else>
<span>{{ baseInfo[item.key] || '-' }}</span>
<span v-if='item.unit' class='ml-1'>{{ item.unit }}</span>
</span>
</div>
</div>
<!-- 随访信息 -->
<div v-if="item.name === '2'">
<div class="info-item" v-if="visitCategory == 2">
<span class='shrink-0 mr-2'>随访人群</span>
<span class='text-end'>{{ info.speciesTrans }}</span>
</div>
<div class="info-item" v-else-if="visitCategory == 1">
<span class='shrink-0 mr-2'>确诊肿瘤</span>
<span class='text-end'>{{ info.diseaseName }}</span>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>随访方式</span>
<span class='text-end'>{{ info.visitModeTrans }}</span>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>随访类型</span>
<span class='text-end'>{{ info.visitTypeTrans }}</span>
</div>
<div class="info-item" v-if="info.lastStatus == 2">
<span class='shrink-0 mr-2'>失访原因</span>
<span class='text-end'>
<span>{{ info.loseReasonTrans }}</span>
<span v-if="info.loseReason == 99" class="ml-2">{{ info.loseReasonOther }}</span>
</span>
</div>
<div class="info-item" v-if="info.lastStatus == 1">
<span class='shrink-0 mr-2'>下次随访日期</span>
<span class='text-end'>{{ info.nextVisitDate }}</span>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>随访日期</span>
<span class='text-end'>{{ info.visitDate }}</span>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>随访单位</span>
<span class='text-end'>{{ info.visitUnitName }}</span>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>随访科室</span>
<span class='text-end'>{{ info.visitDepartName }}</span>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>随访医生</span>
<span class='text-end'>{{ info.visitDoctorName }}</span>
</div>
</div>
<!-- 随访内容 -->
<div v-if="item.name === '3'">
<div class="info-item">
<span class='shrink-0 mr-2'>家族遗传史</span>
<span class='text-end'>{{ visitContent.familyHistory == 1 ? visitContent.familySpeciesTrans : '无' }}</span>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>是否吸烟</span>
<span class='text-end'>{{ visitContent.smokeStateTrans }}</span>
</div>
<div v-if="visitContent.smokeState != 4" class="info-item-block p-2">
<div v-if="![4].includes(visitContent.smokeState)">吸烟开始年龄: {{ visitContent.smokeAge || '-' }}</div>
<div v-if="[1,2,3].includes(visitContent.smokeState)">最近7天是否吸烟: {{ visitContent.smokeAge || '-' }}</div>
<div v-if="[1,2].includes(visitContent.smokeState)">日吸烟量: {{ visitContent.smokeDay || '-' }}</div>
<div v-if="[1,2].includes(visitContent.smokeState)">目标吸烟量: {{ visitContent.smokeTarget || '-' }}</div>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>饮酒</span>
<span class='text-end'>
<span>{{ visitContent.drinkStateTrans }}</span>
<span v-if="visitContent.drinkRate = 1">({{ visitContent.drinkRateTrans }})</span>
</span>
</div>
<div v-if="[1,2].includes(visitContent.drinkState)" class="info-item-block p-2">
<div>日饮酒量: {{ visitContent.drinkDay || '-' }}<span class="ml-1">ml</span></div>
<div>目标饮酒量: {{ visitContent.drinkTarget || '-' }}<span class="ml-1">ml</span></div>
<div>饮酒种类: {{ visitContent.drinkDayTypeTrans || '-' }}</div>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>运动</span>
<span class='text-end'>{{ visitContent.sportsStateTrans }}</span>
</div>
<div v-if="visitContent.sportsState == 1" class="info-item-block p-2">
<div>运动强度: {{ visitContent.sportsStrengthTrans || '-' }}</div>
<div>目标运动强度:
<span>{{ visitContent.sportsTargetWeek }}</span>
<span class="ml-1">次/周,</span>
<span>{{ visitContent.sportsTargetMinute }}</span>
<span class="ml-1">分钟/次</span>
</div>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>饮食</span>
<span class='text-end'>{{ visitContent.foodStateTrans }}</span>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>是否复发患者</span>
<span class='text-end'>{{ visitContent.relapseTrans }}</span>
</div>
<div class="info-item" v-if="visitContent.relapse == 1">
<span class='shrink-0 mr-2'>复发时间</span>
<span class='text-end'>{{ visitContent.relapseDate }}</span>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>是否转移患者</span>
<span class='text-end'>{{ visitContent.transferTrans }}</span>
</div>
<div class="info-item" v-if="visitContent.transfer == 1">
<span class='shrink-0 mr-2'>转移时间</span>
<span class='text-end'>{{ visitContent.transferDate }}</span>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>目前病情</span>
<span class='text-end'>
<span v-if="visitContent.currentCondition == 4">{{ visitContent.currentConditionOther }}</span>
<span v-else>{{ visitContent.currentConditionTrans }}</span>
</span>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>治疗情况</span>
<span class='text-end'>{{ visitContent.treatSituationTrans }}</span>
</div>
<div class="info-item" v-if="visitContent.treatSituation == 2">
<span class='shrink-0 mr-2'>治疗方式</span>
<span class='text-end'>
<span>{{ visitContent.treatItemTrans }}</span>
<span v-if="visitContent.treatItem.includes(10)" class="ml-2">{{ visitContent.treatItemOther }}</span>
</span>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>用药不良反应</span>
<span class='text-end'>{{ visitContent.adverseDrugReactionsTrans }}</span>
</div>
<div v-if="visitContent.adverseDrugReactions == 1" class="info-item-block p-2">
<div>不良反应: {{ visitContent.adverseReactions || '-' }}</div>
<div>医生处置: {{ visitContent.doctorDisposal || '-' }}</div>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>随访情况</span>
<span class='text-end'>{{ visitContent.visitSituation }}</span>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>处置意见</span>
<span class='text-end'>{{ visitContent.disposalOpinions }}</span>
</div>
<div class="py-1">图片上传</div>
<ImagePreview :imgList="visitContent.imgUrlList" class="mt-1"
v-if="visitContent.imgUrlList.length"/>
<span v-if="!visitContent.imgUrlList || !visitContent.imgUrlList.length">-</span>
</div>
<!-- 肿瘤信息 -->
<div v-if="item.name === '4'">
<div class="py-1">临床TNM分期</div>
<div class="info-item-block p-2">
<div>T: {{ tumourInfo.clinicalTnmTTrans || '-' }}</div>
<div>N: {{ tumourInfo.clinicalTnmNTrans || '-' }}</div>
<div>M: {{ tumourInfo.clinicalTnmMTrans || '-' }}</div>
</div>
<div class="py-1">病理TNM分期</div>
<div class="info-item-block p-2">
<div>T: {{ tumourInfo.pathologyTnmTTrans || '-' }}</div>
<div>N: {{ tumourInfo.pathologyTnmNTrans || '-' }}</div>
<div>M: {{ tumourInfo.pathologyTnmMTrans || '-' }}</div>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>临床分期</span>
<span class='text-end'>{{ tumourInfo.clinicalStagesTrans || '-' }}</span>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>病理诊断名称</span>
<span class='text-end'>{{ tumourInfo.diagnosisResultName || '-' }}</span>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>病理诊断编码</span>
<span class='text-end'>{{ tumourInfo.diagnosisResultCode || '-' }}</span>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>继发部位</span>
<span class='text-end'>{{ tumourInfo.secondarySite || '-' }}</span>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>继发部位时间</span>
<span class='text-end'>{{ tumourInfo.secondarySiteDate || '-' }}</span>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>分子分型</span>
<span class='text-end'>{{ tumourInfo.molecularTypingName || '-' }}</span>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>分子分型记录时间</span>
<span class='text-end'>{{ tumourInfo.molecularTypingDate || '-' }}</span>
</div>
<div class="py-1">ICD-O-3编码</div>
<div class="info-item-block p-2">
<div>解剖学C: {{ tumourInfo.anatomyCName || '-' }}</div>
<div>形态学M: {{ tumourInfo.morphologyCName || '-' }}</div>
<div>行为: {{ tumourInfo.morphologyMName || '-' }}</div>
<div>分级: {{ tumourInfo.gradeName || '-' }}</div>
</div>
</div>
<!-- 疗效评估 -->
<div v-if="item.name === '5'">
<div class="info-item">
<span class='shrink-0 mr-2'>疗效评估</span>
<span class='text-end'>{{ efficacyInfo.curativeTrans || '-' }}</span>
</div>
<div class="info-item">
<span class='shrink-0 mr-2'>其他评估</span>
<span class='text-end'>{{ efficacyInfo.otherEvaluationsTrans || '-' }}</span>
</div>
<div class="info-item-block p-2" v-if="efficacyInfo?.otherEvaluations?.includes(1)">
<div>NRS疼痛评分</div>
<div>{{ efficacyInfo.nrsPainTrans }}</div>
<div>评分结果: {{ efficacyInfo.nrsPainScore || '-' }} 分</div>
</div>
<div class="info-item-block p-2 mt-2" v-if="efficacyInfo?.otherEvaluations?.includes(2)">
<div>痛主诉疼痛分级</div>
<div>{{ efficacyInfo.painTrans }}</div>
<div>评分结果: {{ efficacyInfo.painScore || '-' }} 分</div>
</div>
<div class="info-item-block p-2 mt-2" v-if="efficacyInfo?.otherEvaluations?.includes(3)">
<div>肿瘤患者ECOG体力状态评分</div>
<div>{{ efficacyInfo.ceogTrans }}</div>
<div>评分结果: {{ efficacyInfo.ceogScore || '-' }} 分</div>
</div>
<div class="info-item-block p-2 mt-2" v-if="efficacyInfo?.otherEvaluations?.includes(4)">
<div>体能状态评分</div>
<div>{{ efficacyInfo.staminaTrans }}</div>
<div>评分结果: {{ efficacyInfo.staminaScore || '-' }} 分</div>
</div>
</div>
<!-- 健康指导 -->
<div v-if="item.name === '6'">
<div class="info-item-block p-2">
<div v-for="item in guideTextList" class="flex">
<span class="shrink-0">{{item.templateModeTrans}}:</span>
<span class="grow text-wrap">{{ item.templateContent }}</span>
</div>
</div>
</div>
<!-- 健康宣教 -->
<div v-if="item.name === '7'">
<div class="info-item-block p-2" v-if="propagandaTextList.length">
<div v-for="item in propagandaTextList" class="flex">
<span class="shrink-0">{{item.textModeTrans}}:</span>
<span class="grow text-wrap">{{ item.content }}</span>
</div>
</div>
<div class="mt-2" v-if="mp4List.length">
<Mp4 :files="mp4List" :activeMediaUrl="activeMediaUrl"
@play="e => activeMediaUrl = e.annexUrl"/>
</div>
<div class="flex flex-col mt-2" style="row-gap: .06rem;" v-if="mp3List.length">
<Mp3 :file="item" v-for="item in mp3List" :key="item.annexId" :activeMediaUrl="activeMediaUrl"
@play="e => activeMediaUrl = e.annexUrl"/>
</div>
</div>
<!-- 催检 -->
<div v-if="item.name === '8'">
<div class="py-1">催检内容</div>
<div class="info-item-block p-2">
<div>{{ info.urgentInsContent || '-' }}</div>
</div>
</div>
<!-- 推送渠道 -->
<div v-if="item.name === '9'">
<div class="info-item-block p-2">
<span class="mr-2">{{ info.pushMethodTrans }}</span>
<span v-if="info.pushMethod.includes(2)">
{{ info.pushTellTypeTrans }}
<span v-if="info.pushTellType == 1">{{ baseInfo.telephone }}</span>
<span v-if="info.pushTellType == 2">
{{ baseInfo.contactRelation1Trans || '-' }}
{{ baseInfo.contactName1 || '-' }}
{{ baseInfo.contactPhone1 }}
</span>
</span>
</div>
</div>
</van-collapse-item>
</van-collapse>
</div>
</div>
</div>
</template>
<script>
import { showNotify } from 'vant'
import { getVisitById } from '@/tumour/api/visit.js'
import { fetchDataHandle } from '@/utils/common.js'
import DocNavBar from '@/components/docNavBar/DocNavBar.vue'
import ImagePreview from '@/components/imagePreview/ImagePreview.vue'
import Mp3 from '@/components/mediaPlay/Mp3.vue'
import Mp4 from '@/components/mediaPlay/Mp4.vue'
const DefaultCollapseList = [
{ title: '居民信息', name: '1' },
{ title: '随访信息', name: '2' },
{ title: '随访内容', name: '3' },
{ title: '肿瘤信息', name: '4' },
{ title: '疗效评估', name: '5' },
{ title: '健康指导', name: '6' },
{ title: '健康宣教', name: '7' },
{ title: '催检', name: '8' },
{ title: '推送渠道', name: '9' }
]
export default {
components: {
DocNavBar,
ImagePreview,
Mp3,
Mp4
},
data() {
return {
info: {},
activeCollapse: [],
collapseList: [],
// 全部展开、收起
collapseAll: false,
columnsBase: [
{ title: '姓名', key: 'residentName' },
{ title: '证件号码', key: 'idCard' },
{ title: '性别', key: 'genderTrans' },
{ title: '出生日期', key: 'dataBirth' },
{ title: '年龄', key: 'age' },
{ title: '民族', key: 'nationalTrans' },
{ title: '本人电话', key: 'telephone' },
{ title: '现住址', key: 'presentCodeTrans' },
{ title: '详细地址', key: 'nowAddress' },
{ title: '户籍地址', key: 'registeredCodeTrans' },
{ title: '详细地址', key: 'permanentAddress' }
],
activeMediaUrl: null
}
},
computed: {
id() {
return this.$route.query.id || '232987'
},
embed() {
return this.$route.query.embed
},
baseInfo() {
return this.info.residentsInfo || {}
},
// 随访类型
visitType() {
return this.info.visitType || []
},
// 通用 专病
visitCategory() {
return this.info.visitCategory
},
// 健康指导内容
guideTextList() {
const list = this.info.visitHealthGuideList || []
return list.filter(e => e.templateContent)
},
// 健康宣教
propagandaTextList() {
const list = this.info.visitPublicizeList || []
return list.filter(e => e.type == '1')
},
mp3List() {
const list = this.info.visitPublicizeList || []
return list.filter(e => e.type == '2')
},
mp4List() {
const list = this.info.visitPublicizeList || []
return list.filter(e => e.type == '3')
},
// 随访内容
visitContent() {
const visitContent = this.info.visitContent || {}
return fetchDataHandle(visitContent, {
familySpecies: 'strToArrNum',
foodState: 'strToArrNum',
treatItem: 'strToArrNum'
})
},
//诊断
diagnosisFormInfo() {
return this.info.visitDiagnosisInfoList || []
},
//肿瘤
tumourInfo() {
return this.info.visitTumourInfo || {}
},
//疗效评估
efficacyInfo() {
return this.info.visitCurativeEffect || {}
},
},
created() {
document.title = '随访详情'
if (!this.id) {
showNotify({ type: 'warning', message: '未获取到患者信息', duration: 0 })
return
}
this.load()
},
methods: {
load() {
getVisitById(this.id).then(res => {
this.info = fetchDataHandle(res.data, {
healthPushWay: 'strToArrNum',
publicizePushWay: 'strToArrNum',
urgentPushWay: 'strToArrNum',
publicizeType: 'strToArrNum',
visitType: 'strToArrNum',
drinkDayType: 'strToArrNum',
pushMethod: 'strToArrNum',
})
console.log('this.info', this.info)
const exclude = []
if (!this.visitType.includes(1)) { exclude.push('3') }
if (this.info.isTumour != 1) { exclude.push('4') }
if (this.info.isCurativeEffect != 1) { exclude.push('5') }
if (!this.visitType.includes(2)) { exclude.push('6') }
if (!this.visitType.includes(3)) { exclude.push('7') }
if (!this.visitType.includes(4)) { exclude.push('8') }
if (!this.info.pushMethod.length) { exclude.push('9') }
this.collapseList = DefaultCollapseList.filter(e => !exclude.includes(e.name))
})
},
// 折叠面板切换
collapseChange(val) {
// console.log(val, this.activeCollapse)
if (val && val.length <= 2) {
this.activeCollapse = val.slice(val.length - 1)
} else {
if (this.activeCollapse.length > val.length) {
this.activeCollapse = val
}
if (this.activeCollapse.length < val.length) {
this.activeCollapse = val.slice(val.length - 1)
}
}
if (val && val.length === this.collapseList.length) {
this.collapseAll = true
} else {
this.collapseAll = false
}
},
// 全部展开、收起
toggleAll() {
if (this.collapseAll) {
this.activeCollapse = []
} else {
this.activeCollapse = this.collapseList.map(e => e.name)
}
this.collapseAll = !this.collapseAll
}
}
}
</script>
<style lang="less" scoped>
.base-info {
background: linear-gradient(to bottom, #DFF5F4 , #fff 50%);
color: #8c8c8c;
.name {
font-weight: 600;
color: #000;
font-size: 18px;
}
.top-label {
line-height: 22px;
}
.color-b {
color: #262626;
}
}
.cont-box {
background-color: #f9f9f9;
.cont-inner {
background: linear-gradient(to bottom, #DFF5F4, #fff .6rem);
border-top-left-radius: .08rem;
border-top-right-radius: .08rem;
}
}
.collapse-head {
.icon-down {
vertical-align: middle;
font-size: .12rem;
.svg-icon {
transition: all .2s;
}
}
.icon-down-expanded {
.svg-icon {
transform: rotate(-180deg);
}
}
}
.info-item {
display: flex;
justify-content: space-between;
padding: 4px 0;
}
.info-item-block {
background: #F8FAFC;
color: #4D5665;
font-size: 13px;
}
// 折叠面板
:deep(.doc-collapse) {
margin-top: .1rem;
&::after {
display: none;
}
.van-cell {
padding: .1rem 0;
color: #8C8C8C;
font-weight: 600;
background: transparent;
&::after {
display: none;
}
.svg-icon {
font-size: .12rem;
transition: all .2s;
}
}
.van-collapse-item {
.van-collapse-item__content {
padding: 0;
color: #262626;
}
&::after {
display: none;
}
}
// 展开
.van-collapse-item__title--expanded {
.svg-icon {
transform: rotate(-180deg);
}
}
}
</style>
...@@ -87,3 +87,72 @@ export function getInfoByIdCard(idCard) { ...@@ -87,3 +87,72 @@ export function getInfoByIdCard(idCard) {
age age
} }
} }
//原生方法调用 当前方法传参数两种 字符串或者map
export function callMobile(handlerInterface, parameters) {
let classStr = Object.prototype.toString.call(parameters);
if (classStr === '[object String]' || classStr === '[object Object]') {
let param = parameters;
if (classStr === "[object Object]") { //判断传参是str ,还是object
//handlerInterface由iOS addScriptMessageHandler与andorid addJavascriptInterface 代码注入而来。
param = JSON.stringify(parameters);//参数必须转化成json格式
}
try {
if (isIOSWebKit()) {//ios
if (window.webkit !== undefined) {
if (param == '{}') {
window.webkit.messageHandlers[handlerInterface].postMessage(null);
} else {
window.webkit.messageHandlers[handlerInterface].postMessage(param);
}
}
} else if (isAndroid()) {
//安卓传输不了js json对象,只能传输string
if (param == '{}') {
window['function'][handlerInterface]();
} else {
window['function'][handlerInterface](param);
}
}
} catch (e) {
}
}
}
// 判断是否是ios
export function isIOSWebKit() {
const aa = window.navigator.userAgent;
if (!!aa.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)) {// ios端
return true;
}
}
// 判断是否安卓
export function isAndroid() {
const aa = window.navigator.userAgent;
if (aa.indexOf('Android') !== -1 || aa.indexOf('Adr') !== -1) {
return true
}
}
// 判断是否在微信中
export function isWeiXin() {
const ua = window.navigator.userAgent.toLowerCase()
return /micromessenger/.test(ua)
}
//关闭页面
export function backHome() {
if (isIOSWebKit()) { //ios
console.log('ios')
callMobile("onBack", {})
} else if (isAndroid()) {
console.log('Android')
callMobile("closePage", {})
}
if (isWeiXin()) { // 微信中
console.log('weixin')
WeixinJSBridge.call('closeWindow')
}
}
\ No newline at end of file
...@@ -22,7 +22,7 @@ module.exports = defineConfig({ ...@@ -22,7 +22,7 @@ module.exports = defineConfig({
// 设置代理 // 设置代理
proxy: { proxy: {
'/tumour-admin': { '/tumour-admin': {
target: 'http://192.168.1.118:8081', target: 'http://192.168.1.174:8081',
// target: 'https://beta-tumour.zmnyjk.com', // target: 'https://beta-tumour.zmnyjk.com',
changOrigin: true, changOrigin: true,
pathRewrite: { pathRewrite: {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment