Browse Source

优化网络断开

master
LiLongLong 1 month ago
parent
commit
ef611cf3a5
  1. 30
      src/apis/system.ts
  2. 112
      src/app.vue
  3. 2
      src/assets/images/background-login.svg
  4. 245
      src/components/formula/FormulaConfig.vue
  5. 8
      src/components/formula/FormulaTable.vue
  6. 28
      src/components/home/Environment.vue
  7. 84
      src/components/home/HomeOperation.vue
  8. 188
      src/components/home/HomeSetting.vue
  9. 58
      src/components/home/config.vue
  10. 1
      src/components/liquid/LiquidLevel.vue
  11. 4
      src/components/setting/SystemDate.vue
  12. 100
      src/components/system/NetReconnection.vue
  13. 48
      src/layouts/default.vue
  14. 4
      src/libs/constant.ts
  15. 6
      src/libs/countdownTimer.ts
  16. 12
      src/libs/socket.ts
  17. 23
      src/libs/utils.ts
  18. 24
      src/stores/deviceStore.ts
  19. 70
      src/stores/formulaStore.ts
  20. 98
      src/stores/homeStore.ts
  21. 4
      src/stores/initHomeData.ts
  22. 31
      src/stores/liquidStore.ts
  23. 14
      src/stores/sealStore.ts
  24. 34
      src/stores/settingStore.ts
  25. 64
      src/stores/systemStore.ts
  26. 6
      src/types/system.d.ts
  27. 62
      src/views/audit/index.vue
  28. 19
      src/views/liquid/index.vue
  29. 95
      src/views/login/index.vue
  30. 8
      src/views/seal/index.vue

30
src/apis/system.ts

