Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing Details

main
caiyiling 2026-01-27 13:50:28 +08:00
commit 7ab9d0f285
6 changed files with 322 additions and 111 deletions

View File

@ -1,12 +1,7 @@
<template> <template>
<div id="app" style="position: relative"> <div id="app" style="position: relative">
<router-view /> <router-view />
<div <div v-show="show" v-if="$route.matched.length > 0" v-adaptive @click="openI18n" style="
v-show="show"
v-if="$route.matched.length > 0"
v-adaptive
@click="openI18n"
style="
position: fixed; position: fixed;
bottom: 50px; bottom: 50px;
left: 50px; left: 50px;
@ -19,58 +14,27 @@
color: #fff; color: #fff;
border-radius: 50%; border-radius: 50%;
cursor: pointer; cursor: pointer;
" ">
>
i18n i18n
</div> </div>
<el-drawer <el-drawer :title="$t('il8n:title')" :visible.sync="drawer" direction="rtl" size="80%">
:title="$t('il8n:title')"
:visible.sync="drawer"
direction="rtl"
size="80%"
>
<div style="width: 800px"> <div style="width: 800px">
<el-form <el-form label-width="100px" @submit.native.prevent size="small" :inline="true" class="demo-form-inline">
label-width="100px"
@submit.native.prevent
size="small"
:inline="true"
class="demo-form-inline"
>
<el-form-item :label="$t('il8n:search:keyword')"> <el-form-item :label="$t('il8n:search:keyword')">
<el-input v-model="key" @input="keyChange" /> <el-input v-model="key" @input="keyChange" />
</el-form-item> </el-form-item>
<el-form-item :label="$t('il8n:search:state')"> <el-form-item :label="$t('il8n:search:state')">
<el-select <el-select v-model="State" clearable filterable @change="handleStateChange">
v-model="State" <el-option v-for="item of $d.InternationalizationKeyState"
clearable :key="'InternationalizationKeyState' + item.value" :label="item.label" :value="item.value" />
filterable
@change="handleStateChange"
>
<el-option
v-for="item of $d.InternationalizationKeyState"
:key="'InternationalizationKeyState' + item.value"
:label="item.label"
:value="item.value"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
<el-table <el-table :data="tableData" v-adaptive="{ bottomOffset: 50 }" height="100" style="width: 100%"
:data="tableData" @sort-change="handleSortByColumn">
v-adaptive="{ bottomOffset: 50 }" <el-table-column prop="Code" :label="$t('il8n:table:label')" width="300" show-overflow-tooltip
height="100" sortable="custom">
style="width: 100%"
@sort-change="handleSortByColumn"
>
<el-table-column
prop="Code"
:label="$t('il8n:table:label')"
width="300"
show-overflow-tooltip
sortable="custom"
>
</el-table-column> </el-table-column>
<!-- <el-table-column--> <!-- <el-table-column-->
<!-- prop="Description"--> <!-- prop="Description"-->
@ -81,71 +45,37 @@
<!-- {{scope.row.Description}}--> <!-- {{scope.row.Description}}-->
<!-- </template>--> <!-- </template>-->
<!-- </el-table-column>--> <!-- </el-table-column>-->
<el-table-column <el-table-column prop="Value" :label="$t('il8n:table:en')" sortable="custom">
prop="Value"
:label="$t('il8n:table:en')"
sortable="custom"
>
<template slot-scope="scope"> <template slot-scope="scope">
<el-input <el-input v-model="scope.row.Value" @input="
v-model="scope.row.Value" (e) => {
@input=" $set(scope.row, 'Value', e)
(e) => { }
$set(scope.row, 'Value', e) " size="mini"></el-input>
}
"
size="mini"
></el-input>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column prop="ValueCN" :label="$t('il8n:table:cn')" sortable="custom">
prop="ValueCN"
:label="$t('il8n:table:cn')"
sortable="custom"
>
<template slot-scope="scope"> <template slot-scope="scope">
<el-input <el-input v-model="scope.row.ValueCN" @input="
v-model="scope.row.ValueCN" (e) => {
@input=" $set(scope.row, 'ValueCN', e)
(e) => { }
$set(scope.row, 'ValueCN', e) " size="mini"></el-input>
}
"
size="mini"
></el-input>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column prop="ValueCN" :label="$t('il8n:table:state')" sortable="custom">
prop="ValueCN"
:label="$t('il8n:table:state')"
sortable="custom"
>
<template slot-scope="scope"> <template slot-scope="scope">
<el-select <el-select v-model="scope.row.State" clearable filterable size="mini">
v-model="scope.row.State" <el-option v-for="item of $d.InternationalizationKeyState"
clearable :key="'InternationalizationKeyState' + item.value" :label="item.label" :value="item.value" />
filterable
size="mini"
>
<el-option
v-for="item of $d.InternationalizationKeyState"
:key="'InternationalizationKeyState' + item.value"
:label="item.label"
:value="item.value"
/>
</el-select> </el-select>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column prop="Version" :label="$t('il8n:table:Version')" sortable="custom">
prop="Version"
:label="$t('il8n:table:Version')"
sortable="custom"
>
</el-table-column> </el-table-column>
</el-table> </el-table>
<div style="text-align: right; padding-top: 10px; padding-right: 10px"> <div style="text-align: right; padding-top: 10px; padding-right: 10px">
<el-button size="mini" @click="drawer = false" <el-button size="mini" @click="drawer = false">{{ $t('common:button:cancel') }}
>{{ $t('common:button:cancel') }}
</el-button> </el-button>
<el-button size="mini" type="primary" @click="handleSave">{{ <el-button size="mini" type="primary" @click="handleSave">{{
$t('common:button:save') $t('common:button:save')
@ -153,6 +83,7 @@
</div> </div>
</el-drawer> </el-drawer>
<feedBack v-if="$route.matched.length > 0" /> <feedBack v-if="$route.matched.length > 0" />
<timeTag />
</div> </div>
</template> </template>
@ -163,11 +94,12 @@ import {
} from '@/api/dictionary/dictionary' } from '@/api/dictionary/dictionary'
import { getTrialExtralConfig } from '@/api/trials' import { getTrialExtralConfig } from '@/api/trials'
import feedBack from '@/views/trials/trials-layout/components/feedBack' import feedBack from '@/views/trials/trials-layout/components/feedBack'
import timeTag from '@/components/timeTag'
import Vue from 'vue' import Vue from 'vue'
import i18n from './lang' import i18n from './lang'
export default { export default {
name: 'App', name: 'App',
components: { feedBack }, components: { feedBack, timeTag },
data() { data() {
return { return {
drawer: false, drawer: false,
@ -366,13 +298,16 @@ export default {
<style lang="scss"> <style lang="scss">
$light_gray: #606266; $light_gray: #606266;
.el-tooltip__popper { .el-tooltip__popper {
max-width: 400px; max-width: 400px;
} }
.my_multiple { .my_multiple {
.el-input--medium .el-input__inner { .el-input--medium .el-input__inner {
height: 36px !important; height: 36px !important;
} }
.el-select__tags { .el-select__tags {
flex-wrap: nowrap; flex-wrap: nowrap;
overflow: hidden; overflow: hidden;
@ -381,6 +316,7 @@ $light_gray: #606266;
display: block; display: block;
} }
} }
input::-webkit-outer-spin-button, input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button { input::-webkit-inner-spin-button {
-webkit-appearance: none !important; -webkit-appearance: none !important;
@ -393,6 +329,7 @@ input[type='number'] {
.viewer-fixed.viewer-container { .viewer-fixed.viewer-container {
z-index: 10000; z-index: 10000;
} }
textarea { textarea {
white-space: break-spaces; white-space: break-spaces;
word-break: normal; word-break: normal;
@ -401,12 +338,15 @@ textarea {
* { * {
word-break: normal !important; word-break: normal !important;
} }
.box-body .el-button.is-circle:not(.is-disabled) i:before { .box-body .el-button.is-circle:not(.is-disabled) i:before {
color: #428bca; color: #428bca;
} }
.box-body .el-button.is-circle i.el-icon-question:before { .box-body .el-button.is-circle i.el-icon-question:before {
color: #fff; color: #fff;
} }
.system-title { .system-title {
font-size: 35px; font-size: 35px;
color: $light_gray; color: $light_gray;
@ -415,9 +355,11 @@ textarea {
font-family: 'Times New Roman'; font-family: 'Times New Roman';
text-shadow: 1px 0.5px 1.5px #666; text-shadow: 1px 0.5px 1.5px #666;
} }
.title-logo { .title-logo {
height: 40px; height: 40px;
} }
.title-logo { .title-logo {
height: 40px; height: 40px;
} }

View File

@ -4422,4 +4422,12 @@ export function getTrialSubjectVisitMarkList(data) {
method: 'post', method: 'post',
data data
}) })
}
// 更新缩略图
export function updateImageResizePath(data) {
return request({
url: `/Series/updateImageResizePath`,
method: 'post',
data
})
} }

View File

@ -0,0 +1,115 @@
<template>
<div id="timeTag" :style="`color:${suggestionTextColor};background-color: inherit;`">{{ time }}</div>
</template>
<script>
import moment from 'moment'
export default {
name: "timeTag",
data() {
return {
time: '',
timer: null,
suggestionTextColor: '#000'
}
},
created() {
this.getTime()
},
async mounted() {
this.dragDoc()
this.setColor()
},
methods: {
getTime() {
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
this.timer = setInterval(async () => {
this.time = moment(new Date()).format('YYYY-MM-DD HH:mm:ss Z z')
}, 1000)
},
dragDoc() {
const box = document.querySelector('#timeTag')
const body = document.getElementsByTagName('body')[0]
let maxLeft = body.offsetWidth - box.offsetWidth
let maxTop = body.offsetHeight - box.offsetHeight - 30
box.style.cssText += `left:${maxLeft}px;top:0px;`
const sty = (function () {
if (window.document.currentStyle) {
return (dom, attr) => dom.currentStyle[attr]
} else {
return (dom, attr) => getComputedStyle(dom, false)[attr]
}
})()
box.onmousedown = (e) => {
//
const disX = e.clientX - box.offsetLeft
const disY = e.clientY - box.offsetTop
// px
let styL = sty(box, 'left')
let styT = sty(box, 'top')
// ie 50% px
if (styL.includes('%')) {
styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100)
styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100)
} else {
styL = +styL.replace(/\px/g, '')
styT = +styT.replace(/\px/g, '')
}
const oldMousemove = document.onmousemove
document.onmousemove = function (e) {
oldMousemove(e)
//
let left = e.clientX - disX
let top = e.clientY - disY
if (left < 0) {
left = 0
}
if (left > maxLeft) {
left = maxLeft
}
if (top < 0) {
top = 0
}
if (top > maxTop) {
top = maxTop
}
//
box.style.cssText += `;left:${left}px;top:${top}px;`
}
document.onmouseup = function (e) {
document.onmousemove = oldMousemove
document.onmouseup = null
}
}
},
setColor() {
this.suggestionTextColor = "#000"
let pathList = ['/showvisitdicoms', '/showdicom', '/readingDicoms', '/petct', '/noneDicomReading']
if (pathList.includes(window.location.pathname)) {
this.suggestionTextColor = "#fff"
}
}
},
destroyed() {
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
}
}
</script>
<style lang="scss" scoped>
#timeTag {
position: fixed;
z-index: 99999;
cursor: move;
width: 220px;
}
</style>

View File

@ -29,8 +29,12 @@
:src="item.previewImageUrl" :src="item.previewImageUrl"
fit="fill" fit="fill"
/> --> /> -->
<img class="image-preview" :src="item.previewImageUrl" crossorigin="anonymous" alt="" <div class="imageBox" style="width: 72px;height:72px;">
style="width: 72px;height:72px;" fit="fill"> <img class="image-preview" :src="item.previewImageUrl" crossorigin="anonymous" alt=""
style="width: 72px;height:72px;" fit="fill" />
<i class="el-icon-refresh" :title="$t('tip:refreshImage')"
@click.stop="refreshImage(item)"></i>
</div>
<div class="viewernavitextwrapper"> <div class="viewernavitextwrapper">
<div style="padding: 1px 5px 1px 1px;display: flex;justify-content: space-between;"> <div style="padding: 1px 5px 1px 1px;display: flex;justify-content: space-between;">
<div v-if="item.keySeries" style="color:red"> <div v-if="item.keySeries" style="color:red">
@ -136,7 +140,7 @@ import * as cornerstone from 'cornerstone-core'
import * as cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader' import * as cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'
import dicomViewer from '@/components/Dicom/DicomViewer' import dicomViewer from '@/components/Dicom/DicomViewer'
import { getStudyInfo, getSeriesList } from '@/api/reading' import { getStudyInfo, getSeriesList } from '@/api/reading'
import { getInstanceList, getPatientSeriesList, setSeriesStatus, setInstanceStatus } from '@/api/trials' import { getInstanceList, getPatientSeriesList, setSeriesStatus, setInstanceStatus, updateImageResizePath } from '@/api/trials'
import requestPoolManager from '@/utils/request-pool' import requestPoolManager from '@/utils/request-pool'
import store from '@/store' import store from '@/store'
@ -247,6 +251,62 @@ export default {
workSpeedclose(true) workSpeedclose(true)
}, },
methods: { methods: {
async updateImageResizePath(data) {
try {
let res = await updateImageResizePath(data)
if (res.IsSuccess) {
this.$message.success(this.$t("message:tip:updateImageResizePath:success"))
}
} catch (err) {
console.log(err)
}
},
async refreshImage(item) {
let thumbnailPath = item.previewImageUrl.split(this.OSSclientConfig.basePath)[1]
let blob = await this.dicomToPng(item.imageIds[0])
let OSSclient = this.OSSclient
try {
let seriesRes = await OSSclient.put(thumbnailPath, blob)
if (seriesRes && seriesRes.url) {
let path = this.$getObjectName(seriesRes.url)
item.previewImageUrl = seriesRes.url
let data = {
seriesId: item.seriesId,
ImageResizePath: path
}
this.updateImageResizePath(data)
}
} catch (e) {
}
},
dicomToPng(imageId) {
return new Promise((resolve) => {
cornerstone.loadImage(imageId).then(async (image) => {
let width = image.columns, height = image.rows;
let canvas = document.createElement('canvas')
canvas.width = (width * 60) / height
canvas.height = 60
if (image) {
cornerstone.renderToCanvas(canvas, image)
// Canvas PNG
let blob = await this.canvasToBlob(canvas)
resolve(blob)
} else {
resolve()
}
})
}).catch((reason) => {
reason()
})
},
canvasToBlob(canvas) {
return new Promise((resolve) => {
canvas.toBlob((blob) => {
resolve(blob)
})
})
},
async loadStudy() { async loadStudy() {
let params = {} let params = {}
if (this.isPacs) { if (this.isPacs) {
@ -905,7 +965,18 @@ export default {
} }
</script> </script>
<style> <style lang="scss" scoped>
.imageBox {
position: relative;
i {
position: absolute;
top: 0;
left: 0;
cursor: pointer;
}
}
.viewerContainer .el-tabs--border-card { .viewerContainer .el-tabs--border-card {
background: none; background: none;
border: none; border: none;

View File

@ -45,8 +45,12 @@
style="position: relative;margin-bottom:5px;border-radius: 2px;border: 1px solid #404040;" style="position: relative;margin-bottom:5px;border-radius: 2px;border: 1px solid #404040;"
series-type="current" @click="showSeriesImage($event, index, i, series)"> series-type="current" @click="showSeriesImage($event, index, i, series)">
<div class="viewernavigatorwrapper"> <div class="viewernavigatorwrapper">
<img class="image-preview" :src="series.previewImageUrl" crossorigin="anonymous" alt="" <div class="imageBox" style="width: 72px;height:72px;">
style="width: 85px;height:85px;" fit="fill"> <img class="image-preview" :src="series.previewImageUrl" crossorigin="anonymous" alt=""
style="width: 72px;height:72px;" fit="fill" />
<i class="el-icon-refresh" :title="$t('tip:refreshImage')"
@click.stop="refreshImage(series)"></i>
</div>
<div class="viewernavitextwrapper"> <div class="viewernavitextwrapper">
<div style="padding: 1px 5px 1px 1px;display: flex;justify-content: space-between;"> <div style="padding: 1px 5px 1px 1px;display: flex;justify-content: space-between;">
<div>#{{ series.seriesNumber }}</div> <div>#{{ series.seriesNumber }}</div>
@ -184,8 +188,12 @@
<div class="viewernavigatorwrapper" <div class="viewernavigatorwrapper"
style="position: relative;border:1px solid #434343;" series-type="relation" style="position: relative;border:1px solid #434343;" series-type="relation"
@click="showRelationSeriesImage($event, seriesItem, studyIndex, index)"> @click="showRelationSeriesImage($event, seriesItem, studyIndex, index)">
<img class="image-preview" :src="seriesItem.previewImageUrl" crossorigin="anonymous" <div class="imageBox" style="width: 72px;height:72px;">
alt="" style="width: 85px;height:85px;" fit="fill"> <img class="image-preview" :src="seriesItem.previewImageUrl"
crossorigin="anonymous" alt="" style="width: 72px;height:72px;" fit="fill" />
<i class="el-icon-refresh" :title="$t('tip:refreshImage')"
@click.stop="refreshImage(item)"></i>
</div>
<div class="viewernavitextwrapper"> <div class="viewernavitextwrapper">
<div <div
@ -273,7 +281,7 @@ import * as cornerstone from 'cornerstone-core'
import * as cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader' import * as cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'
import dicomViewer from '@/components/Dicom/DicomViewer' import dicomViewer from '@/components/Dicom/DicomViewer'
import { getVisitStudyList, getAllRelationStudyList, getSeriesList } from '@/api/reading' import { getVisitStudyList, getAllRelationStudyList, getSeriesList } from '@/api/reading'
import { setSeriesStatus, setInstanceStatus } from '@/api/trials' import { setSeriesStatus, setInstanceStatus, updateImageResizePath } from '@/api/trials'
import { getTaskUploadedDicomStudyList } from '@/api/reading' import { getTaskUploadedDicomStudyList } from '@/api/reading'
import requestPoolManager from '@/utils/request-pool' import requestPoolManager from '@/utils/request-pool'
import store from '@/store' import store from '@/store'
@ -358,6 +366,62 @@ export default {
workSpeedclose(true) workSpeedclose(true)
}, },
methods: { methods: {
async updateImageResizePath(data) {
try {
let res = await updateImageResizePath(data)
if (res.IsSuccess) {
this.$message.success(this.$t("message:tip:updateImageResizePath:success"))
}
} catch (err) {
console.log(err)
}
},
async refreshImage(item) {
let thumbnailPath = item.previewImageUrl.split(this.OSSclientConfig.basePath)[1]
let blob = await this.dicomToPng(item.imageIds[0])
let OSSclient = this.OSSclient
try {
let seriesRes = await OSSclient.put(thumbnailPath, blob)
if (seriesRes && seriesRes.url) {
let path = this.$getObjectName(seriesRes.url)
item.previewImageUrl = seriesRes.url
let data = {
seriesId: item.seriesId,
ImageResizePath: path
}
this.updateImageResizePath(data)
}
} catch (e) {
}
},
dicomToPng(imageId) {
return new Promise((resolve) => {
cornerstone.loadImage(imageId).then(async (image) => {
let width = image.columns, height = image.rows;
let canvas = document.createElement('canvas')
canvas.width = (width * 60) / height
canvas.height = 60
if (image) {
cornerstone.renderToCanvas(canvas, image)
// Canvas PNG
let blob = await this.canvasToBlob(canvas)
resolve(blob)
} else {
resolve()
}
})
}).catch((reason) => {
reason()
})
},
canvasToBlob(canvas) {
return new Promise((resolve) => {
canvas.toBlob((blob) => {
resolve(blob)
})
})
},
// 访 // 访
async getStudiesInfo() { async getStudiesInfo() {
this.studyList = [] this.studyList = []
@ -947,7 +1011,18 @@ export default {
} }
</script> </script>
<style> <style lang="scss">
.imageBox {
position: relative;
i {
position: absolute;
top: 0;
left: 0;
cursor: pointer;
}
}
.viewerContainer .el-tabs--border-card { .viewerContainer .el-tabs--border-card {
background: none; background: none;
border: none; border: none;

View File

@ -43,7 +43,7 @@
/> />
<!-- 分组(EN) --> <!-- 分组(EN) -->
<el-table-column <el-table-column
prop="QuestionGroupEnName" prop="QuestionGroupName"
v-if="$i18n.locale === 'en'" v-if="$i18n.locale === 'en'"
:label="$t('trials:readingUnit:qsList:title:groupNameEn')" :label="$t('trials:readingUnit:qsList:title:groupNameEn')"
show-overflow-tooltip show-overflow-tooltip