vue3/src/views/devices/Device_Data_Components/ECG_datas.vue
2025-09-03 11:17:03 +08:00

984 lines
28 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<el-dialog
v-model="dialogVisible"
:title="currentDeviceName"
width="80%"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="true"
class="ecg-dialog"
top="5vh"
>
<div class="ecg-container">
<!-- 左侧人员列表 -->
<div class="person-list">
<el-card class="box-card">
<el-input
v-model="searchQuery"
placeholder="请输入姓名&ID"
prefix-icon="el-icon-search"
clearable
/>
<el-scrollbar height="calc(100vh - 400px)">
<el-menu
:default-active="activePerson"
@select="handlePersonSelect"
>
<el-menu-item
v-for="person in filteredPersonList"
:key="person.id"
:index="person.id.toString()"
>
<span>{{ person.username }} (ID: {{ person.userid }})</span>
</el-menu-item>
</el-menu>
</el-scrollbar>
</el-card>
</div>
<!-- 中间时间列表 -->
<div class="time-list">
<el-card class="box-card">
<div class="time-list-header">
<el-date-picker
v-model="dateFilter"
type="date"
placeholder="请选择采集日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
:clearable="true"
@change="handleDateFilter"
style="width: 100%; margin-bottom: 10px;"
/>
</div>
<el-scrollbar height="400px">
<el-menu
:default-active="activeTime"
@select="handleTimeSelect"
>
<el-menu-item
v-for="(time, index) in filteredTimeList"
:key="index"
:index="index.toString()"
>
<span>{{ formatTime(time.collecttime) }}</span>
</el-menu-item>
<div v-if="filteredTimeList.length === 0" class="no-time-data">
暂无采集时间
</div>
</el-menu>
</el-scrollbar>
</el-card>
</div>
<!-- 右侧数据展示区域 -->
<div class="data-display">
<el-card class="box-card">
<div class="ecg-data-content">
<div class="no-selection" v-if="!selectedPerson">
请从左侧选择人员查看数据
</div>
<div v-else>
<div class="no-data" v-if="!hasData">
暂无数据
</div>
<div v-else>
<el-tabs v-model="activeTab" class="ecg-tabs" @tab-change="handleTabChange">
<el-tab-pane label="基础数据" name="basic">
<div class="ecg-fields-container">
<div class="ecg-grid">
<div
v-for="field in basicFields"
:key="field.key"
class="ecg-grid-item">
<div class="ecg-field-content">
<span class="ecg-label">{{ field.label }}</span>
<span class="ecg-value">{{ selectedPersonData && selectedPersonData[field.key] !== undefined ? selectedPersonData[field.key] : '-' }}</span>
</div>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="心电图" name="ecg">
<div class="ecg-image-container">
<template v-if="ecgImages && ecgImages.length > 0">
<!-- 心电图分页导航 -->
<div class="ecg-pagination-header" v-if="ecgImages.length > 1">
<div class="ecg-pagination-info">
第 {{ currentEcgIndex + 1 }} 张,共 {{ ecgImages.length }} 张
</div>
<div class="ecg-pagination-controls">
<el-button
type="primary"
:icon="'el-icon-arrow-left'"
circle
size="small"
:disabled="currentEcgIndex <= 0"
@click="prevEcgImage"
/>
<el-button
type="primary"
:icon="'el-icon-arrow-right'"
circle
size="small"
:disabled="currentEcgIndex >= ecgImages.length - 1"
@click="nextEcgImage"
/>
</div>
</div>
<!-- 心电图显示 -->
<div class="ecg-image-display">
<el-image
:src="ecgImages[currentEcgIndex]"
:preview-src-list="ecgImages"
:initial-index="currentEcgIndex"
fit="contain"
class="ecg-image"
:preview-teleported="true"
/>
</div>
<!-- 心电图缩略图导航 -->
<div class="ecg-thumbnails" v-if="ecgImages.length > 1">
<div
v-for="(image, index) in ecgImages"
:key="index"
class="ecg-thumbnail"
:class="{ 'active': index === currentEcgIndex }"
@click="goToEcgImage(index)"
>
<el-image
:src="image"
fit="cover"
class="thumbnail-image"
/>
<div class="thumbnail-index">{{ index + 1 }}</div>
</div>
</div>
</template>
<template v-else>
<el-empty description="暂无心电图" />
</template>
</div>
<div v-if="selectedPersonData && selectedPersonData.diagnosis" style="margin-top: 20px;">
心电图诊断:{{ selectedPersonData.diagnosis }}
</div>
</el-tab-pane>
<el-tab-pane label="精神压力" name="stress">
<div class="ecg-fields-container">
<div class="ecg-grid">
<div
v-for="field in stressFields"
:key="field.key"
class="ecg-grid-item">
<div class="ecg-field-content">
<span class="ecg-label">{{ field.label }}</span>
<span class="ecg-value">{{ selectedPersonData && selectedPersonData[field.key] !== undefined ? selectedPersonData[field.key] : '-' }}</span>
</div>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="结论" name="conclusion">
<div class="conclusion-container">
<el-input
v-model="conclusionText"
type="textarea"
:rows="8"
placeholder="请输入检查结论..."
resize="none"
class="conclusion-textarea"
/>
<div class="conclusion-actions">
<el-button type="primary" @click="saveConclusion" :disabled="!conclusionText.trim()">
保存结论
</el-button>
<el-button @click="clearConclusion">
清空
</el-button>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
<!-- 添加医生通知输入框和发送按钮 -->
<div class="doctor-notification">
<el-input
v-model="doctorMessage"
type="textarea"
:rows="3"
placeholder="请输入医生通知内容"
resize="none"
/>
<el-button
type="primary"
@click="sendDoctorNotification"
:disabled="!doctorMessage.trim()"
class="doctor-send-btn"
>
<span>发送</span>
<span>通知</span>
</el-button>
</div>
</el-card>
</div>
</div>
</el-dialog>
</template>
<script>
import { DeviceuserApi } from '@/api/deviceuser'
import { EcgdataApi } from '@/api/ecgdata'
import { PersonApi } from '@/api/person'
import { useMessage } from '@/hooks/web/useMessage'
import { DoctornoticeApi } from '@/api/doctornotice'
import { getUserProfile } from '@/api/system/user/profile'
import * as echarts from 'echarts'
export default {
name: 'ECGDatas',
setup() {
const message = useMessage()
return { message }
},
data() {
return {
searchQuery: '',
activePerson: '',
selectedPerson: null,
personList: [], // 这里需要从后端获取人员列表
hasData: false,
dialogVisible: false,
currentDeviceId: '',
currentDeviceName: '',
selectedPersonData: null, // 新增:存储选中人员的心电数据
activeTab: 'basic', // 新增:当前激活的标签页
doctorMessage: '', // 新增:医生通知内容
userProfile: null, // 新增:存储当前登录用户信息
basicFields: [
{ key: 'heartrate', label: '心率(次/分)' },
{ key: 'rhythm', label: '心律类型' },
{ key: 'printerval', label: 'PR间期(ms)' },
{ key: 'qrsduration', label: 'QRS时限(ms)' },
{ key: 'qtinterval', label: 'QT间期(ms)' },
{ key: 'qtcinterval', label: 'QTc间期(ms)' },
{ key: 'paxis', label: 'P电轴(度)' },
{ key: 'qrsaxis', label: 'QRS电轴(度)' },
{ key: 'taxis', label: 'T电轴(度)' },
{ key: 'stsegment', label: 'ST段' },
],
stressFields: [
{ key: 'hrv_meannn', label: 'NN间期均值(ms)' },
{ key: 'hrv_sdnn', label: 'NN间期标准差(ms)' },
{ key: 'hrv_sdann', label: '5分钟NN间期均值标准差(ms)' },
{ key: 'hrv_rmssd', label: '相邻NN间期均方根(ms)' },
{ key: 'hrv_sdnnindex', label: '每5分钟NN间期标准差均值(ms)' },
{ key: 'hrv_pnn50', label: 'pNN50(%)' },
{ key: 'hrv_uflp', label: '超低频功率(ms²)' },
{ key: 'hrv_vlfp', label: '极低频功率(ms²)' },
{ key: 'hrv_lfp', label: '低频功率(ms²)' },
{ key: 'hrv_hfp', label: '高频功率(ms²)' },
{ key: 'hrv_tp', label: '总功率(ms²)' },
{ key: 'hrv_diagResult', label: '诊断结论' },
{ key: 'hrv_heartRisk', label: '心脏风险' }
],
ecgFields: [ // 修改:心电图字段映射
{ key: 'paxis', label: 'P电轴(度)' },
{ key: 'qrsaxis', label: 'QRS电轴(度)' },
{ key: 'taxis', label: 'T电轴(度)' },
{ key: 'stsegment', label: 'ST段' },
{ key: 'diagnosis', label: '心电图诊断' }
],
timeList: [], // 新增:时间列表
activeTime: '', // 新增:当前激活的时间
dateFilter: null, // 新增:日期筛选值
currentEcgIndex: 0, // 新增:当前心电图索引
ecgImages: [], // 新增:心电图图片数组
conclusionText: '', // 新增:结论文本
}
},
computed: {
filteredPersonList() {
if (!this.searchQuery) return this.personList
return this.personList.filter(person =>
person.username.toLowerCase().includes(this.searchQuery.toLowerCase())
)
},
// 新增:根据日期筛选时间列表
filteredTimeList() {
if (!this.dateFilter) return this.timeList
return this.timeList.filter(time => {
const timeDate = new Date(time.collecttime).toISOString().split('T')[0]
return timeDate === this.dateFilter
})
}
},
methods: {
// 打开组件
async open(deviceId, deviceName) {
// 重置所有数据
this.searchQuery = ''
this.activePerson = ''
this.selectedPerson = null
this.personList = []
this.hasData = false
this.selectedPersonData = null
this.activeTab = 'basic'
this.activeTime = ''
this.timeList = []
this.dateFilter = null // 重置日期筛选
this.doctorMessage = '' // 重置医生通知内容
this.currentEcgIndex = 0 // 重置心电图索引
this.ecgImages = [] // 重置心电图数组
this.conclusionText = '' // 重置结论文本
// 打开对话框并设置设备信息
this.dialogVisible = true
this.currentDeviceId = deviceId
this.currentDeviceName = deviceName
// 获取当前登录用户信息
this.userProfile = await getUserProfile()
this.loadPersonList()
},
// 加载人员列表
async loadPersonList() {
try {
const response = await DeviceuserApi.getDeviceuserByDeviceId(this.currentDeviceId)
this.personList = response
} catch (error) {
console.error('获取人员列表失败:', error)
this.$message.error('获取人员列表失败')
}
},
handlePersonSelect(index) {
this.activePerson = index
this.selectedPerson = this.personList.find(person => person.id.toString() === index)
// 重置时间列表
this.timeList = []
this.loadPersonData()
// 重置数据展示状态
this.selectedPersonData = null
this.hasData = false
this.activeTime = ''
this.currentEcgIndex = 0 // 重置心电图索引
this.ecgImages = [] // 重置心电图数组
},
async loadPersonData() {
try {
const response = await EcgdataApi.getEcgdataByUserId(this.selectedPerson.userid)
this.timeList = Array.isArray(response) ? response : [response]
} catch (error) {
console.error('获取心电数据失败:', error)
this.$message.error('获取数据失败')
}
},
async handleTimeSelect(index) {
this.activeTime = index
const selectedTime = this.timeList[parseInt(index)]
try {
const response = await EcgdataApi.getEcgdataByTime(selectedTime.collecttime, this.selectedPerson.userid)
this.selectedPersonData = response
this.hasData = !!this.selectedPersonData
// 处理心电图图片数组
this.processEcgImages()
} catch (error) {
console.error('获取指定时间的心电数据失败:', error)
this.$message.error('获取数据失败')
}
},
formatTime(timestamp) {
const date = new Date(timestamp)
return date.toLocaleString()
},
// 新增:处理日期筛选
handleDateFilter() {
this.activeTime = ''
this.selectedPersonData = null
this.hasData = false
this.currentEcgIndex = 0 // 重置心电图索引
this.ecgImages = [] // 重置心电图数组
},
// 新增:发送医生通知方法
async sendDoctorNotification() {
if (!this.selectedPerson) {
this.message.warning('请先选择人员')
return
}
if (!this.doctorMessage.trim()) {
this.message.warning('请输入通知内容')
return
}
try {
// // 检查用户是否为会员
// const userResponse = await PersonApi.getPerson(this.selectedPerson.userid)
// if (!userResponse || userResponse.isvip === 0) {
// this.message.warning('该用户不是会员,无法发送通知')
// return
// }
// 构建通知数据
const noticeData = {
id: 0, // 新增时后端会自动生成
deviceid: this.currentDeviceId,
userid: this.selectedPerson.userid,
username: this.selectedPerson.username,
orgid: this.userProfile.dept.orgid,
doctorid: this.userProfile.id,
datatime: new Date().getTime(),
noticetype: 2, // 建议提醒
noticetitle: '医生通知',
noticecontent: this.doctorMessage,
noticelevel: 1, // 普通级别
readstatus: 0, // 未读
readtime: null,
createtime: new Date().getTime(),
updatetime: new Date().getTime(),
createby: this.userProfile.nickname,
updateby: this.userProfile.nickname,
isdeleted: 0
}
// 调用创建通知接口
await DoctornoticeApi.createDoctornotice(noticeData)
this.message.success('通知发送成功')
this.doctorMessage = '' // 清空输入框
} catch (error) {
console.error('发送通知失败:', error)
this.message.error('发送通知失败')
}
},
// 新增:处理标签页切换
handleTabChange(tabName) {
if (tabName === 'stress' && this.selectedPersonData) {
this.$nextTick(() => {
this.initStressChart()
})
}
},
// 新增:初始化压力图表
initStressChart() {
const chartContainer = this.$refs.stressChart
if (!chartContainer) return
// 销毁之前的图表实例
if (this.stressChartInstance) {
this.stressChartInstance.dispose()
}
// 创建新的图表实例
this.stressChartInstance = echarts.init(chartContainer)
// 模拟压力数据(实际项目中应该从后端获取)
const mockData = this.generateMockStressData()
const option = {
title: {
text: '压力指数趋势',
left: 'center',
textStyle: {
fontSize: 14,
fontWeight: 'normal'
}
},
tooltip: {
trigger: 'axis',
formatter: function(params) {
const data = params[0]
return `${data.name}<br/>压力指数: ${data.value}`
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: mockData.times,
axisLabel: {
fontSize: 10
}
},
yAxis: {
type: 'value',
name: '压力指数',
min: 0,
max: 100,
axisLabel: {
fontSize: 10
}
},
series: [
{
name: '压力指数',
type: 'line',
smooth: true,
data: mockData.values,
itemStyle: {
color: '#409EFF'
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(64, 158, 255, 0.3)' },
{ offset: 1, color: 'rgba(64, 158, 255, 0.1)' }
]
}
},
lineStyle: {
width: 2
}
}
]
}
this.stressChartInstance.setOption(option)
// 监听窗口大小变化
window.addEventListener('resize', () => {
if (this.stressChartInstance) {
this.stressChartInstance.resize()
}
})
},
// 新增:生成模拟压力数据
generateMockStressData() {
const times = []
const values = []
const now = new Date()
// 生成最近7天的数据
for (let i = 6; i >= 0; i--) {
const date = new Date(now.getTime() - i * 24 * 60 * 60 * 1000)
times.push(date.toLocaleDateString())
// 生成30-80之间的随机压力值
const stressValue = Math.floor(Math.random() * 50) + 30
values.push(stressValue)
}
return { times, values }
},
// 新增:处理心电图图片数组
processEcgImages() {
this.currentEcgIndex = 0
this.ecgImages = []
if (this.selectedPersonData && this.selectedPersonData.ecgimageurls) {
// 检查是否是多个图片URL数组形式
if (Array.isArray(this.selectedPersonData.ecgimageurls)) {
this.ecgImages = this.selectedPersonData.ecgimageurls.filter(url => url && url.trim())
}
} else if (this.selectedPersonData && this.selectedPersonData.ecgimageurl) {
// 兼容旧的字段名 ecgimageurl
if (Array.isArray(this.selectedPersonData.ecgimageurl)) {
this.ecgImages = this.selectedPersonData.ecgimageurl.filter(url => url && url.trim())
} else if (typeof this.selectedPersonData.ecgimageurl === 'string') {
// 如果是字符串可能包含多个URL用逗号分隔
const urls = this.selectedPersonData.ecgimageurl.split(',').map(url => url.trim()).filter(url => url)
this.ecgImages = urls.length > 0 ? urls : [this.selectedPersonData.ecgimageurl]
}
}
},
// 新增:上一张心电图
prevEcgImage() {
if (this.currentEcgIndex > 0) {
this.currentEcgIndex--
}
},
// 新增:下一张心电图
nextEcgImage() {
if (this.currentEcgIndex < this.ecgImages.length - 1) {
this.currentEcgIndex++
}
},
// 新增:跳转到指定心电图
goToEcgImage(index) {
if (index >= 0 && index < this.ecgImages.length) {
this.currentEcgIndex = index
}
},
// 新增:保存结论
async saveConclusion() {
if (!this.conclusionText.trim()) {
this.message.warning('请输入结论内容')
return
}
if (!this.selectedPerson) {
this.message.warning('请先选择人员')
return
}
try {
// 这里可以调用API保存结论到后端
// 暂时使用本地存储模拟
const conclusionData = {
userid: this.selectedPerson.userid,
username: this.selectedPerson.username,
deviceid: this.currentDeviceId,
conclusion: this.conclusionText,
createtime: new Date().getTime(),
createby: this.userProfile ? this.userProfile.nickname : '医生'
}
// TODO: 调用后端API保存结论
// await ConclusionApi.saveConclusion(conclusionData)
this.message.success('结论保存成功')
console.log('保存的结论数据:', conclusionData)
} catch (error) {
console.error('保存结论失败:', error)
this.message.error('保存结论失败')
}
},
// 新增:清空结论
clearConclusion() {
this.conclusionText = ''
this.message.info('已清空结论内容')
},
}
}
</script>
<style scoped>
.ecg-dialog :deep(.el-dialog) {
margin-top: 5vh !important;
}
.ecg-dialog :deep(.el-dialog__body) {
padding: 0;
height: calc(85vh - 100px);
}
.ecg-container {
display: flex;
height: 100%;
gap: 20px;
padding: 15px;
}
.person-list {
width: 280px;
flex-shrink: 0;
}
.time-list {
width: 200px;
flex-shrink: 0;
}
.time-list :deep(.el-card__body) {
padding: 0;
height: 100%;
display: flex;
flex-direction: column;
}
.time-list-header {
padding: 15px;
border-bottom: 1px solid #EBEEF5;
}
.header-title {
font-weight: 500;
margin-bottom: 10px;
}
.time-list :deep(.el-scrollbar__wrap) {
overflow-x: hidden;
}
.time-list :deep(.el-menu) {
border-right: none;
}
.time-list :deep(.el-menu-item) {
height: 40px;
line-height: 40px;
}
.data-display {
flex: 1;
position: relative;
}
.box-card {
height: 100%;
display: flex;
flex-direction: column;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 15px;
}
.no-selection,
.no-data {
display: flex;
justify-content: center;
align-items: center;
height: 200px;
color: #909399;
font-size: 16px;
}
.ecg-data-content {
min-height: 200px;
flex: 1;
overflow-y: auto;
padding-bottom: 120px;
}
.el-scrollbar {
height: calc(100vh - 400px) !important;
}
.ecg-fields-container {
padding: 20px;
}
.ecg-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
padding: 10px;
}
.ecg-grid-item {
background-color: #f5f7fa;
border-radius: 8px;
padding: 12px;
transition: all 0.3s ease;
min-width: 0;
}
.ecg-grid-item:hover {
transform: translateY(-2px);
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.ecg-field-content {
display: flex;
flex-direction: column;
gap: 6px;
}
.ecg-label {
color: #606266;
font-size: 13px;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.ecg-value {
color: #303133;
font-size: 15px;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.ecg-tabs {
padding: 0 20px;
}
.ecg-tabs :deep(.el-tabs__header) {
margin-bottom: 20px;
}
.ecg-tabs :deep(.el-tabs__nav-wrap::after) {
height: 1px;
}
.ecg-image-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 300px;
gap: 15px;
}
.ecg-pagination-header {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 0 20px;
margin-bottom: 10px;
}
.ecg-pagination-info {
font-size: 14px;
color: #606266;
font-weight: 500;
}
.ecg-pagination-controls {
display: flex;
gap: 8px;
}
.ecg-image-display {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
}
.ecg-image {
max-width: 100%;
max-height: 400px;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.08);
cursor: pointer;
transition: transform 0.2s ease;
}
.ecg-image:hover {
transform: scale(1.02);
}
.ecg-thumbnails {
display: flex;
justify-content: center;
gap: 10px;
margin-top: 15px;
flex-wrap: wrap;
max-width: 100%;
padding: 0 20px;
}
.ecg-thumbnail {
position: relative;
width: 80px;
height: 60px;
border-radius: 6px;
overflow: hidden;
cursor: pointer;
border: 2px solid transparent;
transition: all 0.3s ease;
}
.ecg-thumbnail:hover {
border-color: #409EFF;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(64, 158, 255, 0.3);
}
.ecg-thumbnail.active {
border-color: #409EFF;
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.4);
}
.thumbnail-image {
width: 100%;
height: 100%;
}
.thumbnail-index {
position: absolute;
top: 2px;
right: 2px;
background: rgba(0, 0, 0, 0.7);
color: white;
font-size: 12px;
padding: 2px 4px;
border-radius: 3px;
min-width: 16px;
text-align: center;
}
.no-ecg-image {
color: #909399;
font-size: 18px;
}
.no-time-data {
padding: 20px;
text-align: center;
color: #909399;
font-size: 14px;
}
.doctor-notification {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 20px;
background-color: #fff;
display: flex;
gap: 15px;
align-items: flex-start;
z-index: 1;
margin-top: 20px;
}
.doctor-notification :deep(.el-textarea__inner) {
flex: 1;
height: 80px !important;
}
.doctor-send-btn {
height: 80px !important;
min-width: 60px;
white-space: pre-line;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
line-height: 1.1;
padding: 0 16px;
}
.conclusion-container {
padding: 20px;
display: flex;
flex-direction: column;
gap: 15px;
}
.conclusion-textarea {
width: 100%;
}
.conclusion-textarea :deep(.el-textarea__inner) {
font-size: 14px;
line-height: 1.6;
border-radius: 8px;
border: 1px solid #DCDFE6;
transition: border-color 0.3s ease;
}
.conclusion-textarea :deep(.el-textarea__inner):focus {
border-color: #409EFF;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1);
}
.conclusion-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
padding-top: 10px;
border-top: 1px solid #EBEEF5;
}
</style>