@ -1,13 +1,19 @@
import { useSystemStore } from '@/stores/systemStore'
import { createWebSocket } from 'libs/socket' import { createWebSocket } from 'libs/socket'
import { nanoid } from 'nanoid'
const wsClient = createWebSocket() const wsClient = createWebSocket()
export async function sendCmd(resParams: { className: string, fnName: string, params: Record<string, any> }) {
export async function sendCmd(resParams: System.SendCmdParams) {
const { className, fnName, params = {} } = resParams
const systemStore = useSystemStore()
systemStore.updateConnected(wsClient.isConnected.value)
const res = await wsClient.waitAndSend({ const res = await wsClient.waitAndSend({
messageType: 'Command', messageType: 'Command',
fnName: resParams.fnName,
className: resParams.className,
messageId: `msg_${Date.now()}`,
params: resParams.params,
fnName,
className,
messageId: `msg_${nanoid()}`,
params,
}) })
if (res.ackcode === 0) { if (res.ackcode === 0) {
return res.rely return res.rely
@ -16,16 +22,20 @@ export async function sendCmd(resParams: { className: string, fnName: string, pa
throw new Error(res.message) throw new Error(res.message)
} }
} }
export async function syncSendCmd(resParams: { className: string, fnName: string, params: Record<string, any> }) {
export async function syncSendCmd(resParams: System.SendCmdParams) {
const { className, fnName, params = {} } = resParams
const systemStore = useSystemStore()
systemStore.updateConnected(wsClient.isConnected.value)
return await wsClient.sendRequest({ return await wsClient.sendRequest({
messageType: 'Command', messageType: 'Command',
fnName: resParams.fnName,
className: resParams.className,
messageId: `msg_${Date.now()}`,
params: resParams.params,
fnName,
className,
messageId: `msg_${nanoid()}`,
params,
}) })
} }
// 业务状态订阅
export async function subscribeEvent(fromFn: string | '*', callback: (response: Socket.WebSocketResponse) => void) { export async function subscribeEvent(fromFn: string | '*', callback: (response: Socket.WebSocketResponse) => void) {
wsClient.socket.addEventListener('message', (event) => { wsClient.socket.addEventListener('message', (event) => {
const data = JSON.parse(event.data) const data = JSON.parse(event.data)

112
src/app.vue

@ -6,19 +6,38 @@ import { useLiquidStore } from 'stores/liquidStore'
import { onBeforeMount, ref } from 'vue' import { onBeforeMount, ref } from 'vue'
import { useFormulaStore } from './stores/formulaStore' import { useFormulaStore } from './stores/formulaStore'
/**
* 应用初始化组件
* @description 负责系统初始化流程控制包括设备信息获取数据同步及进度展示
*/
//
const deviceStore = useDeviceStore() const deviceStore = useDeviceStore()
const homeStore = useHomeStore() const homeStore = useHomeStore()
const formulaStore = useFormulaStore() const formulaStore = useFormulaStore()
const liquidStore = useLiquidStore() const liquidStore = useLiquidStore()
//
const progress = ref(0) //
const version = __APP_VERSION__ //
let timer: any = null //
/**
* @hook 生命周期钩子 - 组件挂载前执行
* @description 启动初始化流程和数据轮询
*/
onBeforeMount(async () => { onBeforeMount(async () => {
//
startProgress()
// ()
setInterval(async () => {
await readH2o2Data()
}, 2000)
startProgress() //
// 2
// setInterval(async () => {
// await readH2o2Data()
// }, 3000)
}) })
/**
* @function 初始化设备信息
* @desc 从服务端获取设备信息并更新到状态存储
*/
const initDeviceInfo = async () => { const initDeviceInfo = async () => {
const deviceParams = { const deviceParams = {
className: 'DeviceInfoMgrService', className: 'DeviceInfoMgrService',
@ -30,82 +49,91 @@ const initDeviceInfo = async () => {
return res return res
} }
/**
* @function 获取默认配方数据
* @desc 从服务端获取所有配置信息并更新配方配置
*/
const getFormualDefaultData = async () => { const getFormualDefaultData = async () => {
const defaultParams = {
className: 'SettingMgrService',
fnName: 'getAllSetting',
params: {},
}
const res = await sendCmd(defaultParams)
formulaStore.updateFormulaConfigData(res)
formulaStore.getFormualDefaultData()
} }
const progress = ref(0)
let timer: any = null
const version = __APP_VERSION__
/**
* @function 启动初始化进度条
* @desc 模拟初始化进度同步加载各项数据
*/
const startProgress = () => { const startProgress = () => {
timer = setInterval(async () => { timer = setInterval(async () => {
const randomStep = Math.floor(Math.random() * 9 + 1)
// 1-9%
const randomStep = Math.floor((Math.random() * 9) + 1)
progress.value = Math.min(progress.value + randomStep, 100) progress.value = Math.min(progress.value + randomStep, 100)
//
await initData() await initData()
//
await initLiquidConfig() await initLiquidConfig()
await initDeviceInfo() await initDeviceInfo()
//
await getFormualDefaultData() await getFormualDefaultData()
//
if (progress.value >= 100) { if (progress.value >= 100) {
clearInterval(timer) clearInterval(timer)
} }
}, 100) }, 100)
} }
const readH2o2Data = async () => {
const envParams = {
fnName: 'readH2O2SensorData',
className: 'FrontEndRealtimeDisplayContentMgr',
params: {},
}
const resData = await sendCmd(envParams)
if (resData.val.length) {
homeStore.updateHomeData(resData.val)
}
}
/**
* @function 读取过氧化氢传感器数据
* @desc 轮询获取环境传感器数据并更新到主页状态
*/
// const readH2o2Data = async () => {
// const envParams = {
// fnName: 'readH2O2SensorData',
// className: 'FrontEndRealtimeDisplayContentMgr',
// params: {},
// }
// const resData = await sendCmd(envParams)
// if (resData.val.length) {
// homeStore.updateHomeData(resData.val)
// }
// }
/**
* @function 初始化核心数据
* @desc 同步获取消毒状态液体余量和设备状态数据
*/
const initData = async () => { const initData = async () => {
//
//
const disinfectionParams = { const disinfectionParams = {
className: 'DisinfectionCtrlServiceExt', className: 'DisinfectionCtrlServiceExt',
fnName: 'getState', fnName: 'getState',
params: {},
} }
const disinfectionData = await sendCmd(disinfectionParams) const disinfectionData = await sendCmd(disinfectionParams)
homeStore.updateHomeDisinfectionState(disinfectionData) homeStore.updateHomeDisinfectionState(disinfectionData)
// 使
//
const liquidParams = { const liquidParams = {
fnName: 'getState', fnName: 'getState',
className: 'AddLiquidService', className: 'AddLiquidService',
params: {},
} }
const liquidData = await sendCmd(liquidParams) const liquidData = await sendCmd(liquidParams)
liquidStore.updateLiquidState(liquidData) liquidStore.updateLiquidState(liquidData)
//
const deviceParams = { const deviceParams = {
className: 'AppCore', className: 'AppCore',
fnName: 'getState',
params: {},
fnName: 'startStateReport',
} }
//
const deviceData = await sendCmd(deviceParams)
homeStore.setDeviceState(deviceData)
await sendCmd(deviceParams)
// deviceStore.setDeviceState(deviceData)
} }
//
/**
* @function 初始化液体配置
* @desc 获取液体最大容量和更新周期配置
*/
const initLiquidConfig = async () => { const initLiquidConfig = async () => {
const params = { const params = {
className: 'AddLiquidService', className: 'AddLiquidService',
fnName: 'getServiceConfig', fnName: 'getServiceConfig',
params: {},
} }
const liquidConfig = await sendCmd(params) const liquidConfig = await sendCmd(params)
liquidStore.initLiquidConfig(liquidConfig) liquidStore.initLiquidConfig(liquidConfig)

2
src/assets/images/background-login.svg
File diff suppressed because it is too large
View File

245
src/components/formula/FormulaConfig.vue

@ -1,30 +1,81 @@
<script lang="ts" setup> <script lang="ts" setup>
import { FtMessage } from '@/libs/message' import { FtMessage } from '@/libs/message'
import { convertValuesToString } from '@/libs/utils'
import { compareJSON, convertValuesToInt, convertValuesToString } from '@/libs/utils'
import { useFormulaStore } from '@/stores/formulaStore' import { useFormulaStore } from '@/stores/formulaStore'
import { syncSendCmd } from 'apis/system'
import { sendCmd, syncSendCmd } from 'apis/system'
import SelectModal from 'components/common/SelectModal/index.vue' import SelectModal from 'components/common/SelectModal/index.vue'
import SoftKeyboard from 'components/common/SoftKeyboard/index.vue' import SoftKeyboard from 'components/common/SoftKeyboard/index.vue'
import { formulaNameMap } from 'libs/constant' import { formulaNameMap } from 'libs/constant'
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash'
import { inject, onMounted, ref, watch, watchEffect } from 'vue' import { inject, onMounted, ref, watch, watchEffect } from 'vue'
/**
* 配方表单组件 - 用于配置和管理各种配方参数
* 支持三种模式主页模式(home)设置模式(setting)和配方管理模式(formula)
* @component
* @props {string} type - 组件使用模式可选值'home' | 'setting' | 'formula'
*/
const props = defineProps<{ const props = defineProps<{
type: string type: string
}>() }>()
const formulaStore = useFormulaStore() const formulaStore = useFormulaStore()
/**
* 当前表单数据
* 不同模式下有不同的初始值
* - home: 当前选中的配方
* - setting: 默认配方信息
* - formula: 当前选中的配方或默认配方
*/
const formData = ref<Record<string, any>>({ const formData = ref<Record<string, any>>({
...formulaStore.defaultFormulaInfo, ...formulaStore.defaultFormulaInfo,
}) })
/**
* 软键盘当前输入值
*/
const inputValue = ref<string>('') const inputValue = ref<string>('')
/**
* 软键盘是否可见
*/
const keyboardVisible = ref(false) const keyboardVisible = ref(false)
/**
* 软键盘类型'text' 'number'
*/
const keyboardType = ref<'text' | 'number'>('number') const keyboardType = ref<'text' | 'number'>('number')
/**
* 软键盘组件引用
*/
const softKeyboardRef = ref() const softKeyboardRef = ref()
/**
* 当前聚焦的输入字段名称
*/
const focusedInput = ref<string | null>(null) const focusedInput = ref<string | null>(null)
/**
* 注册孙子组件方法的注入函数
*/
const registerGrandsonMethods = inject<(methods: any) => void>('registerGrandsonMethods', () => {}) const registerGrandsonMethods = inject<(methods: any) => void>('registerGrandsonMethods', () => {})
/**
* 配方配置列表
*/
const formulaConfigList = ref(formulaStore.formulaConfigList) const formulaConfigList = ref(formulaStore.formulaConfigList)
/**
* 日志级别选项列表
*/
const options = ref(formulaStore.logLevelOptions) const options = ref(formulaStore.logLevelOptions)
/**
* 标签单位映射表用于显示各参数的单位
*/
const labelUnitMap: Record<string, any> = { const labelUnitMap: Record<string, any> = {
injection_pump_speed: 'g/min', injection_pump_speed: 'g/min',
continued_gs: 'ppm', continued_gs: 'ppm',
@ -35,30 +86,54 @@ const labelUnitMap: Record<string, any> = {
stoped_satur: '%RS', stoped_satur: '%RS',
loglevel: 'Log', loglevel: 'Log',
} }
/**
* 组件挂载时注册方法供父组件调用
*/
onMounted(() => { onMounted(() => {
registerGrandsonMethods && registerGrandsonMethods({ getFormData }) registerGrandsonMethods && registerGrandsonMethods({ getFormData })
}) })
/**
* 模态框选中的值
*/
const selectedValue = ref() const selectedValue = ref()
/**
* 模态框是否打开
*/
const isModalOpen = ref(false) const isModalOpen = ref(false)
/**
* 打开模态框
*/
const openModal = () => { const openModal = () => {
isModalOpen.value = true isModalOpen.value = true
} }
/**
* 监听配方配置列表和表单数据变化
* 根据不同的type属性值初始化表单数据
*/
watchEffect(() => { watchEffect(() => {
formulaConfigList.value = formulaStore.formulaConfigList formulaConfigList.value = formulaStore.formulaConfigList
if (props.type === 'home') { if (props.type === 'home') {
formData.value = cloneDeep(formulaStore.selectedFormulaInfo) || cloneDeep(formulaStore.defaultFormulaInfo) formData.value = cloneDeep(formulaStore.selectedFormulaInfo) || cloneDeep(formulaStore.defaultFormulaInfo)
} }
else if (props.type === 'setting') { else if (props.type === 'setting') {
formData.value = formulaStore.defaultFormulaInfo
formData.value = cloneDeep(formulaStore.defaultFormulaInfo)
} }
else { else {
formData.value = cloneDeep(formulaStore.currentSelectedFormulaInfo) || cloneDeep(formulaStore.defaultFormulaInfo) formData.value = cloneDeep(formulaStore.currentSelectedFormulaInfo) || cloneDeep(formulaStore.defaultFormulaInfo)
} }
// int
formData.value = convertValuesToInt(formData.value)
}) })
//
/**
* 监听软键盘输入值变化更新表单数据
* @param {string | number} newVal - 新的输入值
*/
watch(inputValue, (newVal: string | number) => { watch(inputValue, (newVal: string | number) => {
if (focusedInput.value) { if (focusedInput.value) {
if (focusedInput.value !== 'name') { if (focusedInput.value !== 'name') {
@ -68,56 +143,139 @@ watch(inputValue, (newVal: string | number) => {
} }
}) })
// form
/**
* 获取当前表单数据将值转换为字符串格式
* @returns {Record<string, string>} 转换后的表单数据
*/
const getFormData = () => { const getFormData = () => {
return convertValuesToString(formData.value, 'name') return convertValuesToString(formData.value, 'name')
} }
//
/**
* 监听表单数据变化同步更新软键盘输入值
* @param {Record<string, any>} newValue - 新的表单数据
*/
watch(formData, (newValue) => { watch(formData, (newValue) => {
if (focusedInput.value) { if (focusedInput.value) {
inputValue.value = newValue[focusedInput.value].toString() inputValue.value = newValue[focusedInput.value].toString()
} }
}, { deep: true }) }, { deep: true })
//
/**
* 处理表单提交
* 根据不同的type属性值执行不同的保存逻辑
*/
const handleSubmit = () => { const handleSubmit = () => {
if (!formData.value.name) {
if (props.type !== 'setting' && !formData.value.name) {
FtMessage.warning('请输入配方名称') FtMessage.warning('请输入配方名称')
return return
} }
if (formData.value.formula_id) {
const formulaForm: Record<string, string> = convertValuesToString(formData.value, 'name')
const editParams = {
className: 'SettingMgrService',
fnName: 'updateFormula',
params: {
formula_id: formData.value.formula_id,
formula: cloneDeep(formulaForm),
},
//
if (props.type === 'formula') {
onSaveFormula()
} }
syncSendCmd(editParams).then(() => {
FtMessage.success('操作成功')
formulaStore.initFormulaList()
// getFormualDefaultData()
})
//
if (props.type === 'setting') {
onSaveSetting()
}
}
/**
* 保存配方
* 根据是否有formula_id决定是添加新配方还是编辑已有配方
*/
const onSaveFormula = () => {
console.log('formData.value.formula_id---', formData.value.formula_id)
if (formData.value.formula_id) {
const formulaForm: Record<string, any> = convertValuesToString(formData.value, 'name')
onEditFormula(formulaForm.formula_id, formulaForm as Formula.FormulaItem)
} }
else { else {
onAddFormula() onAddFormula()
} }
} }
/**
* 保存设置
* 比较当前表单数据与默认配方数据的差异只更新有变化的字段
*/
const onSaveSetting = async () => {
//
const diff = compareJSON(formulaStore.defaultFormulaInfo, formData.value)
const diffKeys = Object.keys(diff)
if (diffKeys.length) {
await Promise.all(diffKeys.map(async (key) => {
await setSettingFormulaConfig(key, diff[key].newVal)
}))
FtMessage.success('配方修改成功')
}
}
/**
* 设置配方配置项值
* @param {string} settingName - 设置名称
* @param {string} settingVal - 设置值
* @returns {Promise<void>}
*/
const setSettingFormulaConfig = async (settingName: string, settingVal: string) => {
await sendCmd({
className: 'SettingMgrService',
fnName: 'setSettingVal',
params: {
settingName,
settingVal: settingVal.toString(),
},
})
formulaStore.getFormualDefaultData()
}
/**
* 添加新配方
* 先调用API创建新配方然后更新配方数据
*/
const onAddFormula = () => { const onAddFormula = () => {
const params = { const params = {
className: 'SettingMgrService', className: 'SettingMgrService',
fnName: 'addNewFormula', fnName: 'addNewFormula',
params: {},
} }
syncSendCmd(params).then(() => {
syncSendCmd(params).then((res) => {
if (res.ackcode === 0) {
const item = res.rely
const formulaForm: Record<string, any> = convertValuesToString(formData.value, 'name')
formulaForm.name = item.name
formulaForm.formula_id = item.formula_id
onEditFormula(item.formula_id, formulaForm as Formula.FormulaItem)
}
})
}
/**
* 编辑配方
* @param {string} formula_id - 配方ID
* @param {Formula.FormulaItem} formulaForm - 配方表单数据
*/
const onEditFormula = (formula_id: string, formulaForm: Formula.FormulaItem) => {
const editParams = {
className: 'SettingMgrService',
fnName: 'updateFormula',
params: {
formula_id,
formula: cloneDeep(formulaForm),
},
}
syncSendCmd(editParams).then(() => {
FtMessage.success('操作成功')
formulaStore.initFormulaList() formulaStore.initFormulaList()
formData.value = formulaForm
}) })
} }
/**
* 打开软键盘
* @param {Event} e - 事件对象
*/
const openKeyboard = (e: any) => { const openKeyboard = (e: any) => {
setTimeout(() => { setTimeout(() => {
keyboardVisible.value = true keyboardVisible.value = true
@ -129,23 +287,53 @@ const openKeyboard = (e: any) => {
}, 100) }, 100)
} }
/**
* 取消操作重置配方数据
*/
const handleCancel = () => { const handleCancel = () => {
formulaStore.initFormulaData() formulaStore.initFormulaData()
} }
/**
* 恢复默认设置
*/
const handleResetDefault = async () => {
await sendCmd({
className: 'SettingMgrService',
fnName: 'factoryResetSettings',
})
formulaStore.getFormualDefaultData()
}
/**
* 确认输入值
* @param {string} value - 输入值
*/
const handleConfirm = (value: string) => { const handleConfirm = (value: string) => {
console.log('确认输入:', value) console.log('确认输入:', value)
} }
/**
* 确认日志级别选择
* @param {any} value - 选择的值
*/
const handleLogConfirm = (value: any) => { const handleLogConfirm = (value: any) => {
isModalOpen.value = false isModalOpen.value = false
formData.value.loglevel = value formData.value.loglevel = value
formulaStore.loglevel = value formulaStore.loglevel = value
} }
/**
* 取消日志级别选择
*/
const handleLogCancel = () => { const handleLogCancel = () => {
isModalOpen.value = false isModalOpen.value = false
} }
//
/**
* 根据标签名称确定软键盘类型
* @param {string} labelName - 标签名称
*/
const openKeyboardType = (labelName: string) => { const openKeyboardType = (labelName: string) => {
keyboardType.value = labelName === 'name' ? 'text' : 'number' keyboardType.value = labelName === 'name' ? 'text' : 'number'
} }
@ -154,7 +342,7 @@ const openKeyboardType = (labelName: string) => {
<template> <template>
<div class="formula-form"> <div class="formula-form">
<el-form :model="formData" label-width="auto" label-position="left" class="formulaFormItem" inline> <el-form :model="formData" label-width="auto" label-position="left" class="formulaFormItem" inline>
<el-form-item label="配方名称" style="margin-top:20px">
<el-form-item v-if="type !== 'setting'" label="配方名称" style="margin-top:20px">
<el-input v-model="formData.name" v-prevent-keyboard name="name" placeholder="配方名称" class="formdata-input-home" @focus="openKeyboard" /> <el-input v-model="formData.name" v-prevent-keyboard name="name" placeholder="配方名称" class="formdata-input-home" @focus="openKeyboard" />
</el-form-item> </el-form-item>
<el-form-item <el-form-item
@ -210,7 +398,10 @@ const openKeyboardType = (labelName: string) => {
<el-button type="primary" @click="handleSubmit"> <el-button type="primary" @click="handleSubmit">
确定 确定
</el-button> </el-button>
<el-button @click="handleCancel">
<el-button v-if="type === 'setting'" @click="handleResetDefault">
恢复默认值
</el-button>
<el-button v-else @click="handleCancel">
取消 取消
</el-button> </el-button>
</div> </div>

8
src/components/formula/FormulaTable.vue

@ -1,14 +1,13 @@
<script lang="ts" setup> <script lang="ts" setup>
import { FtMessage } from '@/libs/message' import { FtMessage } from '@/libs/message'
import { FtMessageBox } from '@/libs/messageBox' import { FtMessageBox } from '@/libs/messageBox'
import { convertValuesToInt } from '@/libs/utils'
import { useFormulaStore } from '@/stores/formulaStore' import { useFormulaStore } from '@/stores/formulaStore'
import { useHomeStore } from '@/stores/homeStore'
import { syncSendCmd } from 'apis/system' import { syncSendCmd } from 'apis/system'
import { ElMessageBox } from 'element-plus' import { ElMessageBox } from 'element-plus'
import { onMounted, ref, watchEffect } from 'vue' import { onMounted, ref, watchEffect } from 'vue'
const formulaStore = useFormulaStore() const formulaStore = useFormulaStore()
const homeStore = useHomeStore()
const selectedIndex = ref<number | null>(null) const selectedIndex = ref<number | null>(null)
const recipes = ref<Formula.FormulaItem[]>([]) const recipes = ref<Formula.FormulaItem[]>([])
const currectFormula = ref<Formula.FormulaItem>() const currectFormula = ref<Formula.FormulaItem>()
@ -34,11 +33,13 @@ const initFormulaList = () => {
const selectRecipe = (item: Formula.FormulaItem, index: number) => { const selectRecipe = (item: Formula.FormulaItem, index: number) => {
selectedIndex.value = selectedIndex.value === index ? null : index selectedIndex.value = selectedIndex.value === index ? null : index
item = convertValuesToInt(item) as Formula.FormulaItem
formulaStore.updateSelectedFormulaData(item) formulaStore.updateSelectedFormulaData(item)
} }
const onStartFormula = (item: Formula.FormulaItem) => { const onStartFormula = (item: Formula.FormulaItem) => {
formulaStore.updateSelectedFormulaData(item) formulaStore.updateSelectedFormulaData(item)
//
FtMessageBox.warning('请确认是否使用此配方进行消毒?').then(() => { FtMessageBox.warning('请确认是否使用此配方进行消毒?').then(() => {
currectFormula.value = item currectFormula.value = item
if (item.formula_id) { if (item.formula_id) {
@ -51,8 +52,7 @@ const onStartFormula = (item: Formula.FormulaItem) => {
} }
syncSendCmd(params).then((res) => { syncSendCmd(params).then((res) => {
if (res.ackcode === 0) { if (res.ackcode === 0) {
//
homeStore.subscribeDisinfectEvent()
FtMessage.success('已开始消毒')
} }
}) })
} }

28
src/components/home/Environment.vue

@ -5,10 +5,23 @@ import homeProbe2 from 'assets/images/home/home-probe2.svg'
import { roundNumber } from 'libs/utils' import { roundNumber } from 'libs/utils'
import { onMounted } from 'vue' import { onMounted } from 'vue'
/**
* 环境参数展示组件
* @description 用于展示仓内或探头的环境数据温度湿度过氧化氢浓度等
* @props {Object} envParams - 环境参数对象
* @props {string} lineColor - 线条颜色默认值red
*/
defineProps({ defineProps({
envParams: { envParams: {
type: Object, type: Object,
default: () => {},
default: () => ({
type: 'inside', // inside/env1/env2
title: '仓内', //
temp: 0, //
rh: 0, // 湿
rs: 0, //
h2o2: 0, //
}),
}, },
lineColor: { lineColor: {
type: String, type: String,
@ -16,12 +29,25 @@ defineProps({
}, },
}) })
/**
* 图片资源映射对象
* @type {Record<string, any>}
* @property {string} inside - 仓内图标路径
* @property {string} env1 - 探头1图标路径
* @property {string} env2 - 探头2图标路径
*/
const imgs: Record<string, any> = { const imgs: Record<string, any> = {
inside: homeInside, inside: homeInside,
env1: homeProbe1, env1: homeProbe1,
env2: homeProbe2, env2: homeProbe2,
} }
/**
* @hook 生命周期钩子 - 组件挂载完成时执行
* @description 可在此处添加初始化逻辑当前为空实现
*/
onMounted(() => { onMounted(() => {
//
}) })
</script> </script>

84
src/components/home/HomeOperation.vue

@ -12,11 +12,19 @@ import { startTimer, stopTimer } from 'libs/countdownTimer'
import { deviceStateMap } from 'libs/utils' import { deviceStateMap } from 'libs/utils'
import { computed, onMounted, ref, watchEffect } from 'vue' import { computed, onMounted, ref, watchEffect } from 'vue'
/**
* 消毒操作控制组件
* @description 负责处理消毒流程控制开始/结束状态监听及倒计时逻辑
*/
//
const homeStore = useHomeStore() const homeStore = useHomeStore()
const formulaStore = useFormulaStore() const formulaStore = useFormulaStore()
const systemStore = useSystemStore() const systemStore = useSystemStore()
const curStateRemainTimeS = ref<string>('')
const disinfectionState = ref(homeStore.disinfectionState)
//
const curStateRemainTimeS = ref<string>('') //
const disinfectionState = ref(homeStore.disinfectionState) //
const btnStyle = { const btnStyle = {
width: '27vw', width: '27vw',
height: '7vh', height: '7vh',
@ -24,11 +32,17 @@ const btnStyle = {
borderRadius: '12px', borderRadius: '12px',
textColor: '#FFFFFF', textColor: '#FFFFFF',
} }
let isDisinfection = false
let isDisinfection = false //
/**
* @hook 响应式依赖监听
* @description 监听消毒状态变化处理倒计时逻辑
*/
watchEffect(() => { watchEffect(() => {
disinfectionState.value = homeStore.disinfectionState disinfectionState.value = homeStore.disinfectionState
const time = disinfectionState.value.curStateRemainTimeS const time = disinfectionState.value.curStateRemainTimeS
//
if (disinfectionState.value.state === 'disinfection' && !isDisinfection && time > 0) { if (disinfectionState.value.state === 'disinfection' && !isDisinfection && time > 0) {
isDisinfection = true isDisinfection = true
startTimer(time * 1000, (times: string) => { startTimer(time * 1000, (times: string) => {
@ -38,58 +52,89 @@ watchEffect(() => {
} }
}) })
onMounted(() => {
/**
* @hook 生命周期钩子 - 组件挂载完成时执行
* @description 订阅消毒状态更新事件
*/
onMounted(async () => {
await systemStore.subscribeDisinfectEvent() //
subscribeEvent('stateUpdate', handleDisinfectState) subscribeEvent('stateUpdate', handleDisinfectState)
}) })
/**
* @function 处理消毒状态更新事件
* @param {Socket.WebSocketResponse<Home.DisinfectState>} report - 状态更新报告
* @desc 接收并更新消毒状态数据
*/
const handleDisinfectState = (report: Socket.WebSocketResponse<Home.DisinfectState>) => { const handleDisinfectState = (report: Socket.WebSocketResponse<Home.DisinfectState>) => {
if (report.fromClass === 'DisinfectionCtrlServiceExt') { if (report.fromClass === 'DisinfectionCtrlServiceExt') {
homeStore.updateHomeDisinfectionState(report.rely) homeStore.updateHomeDisinfectionState(report.rely)
} }
} }
//
/**
* @function 开始消毒操作
* @desc 校验设备状态并发起消毒请求
*/
const onStartDisinfect = async () => { const onStartDisinfect = async () => {
//
if (!formulaStore.loglevel) { if (!formulaStore.loglevel) {
FtMessage.warning('选择消毒等级')
FtMessage.warning('选择消毒等级')
return return
} }
//
const statusName = getDeviceStatus() const statusName = getDeviceStatus()
if (statusName) { if (statusName) {
FtMessageBox.error(statusName) FtMessageBox.error(statusName)
return return
} }
systemStore.updateLoading(true)
try {
// 使
if (formulaStore.selectedFormulaInfo && formulaStore.selectedFormulaInfo.formula_id) { if (formulaStore.selectedFormulaInfo && formulaStore.selectedFormulaInfo.formula_id) {
formulaStore.saveDisinfectFormula(formulaStore.selectedFormulaInfo)
await formulaStore.saveDisinfectFormula(formulaStore.selectedFormulaInfo)
} }
else { else {
const startParams = { const startParams = {
className: 'DisinfectionCtrlServiceExt', className: 'DisinfectionCtrlServiceExt',
fnName: 'start', fnName: 'start',
params: {
loglevel: formulaStore.loglevel,
},
params: { loglevel: formulaStore.loglevel },
} }
systemStore.updateLoading(true)
await sendCmd(startParams) await sendCmd(startParams)
} }
homeStore.subscribeDisinfectEvent()
}
finally {
systemStore.updateLoading(false)
}
} }
//
/**
* @function 结束消毒操作
* @desc 停止倒计时并发起结束消毒请求
*/
const onFinishDisinfect = async () => { const onFinishDisinfect = async () => {
stopTimer()
stopTimer() //
systemStore.updateLoading(true)
try {
const stopParams = { const stopParams = {
className: 'DisinfectionCtrlServiceExt', className: 'DisinfectionCtrlServiceExt',
fnName: 'stop', fnName: 'stop',
params: {
loglevel: formulaStore.loglevel,
},
params: { loglevel: formulaStore.loglevel },
} }
systemStore.updateLoading(true)
await sendCmd(stopParams) await sendCmd(stopParams)
} }
finally {
systemStore.updateLoading(false)
}
}
/**
* @computed 计算属性 - 操作状态判断
* @returns {boolean} - 是否处于空闲或已完成状态
* @desc 控制开始/结束按钮的显示逻辑
*/
const operationState = computed(() => { const operationState = computed(() => {
return disinfectionState.value.state === 'idle' || disinfectionState.value.state === 'finished' return disinfectionState.value.state === 'idle' || disinfectionState.value.state === 'finished'
}) })
@ -128,7 +173,7 @@ const operationState = computed(() => {
</template> </template>
</bt-button> </bt-button>
</div> </div>
<!-- 开始消毒时显示 -->
<!-- 开始消毒时显示剩余时间或状态 -->
<div v-if="!operationState" class="home-remain-time"> <div v-if="!operationState" class="home-remain-time">
<div class="home-remaini-label"> <div class="home-remaini-label">
<span v-if="disinfectionState.state === 'disinfection'"> <span v-if="disinfectionState.state === 'disinfection'">
@ -138,7 +183,6 @@ const operationState = computed(() => {
消毒状态 消毒状态
</span> </span>
</div> </div>
<!-- 消毒中显示时间其它情况显示状态 -->
<div v-if="disinfectionState.state === 'disinfection'" class="home-remaini-value"> <div v-if="disinfectionState.state === 'disinfection'" class="home-remaini-value">
{{ curStateRemainTimeS }} {{ curStateRemainTimeS }}
</div> </div>

188
src/components/home/HomeSetting.vue

@ -13,133 +13,187 @@ import { cloneDeep } from 'lodash'
import { computed, onMounted, provide, ref, watchEffect } from 'vue' import { computed, onMounted, provide, ref, watchEffect } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
/**
* 主页操作控制组件
* @description 负责处理压力控制消毒设置图表导航等功能协调组件间通信
*/
//
const configRef = ref() const configRef = ref()
provide<(methods: Home.GrandsonMethods) => void>('registerGrandsonMethods', (methods) => { provide<(methods: Home.GrandsonMethods) => void>('registerGrandsonMethods', (methods) => {
configRef.value = methods configRef.value = methods
}) })
//
const router = useRouter() const router = useRouter()
const formulaStore = useFormulaStore() const formulaStore = useFormulaStore()
const homeStore = useHomeStore() const homeStore = useHomeStore()
const isModalOpen = ref(false)
const optionsLeft = ref<System.Option[]>([])
const optionsRight = ref<System.Option[]>([])
const selectedValue = ref()
const disinfectionState = ref(homeStore.disinfectionState)
//
const disinfectFormulaVisible = ref(false)
const selectedByFormulas = ref(cloneDeep(formulaStore.selectedFormulaInfo))
//
const isModalOpen = ref(false) //
const optionsLeft = ref<System.Option[]>([]) //
const optionsRight = ref<System.Option[]>([]) //
const selectedValue = ref() //
const disinfectionState = ref(homeStore.disinfectionState) //
const disinfectFormulaVisible = ref(false) //
const selectedByFormulas = ref(cloneDeep(formulaStore.selectedFormulaInfo)) //
const pressureConfig = ref(homeStore.pressureConfig)
/**
* @hook 响应式依赖监听
* @description 监听消毒状态和配方变化同步更新组件状态
*/
watchEffect(() => { watchEffect(() => {
disinfectionState.value = homeStore.disinfectionState disinfectionState.value = homeStore.disinfectionState
selectedByFormulas.value = formulaStore.selectedFormulaInfo selectedByFormulas.value = formulaStore.selectedFormulaInfo
pressureConfig.value = homeStore.pressureConfig
}) })
onMounted(() => {
//
homeStore.getPressureConfig()
/**
* @hook 生命周期钩子 - 组件挂载完成时执行
* @description 初始化压力配置
*/
onMounted(async () => {
await getPressureConfig() //
}) })
/**
* @function getPressureConfig
* @desc 获取当前压力配置信息
*/
const getPressureConfig = async () => {
const pressureParams = {
className: 'PipelinePressureControl',
fnName: 'getConfig',
}
const res = await sendCmd(pressureParams)
console.log('res---', res)
homeStore.updatePressureConfig(res)
}
/**
* @computed 计算属性 - 设备状态判断
* @returns {boolean} - 设备是否处于空闲或已完成状态
* @desc 控制按钮可用状态
*/
const deviceState = computed(() => { const deviceState = computed(() => {
return disinfectionState.value.state === 'idle' || disinfectionState.value.state === 'finished' return disinfectionState.value.state === 'idle' || disinfectionState.value.state === 'finished'
}) })
/**
* @function 打开消毒设置对话框
* @desc 根据当前选中配方或默认配方初始化设置
*/
const onDisinfectConfig = () => { const onDisinfectConfig = () => {
if (selectedByFormulas.value) {
formulaStore.updateSelectedFormulaDataByList(cloneDeep(selectedByFormulas.value))
}
else {
formulaStore.updateSelectedFormulaDataByList(cloneDeep(formulaStore.defaultFormulaInfo))
}
formulaStore.updateSelectedFormulaDataByList(selectedByFormulas.value || cloneDeep(formulaStore.defaultFormulaInfo))
disinfectFormulaVisible.value = true disinfectFormulaVisible.value = true
} }
/**
* @function 导航到图表页面
* @desc 路由跳转至消毒图表页面
*/
const onShowChart = () => { const onShowChart = () => {
router.push('/home/chart') router.push('/home/chart')
} }
//
/**
* @function 保存消毒参数
* @desc 处理表单数据保存逻辑区分消毒中与非消毒状态
*/
const onSave = async () => { const onSave = async () => {
const formData = configRef.value?.getFormData() const formData = configRef.value?.getFormData()
console.log('formData---', JSON.stringify(formData))
formulaStore.updateSelectedFormulaDataByList(cloneDeep(formData))
//
if (!homeStore.isDeviceIdle) {
//
const params = {
if (!formData) {
return
}
formulaStore.updateSelectedFormulaDataByList(cloneDeep(formData)) //
if (!homeStore.isDeviceIdle) { //
const res = await sendCmd({
className: 'DisinfectionCtrlServiceExt', className: 'DisinfectionCtrlServiceExt',
fnName: 'getRealtimeConfig', fnName: 'getRealtimeConfig',
params: {}, params: {},
}
const res = await sendCmd(params)
const result = compareJSON(res, formData)
const resultKey = Object.keys(result)
if (resultKey && resultKey.length) {
resultKey.forEach(async (item) => {
await setRealtimeConfig(item, result[item].newVal)
}) })
const diff = compareJSON(res, formData)
const diffKeys = Object.keys(diff)
if (diffKeys.length) {
await Promise.all(diffKeys.map(async (key) => {
await setRealtimeConfig(key, diff[key].newVal)
}))
FtMessage.success('配方修改成功') FtMessage.success('配方修改成功')
} }
} }
else {
//
else { //
if (formData.formula_id) { if (formData.formula_id) {
await formulaStore.saveDisinfectFormula(formData) await formulaStore.saveDisinfectFormula(formData)
} }
} }
onClose()
onClose() //
} }
/**
* @function 设置实时消毒参数
* @param {string} key - 参数键名
* @param {string} val - 参数值
* @desc 向设备发送实时参数修改指令
*/
const setRealtimeConfig = async (key: string, val: string) => { const setRealtimeConfig = async (key: string, val: string) => {
const params = {
await sendCmd({
className: 'DisinfectionCtrlServiceExt', className: 'DisinfectionCtrlServiceExt',
fnName: 'setRealtimeConfig', fnName: 'setRealtimeConfig',
params: {
key,
val,
},
}
await sendCmd(params)
params: { key, val },
})
} }
//
/**
* @function 打开压力控制模态框
* @desc 初始化压力类型和强度选项
*/
const onSetPressure = () => { const onSetPressure = () => {
//
const pressureConfig = homeStore.pressureConfig
const { typeDisplayNames, types } = pressureConfig
const intensitys: Record<string, any> = pressureConfig.intensitys
const left: System.Option[] = []
let negativePressure: string[] = []
if (typeDisplayNames && typeDisplayNames.length) {
typeDisplayNames.forEach((el: string, index: number) => {
left.push({
label: el,
const pressureVal = pressureConfig.value
const { typeDisplayNames, types, intensitys } = pressureVal || {}
//
const leftOptions: System.Option[] = typeDisplayNames?.map((name: string, index: number) => ({
label: name,
value: types[index], value: types[index],
})
if (types[index] === 'positivePressure') {
negativePressure = intensitys.positivePressure
}
})
}
optionsLeft.value = left
const right: System.Option[] = []
if (negativePressure.length) {
negativePressure.forEach((el) => {
right.push({
label: el,
value: el,
})
})) || []
//
const rightOptions: System.Option[] = []
if (types?.includes('positivePressure')) {
intensitys?.positivePressure?.forEach((intensity: string) => {
rightOptions.push({ label: `${intensity}%`, value: intensity })
}) })
} }
optionsRight.value = right
optionsLeft.value = leftOptions
optionsRight.value = rightOptions
isModalOpen.value = true isModalOpen.value = true
} }
/**
* @function 确认压力选择
* @param {string|number[]} value - 选中的压力配置值[类型, 强度]
* @desc 关闭模态框并更新压力配置
*/
const handleConfirm = (value: string | number[]) => { const handleConfirm = (value: string | number[]) => {
isModalOpen.value = false isModalOpen.value = false
homeStore.updatePressure(value) homeStore.updatePressure(value)
} }
/**
* @function 取消压力选择
* @desc 关闭模态框
*/
const handleCancel = () => { const handleCancel = () => {
isModalOpen.value = false isModalOpen.value = false
} }
/**
* @function 关闭消毒设置对话框
* @desc 重置对话框状态
*/
const onClose = () => { const onClose = () => {
disinfectFormulaVisible.value = false disinfectFormulaVisible.value = false
} }

58
src/components/home/config.vue

@ -6,27 +6,48 @@ import SelectModal from 'components/common/SelectModal/index.vue'
import FormulaConfig from 'components/formula/FormulaConfig.vue' import FormulaConfig from 'components/formula/FormulaConfig.vue'
import { computed, onMounted, ref, watchEffect } from 'vue' import { computed, onMounted, ref, watchEffect } from 'vue'
/**
* 配方选择页面组件
* @description 负责处理配方选择逻辑设备状态判断及界面交互
*/
//
const formulaStore = useFormulaStore() const formulaStore = useFormulaStore()
const homeStore = useHomeStore() const homeStore = useHomeStore()
const recipes = ref<Formula.FormulaItem[]>([])
const isModalOpen = ref(false)
const selectedValue = ref()
const isDeviceIdle = ref(homeStore.isDeviceIdle)
//
const recipes = ref<Formula.FormulaItem[]>([]) //
const isModalOpen = ref(false) //
const selectedValue = ref() // ID
const isDeviceIdle = ref(homeStore.isDeviceIdle) //
/**
* @hook 生命周期钩子 - 挂载完成时执行
* @description 初始化时检查配方列表若为空则重新获取
*/
onMounted(() => { onMounted(() => {
// store
if (!formulaStore.formulaList || !formulaStore.formulaList.length) { if (!formulaStore.formulaList || !formulaStore.formulaList.length) {
formulaStore.initFormulaList() formulaStore.initFormulaList()
} }
}) })
/**
* @hook 响应式依赖监听
* @description 监听store数据变化同步更新组件状态
*/
watchEffect(() => { watchEffect(() => {
recipes.value = [...formulaStore.formulaList] recipes.value = [...formulaStore.formulaList]
isDeviceIdle.value = homeStore.isDeviceIdle isDeviceIdle.value = homeStore.isDeviceIdle
}) })
/**
* @computed 计算属性 - 生成选择框选项
* @returns {System.Option[]} 包含label和value的选项数组
*/
const options = computed(() => { const options = computed(() => {
const list = formulaStore.formulaList const list = formulaStore.formulaList
const optionList: System.Option[] = [] const optionList: System.Option[] = []
list.forEach((item) => {
list.forEach((item: Formula.FormulaItem) => {
optionList.push({ optionList.push({
label: item.name.toString(), label: item.name.toString(),
value: item.formula_id, value: item.formula_id,
@ -34,6 +55,12 @@ const options = computed(() => {
}) })
return optionList return optionList
}) })
/**
* @function 确认选择处理函数
* @param {any} value - 选中的配方ID
* @description 关闭模态框更新选中配方并转换数值类型
*/
const handleConfirm = (value: any) => { const handleConfirm = (value: any) => {
isModalOpen.value = false isModalOpen.value = false
let selectedFormula = {} let selectedFormula = {}
@ -42,18 +69,31 @@ const handleConfirm = (value: any) => {
selectedFormula = item selectedFormula = item
} }
}) })
const selectedFormulaToInt = convertValuesToInt(selectedFormula, 'name')
//
const selectedFormulaToInt = convertValuesToInt(selectedFormula)
formulaStore.updateSelectedFormulaDataByList(selectedFormulaToInt as Formula.FormulaItem) formulaStore.updateSelectedFormulaDataByList(selectedFormulaToInt as Formula.FormulaItem)
} }
/**
* @function 取消选择处理函数
* @description 关闭模态框
*/
const handleCancel = () => { const handleCancel = () => {
isModalOpen.value = false isModalOpen.value = false
} }
/**
* @function 打开选择配方模态框
* @description 触发模态框显示
*/
const onChooseFormula = () => { const onChooseFormula = () => {
isModalOpen.value = true isModalOpen.value = true
} }
/**
* @function 应用默认配方
* @description 重置配方数据为默认配置
*/
const onDefaultFormula = () => { const onDefaultFormula = () => {
//
formulaStore.initFormulaData() formulaStore.initFormulaData()
} }
</script> </script>

1
src/components/liquid/LiquidLevel.vue

@ -61,6 +61,7 @@ const liquidHeight = computed(() => {
width: 100%; width: 100%;
background: linear-gradient(to top, #0099ff, #e6f7ff); /* 颜色渐变,可按需调整 */ background: linear-gradient(to top, #0099ff, #e6f7ff); /* 颜色渐变,可按需调整 */
border-radius: 10px; border-radius: 10px;
min-height: 1.5rem;
} }
.liquid-level-middle{ .liquid-level-middle{
background: linear-gradient(352deg, #0093f5, #effafe); background: linear-gradient(352deg, #0093f5, #effafe);

4
src/components/setting/SystemDate.vue

@ -1,9 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import { sendCmd } from '@/apis/system' import { sendCmd } from '@/apis/system'
import { FtMessage } from '@/libs/message' import { FtMessage } from '@/libs/message'
import { formatDateTime } from '@/libs/utils'
import { ref } from 'vue' import { ref } from 'vue'
const dateVal = ref()
const dateVal = ref(formatDateTime())
const onChangeDate = async (value: string) => { const onChangeDate = async (value: string) => {
if (value) { if (value) {
const splitDate = value.split(' ') // const splitDate = value.split(' ') //

100
src/components/system/NetReconnection.vue

@ -0,0 +1,100 @@
<script setup lang="ts">
import { useSystemStore } from '@/stores/systemStore'
import { startTimer, stopTimer } from 'libs/countdownTimer'
import { ref, watchEffect } from 'vue'
import { useRouter } from 'vue-router'
const systemStore = useSystemStore()
const router = useRouter()
const timer = ref()
const countdownToReconnection = () => {
startTimer(30 * 1000, (times: string) => {
if (times === '0') {
router.push('/login')
return
}
timer.value = times
})
}
watchEffect(() => {
if (!systemStore.websocketConnected) {
countdownToReconnection()
}
else {
stopTimer()
}
})
</script>
<template>
<div class="reconnect-modal-overlay">
<div class="reconnect-modal-container">
<div class="reconnect-spinner" />
<h2 class="reconnect-title">
正在重连中 {{ timer }}
</h2>
<p class="reconnect-message">
请稍候系统正在尝试重新连接网格...
</p>
</div>
</div>
</template>
<style scoped>
.reconnect-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.reconnect-modal-container {
background-color: white;
border-radius: 12px;
padding: 40px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
text-align: center;
animation: fadeIn 0.3s ease-out;
}
.reconnect-spinner {
width: 64px;
height: 64px;
border: 6px solid #f3f3f3;
border-radius: 50%;
border-top: 6px solid #3b82f6;
margin: 0 auto 24px;
animation: spin 1s linear infinite;
}
.reconnect-title {
font-size: 1.12rem;
font-weight: bold;
color: #1f2937;
margin-bottom: 12px;
}
.reconnect-message {
font-size: 16px;
color: #4b5563;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes fadeIn {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
</style>

48
src/layouts/default.vue

@ -6,7 +6,8 @@ import { useLiquidStore } from '@/stores/liquidStore'
import { useSealStore } from '@/stores/sealStore' import { useSealStore } from '@/stores/sealStore'
import { useSystemStore } from '@/stores/systemStore' import { useSystemStore } from '@/stores/systemStore'
import HomeAlarmSvg from 'assets/images/home/home-alarm.svg' import HomeAlarmSvg from 'assets/images/home/home-alarm.svg'
import ErrorBox from 'libs/modalUtil'
import NetReconnection from 'components/system/NetReconnection.vue'
// import ErrorBox from 'libs/modalUtil'
import { formatDateTime, openFullscreen } from 'libs/utils' import { formatDateTime, openFullscreen } from 'libs/utils'
import { authRoutes } from 'router/routes' import { authRoutes } from 'router/routes'
import { useDeviceStore } from 'stores/deviceStore' import { useDeviceStore } from 'stores/deviceStore'
@ -30,11 +31,19 @@ const wordStateName = ref<string>('空闲')
const disinfectState = ref(homeStore.disinfectionState.state) const disinfectState = ref(homeStore.disinfectionState.state)
const liquidAddState = ref(liquidStore.liquidAddWorkState) const liquidAddState = ref(liquidStore.liquidAddWorkState)
const liquidDrainState = ref(liquidStore.liquidDrainWorkState) const liquidDrainState = ref(liquidStore.liquidDrainWorkState)
const deviceStete = ref(deviceStore.deviceStete)
const sealInfo = ref(sealStore.sealInfo) const sealInfo = ref(sealStore.sealInfo)
const websocketConnected = ref(systemStore.websocketConnected)
let touchStartTime = 0 let touchStartTime = 0
let touchCount = 0 let touchCount = 0
onMounted(() => { onMounted(() => {
// 3 // 3
onFullScreen()
deviceStore.startSubscribDevice()
})
const onFullScreen = () => {
document.body.addEventListener('touchstart', (event) => { document.body.addEventListener('touchstart', (event) => {
const now = Date.now() const now = Date.now()
const timeDiff = now - touchStartTime const timeDiff = now - touchStartTime
@ -50,18 +59,10 @@ onMounted(() => {
} }
touchStartTime = now touchStartTime = now
}) })
})
const showWorkState = () => {
//
disinfectState.value = homeStore.disinfectionState.state
// /
liquidAddState.value = liquidStore.liquidStateData
liquidDrainState.value = liquidStore.liquidDrainWorkState
//
sealInfo.value = sealStore.sealInfo
}
const showDeviceStateName = () => {
if (deviceStete.value.state.toLocaleLowerCase() !== 'idle') {
if (disinfectState.value !== 'idle' && disinfectState.value !== 'finished') { if (disinfectState.value !== 'idle' && disinfectState.value !== 'finished') {
wordStateName.value = homeStore.disinfectionState.statedisplayName wordStateName.value = homeStore.disinfectionState.statedisplayName
} }
@ -74,6 +75,7 @@ const showWorkState = () => {
else if (sealInfo.value.workState !== 'idle') { else if (sealInfo.value.workState !== 'idle') {
wordStateName.value = sealInfo.value.workStateDisplay wordStateName.value = sealInfo.value.workStateDisplay
} }
}
else { else {
wordStateName.value = '空闲' wordStateName.value = '空闲'
} }
@ -81,15 +83,24 @@ const showWorkState = () => {
watchEffect(() => { watchEffect(() => {
deviceInfo.value = deviceStore.deviceInfo deviceInfo.value = deviceStore.deviceInfo
showWorkState()
websocketConnected.value = systemStore.websocketConnected
//
disinfectState.value = homeStore.disinfectionState.state
// /
liquidAddState.value = liquidStore.liquidStateData
liquidDrainState.value = liquidStore.liquidDrainWorkState
deviceStete.value = deviceStore.deviceStete
//
sealInfo.value = sealStore.sealInfo
showDeviceStateName()
}) })
onUnmounted(() => { onUnmounted(() => {
clearInterval(timeInterval) clearInterval(timeInterval)
}) })
const showErrorModal = () => {
ErrorBox.alert('这是一条警告信息')
}
// const showErrorModal = () => {
// ErrorBox.alert('')
// }
const onLogout = () => { const onLogout = () => {
// //
const hasEx = getDeviceStatus() const hasEx = getDeviceStatus()
@ -156,14 +167,15 @@ const onLogout = () => {
<div class="ip-info"> <div class="ip-info">
IP : {{ deviceInfo.ip }} IP : {{ deviceInfo.ip }}
</div> </div>
<div class="alarm-info" @click="showErrorModal">
<div class="alarm-info">
<img :src="HomeAlarmSvg" width="20" alt="报警"> <img :src="HomeAlarmSvg" width="20" alt="报警">
<div>{{ wordStateName }}</div>
<div>{{ wordStateName || '空闲' }}</div>
</div> </div>
<div class="time"> <div class="time">
{{ currentTime }} {{ currentTime }}
</div> </div>
</footer> </footer>
<NetReconnection v-if="!websocketConnected" />
</el-container> </el-container>
</template> </template>

4
src/libs/constant.ts

@ -22,8 +22,8 @@ export const formulaNameMap: Record<string, any> = {
export const PARSSURE_DATA = { export const PARSSURE_DATA = {
intensitys: { intensitys: {
constantPressure: null, constantPressure: null,
negativePressure: ['0%', '10%', '20%', '30%', '40%', '50%', '60%', '70%', '80%', '90%', '100%'],
positivePressure: ['0%', '10%', '20%', '30%', '40%', '50%', '60%', '70%', '80%', '90%', '100%'],
negativePressure: ['0', '10', '20', '30', '40', '50', '60', '70', '80', '90', '100'],
positivePressure: ['0', '10', '20', '30', '40', '50', '60', '70', '80', '90', '100'],
}, },
typeDisplayNames: ['恒压', '正压', '负压'], typeDisplayNames: ['恒压', '正压', '负压'],
types: ['constantPressure', 'positivePressure', 'negativePressure'], types: ['constantPressure', 'positivePressure', 'negativePressure'],

6
src/libs/countdownTimer.ts

@ -28,9 +28,15 @@ class CountdownTimer {
private initDiaplay(): void { private initDiaplay(): void {
const seconds = Math.floor(this.endTime / 1000) const seconds = Math.floor(this.endTime / 1000)
const curStateRemainTimeS = this.formatTime(seconds) const curStateRemainTimeS = this.formatTime(seconds)
if (this.endTime === 0 || this.endTime < 1000) {
this.callback && this.callback('0')
this.stopTimer()
}
else {
this.callback && this.callback(curStateRemainTimeS) this.callback && this.callback(curStateRemainTimeS)
this.endTime -= 1000 this.endTime -= 1000
} }
}
private updateDisplay(): void { private updateDisplay(): void {
this.timer = window.setInterval(() => { this.timer = window.setInterval(() => {

12
src/libs/socket.ts

@ -13,6 +13,9 @@ export class WebSocketClient {
private responseHandlers = new Map<string, (response: Socket.WebSocketResponse) => void>() private responseHandlers = new Map<string, (response: Socket.WebSocketResponse) => void>()
private eventListeners = new Map<string, ((response: Socket.WebSocketResponse) => void)[]>() private eventListeners = new Map<string, ((response: Socket.WebSocketResponse) => void)[]>()
private connectCount = 0
private maxConnectCount = 10
private intervalVal: any = 0
constructor(url: string) { constructor(url: string) {
this.url = url this.url = url
@ -161,6 +164,11 @@ export class WebSocketClient {
// 重连逻辑 // 重连逻辑
private reconnect() { private reconnect() {
if (this.connectCount > this.maxConnectCount) {
clearInterval(this.intervalVal)
console.log('-----达到最大重连次数,停止重连------')
return
}
this.isConnecting = true this.isConnecting = true
if (this.socket.readyState !== WebSocket.CLOSED && this.socket.readyState !== WebSocket.CLOSING) { if (this.socket.readyState !== WebSocket.CLOSED && this.socket.readyState !== WebSocket.CLOSING) {
this.socket.close() this.socket.close()
@ -174,8 +182,10 @@ export class WebSocketClient {
return return
} }
this.isConnecting = true this.isConnecting = true
setTimeout(() => {
this.intervalVal = setInterval(() => {
this.isConnecting = false this.isConnecting = false
this.connectCount++
console.log('重连------------', this.connectCount)
this.reconnect() this.reconnect()
}, 3000) // 3秒后重连 }, 3000) // 3秒后重连
} }

23
src/libs/utils.ts

@ -30,7 +30,7 @@ export const deviceStateMap = <Record<string, any>>{
finished: '消毒完成', finished: '消毒完成',
dehumidificationBeforeDisinfection: '消毒前除湿', dehumidificationBeforeDisinfection: '消毒前除湿',
dehumidificationAfterDisinfection: '消毒后除湿', dehumidificationAfterDisinfection: '消毒后除湿',
emptyLiquidFromTheLine: '排空管路',
emptyLineLiquid: '排空管路',
} }
export const sealStateMap = <Record<string, any>>{ export const sealStateMap = <Record<string, any>>{
@ -96,10 +96,15 @@ export const convertValuesToString = (jsonObj: Record<string, any>, exName: stri
return result return result
} }
export const convertValuesToInt = (jsonObj: Record<string, any>, exName: string) => {
function isNumber(str: string | number) {
// 先转换为数字,再用 isNaN 判断是否为 NaN
return !Number.isNaN(Number(str)) && Number.isFinite(Number(str))
}
export const convertValuesToInt = (jsonObj: Record<string, any>) => {
const result: Record<string, any> = {} const result: Record<string, any> = {}
for (const [key, value] of Object.entries(jsonObj)) { for (const [key, value] of Object.entries(jsonObj)) {
if (key !== exName) {
if (isNumber(value)) {
result[key] = Number(value) result[key] = Number(value)
} }
else { else {
@ -108,3 +113,15 @@ export const convertValuesToInt = (jsonObj: Record<string, any>, exName: string)
} }
return result return result
} }
export const DEVICE_STATES = {
IDLE: 'idle', // 空闲
INIT: 'init', // 初始化
PREHEAT: 'preheat', // 预热
DISINFECTION: 'disinfection', // 消毒
DEGRADATION: 'degradation', // 降解
FINISHED: 'finished', // 结束
DEHUMIDIFICATION_BEFORE: 'dehumidification_before_disinfection', // 消毒前除湿
DEHUMIDIFICATION_AFTER: 'dehumidification_after_disinfection', // 消毒后除湿
EMPTY_LINE: 'empty_liquid_from_the_line', // 清空管路
}

24
src/stores/deviceStore.ts

@ -1,3 +1,5 @@
import { subscribeEvent } from '@/apis/system'
import { DEVICE_STATES } from '@/libs/utils'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ref } from 'vue' import { ref } from 'vue'
@ -12,12 +14,34 @@ const initDeviceInfo = {
export const useDeviceStore = defineStore('device', () => { export const useDeviceStore = defineStore('device', () => {
const deviceInfo = ref<Device.DeviceInfo>(initDeviceInfo) const deviceInfo = ref<Device.DeviceInfo>(initDeviceInfo)
const deviceStete = ref({ state: DEVICE_STATES.IDLE }) // 设备状态
const updateDeviceInfo = (info: Device.DeviceInfo) => { const updateDeviceInfo = (info: Device.DeviceInfo) => {
deviceInfo.value = info deviceInfo.value = info
} }
/**
* @function setDeviceState
* @param {Device.State} deviceInfo -
* @desc
*/
const setDeviceState = (deviceInfo: Device.State) => {
deviceStete.value = deviceInfo
}
const startSubscribDevice = () => {
subscribeEvent('stateUpdate', (report: Socket.WebSocketResponse<Device.State>) => {
if (report.fromClass === 'AppCore') {
deviceStete.value = report.rely
}
})
}
return { return {
deviceInfo, deviceInfo,
deviceStete,
updateDeviceInfo, updateDeviceInfo,
setDeviceState,
startSubscribDevice,
} }
}) })

70
src/stores/formulaStore.ts

@ -2,7 +2,7 @@ import { FtMessage } from '@/libs/message'
import { sendCmd, syncSendCmd } from 'apis/system' import { sendCmd, syncSendCmd } from 'apis/system'
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import { computed, ref, watch } from 'vue'
// 常量定义 // 常量定义
const LOG_ITEMS = Array.from({ length: 12 }, (_, i) => ({ const LOG_ITEMS = Array.from({ length: 12 }, (_, i) => ({
@ -45,10 +45,17 @@ export const useFormulaStore = defineStore('formula', () => {
const formulaList = ref<Formula.FormulaItem[]>([]) const formulaList = ref<Formula.FormulaItem[]>([])
const loglevel = ref<string>('1') const loglevel = ref<string>('1')
watch(defaultFormulaInfo, (newVal) => {
currentSelectedFormulaInfo.value = cloneDeep(newVal)
}, { deep: true })
// 计算属性 // 计算属性
const isDefaultFormula = computed(() => currentSelectedFormulaInfo.value.name === defaultFormulaInfo.value.name) const isDefaultFormula = computed(() => currentSelectedFormulaInfo.value.name === defaultFormulaInfo.value.name)
// 私有
/**
* @function mapConfigToFormula
* @param {Formula.FormulaConfig[]} config -
* @desc
*/
const mapConfigToFormula = (config: Formula.FormulaConfig[]) => { const mapConfigToFormula = (config: Formula.FormulaConfig[]) => {
const formulaMap: Record<string, any> = {} const formulaMap: Record<string, any> = {}
config.forEach((item) => { config.forEach((item) => {
@ -61,13 +68,32 @@ export const useFormulaStore = defineStore('formula', () => {
return formulaMap as Formula.FormulaItem return formulaMap as Formula.FormulaItem
} }
// 公共方法
/**
* @function updateFormulaConfigData
* @param {Formula.FormulaConfig[]} data -
* @desc
*/
const updateFormulaConfigData = (data: Formula.FormulaConfig[]) => { const updateFormulaConfigData = (data: Formula.FormulaConfig[]) => {
const visibleConfig = data.filter(item => item.is_visible_in_formula_page) const visibleConfig = data.filter(item => item.is_visible_in_formula_page)
formulaConfigList.value = visibleConfig formulaConfigList.value = visibleConfig
defaultFormulaInfo.value = mapConfigToFormula(visibleConfig) defaultFormulaInfo.value = mapConfigToFormula(visibleConfig)
} }
const getFormualDefaultData = async () => {
const defaultParams = {
className: 'SettingMgrService',
fnName: 'getAllSetting',
params: {},
}
const res = await sendCmd(defaultParams)
updateFormulaConfigData(res)
}
/**
* @function updateLogLevels
* @param {Formula.FormulaConfig} logLevelItem -
* @desc
*/
const updateLogLevels = (logLevelItem: Formula.FormulaConfig) => { const updateLogLevels = (logLevelItem: Formula.FormulaConfig) => {
const list: System.Option[] = [] const list: System.Option[] = []
const { enum_display_names, enums } = logLevelItem const { enum_display_names, enums } = logLevelItem
@ -82,22 +108,46 @@ export const useFormulaStore = defineStore('formula', () => {
logLevelOptions.value = list logLevelOptions.value = list
} }
/**
* @function updateLogLevel
* @param {string} level -
* @desc
*/
const updateLogLevel = (level: string) => { const updateLogLevel = (level: string) => {
loglevel.value = level loglevel.value = level
} }
/**
* @function updatePressurList
* @param {string[]} pressurData -
* @desc
*/
const updatePressurList = (pressurData: string[]) => { const updatePressurList = (pressurData: string[]) => {
pressurOptionList.value = cloneDeep(pressurData) pressurOptionList.value = cloneDeep(pressurData)
} }
/**
* @function updateSelectedFormulaData
* @param {Formula.FormulaItem} formulaItem -
* @desc
*/
const updateSelectedFormulaData = (formulaItem: Formula.FormulaItem) => { const updateSelectedFormulaData = (formulaItem: Formula.FormulaItem) => {
currentSelectedFormulaInfo.value = cloneDeep(formulaItem) currentSelectedFormulaInfo.value = cloneDeep(formulaItem)
} }
/**
* @function updateSelectedFormulaDataByList
* @param {Formula.FormulaItem} formulaItem -
* @desc
*/
const updateSelectedFormulaDataByList = (formulaItem: Formula.FormulaItem) => { const updateSelectedFormulaDataByList = (formulaItem: Formula.FormulaItem) => {
selectedFormulaInfo.value = cloneDeep(formulaItem) selectedFormulaInfo.value = cloneDeep(formulaItem)
} }
/**
* @function initFormulaList
* @desc
*/
const initFormulaList = async () => { const initFormulaList = async () => {
try { try {
const params = { const params = {
@ -115,15 +165,28 @@ export const useFormulaStore = defineStore('formula', () => {
} }
} }
/**
* @function initFormulaData
* @desc
*/
const initFormulaData = () => { const initFormulaData = () => {
selectedFormulaInfo.value = null selectedFormulaInfo.value = null
resetToDefaultFormula() resetToDefaultFormula()
} }
/**
* @function resetToDefaultFormula
* @desc
*/
const resetToDefaultFormula = () => { const resetToDefaultFormula = () => {
currentSelectedFormulaInfo.value = cloneDeep(defaultFormulaInfo.value) currentSelectedFormulaInfo.value = cloneDeep(defaultFormulaInfo.value)
} }
/**
* @function saveDisinfectFormula
* @param {Formula.FormulaItem} formData -
* @desc
*/
const saveDisinfectFormula = async (formData: Formula.FormulaItem) => { const saveDisinfectFormula = async (formData: Formula.FormulaItem) => {
try { try {
const params = { const params = {
@ -165,5 +228,6 @@ export const useFormulaStore = defineStore('formula', () => {
saveDisinfectFormula, saveDisinfectFormula,
updateLogLevel, updateLogLevel,
resetToDefaultFormula, resetToDefaultFormula,
getFormualDefaultData,
} }
}) })

98
src/stores/homeStore.ts

@ -1,18 +1,10 @@
import { DEVICE_STATES } from '@/libs/utils'
import { sendCmd } from 'apis/system' import { sendCmd } from 'apis/system'
import { PARSSURE_DATA } from 'libs/constant' import { PARSSURE_DATA } from 'libs/constant'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
// 空闲 idle
// 初始化 init
// 预热 preheat
// 消毒 disinfection
// 降解 degradation
// 结束 finished
// 消毒前除湿 dehumidification_before_disinfection
// 消毒后除湿 dehumidification_after_disinfection
// 清空管路 empty_liquid_from_the_line
// 传感器数据初始值
const h2O2Data: Home.DisplayrelyMgrParams[] = [{ const h2O2Data: Home.DisplayrelyMgrParams[] = [{
type: 'inside', type: 'inside',
title: '仓内', title: '仓内',
@ -23,29 +15,33 @@ const h2O2Data: Home.DisplayrelyMgrParams[] = [{
chartId: 'inside', chartId: 'inside',
}] }]
// 消毒状态初始值
// 消毒状态初始值
const initDisinfectState = { const initDisinfectState = {
curStateRemainTimeS: 0, curStateRemainTimeS: 0,
h2o2SensorData: h2O2Data, h2o2SensorData: h2O2Data,
injectedVelocity: 0, injectedVelocity: 0,
nlog: 0, nlog: 0,
state: 'idle',
state: DEVICE_STATES.IDLE,
statedisplayName: '空闲', statedisplayName: '空闲',
tlog: 0, tlog: 0,
} }
/**
*
* @module useHomeStore
*/
export const useHomeStore = defineStore('home', () => { export const useHomeStore = defineStore('home', () => {
// 状态定义
const h2O2SensorData = ref(h2O2Data) const h2O2SensorData = ref(h2O2Data)
const pressureConfig = ref<Home.ParssureData>(PARSSURE_DATA) const pressureConfig = ref<Home.ParssureData>(PARSSURE_DATA)
const curStateRemainTime = ref<string>() const curStateRemainTime = ref<string>()
const disinfectionState = ref<Home.DisinfectState>(initDisinfectState)// 当前设备正在消毒时的状态
const deviceStete = ref({ state: 'idle' }) // 设备状态
const disinfectionState = ref<Home.DisinfectState>(initDisinfectState) // 当前设备消毒状态
let renderTimer: any let renderTimer: any
/** /**
* @function updateHomeData * @function updateHomeData
* @params data
* @desc
* @param {Home.DisplayrelyMgr[]} data -
* @desc 湿
*/ */
const updateHomeData = (data: Home.DisplayrelyMgr[]) => { const updateHomeData = (data: Home.DisplayrelyMgr[]) => {
if (data && data.length) { if (data && data.length) {
@ -58,24 +54,14 @@ export const useHomeStore = defineStore('home', () => {
} }
} }
/**
* @function getPressureConfig
* @desc
*/
const getPressureConfig = async () => {
const pressureParams = {
className: 'PipelinePressureControl',
fnName: 'getConfig',
params: {},
}
const res = await sendCmd(pressureParams)
pressureConfig.value = res
const updatePressureConfig = (data: Home.ParssureData) => {
pressureConfig.value = data || PARSSURE_DATA
} }
/** /**
* @function updatePressure * @function updatePressure
* @param pressureData : 数组
* @desc
* @param {Array<string|number>} pressureData - [, ]
* @desc
*/ */
const updatePressure = async (pressureData: string | number[]) => { const updatePressure = async (pressureData: string | number[]) => {
if (pressureData && pressureData.length) { if (pressureData && pressureData.length) {
@ -83,19 +69,17 @@ export const useHomeStore = defineStore('home', () => {
const pressureTypeParams = { const pressureTypeParams = {
className: 'PipelinePressureControl', className: 'PipelinePressureControl',
fnName: 'setType', fnName: 'setType',
params: {
type,
},
params: { type },
} }
await sendCmd(pressureTypeParams) await sendCmd(pressureTypeParams)
if (type === 'positivePressure' || type === 'positivePressure') { // type 是正压或负压时保存设置的压力值
// 正压或负压时保存设置的压力值
if (type === 'positivePressure' || type === 'negativePressure') {
const intensity = pressureData[1] const intensity = pressureData[1]
const intensityParams = { const intensityParams = {
className: 'PipelinePressureControl', className: 'PipelinePressureControl',
fnName: 'setIntensity', fnName: 'setIntensity',
params: {
intensity,
},
params: { intensity },
} }
await sendCmd(intensityParams) await sendCmd(intensityParams)
} }
@ -103,22 +87,16 @@ export const useHomeStore = defineStore('home', () => {
} }
/** /**
* @function setDeviceState
* @param deviceInfo
*/
const setDeviceState = (deviceInfo: Device.State) => {
deviceStete.value = deviceInfo
}
/**
* @function updateHomeDisinfectionState * @function updateHomeDisinfectionState
* @param disinfectState
* @param {Home.DisinfectState} disinfectState -
* @desc 3
*/ */
const updateHomeDisinfectionState = (disinfectState: Home.DisinfectState) => { const updateHomeDisinfectionState = (disinfectState: Home.DisinfectState) => {
disinfectionState.value = disinfectState disinfectionState.value = disinfectState
if (!renderTimer) { if (!renderTimer) {
renderTimer = setTimeout(() => { renderTimer = setTimeout(() => {
h2O2SensorData.value = [...disinfectionState.value.h2o2SensorData]// 创建副本 h2O2SensorData.value = [...disinfectionState.value.h2o2SensorData]// 创建副本
// updateHomeData(disinfectionState.value.h2o2SensorData)
renderTimer = null renderTimer = null
}, 3000) as unknown as NodeJS.Timeout // 类型断言兼容不同环境 }, 3000) as unknown as NodeJS.Timeout // 类型断言兼容不同环境
} }
@ -126,40 +104,34 @@ export const useHomeStore = defineStore('home', () => {
/** /**
* @function updateHomeRemainTime * @function updateHomeRemainTime
* @param timer
* @param {string} timer -
* @desc
*/ */
const updateHomeRemainTime = (timer: string) => { const updateHomeRemainTime = (timer: string) => {
curStateRemainTime.value = timer curStateRemainTime.value = timer
} }
/**
* @computed isDeviceIdle
* @returns {boolean} -
* @desc
*/
const isDeviceIdle = computed(() => { const isDeviceIdle = computed(() => {
return disinfectionState.value.state.toLocaleLowerCase() === 'idle' || disinfectionState.value.state === 'finished'
return disinfectionState.value.state.toLocaleLowerCase() === DEVICE_STATES.IDLE || disinfectionState.value.state === DEVICE_STATES.FINISHED
}) })
const subscribeDisinfectEvent = () => {
// 发起订阅
const subParams = {
className: 'DisinfectionCtrlServiceExt',
fnName: 'startStateReport',
params: {},
}
sendCmd(subParams)
}
return { return {
// 属性
// 状态属性
h2O2SensorData, h2O2SensorData,
deviceStete,
disinfectionState, disinfectionState,
pressureConfig, pressureConfig,
curStateRemainTime, curStateRemainTime,
isDeviceIdle, isDeviceIdle,
// 方法
// 操作方法
updateHomeData, updateHomeData,
updatePressure, updatePressure,
setDeviceState,
updateHomeDisinfectionState, updateHomeDisinfectionState,
getPressureConfig,
updatePressureConfig,
updateHomeRemainTime, updateHomeRemainTime,
subscribeDisinfectEvent,
} }
}) })

4
src/stores/initHomeData.ts

@ -1,8 +1,10 @@
import { sendCmd } from '@/apis/system' import { sendCmd } from '@/apis/system'
import { useHomeStore } from 'stores/homeStore' import { useHomeStore } from 'stores/homeStore'
import { useLiquidStore } from 'stores/liquidStore' import { useLiquidStore } from 'stores/liquidStore'
import { useDeviceStore } from './deviceStore'
const homeStore = useHomeStore() const homeStore = useHomeStore()
const deviceStore = useDeviceStore()
const liquidStore = useLiquidStore() const liquidStore = useLiquidStore()
export const initData = async () => { export const initData = async () => {
@ -60,5 +62,5 @@ const initBaseData = async () => {
} }
// 当前设备状态 // 当前设备状态
const deviceData = await sendCmd(deviceParams) const deviceData = await sendCmd(deviceParams)
homeStore.setDeviceState(deviceData)
deviceStore.setDeviceState(deviceData)
} }

31
src/stores/liquidStore.ts

@ -6,40 +6,67 @@ const liquidItem: Liquid.LiquidData = {
workState: 'idle', workState: 'idle',
workStateDisplay: '空闲', workStateDisplay: '空闲',
} }
/**
*
* @module useLiquidStore
*/
export const useLiquidStore = defineStore('Liquid', () => { export const useLiquidStore = defineStore('Liquid', () => {
// 状态定义
const liquidAddWorkState = ref<Liquid.LiquidData>(liquidItem) const liquidAddWorkState = ref<Liquid.LiquidData>(liquidItem)
const liquidDrainWorkState = ref<Liquid.LiquidData>(liquidItem) const liquidDrainWorkState = ref<Liquid.LiquidData>(liquidItem)
const liquidStateData = ref(liquidItem) const liquidStateData = ref(liquidItem)
const liquidTotal = ref<number>(2500) const liquidTotal = ref<number>(2500)
const liquidPeriod = ref(300) const liquidPeriod = ref(300)
/**
* @function updateAddLiquidWorkState
* @param {Liquid.LiquidData} item -
* @desc
*/
const updateAddLiquidWorkState = (item: Liquid.LiquidData) => { const updateAddLiquidWorkState = (item: Liquid.LiquidData) => {
liquidAddWorkState.value = item liquidAddWorkState.value = item
liquidStateData.value = item liquidStateData.value = item
} }
/**
* @function updateDrainLiquidWorkState
* @param {Liquid.LiquidData} item -
* @desc
*/
const updateDrainLiquidWorkState = (item: Liquid.LiquidData) => { const updateDrainLiquidWorkState = (item: Liquid.LiquidData) => {
liquidDrainWorkState.value = item liquidDrainWorkState.value = item
liquidStateData.value = item liquidStateData.value = item
} }
/**
* @function updateLiquidState
* @param {Liquid.LiquidData} liquidInfo -
* @desc
*/
const updateLiquidState = (liquidInfo: Liquid.LiquidData) => { const updateLiquidState = (liquidInfo: Liquid.LiquidData) => {
liquidStateData.value = liquidInfo liquidStateData.value = liquidInfo
} }
/**
* @function initLiquidConfig
* @param {Liquid.LiquidConfig} liquidConfig -
* @desc
*/
const initLiquidConfig = async (liquidConfig: Liquid.LiquidConfig) => { const initLiquidConfig = async (liquidConfig: Liquid.LiquidConfig) => {
liquidTotal.value = liquidConfig.maxLiquid liquidTotal.value = liquidConfig.maxLiquid
liquidPeriod.value = liquidConfig.updatePeriod liquidPeriod.value = liquidConfig.updatePeriod
} }
return { return {
// 属性
// 状态属性
liquidAddWorkState, liquidAddWorkState,
liquidDrainWorkState, liquidDrainWorkState,
liquidTotal, liquidTotal,
liquidPeriod, liquidPeriod,
liquidStateData, liquidStateData,
// 方法
// 操作方法
updateAddLiquidWorkState, updateAddLiquidWorkState,
updateDrainLiquidWorkState, updateDrainLiquidWorkState,
initLiquidConfig, initLiquidConfig,

14
src/stores/sealStore.ts

@ -1,20 +1,34 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ref } from 'vue' import { ref } from 'vue'
/**
*
* @module useSealStore
*/
export const useSealStore = defineStore('seal', () => { export const useSealStore = defineStore('seal', () => {
// 状态定义
const sealState = ref('idle') const sealState = ref('idle')
const sealInfo = ref<Seal.SealStateItem>({ const sealInfo = ref<Seal.SealStateItem>({
pressure: '0', pressure: '0',
workState: 'idle', workState: 'idle',
workStateDisplay: '空闲', workStateDisplay: '空闲',
}) })
/**
* @function updateSealInfo
* @param {Seal.SealStateItem} dataInfo -
* @desc
*/
const updateSealInfo = (dataInfo: Seal.SealStateItem) => { const updateSealInfo = (dataInfo: Seal.SealStateItem) => {
sealInfo.value = dataInfo sealInfo.value = dataInfo
} }
return { return {
// 状态属性
sealState, sealState,
sealInfo, sealInfo,
// 操作方法
updateSealInfo, updateSealInfo,
} }
}) })

34
src/stores/settingStore.ts

@ -1,7 +1,12 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ref } from 'vue' import { ref } from 'vue'
/**
*
* @module useSettingStore
*/
export const useSettingStore = defineStore('setting', () => { export const useSettingStore = defineStore('setting', () => {
// 设置菜单配置
const settingMenus = [{ const settingMenus = [{
name: '消毒历史记录', name: '消毒历史记录',
code: 'history', code: 'history',
@ -19,27 +24,54 @@ export const useSettingStore = defineStore('setting', () => {
code: 'deviceInfo', code: 'deviceInfo',
}] }]
// 历史记录数据
const historyList: Record<string, string>[] = [] const historyList: Record<string, string>[] = []
/* ********************** 用户管理 **************************** */ /* ********************** 用户管理 **************************** */
const userList = ref<User.UserItem[]>([]) const userList = ref<User.UserItem[]>([])
const addUserVisible = ref(false) const addUserVisible = ref(false)
const modifyPwdVisible = ref(false) const modifyPwdVisible = ref(false)
const userModalState = ref('add') const userModalState = ref('add')
const currentEditUser = ref<User.UserItem>() const currentEditUser = ref<User.UserItem>()
/**
* @function updateUserModalState
* @param {string} state - ('add' | 'edit')
* @desc
*/
const updateUserModalState = (state: string) => { const updateUserModalState = (state: string) => {
userModalState.value = state userModalState.value = state
} }
/**
* @function updateCurrentEditUser
* @param {User.UserItem} userItem -
* @desc
*/
const updateCurrentEditUser = (userItem: User.UserItem) => { const updateCurrentEditUser = (userItem: User.UserItem) => {
currentEditUser.value = userItem currentEditUser.value = userItem
} }
/**
* @function updateVisible
* @param {boolean} visible -
* @desc
*/
const updateVisible = (visible: boolean) => { const updateVisible = (visible: boolean) => {
addUserVisible.value = visible addUserVisible.value = visible
} }
/**
* @function updatePwdVisible
* @param {boolean} visible -
* @desc
*/
const updatePwdVisible = (visible: boolean) => { const updatePwdVisible = (visible: boolean) => {
modifyPwdVisible.value = visible modifyPwdVisible.value = visible
} }
return { return {
// 状态属性
settingMenus, settingMenus,
historyList, historyList,
addUserVisible, addUserVisible,
@ -47,6 +79,8 @@ export const useSettingStore = defineStore('setting', () => {
userModalState, userModalState,
currentEditUser, currentEditUser,
userList, userList,
// 操作方法
updateVisible, updateVisible,
updateUserModalState, updateUserModalState,
updateCurrentEditUser, updateCurrentEditUser,

64
src/stores/systemStore.ts

@ -1,7 +1,9 @@
import { sendCmd } from '@/apis/system'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ref } from 'vue' import { ref } from 'vue'
export const useSystemStore = defineStore('system', () => { export const useSystemStore = defineStore('system', () => {
const websocketConnected = ref(true)
const systemUser = ref({ const systemUser = ref({
username: '', username: '',
}) })
@ -32,6 +34,62 @@ export const useSystemStore = defineStore('system', () => {
}, 1500) }, 1500)
} }
const updateConnected = (isConnected: boolean) => {
websocketConnected.value = isConnected
}
/**
* @function subscribeDisinfectEvent
* @desc
*/
const subscribeDisinfectEvent = async () => {
// 发起订阅
const subParams = {
className: 'DisinfectionCtrlServiceExt',
fnName: 'startStateReport',
}
await sendCmd(subParams)
}
/**
* @function subscribeAddLiquidEvent
* @desc
*/
const subscribeAddLiquidEvent = async () => {
// 发起订阅
const subParams = {
className: 'AddLiquidService',
fnName: 'startStateReport',
}
await sendCmd(subParams)
}
/**
* @function subscribeDrainLiquidEvent
* @desc
*/
const subscribeDrainLiquidEvent = async () => {
// 发起订阅
const subParams = {
className: 'DrainLiquidService',
fnName: 'startStateReport',
}
await sendCmd(subParams)
}
/**
* @function subscribeSealEvent
* @desc
*/
const subscribeSealEvent = async () => {
// 执行订阅
const subParams = {
className: 'AirLeakDetectTest',
fnName: 'startStateReport',
}
await sendCmd(subParams)
}
return { return {
systemUser, systemUser,
loginForm, loginForm,
@ -41,6 +99,12 @@ export const useSystemStore = defineStore('system', () => {
streamVisible, streamVisible,
systemList, systemList,
loading, loading,
websocketConnected,
updateLoading, updateLoading,
updateConnected,
subscribeDisinfectEvent,
subscribeAddLiquidEvent,
subscribeDrainLiquidEvent,
subscribeSealEvent,
} }
}) })

6
src/types/system.d.ts

@ -11,4 +11,10 @@ declare namespace System {
list: T[] list: T[]
total: number total: number
} }
interface SendCmdParams {
className: string
fnName: string
params?: Record<string, any> // 改为可选属性,搭配后面的默认值处理
}
} }

62
src/views/audit/index.vue

@ -3,16 +3,32 @@ import { FtMessage } from '@/libs/message'
import { sendCmd } from 'apis/system' import { sendCmd } from 'apis/system'
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
const pageNum = ref(1)
const pageSize = ref(10)
const selectedUserList = ref<Audit.AuditItem[]>([])
const totle = ref(0)
/**
* 审计日志管理组件
* @description 展示审计日志列表支持分页和单条记录导出功能
*/
//
const pageNum = ref(1) //
const pageSize = ref(10) //
const totle = ref(0) //
//
const tableData = ref<Audit.AuditItem[]>([]) //
const selectedUserList = ref<Audit.AuditItem[]>([]) //
/**
* @hook 生命周期钩子 - 组件挂载完成时执行
* @description 初始化加载审计日志列表
*/
onMounted(() => { onMounted(() => {
//
getAuditList() getAuditList()
}) })
/**
* @function 获取审计日志列表
* @desc 从服务端获取分页后的审计日志数据
*/
const getAuditList = async () => { const getAuditList = async () => {
const params = { const params = {
className: 'AuditMgrService', className: 'AuditMgrService',
@ -22,15 +38,24 @@ const getAuditList = async () => {
pageSize: pageSize.value, pageSize: pageSize.value,
}, },
} }
try {
const res = await sendCmd(params) const res = await sendCmd(params)
const items = res.items
tableData.value = items
totle.value = res.total
tableData.value = res.items || []
totle.value = res.total || 0
}
catch (error) {
FtMessage.error('获取日志列表失败')
console.error('获取审计日志失败:', error)
}
} }
const tableData = ref()
/**
* @function 导出选中的审计记录
* @desc 导出单条审计记录需先选择一条记录
*/
const onExportRecord = () => { const onExportRecord = () => {
if (selectedUserList.value.length !== 1) { if (selectedUserList.value.length !== 1) {
FtMessage.warning('选择一条数据进行导出')
FtMessage.warning('选择一条数据进行导出')
return return
} }
const params = { const params = {
@ -39,12 +64,29 @@ const onExportRecord = () => {
params: {}, params: {},
} }
sendCmd(params) sendCmd(params)
.then(() => {
FtMessage.success('导出成功')
})
.catch((error) => {
console.error('导出审计记录失败:', error)
})
} }
/**
* @function 页码变更处理
* @param {number} page - 新的页码
* @desc 更新页码并重新加载数据
*/
const handleCurrentChange = (page: number) => { const handleCurrentChange = (page: number) => {
pageNum.value = page pageNum.value = page
getAuditList() getAuditList()
} }
/**
* @function 表格选择变更处理
* @param {Audit.AuditItem[]} users - 选中的记录列表
* @desc 更新选中的记录
*/
const handleSelectionChange = (users: Audit.AuditItem[]) => { const handleSelectionChange = (users: Audit.AuditItem[]) => {
selectedUserList.value = users selectedUserList.value = users
} }

19
src/views/liquid/index.vue

@ -40,6 +40,8 @@ const btnStyle = {
} }
onMounted(() => { onMounted(() => {
systemStore.subscribeAddLiquidEvent()
systemStore.subscribeDrainLiquidEvent()
subScribeLiquid() subScribeLiquid()
}) })
@ -101,13 +103,6 @@ const onStartAddLiquid = async () => {
} }
systemStore.updateLoading(true) systemStore.updateLoading(true)
syncSendCmd(params) syncSendCmd(params)
//
const subParams = {
className: 'AddLiquidService',
fnName: 'startStateReport',
params: {},
}
syncSendCmd(subParams)
} }
const onStopAddLiquid = () => { const onStopAddLiquid = () => {
@ -134,13 +129,6 @@ const onStartDrainLiquid = async () => {
} }
systemStore.updateLoading(true) systemStore.updateLoading(true)
await sendCmd(params) await sendCmd(params)
//
const subParams = {
className: 'DrainLiquidService',
fnName: 'startStateReport',
params: {},
}
syncSendCmd(subParams)
} }
const onStopDrainLiquid = async () => { const onStopDrainLiquid = async () => {
@ -218,13 +206,14 @@ const onStopDrainLiquid = async () => {
</bt-button> </bt-button>
<bt-button <bt-button
v-else v-else
button-text="停止加液"
:button-text="addWorkState.workState === 'emptyLineLiquid' ? '排空管路中' : '停止加液'"
bg-color="#FF6767" bg-color="#FF6767"
:text-color="btnStyle.textColor" :text-color="btnStyle.textColor"
:width="btnStyle.width" :width="btnStyle.width"
:height="btnStyle.height" :height="btnStyle.height"
:text-size="btnStyle.textSize" :text-size="btnStyle.textSize"
:border-radius="btnStyle.borderRadius" :border-radius="btnStyle.borderRadius"
:disabled="addWorkState.workState === 'emptyLineLiquid'"
@click="onStopAddLiquid" @click="onStopAddLiquid"
> >
<template #icon> <template #icon>

95
src/views/login/index.vue

@ -1,16 +1,25 @@
<script setup lang="ts"> <script setup lang="ts">
import { FtMessage } from '@/libs/message'
import { setToken } from '@/libs/token' import { setToken } from '@/libs/token'
import { login } from 'apis/user' import { login } from 'apis/user'
import logo from 'assets/images/logo.svg' import logo from 'assets/images/logo.svg'
import password from 'assets/images/password_icon.svg' import password from 'assets/images/password_icon.svg'
import user from 'assets/images/user_icon.svg' import user from 'assets/images/user_icon.svg'
import SoftKeyboard from 'components/common/SoftKeyboard/index.vue'
import { useSystemStore } from 'stores/systemStore' import { useSystemStore } from 'stores/systemStore'
import { ref } from 'vue'
import { ref, watchEffect } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const router = useRouter() const router = useRouter()
const sys = useSystemStore() const sys = useSystemStore()
const formRef = ref() const formRef = ref()
const loading = ref(false)
const inputValue = ref('')
const keyboardVisible = ref(false)
const keyboardType = ref<'text' | 'number'>('text')
const softKeyboardRef = ref()
const loginForm = ref<Record<string, any>>(sys.loginForm)
const focusedInput = ref<string | null>(null)
const rules = { const rules = {
username: [ username: [
@ -23,20 +32,45 @@ const rules = {
], ],
} }
watchEffect(() => {
if (focusedInput.value) {
loginForm.value[focusedInput.value] = inputValue.value
}
})
const openKeyboard = (e: { target: { name: string | null } }) => {
keyboardVisible.value = true
if (e.target.name) {
const labelName = e.target.name
const formValue = loginForm.value[labelName]
inputValue.value = formValue ? formValue.toString() : ''
focusedInput.value = e.target.name
}
}
const loginHandle = async () => { const loginHandle = async () => {
try { try {
const valid = await formRef.value.validate() const valid = await formRef.value.validate()
if (!valid) { if (!valid) {
return return
} }
login(sys.loginForm).then(async (res) => {
loading.value = true
login(loginForm.value as User.Login).then(async (res) => {
if (res.ackcode === 0) { if (res.ackcode === 0) {
setToken('login success') setToken('login success')
await router.push('/home') await router.push('/home')
} }
loading.value = false
}).catch(() => {
loading.value = false
FtMessage.error('网络连接异常,请检查网络')
}) })
} }
catch (e) { catch (e) {
if (!sys.websocketConnected) {
FtMessage.error('网络连接异常,请检查网络')
return
}
console.log(e) console.log(e)
} }
} }
@ -49,29 +83,45 @@ const loginHandle = async () => {
<img :src="logo" style="width:20vw" alt=""> <img :src="logo" style="width:20vw" alt="">
</div> </div>
<br/> <br/>
<el-form ref="formRef" :model="sys.loginForm" :rules="rules" style="width: 100%">
<div class="input-title">
<el-form ref="formRef" class="input-username" :model="loginForm" :rules="rules" style="width: 100%" >
<!-- <div class="input-title">
用户名 用户名
</div>
<el-form-item>
<el-input v-model="sys.loginForm.name" v-prevent-keyboard size="large" placeholder="请输入用户名">
</div> -->
<el-form-item
prop="name"
:rules="{
required: true,
message: '输入用户名',
trigger: ['blur', 'change'],
}"
>
<el-input v-model="loginForm.name" v-prevent-keyboard size="large" name="name" placeholder="请输入用户名" @focus="openKeyboard">
<template #prepend> <template #prepend>
<img class="input-icon" :src="user" alt=""> <img class="input-icon" :src="user" alt="">
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<div class="input-title">
<!-- <div class="input-title">
密码 密码
</div>
<el-form-item>
<el-input v-model="sys.loginForm.pwd" size="large" placeholder="请输入密码" type="password">
</div> -->
<el-form-item
class="input-title"
prop="pwd"
:rules="{
required: true,
message: '请输入密码',
trigger: ['blur', 'change'],
}"
>
<el-input v-model="loginForm.pwd" v-prevent-keyboard size="large" name="pwd" placeholder="请输入密码" type="password" @focus="openKeyboard">
<template #prepend> <template #prepend>
<img class="input-icon" :src="password" alt=""> <img class="input-icon" :src="password" alt="">
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item>
<el-form-item class="input-title">
<bt-button <bt-button
:loading="loading"
button-text="登录" button-text="登录"
type="primary" type="primary"
height="5vh" height="5vh"
@ -80,6 +130,15 @@ const loginHandle = async () => {
/> />
</el-form-item> </el-form-item>
</el-form> </el-form>
<SoftKeyboard
ref="softKeyboardRef"
v-model="inputValue"
:is-visible="keyboardVisible"
:keyboard-type="keyboardType"
@update-keyboard-visible="(visible: boolean) => keyboardVisible = visible"
@confirm="keyboardVisible = false"
@close="keyboardVisible = false"
/>
</div> </div>
</div> </div>
</template> </template>
@ -92,7 +151,7 @@ const loginHandle = async () => {
align-items: center; align-items: center;
.login-box-content { .login-box-content {
padding: 50px 50px; padding: 50px 50px;
background: white;
background: linear-gradient(180deg, rgba(250, 252, 255, 0.51) 0%, rgba(255, 255, 255, 0.52) 100%);
border-radius: 10px; border-radius: 10px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -113,14 +172,22 @@ const loginHandle = async () => {
.input-icon { .input-icon {
width: 15px !important; width: 15px !important;
} }
.input-username{
font-size: 14px;
color: #2C3E59;
// margin-bottom: 10px;
margin-top: 3rem;
}
.input-title { .input-title {
font-size: 14px; font-size: 14px;
color: #2C3E59; color: #2C3E59;
margin-bottom: 10px;
// margin-bottom: 10px;
margin-top: 4rem;
} }
.login-btn{ .login-btn{
height: 2.5vh; height: 2.5vh;
display: flex; display: flex;
align-items: center; align-items: center;
margin-top: 4rem;
} }
</style> </style>

8
src/views/seal/index.vue

@ -28,6 +28,7 @@ const loading = ref(false)
onMounted(() => { onMounted(() => {
// initData() // initData()
systemStore.subscribeSealEvent()
subscribeSealState() subscribeSealState()
}) })
@ -91,13 +92,6 @@ const onStartTest = () => {
startPosityveTimer((time) => { startPosityveTimer((time) => {
sealRemainTimeS.value = time sealRemainTimeS.value = time
}) })
//
const subParams = {
className: 'AirLeakDetectTest',
fnName: 'startStateReport',
params: {},
}
syncSendCmd(subParams)
}) })
} }
const onFinishTest = () => { const onFinishTest = () => {

Loading…
Cancel
Save