Browse Source

优化

master
LiLongLong 1 month ago
parent
commit
f3d90c4893
  1. 2
      components.d.ts
  2. BIN
      dist-v0.0.23.7z
  3. BIN
      dist-v0.0.24.7z
  4. 245
      src/components/SoftKeyboard.vue
  5. 4
      src/components/dialogs/AlarmDetailByType.vue
  6. 11
      src/pages/Index/History.vue
  7. 51
      src/pages/Index/Index.vue
  8. 28
      src/pages/Index/Regular/Running.vue
  9. 224
      src/pages/Index/Settings/Device.vue
  10. 38
      src/pages/Index/Settings/EditLis.vue
  11. 35
      src/pages/Index/Settings/Lis.vue
  12. 114
      src/pages/Index/Settings/Network.vue
  13. 98
      src/pages/Index/Settings/Version.vue
  14. 10
      src/services/osControl/os.ts
  15. 22
      src/store/modules/useSystemStore.ts
  16. 8
      src/types/Index/Settings.ts
  17. 11
      src/types/Index/System.ts
  18. 8
      src/utils/getServerInfo.ts
  19. 1
      src/websocket/socket.ts

2
components.d.ts

@ -27,12 +27,12 @@ declare module 'vue' {
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElLink: typeof import('element-plus/es')['ElLink']
ElOption: typeof import('element-plus/es')['ElOption']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTag: typeof import('element-plus/es')['ElTag']

BIN
dist-v0.0.23.7z

BIN
dist-v0.0.24.7z

245
src/components/SoftKeyboard.vue

@ -1,5 +1,4 @@
<script lang="ts" setup>
// import pinyinDict from 'libs/pinyinDict.json'
import { computed, defineEmits, defineProps, onMounted, ref, watch, watchEffect } from 'vue'
const props = defineProps<{
@ -14,9 +13,7 @@ const emits = defineEmits<{
(e: 'confirm', value: string): void
(e: 'close'): void
}>()
const languageType = ref('en')
// const cnList = ref<string[]>([])
// const pinyinMap: Record<string, string> = pinyinDict as unknown as Record<string, string>
onMounted(() => {
document.addEventListener('click', (e: any) => {
if (isOpen.value && !e.target?.name) {
@ -28,65 +25,52 @@ onMounted(() => {
const isOpen = ref(false)
watchEffect(() => {
// EventListener
setTimeout(() => {
isOpen.value = props.isVisible
}, 100)
})
const activeKey = ref('')
//
const numberLayout = computed(() => [
['1', '2', '3', 'del'],
['4', '5', '6', '.'],
['7', '8', '9', 'enter'],
['+', '0', '-', 'enter']
])
//
const textLayout = computed(() => [
['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'del'],
['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '/'],
['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', 'enter'],
['z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '?', '.', ':'],
[' ',]
])
// keyboardType
const keyboardLayout = computed(() => {
if (props.keyboardType === 'number') {
return [
['1', '2', '3'],
['4', '5', '6'],
['7', '8', '9'],
['.', '0', 'del'],
]
}
return [
['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'del'],
['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '/'],
['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', 'enter'],
['z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '?', '.', ':'],
[' ', 'close'],
]
return props.keyboardType === 'number' ? numberLayout.value : textLayout.value
})
const specialKeys = ['del', 'enter', ' ']
// const handleKeyCn = (cn: string) => {
// console.log('cn===', cn)
// }
const specialKeys = ['del', 'enter', ' ', '.', 'close']
const handleKeyPress = (key: string) => {
activeKey.value = key
setTimeout(() => {
activeKey.value = ''
}, 150)
}, 10)
if (key === 'del') {
const value = props.modelValue.slice(0, -1)
console.log('value--------', value)
emits('update:modelValue', value)
}
else if (key === 'enter') {
} else if (key === 'enter') {
emits('confirm', props.modelValue)
closeKeyboard()
}
else if (key === 'close') {
} else if (key === 'close') {
closeKeyboard()
}
else if ((key === 'en' || key === 'cn') && props.keyboardType === 'text') {
languageType.value = key === 'en' ? 'cn' : 'en'
keyboardLayout.value[4][1] = key === 'en' ? 'cn' : 'en'
}
else {
} else {
const keyValue = props.modelValue + key
emits('update:modelValue', keyValue)
// if (props.keyboardType === 'text' && languageType.value === 'cn') {
// const cn = pinyinMap[keyValue]
// if (cn.length) {
// cnList.value = cn as unknown as string[]
// }
// }
}
}
@ -101,47 +85,58 @@ watch(() => props.isVisible, (newVal) => {
</script>
<template>
<div v-if="isOpen" class="soft-keyboard" :class="{ 'keyboard-open': isOpen }">
<!-- <div class="keyboard-header">
<button @click="closeKeyboard">
关闭键盘
</button>
</div> -->
<div
v-if="isOpen"
class="soft-keyboard"
:class="{
'keyboard-open': isOpen,
'number-keyboard': keyboardType === 'number',
'text-keyboard': keyboardType === 'text'
}"
>
<div class="keyboard-body">
<!-- <div v-if="keyboardType === 'text' && cnList.length">
<button
v-for="(cnName, cnIndex) in cnList"
:key="cnIndex"
@click="(e) => {
e.stopPropagation()
handleKeyCn(cnName)
}"
<el-input
name="modelVal"
v-model="props.modelValue"
style="width: 93%; height: 5rem; font-size: 2rem; margin-bottom: 10px;"
/>
<div
class="keyboard-grid"
:class="{
'number-grid': keyboardType === 'number',
'text-grid': keyboardType === 'text'
}"
>
<div
v-for="(row, rowIndex) in keyboardLayout"
:key="rowIndex"
class="keyboard-row"
>
{{ cnName }}
</button>
</div> -->
<div>
<el-input name="modelVal" v-model="props.modelValue" style="width: 93%;height: 5rem;font-size: 2rem;"></el-input>
</div>
<div class="keyboard-row" v-for="(row, index) in keyboardLayout" :key="index">
<button
v-for="(key, keyIndex) in row"
:key="keyIndex"
style="font-size: 24px"
:class="{
'key-space': key === ' ',
'key-special': specialKeys.includes(key),
'key-active': activeKey === key,
'key-number': keyboardType === 'number',
'key-text': key !== ' ' && keyboardType === 'text',
}"
@click="(e) => {
e.stopPropagation()
handleKeyPress(key)
}"
>
{{ key === ' ' ? '空格' : key === 'del' ? '删除' : key === 'enter' ? '确认' : key === 'close' ? '关闭' : key }}
</button>
<div
v-for="(key, colIndex) in row"
:key="colIndex"
class="keyboard-cell"
:class="{
'key-active': activeKey === key,
'key-special': specialKeys.includes(key)&& keyboardType === 'number',
'key-confirm': key === 'enter',
'key-space': key === ' ',
'key-number-w': !specialKeys.includes(key) && keyboardType === 'number'
}"
@click="(e) => {
e.stopPropagation()
handleKeyPress(key)
}"
>
{{
key === 'del' ? '删除' :
key === 'enter' ? '确认' :
key === 'close' ? '关闭' :
key === ' ' ? '空格' :
key
}}
</div>
</div>
</div>
</div>
</div>
@ -165,70 +160,82 @@ watch(() => props.isVisible, (newVal) => {
bottom: 0;
}
.keyboard-header {
margin-bottom: 10px;
position: absolute;
float:right;
.keyboard-body {
display: flex;
flex-direction: column;
align-items: center;
padding: 10px;
}
.keyboard-header button {
padding: 8px 15px;
background-color: #e0e0e0;
border: none;
border-radius: 5px;
cursor: pointer;
/* 数字键盘网格布局 */
.number-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 5px;
width: 88vw;
height: 30vh;
}
.keyboard-body {
display: flex;
flex-direction: column;
gap: 8px;
/* 字母键盘网格布局 */
.text-grid {
display: grid;
grid-template-columns: repeat(11, 1fr);
gap: 3px;
width: 90vw;
}
.keyboard-row {
display: flex;
justify-content: center;
gap: 5px;
display: contents;
}
.keyboard-row button {
min-width: 50px;
.keyboard-cell {
display: flex;
justify-content: center;
align-items: center;
height: 5rem;
font-size: 18px;
border: none;
border-radius: 5px;
background-color: #fff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
border: 1px solid #ddd;
border-radius: 5px;
cursor: pointer;
transition: all 0.2s;
font-size: 2rem;
}
.keyboard-row button:hover {
/* background-color: #e0e0e0; */
}
.keyboard-row button:active {
.keyboard-cell:active {
transform: scale(0.95);
}
.key-space {
width: 45vw;;
.key-active {
background-color: #a0c4ff;
transform: scale(0.95);
}
.key-special {
background-color: #e0e0e0;
width: 9rem;
height: 7rem;
}
.key-active {
background-color: #a0c4ff;
transform: scale(0.95);
/* 数字键盘确认按钮跨行吗样式 */
.number-grid .key-confirm {
grid-row: 3 / 5;
grid-column: 4 / 5;
height: auto;
}
.key-number{
width: 30vw;
/* 字母键盘确认按钮样式 */
.text-grid .key-confirm {
background-color: #e0e0e0;
}
/* 空格按钮样式(仅字母键盘) */
.key-space {
grid-column: span 11;
width: 90vw;
}
.key-text {
width: 8vw;
.key-number-w{
width: 15rem;
height: 7rem;
}
</style>
</style>

4
src/components/dialogs/AlarmDetailByType.vue

@ -3,7 +3,7 @@
<div v-if="currEventReport && currEventReport.errorPromptInfo">
<div>
<span class="error-title">错误类型</span>
<span class="error-title-content">{{currEventReport.errorPromptInfo.title || (currEventReport.flagType === 'ErrorFlag' ? '错误' : '')}}</span>
<span class="error-title-content">{{currEventReport.errorPromptInfo.title || ((currEventReport.flagType === 'ErrorFlag' || currEventReport.flagType === 'ERROR') ? '错误' : '')}}</span>
</div>
<div>
<span class="error-title">错误信息</span>
@ -47,7 +47,7 @@
v-for="(row, index) in currEventReport.errorPromptInfo.detailInfo.vars"
:key="index"
>
<div v-for="(col, idx) in row" :key="idx">
<div v-for="(col, idx) in row" :key="idx" :style="col.messageLevel === 'Error'? 'color: red' : ''">
{{ col.val }}
</div>
</div>

11
src/pages/Index/History.vue

@ -22,7 +22,7 @@
</template>
</el-table-column>
<el-table-column prop="sampleId" label="患者ID" ></el-table-column>
<el-table-column prop="sampleBloodType" label="样本类型">
<el-table-column prop="sampleBloodType" label="样本类型" width="100">
<template #default="scope">
{{
settingTubeStore.bloodTypeKeyMap[scope.row.sampleBloodType].name
@ -32,7 +32,7 @@
<el-table-column prop="lotId" label="批次" ></el-table-column>
<el-table-column prop="results" label="结果" width="150px">
<template #default="scope">
<div v-for="(r, idx) in scope.row.results" :key="idx" v-html="showResult(r)">
<div v-for="(r, idx) in scope.row.results" :key="idx" v-html="showResult(r)" class="history-result">
</div>
</template>
</el-table-column>
@ -186,7 +186,7 @@ const showResult = (t: ResultItem) => {
}
const unit1 = t.resultConverters[0]
const html = `<div style="display: flex">
<div style="width: 54px; white-space: nowrap;text-overflow: ellipsis;overflow: hidden">${t.subProjName}:</div>
<div style="width: 54px; white-space: nowrap;text-overflow: ellipsis;overflow: hidden;">${t.subProjName}:</div>
<div>${(t.result * unit1.A + unit1.B).toFixed(2)}</div>
<div>${unit1.uintstr}</div>
</div>`
@ -543,6 +543,11 @@ watchEffect(() => {
}
}
}
.history-result{
height: 3rem;
display: flex;
align-items: center;
}
:deep(.table-header) {
height: 60px;
}

51
src/pages/Index/Index.vue

@ -42,15 +42,17 @@
data-tab="常规"
>常规</router-link
>
<el-badge :value="badgeValue" class="item">
<router-link
to="/index/history"
class="nav-tab"
:class="{ active: currentRoute.name === 'history'}"
data-tab="历史"
>历史</router-link
>
</el-badge>
<router-link
to="/index/history"
class="nav-tab"
:class="{ active: currentRoute.name === 'history'}"
data-tab="历史"
>
<el-badge :value="badgeValue" class="item">
历史
</el-badge>
</router-link>
<router-link
to="/index/setting"
@ -365,7 +367,7 @@
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { useRouter, useRoute } from 'vue-router'
import { ref, onMounted, computed, watch, nextTick, useTemplateRef, watchEffect } from 'vue'
import { ElDialog, ElMessageBox } from 'element-plus'
import { Time, InitWarn, LoadingModal } from './components/Consumables'
@ -428,6 +430,7 @@ import { useSystemStore } from '@/store/modules/useSystemStore.ts' // 引入 sys
const systemStore = useSystemStore() // 使 systemStor
const router = useRouter()
const route = useRoute()
//
const currentRoute = computed(() => router.currentRoute.value)
console.log(currentRoute.value)
@ -524,6 +527,8 @@ const wsState = createWebSocket(stateUrl.wsUrl)
const eventReports = ref<PromptAvailable[]>([])
const showEventReportDlg = ref(false)
const countdown = ref()
const currEventReport = computed(() => {
return eventReports.value.length > 0 ? eventReports.value[0] : undefined
})
@ -827,6 +832,15 @@ const getBloodTypeList = async () => {
}
}
onMounted(() => {
// 退
startCountdown(systemStore.systemInfo.autoLogoutTimeout)
document.addEventListener('click', () => {
clearTimeout(countdown.value)
if(systemStore.systemInfo.autoLogout){
//
startCountdown(systemStore.systemInfo.autoLogoutTimeout)
}
})
eventBus.on('initDevice', showInitDeviceAlert)
eventBus.on('socketClosed', handleSocketClose)
//
@ -895,6 +909,19 @@ onMounted(() => {
// .map(item => `${item.typechinfo} `);
// };
const startCountdown = (time) => {
console.log('time---', time)
console.log('deviceStore.deviceState.workState--', deviceStore.deviceState.workState)
console.log('route.fullPath--', route.fullPath)
countdown.value = setTimeout(() => {
if(deviceStore.deviceState.workState === 'IDLE' && route.fullPath){
sessionStorage.setItem('token', '')
onLogout()
// router.push('/login')
}
}, time * 60 * 1000)
}
const untilDeviceReady = async () => {
deviceWaitingModelInfo.value = {
title: '设备正在响应中',
@ -1101,10 +1128,6 @@ watch(
// warnMessage.value = '退'
// return
// }
router.push({
path: '/login',
})
sessionStorage.setItem('token', '')
}
},
)

28
src/pages/Index/Regular/Running.vue

@ -149,6 +149,9 @@
</div>
<div class="tube_info"><div class="tube-label">样本类型</div><div>{{settingTubeStore.bloodTypeKeyMap[tube.bloodType].name}}</div></div>
<div class="tube_info"><div class="tube-label">状态</div><div>{{emergencyStateDesc[tube.state]}}</div></div>
<div v-if="tube.state === 'ERROR'" class="tube_info"><div class="tube-label">错误信息</div><div>
<el-button link class="error-detail" @click="onShowErrorDetail(tube)">查看详细</el-button>
</div></div>
<div class="tube_info"><div class="tube-label">用户ID</div><div>{{tube.sampleId}}</div></div>
<div class="tube_info"><div class="tube-label">Barcode: </div><div>{{tube.sampleBarcode}}</div></div>
</div>
@ -216,6 +219,9 @@
<el-dialog v-model="alarmRecordVisible" title="状态历史记录" width="50vh">
<AlarmRecordModal />
</el-dialog>
<el-dialog v-model="tubeErrorVisible" title="错误详细" width="50vh">
<AlarmDetailByTypeModal />
</el-dialog>
</div>
<!-- 弹窗提示 -->
<!-- <teleport to="body">
@ -255,6 +261,7 @@ import AlarmModal from '@/components/dialogs/AlarmModal.vue'
import AlarmRecordModal from '@/components/dialogs/AlarmRecordModal.vue'
import { useSystemStore } from '@/store/modules/useSystemStore.ts'
import AlarmModalHistory from '@/components/dialogs/AlarmModalHistory.vue'
import AlarmDetailByTypeModal from '@/components/dialogs/AlarmDetailByType.vue'
const consumablesStore = useConsumablesStore()
const runningStore = useRunningStore()
@ -270,6 +277,7 @@ const hasAlarm = ref(false)
const alarmRecordVisible = ref(false)
const emergencyInfo = ref(emergencyStore.emergencyInfo)
const statusList = ref(systemStore.statusList)
const tubeErrorVisible = ref(false)
watchEffect(() => {
alarmList.value = deviceStore.alarmList
@ -361,6 +369,18 @@ const onShowAlarmHistory = () => {
const onShowAlarmRecordModal = () => {
alarmRecordVisible.value = true
}
const onShowErrorDetail = (tubeInfo) => {
console.log('tubeInfo---', tubeInfo)
const errorItem = {
errorPromptInfo:tubeInfo.errorInfo,
flagType: tubeInfo.state,
}
deviceStore.updateAlarmItem(errorItem)
tubeErrorVisible.value = true;
}
</script>
<style lang="less" scoped>
#running-container {
@ -532,7 +552,7 @@ const onShowAlarmRecordModal = () => {
.full-state {
position: absolute;
left: 50%;
bottom: 55px;
bottom: 2.5rem;
transform: translateX(-50%);
color: #fff;
font-size: 20px;
@ -713,8 +733,8 @@ const onShowAlarmRecordModal = () => {
.scan-main {
position: absolute;
top: 42vh;
right: -6vw;
bottom: -5px;
padding: 0.5rem 0.3125rem;
background-color: #dfedf8;
border-radius: 0.3125rem;
@ -765,4 +785,8 @@ const onShowAlarmRecordModal = () => {
font-weight: 600;
}
}
.error-detail{
color: #e45858;
font-size: 14px;
}
</style>

224
src/pages/Index/Settings/Device.vue

@ -149,80 +149,6 @@
</button>
</div>
</div>
<el-dialog v-model="networkVisible" width="600" title="WAN口基本设置">
<div class="setting-item-netword">
<div v-if="networkForm.networkModel === 'STATIC_IP'">
<div class="setting-item">
<span class="label">IP地址</span>
<div>
<input
style="width: 20rem"
v-model="networkForm.ip"
name="ip"
@focus="openKeyboard"
readonly
/>
</div>
</div>
<div class="setting-item">
<span class="label">子网掩码</span>
<div>
<input
style="width: 20rem"
v-model="networkForm.netmask"
name="netmask"
@focus="openKeyboard"
readonly
/>
</div>
</div>
<div class="setting-item">
<span class="label">网关</span>
<div>
<input
style="width: 20rem"
v-model="networkForm.gateway"
name="gateway"
@focus="openKeyboard"
readonly
/>
</div>
</div>
</div>
<div class="setting-item">
<span class="label">首先DNS</span>
<div>
<input
style="width: 20rem"
v-model="networkForm.firstDns"
name="firstDns"
@focus="openKeyboard"
readonly
/>
</div>
</div>
<div class="setting-item">
<span class="label">备选DNS</span>
<div>
<input
style="width: 20rem"
v-model="networkForm.spareDns"
name="spareDns"
@focus="openKeyboard"
readonly
/>
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="networkVisible = false">取消</el-button>
<el-button type="primary" @click="onSaveNetwork">
确认
</el-button>
</div>
</template>
</el-dialog>
</div>
<!-- 键盘 -->
<transition name="slide-up">
@ -252,32 +178,21 @@ import {
setAllTemperature,
setIncubateBoxTemperature,
setPlateBoxTemperature,
setDateAndTime, setNetworkSetting, getNetworkSetting
setDateAndTime,
} from '@/services'
import { eMessage, isValidIPv4 } from '../utils'
import { useDeviceStore } from '@/store/index'
import DatePicker from '@/pages/Index/components/Setting/DatePicker.vue'
import message from 'element-plus/es/components/message/index.mjs'
import SoftKeyboard from '@/components/SoftKeyboard.vue'
import { useSystemStore } from '@/store/modules/useSystemStore.ts'
import { SystemInfo } from '@/types/Index/System.ts'
const deviceStore = useDeviceStore()
const inputValue = ref('')
const keyboardType = ref<'text' | 'number'>('number')
const softKeyboardRef = ref()
//
interface Settings {
language: string
autoPrint: boolean
autoLogout: boolean
autoLogoutTimeout: number
allTemperature: number | undefined
incubateBoxTemperature: number
plateBoxTemperature: number
DHCP: boolean
localIp: string
}
const settings = ref<Settings>({
const settings = ref<SystemInfo>({
language: 'zh-CN',
autoPrint: true,
autoLogout: false,
@ -289,118 +204,13 @@ const settings = ref<Settings>({
localIp: '',
})
type Network = 'STATIC_IP' | 'DYNAMIC_IP'
const networkVisible = ref(false)
interface NetworkForm {
ip: string,
netmask: string,
gateway: string,
firstDns: string,
spareDns: string,
networkMode: Network
}
const networkForm = ref<NetworkForm>({
id: '',
ip: '0.0.0.0',
netmask: '0.0.0.0',
gateway: '0.0.0.0',
firstDns: '0.0.0.0',
spareDns: '0.0.0.0',
networkMode: 'STATIC_IP',
})
const networkModeList = [{
label: '固定IP地址',
value: 'STATIC_IP',
},{
label: '动态获取IP地址',
value: 'DYNAMIC_IP',
}]
const selectedNetworkModel = ref<Network>('STATIC_IP')
const updateNetworkModel = (network) => {
networkForm.value.networkModel = network.value
networkVisible.value = true
}
const getNetwork = () => {
getNetworkSetting().then(res => {
if(res.success) {
const {id, networkMode, staticIPConfig } = res.data
networkForm.value = {
id,
ip: staticIPConfig.ip,
gateway: staticIPConfig.gateway,
netmask: staticIPConfig.netmask,
firstDns: staticIPConfig.dnsList[0],
spareDns: staticIPConfig.dnsList[1],
networkMode
}
console.log('networkForm--', networkForm.value)
}
})
}
const onSaveNetwork = () => {
console.log('onSaveNetwork==setForm=== ', networkForm.value)
const {ip, gateway, netmask, spareDns, firstDns, networkMode} = networkForm.value
if(networkMode === 'STATIC_IP'){
if(!ip){
message.error('请输入IP地址')
return;
}
if(!gateway){
message.error('请输入网关地址')
return;
}
if(!netmask){
message.error('请输入子网掩码地址')
return;
}
if(!firstDns){
message.error('请输入首先DNS')
return;
}
if(!spareDns){
message.error('请输入血液DNS')
return;
}
}
const params = {
id: networkForm.value.id,
networkMode: selectedNetworkModel.value,
staticIPConfig: {},
dynamicIpConfig: {},
}
const staticConfig = {
ip,
netmask: netmask,
gateway: gateway,
dnsList: [firstDns, spareDns],
}
params.staticIPConfig = staticConfig
params.dynamicIpConfig = {
dnsList: [firstDns, spareDns]
}
setNetworkSetting(params).then(res => {
if(res.success){
message.success('设置成功')
networkVisible.value = false
selectedNetworkModel.value = networkForm.value.networkModel
getNetwork()
}
})
}
//
const currentDate = ref(new Date())
const time = ref('00:00:00')
let pickerKey = ref(0)
// const dateFormats = ['MM.dd.yyyy', 'dd.MM.yyyy', 'yyyy.MM.dd']
// const selectedDateFormat = ref(dateFormats[2])
const systemStore = useSystemStore()
//
const languages = [
@ -431,6 +241,7 @@ const fetchSettings = async () => {
const res = await getSystemSettings()
if (res.success) {
settings.value = res.data
systemStore.updateSystemInfo(res.data)
if (res.data.incubateBoxTemperature === res.data.plateBoxTemperature) {
settings.value.allTemperature = res.data.incubateBoxTemperature
}
@ -452,12 +263,7 @@ const focusedInput = ref<string | null>(null)
const openKeyboard = (e) => {
keyboardVisible.value = true
const labelName: string = e.target.name
let formValue = ''
if(networkVisible.value) {
formValue = networkForm.value[labelName]
}else {
formValue = settings.value[labelName]
}
let formValue = settings.value[labelName]
inputValue.value = formValue ? formValue.toString() : ''
focusedInput.value = labelName
}
@ -465,11 +271,7 @@ const openKeyboard = (e) => {
watch(inputValue, (newVal: string | number) => {
if (focusedInput.value) {
console.log('focusedInput.value--',focusedInput.value)
if(networkVisible.value){
networkForm.value[focusedInput.value] = newVal
}else{
settings.value[focusedInput.value] = newVal
}
settings.value[focusedInput.value] = newVal
console.log('settings.value--',settings.value)
}
})
@ -498,9 +300,15 @@ const updateSetting = async (key: string, value: any) => {
break
case 'autoLogout':
res = await setAutoLogout(value)
systemStore.updateSystemInfo({
autoLogout: value
})
break
case 'autoLogoutTimeout':
res = await setAutoLogoutTime(value)
systemStore.updateSystemInfo({
autoLogoutTimeout: value
})
break
case 'allTemperature':
if (!isIntegerInRange(value)) {
@ -576,8 +384,6 @@ const temperatures = ref<number[]>([])
//
onMounted(async () => {
await fetchSettings()
//
await getNetwork()
//
startTimer()
const res = await getTemperatureRange()

38
src/pages/Index/Settings/EditLis.vue

@ -159,7 +159,7 @@ const saveLisForm = async () => {
console.log('lisForm.lisIf00===', lisForm.value.lisIf)
if(lisForm && lisForm.value.lisIf === 'NETWORK'){
const p = lisForm.value.LISNetPortStr.toString().trim()
if (!/^\d+$/.test(p)) {
if (!isValidPort(p)) {
eMessage.error('请输入合法的端口值')
return
}
@ -208,6 +208,35 @@ const showKeyboard = (field: 'ip' | 'port') => {
keyboardVisible.value = true
}
const onCheck = (field: 'ip' | 'port' | '') => {
console.log('field========',field)
if(!field){
return;
}
let fieldValue = ''
if (field === 'ip') {
fieldValue = lisForm.value?.lisNetIp || ''
let addr = fieldValue.trim()
if (!isValidIPv4(addr)) {
eMessage.error('请输入合法的IP地址')
return
}
}
if (field === 'port') {
fieldValue = lisForm.value?.LISNetPortStr || ''
if (!isValidPort(fieldValue)) {
eMessage.error('请输入合法的端口值')
return
}
}
}
const isValidPort = (port) => {
// 0-65535
const regex = /^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/;
return regex.test(port);
}
//
const handleKeyboardInput = (value: string) => {
//
@ -225,6 +254,13 @@ watch(inputValue, (newVal: string) => {
handleKeyboardInput(newVal)
})
watch(keyboardVisible, (newVal)=>{
if(!newVal){
onCheck(currentInputField.value)
}
})
//
const hideKeyboard = () => {
keyboardVisible.value = false

35
src/pages/Index/Settings/Lis.vue

@ -3,31 +3,19 @@
<div class="setting-item">
<span class="label">类型</span>
<div class="options">
<button
class="active"
>
{{ LISTypeMap[lisSettings && lisSettings.lisType || ''] }}
</button>
{{ LISTypeMap[lisSettings && lisSettings.lisType || ''] }}
</div>
</div>
<div class="setting-item">
<span class="label">协议</span>
<div class="options">
<button
class="active"
>
Boditech
</button>
Boditech
</div>
</div>
<div class="setting-item">
<span class="label">接口</span>
<div class="options">
<button
class="active"
>
{{ LISInterfaceMap[lisSettings && lisSettings.lisIf || ''] }}
</button>
{{ LISInterfaceMap[lisSettings && lisSettings.lisIf || ''] }}
</div>
</div>
<div
@ -54,14 +42,10 @@
>
<span class="label">传输速度</span>
<div class="options">
<button
class="active"
>
{{ LISSerialBaudrateMap[lisSettings && lisSettings.lisSerialBaudrate || '']}}
</button>
{{ LISSerialBaudrateMap[lisSettings && lisSettings.lisSerialBaudrate || '']}}
</div>
</div>
<div>
<div class="lis-btn">
<button class="edit-lis" @click="onEditList" >
编辑LIS
</button>
@ -239,7 +223,7 @@ onUnmounted(() => {
}
}
.edit-lis{
width: 100%;
width: 100vw;
height: 5rem;
padding: 0 24px;
border: 1px solid #dcdfe6;
@ -247,11 +231,14 @@ onUnmounted(() => {
font-size: 24px;
cursor: pointer;
transition: all 0.3s ease;
color: #fff;
background-color: #409eff;
}
.lis-btn{
width: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
background-color: #409eff;
}
.keyboard {
position: fixed;

114
src/pages/Index/Settings/Network.vue

@ -52,25 +52,30 @@
/>
</div>
</div>
<div
class="setting-item"
v-for="(dns, index) in networkForm.dnsList"
:key="index"
>
<span class="label">{{ index === 0 ? '首先DNS' : '备选DNS' }}</span>
<div>
<el-input
style="width: 21rem"
v-model="networkForm.dnsList[index]"
name="firstDns"
@focus="e=>openKeyboard(e, index)"
readonly
>
<template #append>
<el-icon v-if="index === 0 && networkForm.dnsList.length !== 3" @click="onAddDns()"><Plus /></el-icon>
<el-icon v-else @click="onDelDns(index)"><Minus /></el-icon>
</template>
</el-input>
<div class="dns-list">
<div
class="setting-item dns-item"
v-for="(dns, index) in networkForm.dnsList"
:key="index"
>
<span class="label">{{ index === 0 ? 'DNS' : '' }}</span>
<div class="dns-input">
<span class="dns-title">
{{ index === 0 ? '首先' : '备选' }}
</span>
<el-input
style="width: 21rem"
v-model="networkForm.dnsList[index]"
name="firstDns"
@focus="e=>openKeyboard(e, index)"
readonly
>
<template #append>
<el-icon v-if="index === 0 && networkForm.dnsList.length !== 3" @click="onAddDns()"><Plus /></el-icon>
<el-icon v-else @click="onDelDns(index)"><Close /></el-icon>
</template>
</el-input>
</div>
</div>
</div>
</template>
@ -102,29 +107,33 @@
</div>
</div>
<template v-if="!networkForm.autoDns && networkForm.networkMode === 'DYNAMIC_IP'">
<div
class="setting-item"
v-for="(dns, index) in networkForm.dnsList"
:key="index"
>
<span class="label">{{ index === 0 ? '首先DNS' : '备选DNS' }}</span>
<div>
<el-input
style="width: 21rem"
v-model="networkForm.dnsList[index]"
name="firstDns"
@focus="(e) => openKeyboard(e, index)"
readonly
>
<template #append>
<el-icon v-if="index === 0 && networkForm.dnsList.length !== 3" @click="onAddDns('dynamicIpConfig')"
<div class="dns-list">
<div
class="setting-item dns-item"
v-for="(dns, index) in networkForm.dnsList"
:key="index"
>
<span class="label">{{ index === 0 ? 'DNS' : '' }}</span>
<div class="dns-input">
<span class="dns-title">
{{ index === 0 ? '首先' : '备选' }}
</span>
<el-input
style="width: 21rem"
v-model="networkForm.dnsList[index]"
name="firstDns"
@focus="(e) => openKeyboard(e, index)"
readonly
>
<template #append>
<el-icon v-if="index === 0 && networkForm.dnsList.length !== 3" @click="onAddDns('dynamicIpConfig')"
><Plus
/></el-icon>
<el-icon v-else @click="onDelDns('dynamicIpConfig')"
><Minus
/></el-icon>
</template>
</el-input>
/></el-icon>
<el-icon v-else @click="onDelDns('dynamicIpConfig')">
<Close /></el-icon>
</template>
</el-input>
</div>
</div>
</div>
</template>
@ -185,11 +194,11 @@ const defaultDnsData = ref()
const networkModeList = [
{
label: '固定IP地址',
label: '静态IP',
value: 'STATIC_IP',
},
{
label: '动态获取IP地址',
label: '动态IP',
value: 'DYNAMIC_IP',
},
]
@ -498,4 +507,23 @@ onUnmounted(() => {
color: #fff;
background-color: #409eff;
}
.dns-list{
background-color: #ffffff;
padding: 10px;
.dns-item{
box-shadow: none;
padding: 10px 1.5rem;
}
}
.dns-title{
font-size: 1.5rem;
color: #888888;
padding-right: 8px;
}
.dns-input{
display: flex;
align-items: center;
}
</style>

98
src/pages/Index/Settings/Version.vue

@ -1,7 +1,7 @@
<template>
<div class="version-setting">
<div class="setting-item">
<span class="label">SN</span>
<span class="label">设备ID</span>
<span class="value">{{ info?.sn }}</span>
</div>
<div class="setting-item">
@ -9,12 +9,12 @@
<span class="value">{{ info?.assetId }}</span>
</div>
<div class="setting-item">
<span class="label">App</span>
<span class="label">应用软件版本</span>
<span class="value">{{ info?.appVersion }}</span>
</div>
<div class="setting-item">
<span class="label">MCU</span>
<span class="value">{{ info?.mcuVersion }}</span>
<span class="label">MCU软件版本</span>
<el-link type="primary" class="mcu-version" @click="onShowMCUDetail">{{ info?.mcuVersion }}</el-link>
</div>
<div class="setting-item">
<span class="label">Local IP</span>
@ -24,16 +24,43 @@
<span class="label">系统版本号</span>
<span class="value">v{{ version}}</span>
</div>
<el-dialog v-model="mcuVisible" title="MCU版本详细信息" width="80vw" style="min-height: 30vh">
<div class="mcu-detail">
<table class="mcu-table">
<thead>
<tr>
<th>名称</th>
<th>版本号</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in boardVersions" :key="index">
<td style="text-align:left">{{ item.boardName }}</td>
<td>{{ item.version }}</td>
</tr>
</tbody>
</table>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="mcuVisible = false">返回</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { DeviceInfo, getDeviceInfo } from '@/services';
import { DeviceInfo, getDeviceInfo, getMcuVersionDetailInfo } from '@/services'
import { onActivated, onMounted, ref } from 'vue';
import { eMessage } from '../utils';
import { McuVersionInfo } from '@/types/Index'
const info = ref<DeviceInfo>()
const version = import.meta.env.VITE_APP_VERSION;
const mcuVisible = ref(false);
const boardVersions = ref<McuVersionInfo[]>()
const getInfo = async () => {
const res = await getDeviceInfo()
if (res && res.success) {
@ -48,6 +75,16 @@ onMounted(() => {
onActivated(() => {
getInfo()
})
const onShowMCUDetail = () => {
getMcuVersionDetailInfo().then(res => {
if(res.success) {
boardVersions.value = res.data.boardVersions
mcuVisible.value = true;
}
})
}
</script>
<style lang="less" scoped>
.version-setting {
@ -78,6 +115,57 @@ onActivated(() => {
font-size: 28px;
color: #606266;
}
.mcu-version{
font-size: 28px;
}
}
}
.column-version{
font-weight: 600;
font-size: 1.6rem;
color: #303133;
}
/* 页面整体背景色,可根据喜好调整 */
.mcu-detail {
background-color: #f4f4f4;
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
}
/* 表格整体样式 */
.mcu-table {
width: 100%; /* 可调整表格宽度,比如设为100%占满容器 */
margin: 20px auto; /* 让表格居中 */
border-collapse: collapse; /* 合并边框 */
background-color: #fff; /* 表格背景色 */
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1); /* 增加点阴影让表格立体些,可选 */
font-size: 2rem;
}
/* 表头样式 */
.mcu-table thead th {
padding: 10px;
background-color: #f1f1f1;
color: #353535;
}
/* 表格行样式,隔行换色 */
.mcu-table tbody tr:nth-child(even) {
background-color: #f8f9fa; /* 偶数行背景色,可调整 */
}
.mcu-table tbody tr:nth-child(odd) {
background-color: #ffffff; /* 奇数行背景色,可调整 */
}
/* 表格单元格样式 */
.mcu-table tbody td {
padding: 10px;
border-bottom: 1px solid #ddd; /* 底部边框分隔 */
width: 5rem;
}
</style>

10
src/services/osControl/os.ts

@ -155,4 +155,12 @@ export const disableDevice = async () => {
}
}
// MCU详细版本信息
export const getMcuVersionDetailInfo = async () => {
try {
const res = await apiClient.post('/api/v1/app/DeviceInfo/getMcuVersionDetail')
return res.data
} catch (error) {
console.log('MCU详细版本信息获取报错', error)
}
}

22
src/store/modules/useSystemStore.ts

@ -1,5 +1,6 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { SystemInfo } from '@/types/Index/System.ts'
export const useSystemStore = defineStore('system', () => {
const isDebug = ref(false)
@ -23,6 +24,24 @@ export const useSystemStore = defineStore('system', () => {
const updateStatusList = (status) => {
statusList.value.push(status)
}
const systemInfo = ref<SystemInfo>({
language: 'zh-CN',
autoPrint: true,
autoLogout: false,
autoLogoutTimeout: 10,
allTemperature: 20,
incubateBoxTemperature: 20,
plateBoxTemperature: 20,
DHCP: true,
localIp: '',
})
const updateSystemInfo = (data) => {
systemInfo.value = {
...systemInfo.value,
...data,
}
}
return {
isDebug,
@ -34,5 +53,8 @@ export const useSystemStore = defineStore('system', () => {
statusList,
updateStatusList,
systemInfo,
updateSystemInfo,
}
})

8
src/types/Index/Settings.ts

@ -26,3 +26,11 @@ export interface ResponseData {
timestamp: number
success: boolean
}
export interface McuVersionInfo {
boardName: string
mid: string
version: string
versionCode: string
}

11
src/types/Index/System.ts

@ -0,0 +1,11 @@
export interface SystemInfo {
language: string
autoPrint: boolean
autoLogout: boolean
autoLogoutTimeout: number
allTemperature: number | undefined
incubateBoxTemperature: number
plateBoxTemperature: number
DHCP: boolean
localIp: string
}

8
src/utils/getServerInfo.ts

@ -5,15 +5,15 @@ export function getServerInfo(wsPath: string = '/api/v1/app/ws/state') {
// 获取主机名(IP 或域名)和端口号
// const host = url.hostname // 例如: "192.168.1.100" 或 "localhost"
const host = window.location.hostname;
// const host = window.location.hostname;
// const host = window.location.host;
// const host = "192.168.8.175";
const host = "127.0.0.1";
// const port = '8082' // 使用固定的后端端口;由于本地开发时,8080被占用导致ws连接失败,所以使用8082
const port = "80"
// 构建 WebSocket URL
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
// const wsUrl = `${wsProtocol}//${host}:${port}${wsPath}`
const wsUrl = `${wsProtocol}//${host}${wsPath}`
const wsUrl = `${wsProtocol}//${host}:${port}${wsPath}`
// const wsUrl = `${wsProtocol}//${host}${wsPath}`
// 构建 HTTP URL
const httpUrl = `${window.location.protocol}//${host}:${port}` // 例如: "http://192.168.1.100:8082" 或 "http://localhost:8082"

1
src/websocket/socket.ts

@ -255,6 +255,7 @@ interface TubeHolderStateMessage extends BaseMessage {
projIds: number[]
state: RunningTubeState
errors: string[]
erroInfo: string
}>
state: string
}

Loading…
Cancel
Save