2279 lines
65 KiB
Vue
2279 lines
65 KiB
Vue
<template>
|
||
<div class="calibration-page">
|
||
<el-header class="calibration-header">
|
||
<div class="header-left">
|
||
<el-select v-model="selectedScheme" placeholder="选择方案" :disabled="playState" style="width: 200px; margin-right: 15px" @change="schemeChange">
|
||
<el-option v-for="(item, index) in schemeList" :key="index" :label="item.schemeName" :value="item.id"></el-option>
|
||
</el-select>
|
||
<el-button icon="CaretRight" type="primary" :disabled="playState" @click="startExecution">开始执行</el-button>
|
||
<el-button icon="RemoveFilled" type="danger" @click="stopExecution">停止</el-button>
|
||
<span style="margin-left: 10px">操作员编号:</span>
|
||
<el-input v-model="operatorCode" placeholder="输入操作员编号" style="width: 120px; margin-left: 10px" />
|
||
<div class="statistics-box">
|
||
生产总数/合格数/不合格次数: <span class="statistics-number">{{ equipmentStatistics.totalDevice }}/{{ equipmentStatistics.successDevice }}/{{ equipmentStatistics.failCount }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="header-right">
|
||
<el-button icon="RefreshRight" type="primary" :disabled="playState" @click="generateDeviceList">刷新列表</el-button>
|
||
<el-button icon="Setting" :disabled="playState" style="margin-left: 10px" @click="openSettings">配置</el-button>
|
||
</div>
|
||
</el-header>
|
||
<div v-if="activeScheme && activeScheme.series" class="scheme-list list-one">
|
||
<span class="scheme-item"
|
||
>系列: <span class="scheme-value">{{ activeScheme.series }}</span></span
|
||
>
|
||
<span class="scheme-item"
|
||
>框架: <span class="scheme-value">{{ activeScheme.framework }}</span></span
|
||
>
|
||
<span class="scheme-item"
|
||
>P位: <span class="scheme-value">{{ activeScheme.schemeType }}</span></span
|
||
>
|
||
<span class="scheme-item"
|
||
>额定电压(V): <span class="scheme-value">{{ activeScheme.configParam.format[1].value }}</span></span
|
||
>
|
||
<span class="scheme-item"
|
||
>额定电流(A): <span class="scheme-value">{{ activeScheme.configParam.format[2].value }}</span></span
|
||
>
|
||
<span class="scheme-item"
|
||
>漏电流(mA): <span class="scheme-value">{{ activeScheme.configParam.format[5].value }}</span></span
|
||
>
|
||
<span class="scheme-item"></span>
|
||
</div>
|
||
<div v-if="activeScheme && activeScheme.series" class="scheme-list list-two">
|
||
<template v-for="(item, index) in activeScheme.configParam.format">
|
||
<span v-if="item.key !== 'reserve' && index !== 1 && index !== 2 && index !== 5" :key="index" class="scheme-item"
|
||
>{{ item.unit ? item.name + '(' + item.unit + ')' : item.name }}:
|
||
<span v-if="index === 6 || index === 7 || index === 8" class="scheme-value"
|
||
>2<sup style="margin-left: 1px; font-size: 10px">{{ item.value }}</sup></span
|
||
><span v-else class="scheme-value">{{ item.value }}</span></span
|
||
>
|
||
</template>
|
||
</div>
|
||
|
||
<el-main class="calibration-main">
|
||
<div class="device-grid">
|
||
<el-card v-for="(device, index) in devices" :key="device.id" class="device-card" :class="activeDeviceIndex === index ? 'active' : ''" @click="selectDevice(device, index)">
|
||
<template #header>
|
||
<div class="device-card-header">
|
||
<!-- <span>{{ device.code }}</span>-->
|
||
<span>设备{{ device.id }}</span>
|
||
<el-tag :type="device.connectStatus === 1 ? 'success' : 'info'" size="small">
|
||
{{ device.connectStatus === 1 ? '已连接' : '未连接' }}
|
||
</el-tag>
|
||
</div>
|
||
</template>
|
||
<div class="device-card-body">
|
||
<div v-if="device.activeStep" class="process-status" :class="device.stepStatus === -1 ? 'color-red' : device.stepStatus === 1 ? 'color-green' : 'color-orange'">{{ device.activeStep }}</div>
|
||
<div
|
||
v-if="device.connectStatus === 1"
|
||
:class="[
|
||
'iconfont',
|
||
'circuit-breaker',
|
||
device.switch === 0 ? 'icon-zhahe' : 'icon-zhakai',
|
||
device.result === 1 ? 'color-green' : device.result === -1 ? 'color-red' : device.result === 2 ? 'color-orange' : ''
|
||
]"
|
||
></div>
|
||
<div
|
||
v-else
|
||
:class="['iconfont', 'icon-zhakai', 'circuit-breaker', device.switch === 0 ? 'icon-zhahe' : 'icon-zhakai', device.result === 1 ? 'color-green' : device.result === -1 ? 'color-red' : '']"
|
||
></div>
|
||
<p>
|
||
芯片ID:
|
||
<span v-if="device.cpuId"
|
||
><el-tooltip class="box-item" effect="dark" :content="device.cpuId" placement="top-start">
|
||
{{ device.cpuId || '无' }}
|
||
</el-tooltip></span
|
||
>
|
||
<span v-else>{{ device.cpuId || '无' }}</span>
|
||
</p>
|
||
<p>设备端口: {{ device.devicePort || '无' }}</p>
|
||
<p>
|
||
标定结果:
|
||
<span :class="['device-result', device.result === 1 ? 'color-green' : device.result === -1 ? 'color-red' : device.result === 2 ? 'color-orange' : '']">{{
|
||
device.result === 1 ? '成功' : device.result === -1 ? '失败' : device.result === 2 ? '标定中' : '无' || '无'
|
||
}}</span>
|
||
</p>
|
||
<p>
|
||
结果描述:
|
||
<span v-if="device.resultTxt" class="color-red"
|
||
><el-tooltip class="box-item" effect="dark" :content="device.resultTxt" placement="top-start">
|
||
{{ device.resultTxt || '无' }}
|
||
</el-tooltip></span
|
||
>
|
||
<span v-else>{{ device.resultTxt || '无' }}</span>
|
||
</p>
|
||
</div>
|
||
</el-card>
|
||
</div>
|
||
</el-main>
|
||
|
||
<el-footer class="calibration-footer" :class="isOpenLog ? 'open-log' : ''">
|
||
<div id="log-box-main" class="log-box-main">
|
||
<!-- 浮窗搜索栏 -->
|
||
<div v-if="isSearchActive" class="floating-search-bar">
|
||
<div class="search-input-wrapper">
|
||
<el-input ref="searchInputRef" v-model="searchKeyword" placeholder="搜索日志..." size="small" clearable @keyup.enter="navigateSearch('next')" @keyup.esc="closeSearch" @input="performSearch">
|
||
<template #prefix>
|
||
<el-icon><Search /></el-icon>
|
||
</template>
|
||
</el-input>
|
||
</div>
|
||
<div v-if="searchResults.length > 0" class="search-stats">{{ currentSearchIndex + 1 }}/{{ totalSearchCount }}</div>
|
||
<div class="search-navigation">
|
||
<el-button v-if="searchResults.length > 0" size="mini" :disabled="currentSearchIndex <= 0" @click="navigateSearch('prev')">
|
||
<el-icon><ArrowUp /></el-icon>
|
||
</el-button>
|
||
<el-button v-if="searchResults.length > 0" size="mini" :disabled="currentSearchIndex >= searchResults.length - 1" @click="navigateSearch('next')">
|
||
<el-icon><ArrowDown /></el-icon>
|
||
</el-button>
|
||
<el-button size="mini" @click="closeSearch">
|
||
<el-icon><Close /></el-icon>
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="log-list">
|
||
<div
|
||
v-for="(item, index) in logs"
|
||
:key="index"
|
||
class="log-item"
|
||
:class="{
|
||
'search-highlight': isSearchActive && searchResults.some(result => result.originalIndex === index),
|
||
'search-current': isSearchActive && searchResults[currentSearchIndex] && searchResults[currentSearchIndex].originalIndex === index,
|
||
'finish-txt': item.type == 'calibrate_finish'
|
||
}"
|
||
>
|
||
<el-tooltip v-if="item.type == 'calibrate_error'" effect="light" content="错误信息" placement="top">
|
||
<span class="iconfont icon-bug-fill" style="color: #ff3b2b"></span>
|
||
</el-tooltip>
|
||
<el-tooltip v-else-if="item.type == 'calibrate_result_error'" effect="light" content="标定错误" placement="top">
|
||
<span class="iconfont icon-xwtubiaoku-15" style="color: #ff3b2b"></span>
|
||
</el-tooltip>
|
||
<el-tooltip v-else-if="item.type == 'calibrate_result_success'" effect="light" content="标定成功" placement="top">
|
||
<span class="iconfont icon-xwtubiaoku-13" style="color: #55e800"></span>
|
||
</el-tooltip>
|
||
<el-tooltip v-else effect="light" content="系统" placement="top">
|
||
<span class="iconfont icon-rizhi1"></span>
|
||
</el-tooltip>
|
||
<span class="log-item-txt" v-html="getHighlightedText(item)"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="calibration-footer-btn">
|
||
<div class="btn-box">
|
||
<view class="log-box-operate-item">
|
||
<div>日志状态:</div>
|
||
<div v-if="socketStatus" class="log-socket-state" @mouseover="logSocketStateHover = true" @mouseleave="logSocketStateHover = false" @click="reconnectSocket">
|
||
{{ logSocketStateHover ? '重连' : '连接' }} <span v-if="logSocketStateHover" span class="iconfont icon-icon_gengxin"></span>
|
||
</div>
|
||
<div v-else style="color: #ff3b2b; display: flex; align-items: center" @click="reconnectSocket">
|
||
<el-tooltip class="box-item" content="重新连接" effect="dark" placement="bottom">
|
||
<div style="display: flex; align-items: center"><span>断连</span><span class="iconfont icon-icon_gengxin"></span></div>
|
||
</el-tooltip>
|
||
</div>
|
||
</view>
|
||
<view class="log-box-operate-item" @click="toggleIsScroll">
|
||
<el-tooltip v-if="isScroll" class="box-item" content="关闭滚动" effect="dark" placement="bottom">
|
||
<span class="iconfont icon-unlock"></span>
|
||
</el-tooltip>
|
||
<el-tooltip v-else class="box-item" content="开启滚动" effect="dark" placement="bottom">
|
||
<span class="iconfont icon-icon_suoding"></span>
|
||
</el-tooltip>
|
||
</view>
|
||
<view class="log-box-operate-item" @click="openSearch">
|
||
<el-tooltip class="box-item" content="搜索日志" effect="dark" placement="bottom">
|
||
<span class="iconfont icon-sousuo"></span>
|
||
</el-tooltip>
|
||
</view>
|
||
<view class="log-box-operate-item" @click="clearLog">
|
||
<el-tooltip class="box-item" content="清空日志" effect="dark" placement="bottom">
|
||
<span class="iconfont icon-icon_shanchu"></span>
|
||
</el-tooltip>
|
||
</view>
|
||
<view class="log-box-operate-item" @click="toggleOpenLog">
|
||
<el-tooltip v-if="isOpenLog" class="box-item" content="收缩日志" effect="dark" placement="bottom">
|
||
<span class="iconfont icon-up-arrow"></span>
|
||
</el-tooltip>
|
||
<el-tooltip v-else class="box-item" content="展开日志" effect="dark" placement="bottom">
|
||
<span class="iconfont icon-shousuoshangjiantou"></span>
|
||
</el-tooltip>
|
||
</view>
|
||
</div>
|
||
<div class="calibration-footer-btn-right">
|
||
<el-tooltip placement="top">
|
||
<template #content>
|
||
<el-table :data="shortcutKeyList" style="width: 100%" class="dark-table">
|
||
<el-table-column prop="action" label="功能" width="160" />
|
||
<el-table-column prop="label" label="快捷键" width="100" />
|
||
</el-table>
|
||
</template>
|
||
<span class="iconfont icon-kuaijiejian"></span>
|
||
</el-tooltip>
|
||
<div class="version-info">V{{ version }}</div>
|
||
</div>
|
||
</div>
|
||
</el-footer>
|
||
|
||
<!-- 方案配置抽屉 -->
|
||
<el-drawer v-model="schemeDrawerVisible" title="方案配置" size="80%" :destroy-on-close="true">
|
||
<div class="scheme-drawer-content">
|
||
<div class="scheme-list-header">
|
||
<div class="scheme-list-header-left">
|
||
<el-button type="primary" @click="addScheme">新增方案</el-button>
|
||
<el-button type="success" @click="exportSelectedSchemes">导出方案</el-button>
|
||
<el-button type="warning" @click="importSchemes">导入方案</el-button>
|
||
<el-button icon="Download" type="success" style="margin-left: 10px" @click="showExportProdDialog = true">导出生产数据</el-button>
|
||
<input ref="importInputRef" type="file" accept="application/json" style="display: none" @change="handleImportFile" />
|
||
</div>
|
||
<el-button type="primary" @click="loadSchemes">刷新</el-button>
|
||
</div>
|
||
<el-table ref="schemeTableRef" v-loading="loading" :data="schemeList" style="width: 100%" @selection-change="handleSelectionChange">
|
||
<el-table-column type="selection" width="55" />
|
||
<el-table-column type="index" width="50" />
|
||
<el-table-column prop="schemeName" label="方案名称" />
|
||
<el-table-column prop="schemeType" label="断路器类型"></el-table-column>
|
||
<el-table-column label="操作" width="200">
|
||
<template #default="scope">
|
||
<el-button type="success" link @click="copyScheme(scope.row)">复制</el-button>
|
||
<el-button type="primary" link @click="viewScheme(scope.row)">修改</el-button>
|
||
<el-button type="danger" link @click="deleteScheme(scope.row)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</el-drawer>
|
||
|
||
<!-- 方案详情对话框 -->
|
||
<el-dialog v-model="schemeDetailVisible" :title="currentScheme.schemeName" width="95%" :destroy-on-close="true" align-center :close-on-click-modal="false">
|
||
<el-form :model="currentScheme" label-width="100px">
|
||
<el-form-item label="方案名称">
|
||
<el-input v-model="currentScheme.schemeName" :disabled="isEditMode" />
|
||
</el-form-item>
|
||
<el-row>
|
||
<el-col :span="5">
|
||
<el-form-item label="系列">
|
||
<el-select v-model="currentScheme.series" placeholder="请选择系列">
|
||
<el-option label="B7" value="B7" />
|
||
<el-option label="B7L" value="B7L" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="5">
|
||
<el-form-item label="框架">
|
||
<el-select v-model="currentScheme.framework" placeholder="请选择框架">
|
||
<el-option label="100ZS" value="100ZS" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="5">
|
||
<el-form-item label="断路器类型">
|
||
<el-select v-model="currentScheme.schemeType" placeholder="请选择断路器类型">
|
||
<el-option label="1P" value="1P" />
|
||
<el-option label="2P" value="2P" />
|
||
<el-option label="3P" value="3P" />
|
||
<el-option label="4P" value="4P" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="4">
|
||
<el-form-item label="标定中检测">
|
||
<el-switch v-model="currentScheme.middleDetect" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="4">
|
||
<el-form-item label="标定后检测">
|
||
<el-switch v-model="currentScheme.laterDetect" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<el-divider>断路器校准参数</el-divider>
|
||
<div class="calibrate-params">
|
||
<template v-for="(item, index) in defaultConfList.format">
|
||
<el-form-item v-if="item.key !== 'reserve'" :key="item.key" :label="item.unit ? item.name + '(' + item.unit + ')' : item.name" :label-width="145">
|
||
<el-input v-model="currentScheme.configParam.format[index].value"> </el-input>
|
||
</el-form-item>
|
||
</template>
|
||
</div>
|
||
|
||
<!-- <el-divider>断路器类型参数</el-divider>
|
||
<div class="device-params">
|
||
<div v-for="item in currentScheme.propertyParam" :key="item.id" class="device-param-item">
|
||
<span class="param-label">{{ item.unit ? item.name + '(' + item.unit + ')' : item.name }}</span>
|
||
</div>
|
||
</div> -->
|
||
|
||
<el-divider>校准精度(%)</el-divider>
|
||
<div class="accuracy-title">普通电流</div>
|
||
<div class="accuracy-params">
|
||
<el-form-item v-for="(value, key) in currentScheme.errorRange.range" :key="key" label-width="auto" :label="accuracyType[key]">
|
||
<el-input-number v-model="currentScheme.errorRange.range[key]" style="width: 120px" :min="0" :max="100" :precision="2" />
|
||
</el-form-item>
|
||
</div>
|
||
<div class="accuracy-title">小电流</div>
|
||
<div class="accuracy-params">
|
||
<el-form-item v-for="(value, key) in currentScheme.errorRange.smallRange" :key="key" label-width="auto" :label="accuracyType[key]">
|
||
<el-input-number v-model="currentScheme.errorRange.smallRange[key]" style="width: 120px" :min="0" :max="100" :precision="2" />
|
||
</el-form-item>
|
||
</div>
|
||
|
||
<el-divider>标定中检测步骤</el-divider>
|
||
<div class="calibration-steps">
|
||
<div class="steps-header">
|
||
<el-button type="primary" @click="addCalibrationStep">添加步骤</el-button>
|
||
</div>
|
||
<el-table :data="currentScheme.steps" size="small" style="width: 100%">
|
||
<!-- <el-table-column type="index" width="50" />-->
|
||
<el-table-column prop="id" align="center" label="排序" width="80" />
|
||
<!-- <el-table-column prop="id" align="center" label="步骤值">-->
|
||
<!-- <template #default="scope">-->
|
||
<!-- <el-input-number v-model="scope.row.id" :min="0" />-->
|
||
<!-- </template>-->
|
||
<!-- </el-table-column>-->
|
||
<el-table-column prop="step" align="center" label="步骤值">
|
||
<template #default="scope">
|
||
<el-input-number v-model="scope.row.step" :min="1" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="voltage" align="center" label="电压U(%)">
|
||
<template #default="scope">
|
||
<el-input-number v-model="scope.row.voltage" :min="0" :precision="1" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="current" align="center" label="电流I(%)/漏电流(%)">
|
||
<template #default="scope">
|
||
<div v-if="!scope.row.isILeak" style="display: flex; align-items: center;">
|
||
<!-- <el-tooltip class="box-item" effect="dark" content="电流I(%)" placement="left">
|
||
<el-input-number v-model="scope.row.current" :min="0" :precision="1" />
|
||
</el-tooltip> -->
|
||
<p style="width:50px">电流: </p>
|
||
<el-input-number v-model="scope.row.current" :min="0" :precision="1" />
|
||
</div>
|
||
<!-- <el-input-number v-model="scope.row.current" :min="0" :precision="1" /> -->
|
||
<div v-else style="display: flex; align-items: center;">
|
||
<!-- <el-tooltip class="box-item" effect="dark" content="漏电流(%)" placement="left">
|
||
<el-input-number v-model="scope.row.current" :min="0" :precision="1" />
|
||
</el-tooltip> -->
|
||
<p style="width:50px">漏电流: </p>
|
||
<el-input-number v-model="scope.row.current" :min="0" :precision="1" />
|
||
</div>
|
||
<!-- <span v-else>{{ currentScheme.configParam.format[5].value + currentScheme.configParam.format[5].unit }}</span> -->
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="powerFactor" align="center" label="功率因数" width="120">
|
||
<template #default="scope">
|
||
<el-select v-model="scope.row.powerFactor">
|
||
<el-option label="0.5L" value="0.5L" />
|
||
<el-option label="0.8L" value="0.8L" />
|
||
<el-option label="1" value="1" />
|
||
<el-option label="0.5C" value="0.5C" />
|
||
<el-option label="0.8C" value="0.8C" />
|
||
</el-select>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="isILeak" align="center" label="检测漏电" width="120">
|
||
<template #default="scope">
|
||
<el-switch v-model="scope.row.isILeak" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="80">
|
||
<template #default="scope">
|
||
<el-button type="danger" link @click="deleteCalibrationStep(scope.$index)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
|
||
<el-divider>标定后检测步骤</el-divider>
|
||
<div class="calibration-steps">
|
||
<div class="steps-header">
|
||
<!-- <el-button type="primary" @click="addCalibrationLaterStep">添加步骤</el-button> -->
|
||
</div>
|
||
<el-table :data="currentScheme.laterSteps" size="small" style="width: 100%">
|
||
<!-- <el-table-column type="index" width="50" />-->
|
||
<el-table-column prop="id" align="center" label="排序" width="80" />
|
||
<!-- <el-table-column prop="id" align="center" label="步骤值">-->
|
||
<!-- <template #default="scope">-->
|
||
<!-- <el-input-number v-model="scope.row.id" :min="0" />-->
|
||
<!-- </template>-->
|
||
<!-- </el-table-column>-->
|
||
<el-table-column prop="step" align="center" label="步骤值">
|
||
<template #default="scope">
|
||
<el-input-number v-model="scope.row.step" :min="1" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="voltage" align="center" label="电压U(%)">
|
||
<template #default="scope">
|
||
<el-input-number v-model="scope.row.voltage" :min="0" :precision="1" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="current" align="center" label="电流I(%)/漏电流(%)">
|
||
|
||
<template #default="scope">
|
||
<div v-if="!scope.row.isILeak" style="display: flex; align-items: center;">
|
||
<!-- <el-tooltip class="box-item" effect="dark" content="电流I(%)" placement="left">
|
||
<el-input-number v-model="scope.row.current" :min="0" :precision="1" />
|
||
</el-tooltip> -->
|
||
<p style="width:50px">电流: </p>
|
||
<el-input-number v-model="scope.row.current" :min="0" :precision="1" />
|
||
</div>
|
||
<!-- <el-input-number v-model="scope.row.current" :min="0" :precision="1" /> -->
|
||
<div v-else style="display: flex; align-items: center;">
|
||
<!-- <el-tooltip class="box-item" effect="dark" content="漏电流(%)" placement="left">
|
||
<el-input-number v-model="scope.row.current" :min="0" :precision="1" />
|
||
</el-tooltip> -->
|
||
<p style="width:50px">漏电流: </p>
|
||
<el-input-number v-model="scope.row.current" :min="0" :precision="1" />
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<!-- <el-table-column prop="current" align="center" label="电流Ib(%)">
|
||
<template #default="scope">
|
||
<el-input-number v-model="scope.row.current" :min="0" :precision="1" />
|
||
</template>
|
||
</el-table-column> -->
|
||
<el-table-column prop="powerFactor" align="center" label="功率因数" width="120">
|
||
<template #default="scope">
|
||
<el-select v-model="scope.row.powerFactor">
|
||
<el-option label="0.5L" value="0.5L" />
|
||
<el-option label="0.8L" value="0.8L" />
|
||
<el-option label="1" value="1" />
|
||
<el-option label="0.5C" value="0.5C" />
|
||
<el-option label="0.8C" value="0.8C" />
|
||
</el-select>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="isILeak" align="center" label="检测漏电" width="120">
|
||
<template #default="scope">
|
||
<el-switch v-model="scope.row.isILeak" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="80">
|
||
<template #default="scope">
|
||
<!-- <el-button type="danger" link @click="deleteCalibrationLaterStep(scope.$index)">删除</el-button> -->
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
|
||
<!-- 新增:漏电检测设置分组 -->
|
||
<el-divider>漏电检测设置</el-divider>
|
||
<el-row :gutter="20">
|
||
<el-col :span="6">
|
||
<el-form-item label="漏电检测">
|
||
<el-switch v-model="currentScheme.iLeakDetectObj.iLeakDetect" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="检测电流(mA)">
|
||
<el-input-number v-model="currentScheme.iLeakDetectObj.detectInput" :min="0" :step="1" style="width: 120px" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="漏电保护">
|
||
<el-switch v-model="currentScheme.iLeakDetectObj.iLeakProtect" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="保护电流(mA)">
|
||
<el-input-number v-model="currentScheme.iLeakDetectObj.protectInput" :min="0" :step="1" style="width: 120px" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
<template #footer>
|
||
<span class="dialog-footer">
|
||
<el-button @click="schemeDetailVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="saveScheme">保存</el-button>
|
||
</span>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 方案配置抽屉 -->
|
||
<el-drawer v-model="logVisible" title="设备日志" size="90%" :destroy-on-close="true">
|
||
<div class="scheme-drawer-content log-content">
|
||
<div class="device-info">
|
||
<div class="device-info-item">设备: {{ activeDeviceInfo.id || '-' }}</div>
|
||
<div class="device-info-item">芯片ID: {{ activeDeviceInfo.cpuId || '-' }}</div>
|
||
<div class="device-info-item">设备端口: {{ activeDeviceInfo.devicePort || '-' }}</div>
|
||
<div class="device-info-item">
|
||
标定结果:
|
||
<span style="font-weight: bold" :class="['device-result', activeDeviceInfo.result === 1 ? 'color-green' : activeDeviceInfo.result === -1 ? 'color-red' : '']">{{
|
||
activeDeviceInfo.result === 1 ? '成功' : activeDeviceInfo.result === -1 ? '失败' : '未标定' || '无'
|
||
}}</span>
|
||
</div>
|
||
<div class="device-info-item">结果描述: {{ activeDeviceInfo.resultTxt || '-' }}</div>
|
||
</div>
|
||
<el-tabs v-model="logTabActive" class="demo-tabs" @tab-click="logTabChange">
|
||
<el-tab-pane label="标定日志" name="log">
|
||
<el-table :data="stepLogRows" style="width: 100%" size="small" scrollbar-always-on resizable empty-text="暂无日志">
|
||
<el-table-column prop="step" label="步骤" fixed align="center" />
|
||
<el-table-column v-for="col in logParamColumns" :key="col.key" :prop="'paramMap.' + col.valueKey + '.actualError'" :label="col.valueKey" align="center">
|
||
<template #header>
|
||
<el-tooltip effect="dark" :content="(col.valueName || col.valueKey) + (col.valueUnit ? ' [' + col.valueUnit + ']' : '')" placement="top">
|
||
<span>{{ (col.valueName || col.valueKey) + (col.valueUnit ? ' [' + col.valueUnit + ']' : '') }}</span>
|
||
</el-tooltip>
|
||
</template>
|
||
<template #default="scope">
|
||
<el-tooltip
|
||
effect="dark"
|
||
:content="
|
||
'差值百分比: ' +
|
||
(scope.row.paramMap[col.valueKey]?.actualError + '%' ?? '') +
|
||
'\n' +
|
||
'范围百分比: ' +
|
||
(scope.row.paramMap[col.valueKey]?.expectedError + '%' ?? '') +
|
||
'\n' +
|
||
'源输出值: ' +
|
||
(scope.row.paramMap[col.valueKey]?.outputValue ?? '') +
|
||
'\n' +
|
||
'读取值: ' +
|
||
(scope.row.paramMap[col.valueKey]?.actualValue ?? '') +
|
||
'\n' +
|
||
'范围最小值: ' +
|
||
(scope.row.paramMap[col.valueKey]?.ExpectedMin ?? '') +
|
||
'\n' +
|
||
'范围最大值: ' +
|
||
(scope.row.paramMap[col.valueKey]?.expectedMax ?? '')
|
||
"
|
||
placement="top"
|
||
>
|
||
<span :style="{ color: scope.row.paramMap[col.valueKey]?.result === 1 ? 'green' : 'red' }">
|
||
{{
|
||
typeof scope.row.paramMap[col.valueKey]?.actualError === 'number'
|
||
? Number(scope.row.paramMap[col.valueKey].actualError.toFixed(6)).toString()
|
||
: scope.row.paramMap[col.valueKey]?.actualError
|
||
}}<span v-if="scope.row.paramMap[col.valueKey]">%</span>
|
||
<template v-if="scope.row.paramMap[col.valueKey]">
|
||
<el-icon v-if="scope.row.paramMap[col.valueKey].actualValue > scope.row.paramMap[col.valueKey].outputValue"><Top /></el-icon>
|
||
<el-icon v-else-if="scope.row.paramMap[col.valueKey].actualValue < scope.row.paramMap[col.valueKey].outputValue"><Bottom /></el-icon>
|
||
</template>
|
||
</span>
|
||
</el-tooltip>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="result" fixed="right" label="结果" align="center" width="80">
|
||
<template #default="scope">
|
||
<span :style="{ color: scope.row.result === 1 ? 'green' : 'red' }">
|
||
{{ scope.row.result === 1 ? '成功' : '失败' }}
|
||
</span>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</el-tab-pane>
|
||
<el-tab-pane label="错误日志" name="second">
|
||
<el-table :data="errorLogList" size="small" style="width: 100%">
|
||
<el-table-column type="index" width="50" />
|
||
<el-table-column prop="errorCode" label="错误码" width="180">
|
||
<template #default="scope">
|
||
<span style="color: red">
|
||
{{ scope.row.errorCode }}
|
||
</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="logMsg" label="错误日志">
|
||
<template #default="scope">
|
||
<el-tooltip effect="dark" :content="scope.row.logMsg" placement="top">
|
||
<span class="log-msg">{{ scope.row.logMsg }}</span>
|
||
</el-tooltip>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
</div>
|
||
</el-drawer>
|
||
<!-- 导出生产数据弹窗 -->
|
||
<el-dialog v-model="showExportProdDialog" title="导出生产数据" width="450px" :close-on-click-modal="false">
|
||
<el-form>
|
||
<el-form-item label="时间范围">
|
||
<el-date-picker
|
||
v-model="exportProdRange"
|
||
type="daterange"
|
||
unlink-panels
|
||
range-separator="至"
|
||
start-placeholder="开始日期"
|
||
end-placeholder="结束日期"
|
||
:shortcuts="exportProdPickerOptions"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="showExportProdDialog = false">取消</el-button>
|
||
<el-button type="primary" @click="handleExportProd">导出</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, onUnmounted, computed, watch, nextTick } from 'vue';
|
||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||
import { Search, ArrowUp, ArrowDown, Top, Bottom, Close } from '@element-plus/icons-vue';
|
||
import { throttle } from 'lodash-es';
|
||
import dayjs from 'dayjs';
|
||
import axios from 'axios';
|
||
// import config from '@renderer/util/config.js';
|
||
const config = window.electron.readConfig();
|
||
console.log('当前获取config', config);
|
||
|
||
import { logWebSocketStore } from '@renderer/stores/logWebSocket.js';
|
||
const webSocketStore = logWebSocketStore();
|
||
|
||
const selectedScheme = ref('');
|
||
const playState = ref(false);
|
||
const devices = ref([]);
|
||
const isScroll = ref(true);
|
||
const logs = ref([]);
|
||
const version = ref('V1.0.0');
|
||
const logTabActive = ref('log');
|
||
const operatorCode = ref('');
|
||
|
||
const loading = ref(true);
|
||
const isOpenLog = ref(false);
|
||
const logSocketStateHover = ref(false);
|
||
|
||
// 方案配置相关
|
||
const schemeDrawerVisible = ref(false);
|
||
const schemeDetailVisible = ref(false);
|
||
const schemeList = ref([]);
|
||
const activeScheme = ref();
|
||
|
||
const equipmentStatistics = ref({
|
||
totalDevice: 0,
|
||
successDevice: 0,
|
||
failCount: 0
|
||
});
|
||
|
||
const accuracyType = ref({
|
||
voltage: '电压',
|
||
current: '电流',
|
||
powerFactor: '功率因数',
|
||
activePower: '有功功率',
|
||
reactivePower: '无功功率',
|
||
apparentPower: '视在功率'
|
||
});
|
||
|
||
const defaultConfList = ref({
|
||
id: 0,
|
||
key: 'calibration_conf',
|
||
name: '标定参数',
|
||
addr: 30018,
|
||
len: 4,
|
||
dataType: 'uint16_t',
|
||
rwType: 'R',
|
||
default: '',
|
||
precision: '',
|
||
unit: '',
|
||
format: []
|
||
});
|
||
|
||
const defaultiLeakDetectObj = ref({
|
||
iLeakDetect: false,
|
||
detectInput: 100,
|
||
iLeakProtect: false,
|
||
protectInput: 100
|
||
});
|
||
|
||
const logVisible = ref(false);
|
||
|
||
// 搜索相关变量
|
||
const searchKeyword = ref('');
|
||
const searchResults = ref([]);
|
||
const currentSearchIndex = ref(0);
|
||
const totalSearchCount = ref(0);
|
||
const isSearchActive = ref(false);
|
||
|
||
const logLoading = ref(false);
|
||
|
||
const logBoxRef = ref(null);
|
||
const MAX_LOG_LENGTH = 2000;
|
||
|
||
const activeDeviceIndex = ref(0);
|
||
|
||
const activeDeviceInfo = ref({
|
||
id: 0,
|
||
devicePort: '',
|
||
cpuId: '',
|
||
connectStatus: 0,
|
||
switch: 0,
|
||
result: 0,
|
||
resultTxt: '',
|
||
stepStatus: 0,
|
||
activeStep: ''
|
||
});
|
||
|
||
const defaultPropList = ref([]);
|
||
|
||
const shortcutKeyList = ref([
|
||
{
|
||
label: 'Ctrl+F',
|
||
action: '显/隐日志查询框'
|
||
},
|
||
{
|
||
label: '↑',
|
||
action: '上一个日志(查询时)'
|
||
},
|
||
{
|
||
label: '↓',
|
||
action: '下一个日志(查询时)'
|
||
},
|
||
{
|
||
label: 'Enter',
|
||
action: '下一个日志(查询时)'
|
||
},
|
||
{
|
||
label: 'Esc',
|
||
action: '退出查询'
|
||
}
|
||
]);
|
||
|
||
const currentScheme = ref({
|
||
schemeName: '',
|
||
schemeType: '1P',
|
||
series: 'B7',
|
||
framework: '100ZS',
|
||
middleDetect: true,
|
||
laterDetect: false,
|
||
configParam: defaultConfList,
|
||
propertyParam: defaultPropList,
|
||
errorRange: {
|
||
range: {
|
||
voltage: 0.5,
|
||
current: 0.5,
|
||
powerFactor: 0.5,
|
||
activePower: 0.5,
|
||
reactivePower: 0.5,
|
||
apparentPower: 0.5
|
||
},
|
||
smallRange: {
|
||
voltage: 1,
|
||
current: 1,
|
||
powerFactor: 1,
|
||
activePower: 1,
|
||
reactivePower: 1,
|
||
apparentPower: 1
|
||
}
|
||
},
|
||
steps: [],
|
||
laterSteps: [
|
||
{
|
||
id: 1,
|
||
step: 1,
|
||
voltage: 110,
|
||
current: 110,
|
||
powerFactor: '0.5L',
|
||
isILeak: false
|
||
},
|
||
{
|
||
id: 2,
|
||
step: 2,
|
||
voltage: 80,
|
||
current: 80,
|
||
powerFactor: '0.5L',
|
||
isILeak: false
|
||
},
|
||
{
|
||
id: 3,
|
||
step: 3,
|
||
voltage: 100,
|
||
current: 80,
|
||
powerFactor: '0.5L',
|
||
isILeak: true
|
||
}
|
||
],
|
||
iLeakDetectObj: defaultiLeakDetectObj
|
||
});
|
||
|
||
// 1. 新增:设备日志参数集合和step分组数据
|
||
const logParamColumns = ref([]); // [{ valueKey, valueName, valueUnit }]
|
||
const stepLogRows = ref([]); // [{ step, paramMap: { valueKey: item }, result: 0/1 }]
|
||
const errorLogList = ref([]);
|
||
|
||
const logTabChange = (tab, event) => {
|
||
console.log(tab, event);
|
||
};
|
||
|
||
const selectDevice = async (item, index) => {
|
||
activeDeviceInfo.value = item;
|
||
activeDeviceIndex.value = index;
|
||
// 调用日志接口获取设备日志
|
||
await fetchDeviceLogs(item.cpuId);
|
||
logVisible.value = true;
|
||
};
|
||
|
||
// 获取设备日志数据
|
||
const fetchDeviceLogs = async deviceId => {
|
||
logLoading.value = true;
|
||
try {
|
||
const response = await axios.get(config.url + '/master/calibrate/result', {
|
||
params: { cpuId: deviceId }
|
||
});
|
||
|
||
if (response.data.code === 0) {
|
||
processLogData(response.data.data.result);
|
||
errorLogList.value = response.data.data.log || [];
|
||
} else {
|
||
ElMessage.error(response.data.message || '获取设备日志失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('获取设备日志失败:', error);
|
||
ElMessage.error('获取设备日志失败');
|
||
} finally {
|
||
logLoading.value = false;
|
||
}
|
||
};
|
||
|
||
// 重写 processLogData
|
||
const processLogData = apiData => {
|
||
if (!apiData || !Array.isArray(apiData)) {
|
||
logParamColumns.value = [];
|
||
stepLogRows.value = [];
|
||
return;
|
||
}
|
||
// 收集所有参数,优先从activeScheme.propertyParam获取名称和单位
|
||
const paramMap = new Map();
|
||
apiData.forEach(item => {
|
||
let valueName = item.valueName || item.valueKey;
|
||
let valueUnit = item.valueUnit || '';
|
||
if (activeScheme.value && Array.isArray(defaultPropList.value)) {
|
||
const found = defaultPropList.value.find(param => param.key === item.valueKey);
|
||
if (found) {
|
||
valueName = found.name || valueName;
|
||
valueUnit = found.unit || valueUnit;
|
||
}
|
||
}
|
||
const key = item.valueKey + (valueUnit ? `(${valueUnit})` : '');
|
||
if (!paramMap.has(key)) {
|
||
paramMap.set(key, {
|
||
valueKey: item.valueKey,
|
||
valueName,
|
||
valueUnit,
|
||
key: key
|
||
});
|
||
}
|
||
});
|
||
logParamColumns.value = Array.from(paramMap.values());
|
||
|
||
// 按step分组
|
||
const stepGroups = {};
|
||
apiData.forEach(item => {
|
||
if (!stepGroups[item.step]) stepGroups[item.step] = [];
|
||
stepGroups[item.step].push(item);
|
||
});
|
||
// 生成每行数据
|
||
stepLogRows.value = Object.entries(stepGroups).map(([step, arr]) => {
|
||
// paramMap: { valueKey: item }
|
||
const paramMapRow = {};
|
||
arr.forEach(item => {
|
||
paramMapRow[item.valueKey] = item;
|
||
});
|
||
// 结果判定
|
||
// 检查是否有任何参数的result为0,如果有则直接判定为失败
|
||
// const hasFailure = logParamColumns.value.some(col => paramMapRow[col.valueKey]?.result === 0);
|
||
// if (hasFailure) return {
|
||
// step,
|
||
// paramMap: paramMapRow,
|
||
// result: 0
|
||
// };
|
||
// }
|
||
// 检查所有有result值的参数是否都为1
|
||
const allOk = logParamColumns.value.every(col => {
|
||
const param = paramMapRow[col.valueKey];
|
||
// 没有result值的参数不参与判断
|
||
return param?.result === undefined || param?.result === 1;
|
||
});
|
||
return {
|
||
step,
|
||
paramMap: paramMapRow,
|
||
result: allOk ? 1 : 0
|
||
};
|
||
});
|
||
};
|
||
|
||
const schemeChange = e => {
|
||
if (e) {
|
||
const scheme = schemeList.value.filter(item => item.id === e);
|
||
activeScheme.value = scheme[0];
|
||
} else {
|
||
activeScheme.value = {};
|
||
}
|
||
};
|
||
|
||
// 安全解析 JSON
|
||
const tryParseJSON = data => {
|
||
try {
|
||
return JSON.parse(data);
|
||
} catch {
|
||
return data;
|
||
}
|
||
};
|
||
// 节流滚动
|
||
const throttledScroll = throttle(() => {
|
||
logBoxRef.value.scrollTop = logBoxRef.value.scrollHeight;
|
||
}, 500);
|
||
|
||
const setDeviceInfo = msg => {
|
||
let deviceInfo = tryParseJSON(msg);
|
||
let type = false;
|
||
if (deviceInfo) {
|
||
devices.value.forEach((item, index) => {
|
||
if (item.id === deviceInfo.id) {
|
||
// debugger
|
||
devices.value[index] = deviceInfo;
|
||
// item=deviceInfo;
|
||
if (deviceInfo.result === -1) {
|
||
type = 'calibrate_result_error';
|
||
} else if (deviceInfo.result === 1) {
|
||
type = 'calibrate_result_success';
|
||
} else {
|
||
type = 'calibrate_result_hidden';
|
||
}
|
||
}
|
||
});
|
||
}
|
||
return type;
|
||
};
|
||
|
||
const getSocketMeassage = message => {
|
||
const msg = tryParseJSON(message.data);
|
||
console.log('msg', msg);
|
||
const newLog = {
|
||
time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||
msg: msg.data ?? msg,
|
||
type: msg.msgType ?? null
|
||
};
|
||
|
||
if (msg.msgType === 'calibrate_result') {
|
||
let resultType = setDeviceInfo(msg.data);
|
||
if (resultType !== 'calibrate_result_hidden') {
|
||
newLog.type = resultType;
|
||
} else {
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (msg.msgType === 'calibrate_finish') {
|
||
playState.value = false;
|
||
ElMessage.success('标定完成');
|
||
getQquipmentStatistics();
|
||
}
|
||
|
||
// 批量更新日志数组
|
||
logs.value = [...logs.value.slice(-MAX_LOG_LENGTH + 1), newLog];
|
||
|
||
// 按需触发滚动
|
||
// if (isScroll.value) throttledScroll();
|
||
|
||
// let msg = JSON.parse(message.data);
|
||
// if (msg.msgType !== undefined) {
|
||
// if (logs.value.length > 400) {
|
||
// logs.value.shift();
|
||
// }
|
||
// logs.value.push({
|
||
// time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||
// msg: msg.data,
|
||
// type: msg.msgType
|
||
// });
|
||
// } else {
|
||
// if (logs.value.length > 400) {
|
||
// logs.value.shift();
|
||
// }
|
||
// logs.value.push({
|
||
// time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||
// msg,
|
||
// type: null
|
||
// });
|
||
// }
|
||
// if (isScroll.value) {
|
||
// requestAnimationFrame(() => {
|
||
// logBoxRef.value.scrollTop = logBoxRef.value.scrollHeight;
|
||
// });
|
||
// }
|
||
};
|
||
|
||
const initSocket = () => {
|
||
webSocketStore.init(getSocketMeassage);
|
||
};
|
||
|
||
const reconnectSocket = () => {
|
||
webSocketStore.close();
|
||
webSocketStore.init(getSocketMeassage);
|
||
};
|
||
|
||
// socket连接状态
|
||
const socketStatus = computed(() => {
|
||
return webSocketStore.socket_open;
|
||
});
|
||
|
||
// 键盘快捷键处理
|
||
const handleKeydown = event => {
|
||
// Ctrl+F: 打开搜索
|
||
if (event.ctrlKey && event.key === 'f') {
|
||
event.preventDefault();
|
||
openSearch();
|
||
}
|
||
|
||
// 在搜索模式中的快捷键
|
||
if (isSearchActive.value) {
|
||
switch (event.key) {
|
||
case 'ArrowUp':
|
||
// ↑: 上一个结果
|
||
event.preventDefault();
|
||
navigateSearch('prev');
|
||
break;
|
||
case 'ArrowDown':
|
||
// ↓: 下一个结果
|
||
event.preventDefault();
|
||
navigateSearch('next');
|
||
break;
|
||
case 'Escape':
|
||
// Esc: 关闭搜索
|
||
event.preventDefault();
|
||
closeSearch();
|
||
break;
|
||
}
|
||
}
|
||
};
|
||
|
||
const toggleIsScroll = () => {
|
||
isScroll.value = !isScroll.value;
|
||
};
|
||
|
||
const startExecution = () => {
|
||
if (selectedScheme.value === '') {
|
||
ElMessage.error({ message: '请先选择方案' });
|
||
return;
|
||
}
|
||
if (operatorCode.value.trim() === '') {
|
||
ElMessage.error({ message: '请先输入操作员编号', duration: 1000, showClose: true });
|
||
return;
|
||
}
|
||
// 开始标定之前重新连接socket
|
||
reconnectSocket();
|
||
const scheme = schemeList.value.filter(item => item.id === selectedScheme.value);
|
||
console.log(scheme);
|
||
ElMessage.success('开始执行方案: ' + scheme[0].schemeName);
|
||
playState.value = true;
|
||
playCalibration();
|
||
getQquipmentStatistics();
|
||
// Add actual start logic here
|
||
};
|
||
|
||
const stopExecution = async () => {
|
||
try {
|
||
const response = await axios.get(config.url + '/master/scheme/stop');
|
||
if (response.data.code === 0) {
|
||
// devices.value = response.data.data.result || [];
|
||
playState.value = false;
|
||
ElMessage.success('停止成功');
|
||
} else {
|
||
ElMessage.error(response.data.message || '停止失败');
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error('停止失败');
|
||
}
|
||
playState.value = false;
|
||
};
|
||
|
||
const openSettings = () => {
|
||
ElMessageBox.prompt('请输入管理员密码', '密码验证', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
inputType: 'password',
|
||
inputValue: '',
|
||
inputPlaceholder: '请输入密码',
|
||
inputValidator: value => {
|
||
if (!value.trim()) return '请输入密码';
|
||
return true;
|
||
}
|
||
})
|
||
.then(async ({ value }) => {
|
||
try {
|
||
const response = await axios.post(config.url + '/master/scheme/auth?secret=' + value.trim());
|
||
if (response.data.code === 0) {
|
||
if (response.data.data.result) {
|
||
schemeDrawerVisible.value = true;
|
||
loadSchemes();
|
||
} else {
|
||
ElMessage.error(response.data.message || '密码错误');
|
||
}
|
||
} else {
|
||
ElMessage.error(response.data.message || '密码验证失败');
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error('密码验证失败');
|
||
}
|
||
})
|
||
.catch(() => {
|
||
// 用户取消,无需处理
|
||
});
|
||
};
|
||
|
||
// 获取默认标定参数
|
||
const getSchemeDefaultConf = async () => {
|
||
try {
|
||
const response = await axios.get(config.url + '/master/scheme/defaultConf');
|
||
if (response.data.code === 0) {
|
||
defaultConfList.value = response.data.data.config || [];
|
||
} else {
|
||
ElMessage.error(response.data.message || '获取默认标定参数列表失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('获取默认标定参数列表失败:', error);
|
||
ElMessage.error('获取默认标定参数列表失败');
|
||
}
|
||
};
|
||
|
||
// 获取默认属性参数
|
||
const getSchemeDefaultProp = async () => {
|
||
try {
|
||
const response = await axios.get(config.url + '/master/scheme/defaultProp');
|
||
if (response.data.code === 0) {
|
||
defaultPropList.value = response.data.data.config || [];
|
||
} else {
|
||
ElMessage.error(response.data.message || '获取默认属性参数列表失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('获取默认属性参数列表失败:', error);
|
||
ElMessage.error('获取默认属性参数列表失败');
|
||
}
|
||
};
|
||
|
||
// 从API加载方案列表
|
||
const loadSchemes = async () => {
|
||
loading.value = true;
|
||
try {
|
||
const response = await axios.get(config.url + '/master/scheme/query');
|
||
if (response.data.code === 0) {
|
||
schemeList.value = response.data.data.schemes || [];
|
||
// 新增同步逻辑,确保activeScheme与selectedScheme一致
|
||
let found = null;
|
||
if (selectedScheme.value) {
|
||
found = schemeList.value.find(item => item.id === selectedScheme.value);
|
||
}
|
||
if (found) {
|
||
activeScheme.value = found;
|
||
}
|
||
// else if (schemeList.value.length > 0) {
|
||
// selectedScheme.value = schemeList.value[0].id;
|
||
// activeScheme.value = schemeList.value[0];
|
||
// }
|
||
else {
|
||
selectedScheme.value = '';
|
||
activeScheme.value = {};
|
||
}
|
||
ElMessage.success({ message: '获取方案列表成功', duration: 1000 });
|
||
} else {
|
||
ElMessage.error(response.data.message || '获取方案列表失败');
|
||
}
|
||
loading.value = false;
|
||
} catch (error) {
|
||
loading.value = false;
|
||
console.error('加载方案列表失败:', error);
|
||
ElMessage.error('加载方案列表失败');
|
||
}
|
||
};
|
||
|
||
// 保存方案
|
||
const saveScheme = async () => {
|
||
// 新增校验:标定中检测和标定后检测必须至少有一个为true
|
||
if (!currentScheme.value.middleDetect && !currentScheme.value.laterDetect) {
|
||
ElMessage.error('"标定中检测"和"标定后检测"必须至少选择一个');
|
||
return;
|
||
}
|
||
try {
|
||
// 处理精度赋值
|
||
const scheme = JSON.parse(JSON.stringify(currentScheme.value));
|
||
|
||
// 遍历propertyParam,匹配精度并赋值
|
||
scheme.propertyParam.forEach(param => {
|
||
// 遍历accuracyType,查找匹配的精度类型
|
||
Object.entries(accuracyType.value).forEach(([key, value]) => {
|
||
// 使用正则匹配参数名称
|
||
if (param.name.includes(value)) {
|
||
// 为每个参数设置bigRange和smallRange
|
||
param.errorRange = {
|
||
bigRange: scheme.errorRange.range[key],
|
||
smallRange: scheme.errorRange.smallRange[key]
|
||
};
|
||
}
|
||
});
|
||
});
|
||
console.log('提交方案', scheme.propertyParam);
|
||
const isEdit = scheme.id !== undefined;
|
||
const url = isEdit ? '/master/scheme/update' : '/master/scheme/add';
|
||
let response;
|
||
|
||
if (isEdit) {
|
||
response = await axios.put(config.url + url, scheme);
|
||
} else {
|
||
response = await axios.post(config.url + url, scheme);
|
||
}
|
||
|
||
if (response.data.code === 0) {
|
||
ElMessage.success(isEdit ? '修改成功' : '添加成功');
|
||
schemeDetailVisible.value = false;
|
||
loadSchemes(); // 重新加载方案列表
|
||
} else {
|
||
ElMessage.error(response.data.message || (isEdit ? '修改失败' : '添加失败'));
|
||
}
|
||
} catch (error) {
|
||
console.error('保存方案失败:', error);
|
||
ElMessage.error('保存方案失败');
|
||
}
|
||
};
|
||
|
||
// 删除方案
|
||
const deleteScheme = scheme => {
|
||
ElMessageBox.confirm('确定要删除该方案吗?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}).then(async () => {
|
||
try {
|
||
const response = await axios.delete(config.url + '/master/scheme/delete?id=' + scheme.id);
|
||
|
||
if (response.data.code === 0) {
|
||
ElMessage.success('删除成功');
|
||
loadSchemes(); // 重新加载方案列表
|
||
} else {
|
||
ElMessage.error(response.data.message || '删除失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('删除方案失败:', error);
|
||
ElMessage.error('删除方案失败');
|
||
}
|
||
});
|
||
};
|
||
|
||
// 添加新方案
|
||
const addScheme = () => {
|
||
currentScheme.value = {
|
||
schemeName: `方案${schemeList.value.length + 1}`,
|
||
schemeType: '1P',
|
||
series: 'B7',
|
||
framework: '100ZS',
|
||
middleDetect: true,
|
||
laterDetect: false,
|
||
configParam: defaultConfList,
|
||
propertyParam: defaultPropList,
|
||
errorRange: {
|
||
range: {
|
||
voltage: 0.5,
|
||
current: 0.5,
|
||
powerFactor: 0.5,
|
||
activePower: 0.5,
|
||
reactivePower: 0.5,
|
||
apparentPower: 0.5
|
||
},
|
||
smallRange: {
|
||
voltage: 1,
|
||
current: 1,
|
||
powerFactor: 1,
|
||
activePower: 1,
|
||
reactivePower: 1,
|
||
apparentPower: 1
|
||
}
|
||
},
|
||
steps: [],
|
||
laterSteps: [
|
||
{
|
||
id: 1,
|
||
step: 1,
|
||
voltage: 110,
|
||
current: 110,
|
||
powerFactor: '0.5L',
|
||
isILeak: false
|
||
},
|
||
{
|
||
id: 2,
|
||
step: 2,
|
||
voltage: 80,
|
||
current: 80,
|
||
powerFactor: '0.5L',
|
||
isILeak: false
|
||
},
|
||
{
|
||
id: 3,
|
||
step: 3,
|
||
voltage: 100,
|
||
current: 80,
|
||
powerFactor: '0.5L',
|
||
isILeak: true
|
||
}
|
||
],
|
||
iLeakDetectObj: defaultiLeakDetectObj
|
||
};
|
||
isEditMode.value = false;
|
||
schemeDetailVisible.value = true;
|
||
};
|
||
|
||
// 修改方案
|
||
const viewScheme = scheme => {
|
||
currentScheme.value = JSON.parse(JSON.stringify(scheme));
|
||
isEditMode.value = true;
|
||
schemeDetailVisible.value = true;
|
||
};
|
||
|
||
// // 监听断路器类型变化
|
||
// const handleTypeChange = type => {
|
||
// currentScheme.value.propertyParam = type === '1p2p' ? JSON.parse(JSON.stringify(swich1p2pAttrList)) : JSON.parse(JSON.stringify(swich3p4pAttrList));
|
||
// };
|
||
|
||
// 添加标定步骤
|
||
const addCalibrationStep = () => {
|
||
currentScheme.value.steps.push({
|
||
id: currentScheme.value.steps.length + 1,
|
||
step: currentScheme.value.steps.length + 1,
|
||
voltage: 100,
|
||
current: 100,
|
||
powerFactor: '0.5L',
|
||
isILeak: false
|
||
});
|
||
};
|
||
|
||
// 添加标定步骤
|
||
const addCalibrationLaterStep = () => {
|
||
currentScheme.value.laterSteps.push({
|
||
id: currentScheme.value.laterSteps.length + 1,
|
||
step: currentScheme.value.laterSteps.length + 1,
|
||
voltage: 100,
|
||
current: 100,
|
||
powerFactor: '0.5L',
|
||
isILeak: false
|
||
});
|
||
};
|
||
|
||
const toggleOpenLog = () => {
|
||
isOpenLog.value = !isOpenLog.value;
|
||
};
|
||
|
||
const clearLog = () => {
|
||
logs.value = [];
|
||
};
|
||
|
||
// 搜索相关函数
|
||
const searchInputRef = ref(null);
|
||
|
||
const openSearch = () => {
|
||
if (isSearchActive.value) {
|
||
closeSearch();
|
||
} else {
|
||
isSearchActive.value = true;
|
||
searchKeyword.value = '';
|
||
searchResults.value = [];
|
||
currentSearchIndex.value = 0;
|
||
totalSearchCount.value = 0;
|
||
|
||
// 聚焦到搜索输入框
|
||
setTimeout(() => {
|
||
if (searchInputRef.value) {
|
||
searchInputRef.value.focus();
|
||
}
|
||
}, 100);
|
||
}
|
||
};
|
||
|
||
const closeSearch = () => {
|
||
isSearchActive.value = false;
|
||
searchKeyword.value = '';
|
||
searchResults.value = [];
|
||
currentSearchIndex.value = 0;
|
||
totalSearchCount.value = 0;
|
||
};
|
||
|
||
const performSearch = () => {
|
||
if (!searchKeyword.value.trim()) {
|
||
searchResults.value = [];
|
||
currentSearchIndex.value = 0;
|
||
totalSearchCount.value = 0;
|
||
return;
|
||
}
|
||
|
||
const keyword = searchKeyword.value.toLowerCase();
|
||
const results = [];
|
||
|
||
logs.value.forEach((log, index) => {
|
||
const logText = `${log.time} ${log.msg}`.toLowerCase();
|
||
if (logText.includes(keyword)) {
|
||
results.push({
|
||
originalIndex: index,
|
||
time: log.time,
|
||
msg: log.msg,
|
||
type: log.type
|
||
});
|
||
}
|
||
});
|
||
|
||
searchResults.value = results;
|
||
currentSearchIndex.value = results.length > 0 ? 0 : -1;
|
||
totalSearchCount.value = results.length;
|
||
|
||
// 如果有搜索结果,滚动到第一个结果
|
||
if (results.length > 0) {
|
||
scrollToSearchResult(results[0].originalIndex);
|
||
}
|
||
};
|
||
|
||
const getHighlightedText = item => {
|
||
if (!searchKeyword.value.trim()) {
|
||
return `${item.time} ${item.msg}`;
|
||
}
|
||
|
||
const text = `${item.time} ${item.msg}`;
|
||
const keyword = searchKeyword.value;
|
||
const regex = new RegExp(`(${keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
|
||
return text.replace(regex, '<span class="search-highlight-text">$1</span>');
|
||
};
|
||
|
||
const navigateSearch = direction => {
|
||
if (searchResults.value.length === 0) return;
|
||
|
||
if (direction === 'next') {
|
||
currentSearchIndex.value = (currentSearchIndex.value + 1) % searchResults.value.length;
|
||
} else if (direction === 'prev') {
|
||
currentSearchIndex.value = currentSearchIndex.value === 0 ? searchResults.value.length - 1 : currentSearchIndex.value - 1;
|
||
}
|
||
|
||
// 滚动到当前搜索结果
|
||
const currentResult = searchResults.value[currentSearchIndex.value];
|
||
if (currentResult) {
|
||
scrollToSearchResult(currentResult.originalIndex);
|
||
}
|
||
};
|
||
|
||
const scrollToSearchResult = index => {
|
||
setTimeout(() => {
|
||
const logElements = document.querySelectorAll('.log-item');
|
||
if (logElements[index]) {
|
||
logElements[index].scrollIntoView({
|
||
behavior: 'smooth',
|
||
block: 'center'
|
||
});
|
||
}
|
||
}, 100);
|
||
};
|
||
|
||
// 删除标定步骤
|
||
const deleteCalibrationStep = index => {
|
||
currentScheme.value.steps.splice(index, 1);
|
||
// 重新编号
|
||
currentScheme.value.steps.forEach((step, idx) => {
|
||
step.id = idx + 1;
|
||
});
|
||
};
|
||
|
||
// 删除标定步骤
|
||
const deleteCalibrationLaterStep = index => {
|
||
currentScheme.value.laterSteps.splice(index, 1);
|
||
// 重新编号
|
||
currentScheme.value.laterSteps.forEach((step, idx) => {
|
||
step.id = idx + 1;
|
||
});
|
||
};
|
||
const generateDeviceList = async () => {
|
||
try {
|
||
const response = await axios.get(config.url + '/master/device/list');
|
||
if (response.data.code === 0) {
|
||
devices.value = response.data.data.result || [];
|
||
//测试输出结果
|
||
// setDeviceInfo("{\"id\":1,\"devicePort\":\"com3\",\"cpuId\":\"SN\",\"connectStatus\":1,\"switch\":0,\"result\":-1,\"resultTxt\":\"\"}")
|
||
} else {
|
||
ElMessage.error(response.data.message || '刷新列表失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('刷新列表失败:', error);
|
||
ElMessage.error('刷新列表失败');
|
||
}
|
||
};
|
||
|
||
const playCalibration = async () => {
|
||
try {
|
||
const response = await axios.get(config.url + '/master/scheme/start?schemeId=' + selectedScheme.value + '&operatorCode=' + operatorCode.value);
|
||
if (response.data.code === 0) {
|
||
// devices.value = response.data.data.result || [];
|
||
} else {
|
||
ElMessageBox.alert(response.data.message, '告警', {
|
||
// if you want to disable its autofocus
|
||
// autofocus: false,
|
||
dangerouslyUseHTMLString: true,
|
||
confirmButtonText: '确定',
|
||
confirmButtonClass: 'custom-message-box',
|
||
callback: action => {}
|
||
});
|
||
playState.value = false;
|
||
// ElMessage.error(response.data.message || '开始执行失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('获取设备列表失败:', error);
|
||
|
||
// ElMessage.error('开始执行失败');
|
||
}
|
||
};
|
||
|
||
const copyScheme = scheme => {
|
||
const newScheme = JSON.parse(JSON.stringify(scheme));
|
||
delete newScheme.id;
|
||
if (typeof newScheme.schemeName === 'string') {
|
||
newScheme.schemeName = newScheme.schemeName + '_1';
|
||
}
|
||
currentScheme.value = newScheme;
|
||
isEditMode.value = false;
|
||
schemeDetailVisible.value = true;
|
||
};
|
||
|
||
const importInputRef = ref(null);
|
||
|
||
const exportAllSchemes = () => {
|
||
if (!schemeList.value.length) {
|
||
ElMessage.warning('暂无可导出的方案');
|
||
return;
|
||
}
|
||
const exportArr = schemeList.value.map(sch => {
|
||
const obj = JSON.parse(JSON.stringify(sch));
|
||
obj.id = '';
|
||
return obj;
|
||
});
|
||
const blob = new Blob([JSON.stringify(exportArr, null, 2)], { type: 'application/json' });
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = '谷云校表方案' + dayjs().format('YYYY-MM-DD HH:mm:ss') + '.json';
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
document.body.removeChild(a);
|
||
URL.revokeObjectURL(url);
|
||
ElMessage.success('导出成功');
|
||
};
|
||
|
||
const importSchemes = () => {
|
||
if (importInputRef.value) {
|
||
importInputRef.value.value = '';
|
||
importInputRef.value.click();
|
||
}
|
||
};
|
||
|
||
const handleImportFile = async e => {
|
||
const file = e.target.files[0];
|
||
if (!file) return;
|
||
const reader = new FileReader();
|
||
reader.onload = async evt => {
|
||
try {
|
||
const arr = JSON.parse(evt.target.result);
|
||
if (!Array.isArray(arr)) {
|
||
ElMessage.error('导入文件格式错误,应为方案数组');
|
||
return;
|
||
}
|
||
let successCount = 0,
|
||
failCount = 0;
|
||
const duplicateNames = [];
|
||
for (const sch of arr) {
|
||
const newSch = JSON.parse(JSON.stringify(sch));
|
||
delete newSch.id;
|
||
try {
|
||
const res = await axios.post(config.url + '/master/scheme/add', newSch);
|
||
if (res.data.code === 0) successCount++;
|
||
else {
|
||
failCount++;
|
||
if (res.data.code === 50 && res.data.message && res.data.message.includes('方案名称重复')) {
|
||
duplicateNames.push(newSch.schemeName);
|
||
}
|
||
}
|
||
} catch (err) {
|
||
failCount++;
|
||
}
|
||
}
|
||
let msg = `导入完成,成功${successCount}条,失败${failCount}条`;
|
||
if (duplicateNames.length) {
|
||
msg += `\n名称重复:${duplicateNames.join(',')}`;
|
||
}
|
||
ElMessage({ message: msg, type: duplicateNames.length ? 'error' : 'success', duration: 8000 });
|
||
loadSchemes();
|
||
} catch {
|
||
ElMessage.error('导入文件解析失败');
|
||
}
|
||
};
|
||
reader.readAsText(file);
|
||
};
|
||
|
||
const schemeTableRef = ref(null);
|
||
const selectedSchemes = ref([]);
|
||
const isEditMode = ref(false);
|
||
|
||
const handleSelectionChange = val => {
|
||
selectedSchemes.value = val;
|
||
};
|
||
|
||
const exportSelectedSchemes = () => {
|
||
const exportArr = selectedSchemes.value.length ? selectedSchemes.value : [];
|
||
if (!exportArr.length) {
|
||
ElMessage.warning('请先选择要导出的方案');
|
||
return;
|
||
}
|
||
const arr = exportArr.map(sch => {
|
||
const obj = JSON.parse(JSON.stringify(sch));
|
||
obj.id = '';
|
||
return obj;
|
||
});
|
||
const blob = new Blob([JSON.stringify(arr, null, 2)], { type: 'application/json' });
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = '谷云校表方案' + dayjs().format('YYYY-MM-DD HH:mm:ss') + '.json';
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
document.body.removeChild(a);
|
||
URL.revokeObjectURL(url);
|
||
ElMessage.success('导出成功');
|
||
};
|
||
|
||
const showExportProdDialog = ref(false);
|
||
const exportProdRange = ref([]);
|
||
const exportProdPickerOptions = [
|
||
{
|
||
text: '本日',
|
||
value: () => {
|
||
const end = new Date();
|
||
const start = new Date();
|
||
// start.setTime(start.getTime() - 3600 * 1000 * 24);
|
||
return [start, end];
|
||
}
|
||
},
|
||
{
|
||
text: '最近一周',
|
||
value: () => {
|
||
const end = new Date();
|
||
const start = new Date();
|
||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
|
||
return [start, end];
|
||
}
|
||
},
|
||
{
|
||
text: '最近一个月',
|
||
value: () => {
|
||
const end = new Date();
|
||
const start = new Date();
|
||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
|
||
return [start, end];
|
||
}
|
||
},
|
||
{
|
||
text: '最近三个月',
|
||
value: () => {
|
||
const end = new Date();
|
||
const start = new Date();
|
||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
|
||
return [start, end];
|
||
}
|
||
}
|
||
];
|
||
|
||
const handleExportProd = async () => {
|
||
console.log(window.electron);
|
||
if (!exportProdRange.value || exportProdRange.value.length !== 2) {
|
||
ElMessage.warning('请选择导出时间范围');
|
||
return;
|
||
}
|
||
const [start, end] = exportProdRange.value;
|
||
// 补全时间
|
||
const beginTime = new Date(dayjs(start).format('YYYY-MM-DD') + ' 00:00:00').getTime();
|
||
const endTime = new Date(dayjs(end).format('YYYY-MM-DD') + ' 23:59:59').getTime();
|
||
try {
|
||
const response = await axios.get(config.url + `/master/calibrate/resultExport?beginTime=${beginTime}&endTime=${endTime}`);
|
||
if (response.data.code === 0) {
|
||
ElMessage.success('生成成功');
|
||
// 打开exePath/export目录
|
||
if (window.electron && window.electron.shell && window.electron.exePath) {
|
||
window.electron.shell.openPath(window.electron.exePath + '/export');
|
||
} else {
|
||
ElMessage.info('请手动前往导出目录"软件安装目录/resources/service/export"');
|
||
}
|
||
} else {
|
||
ElMessage.error(response.data.message || '导出失败');
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error('导出失败');
|
||
}
|
||
showExportProdDialog.value = false;
|
||
};
|
||
|
||
const getQquipmentStatistics = () => {
|
||
axios
|
||
.get(config.url + '/master/calibrate/stat')
|
||
.then(response => {
|
||
if (response.data.code === 0 && response.data.data.state) {
|
||
equipmentStatistics.value = response.data.data.state;
|
||
} else {
|
||
ElMessage.error(response.data.message);
|
||
}
|
||
})
|
||
.catch(error => {
|
||
ElMessage.error(error);
|
||
});
|
||
};
|
||
|
||
// 监听日志变化滚到到底部
|
||
watch(
|
||
logs,
|
||
() => {
|
||
if (isScroll.value) {
|
||
nextTick(() => {
|
||
throttledScroll();
|
||
});
|
||
}
|
||
},
|
||
{
|
||
deep: true
|
||
}
|
||
);
|
||
|
||
onMounted(() => {
|
||
logBoxRef.value = document.querySelector('#log-box-main');
|
||
initSocket();
|
||
if (window.electron && typeof window.electron.getAppVersion === 'function') {
|
||
window.electron.getAppVersion().then(v => {
|
||
version.value = v;
|
||
});
|
||
}
|
||
|
||
// generateMockDevices();
|
||
getSchemeDefaultConf();
|
||
getSchemeDefaultProp();
|
||
generateDeviceList();
|
||
loadSchemes();
|
||
getQquipmentStatistics();
|
||
|
||
// 添加键盘快捷键监听
|
||
document.addEventListener('keydown', handleKeydown);
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
if (webSocketStore) {
|
||
webSocketStore.close();
|
||
}
|
||
// 移除键盘快捷键监听
|
||
document.removeEventListener('keydown', handleKeydown);
|
||
});
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.calibration-page {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100vh;
|
||
background-color: #f0f2f5;
|
||
}
|
||
:deep(.asterisk-left.el-form-item) {
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.calibration-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 0 10px;
|
||
height: 60px;
|
||
background-color: #404040; /* Dark grey similar to image */
|
||
color: white;
|
||
flex-shrink: 0;
|
||
}
|
||
.scheme-list {
|
||
background: #ddd;
|
||
padding: 6px;
|
||
font-size: 13px;
|
||
display: flex;
|
||
align-items: center;
|
||
user-select: text;
|
||
&.list-two {
|
||
// justify-content: space-between;
|
||
}
|
||
.scheme-item {
|
||
margin-left: 10px;
|
||
width: 14%;
|
||
&:first-child {
|
||
margin-left: 0;
|
||
}
|
||
.scheme-value {
|
||
font-weight: bold;
|
||
}
|
||
}
|
||
&.list-one {
|
||
padding-bottom: 0;
|
||
.scheme-item {
|
||
// margin-right: 55px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.header-left,
|
||
.header-center,
|
||
.header-right {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.header-center .el-button {
|
||
margin-left: 10px;
|
||
}
|
||
.statistics-box {
|
||
margin-left: 10px;
|
||
font-size: 13px;
|
||
.statistics-number {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
}
|
||
}
|
||
|
||
.calibration-main {
|
||
flex-grow: 1;
|
||
padding: 20px 10px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.device-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(8, 1fr);
|
||
gap: 15px;
|
||
}
|
||
|
||
.device-card {
|
||
cursor: pointer;
|
||
/* background-color: #ffffff; */ /* Default card color is fine */
|
||
border: 1px solid #e6e6e6;
|
||
&.active {
|
||
border: 1px solid #67c23a;
|
||
}
|
||
}
|
||
|
||
.device-card-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
font-size: 14px;
|
||
}
|
||
|
||
@keyframes dot-animation {
|
||
0% {
|
||
content: '.';
|
||
}
|
||
33% {
|
||
content: '..';
|
||
}
|
||
66% {
|
||
content: '...';
|
||
}
|
||
100% {
|
||
content: '.';
|
||
}
|
||
}
|
||
|
||
.device-card-body {
|
||
font-size: 12px;
|
||
text-align: center;
|
||
position: relative;
|
||
.process-status {
|
||
position: absolute;
|
||
top: -6px;
|
||
left: 0;
|
||
width: 100%;
|
||
&.color-orange {
|
||
&::after {
|
||
content: '...';
|
||
animation: dot-animation 1.5s infinite;
|
||
}
|
||
}
|
||
}
|
||
.device-result {
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
}
|
||
}
|
||
|
||
.circuit-breaker {
|
||
font-size: 70px;
|
||
color: #999;
|
||
}
|
||
|
||
.upsideDown {
|
||
transform: scaleY(-1);
|
||
}
|
||
|
||
.color-green {
|
||
color: #67c23a;
|
||
/* Green for connected */
|
||
}
|
||
|
||
.color-orange {
|
||
color: #e6a23c;
|
||
/* Green for connected */
|
||
}
|
||
|
||
.calibration-footer {
|
||
height: 200px; /* Fixed height for logs */
|
||
background-color: #303030; /* Darker grey for logs */
|
||
color: #a7a7a7; /* Light grey text for logs */
|
||
padding: 10px 20px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex-shrink: 0;
|
||
position: relative;
|
||
&.open-log {
|
||
height: 350px;
|
||
}
|
||
}
|
||
|
||
.log-output {
|
||
flex-grow: 1;
|
||
overflow-y: auto;
|
||
font-family: 'Courier New', Courier, monospace;
|
||
font-size: 12px;
|
||
white-space: pre-wrap;
|
||
/* Ensures logs wrap and preserve formatting */
|
||
border: 1px solid #555;
|
||
padding: 5px;
|
||
background-color: #202020;
|
||
}
|
||
|
||
.log-output pre {
|
||
margin: 2px 0;
|
||
color: #dcdcdc;
|
||
}
|
||
|
||
.log-box-main {
|
||
font-family: 'Courier New', Courier, monospace;
|
||
font-size: 10px;
|
||
white-space: pre-wrap;
|
||
flex-grow: 1;
|
||
overflow-y: auto;
|
||
border: 1px solid #555;
|
||
background-color: #202020;
|
||
color: #dcdcdc;
|
||
scroll-behavior: smooth;
|
||
.log-list {
|
||
// padding: 5px;
|
||
.log-item {
|
||
padding: 0 5px;
|
||
word-wrap: break-word;
|
||
word-break: break-all;
|
||
font-size: 12px;
|
||
line-height: 18px;
|
||
font-weight: 500;
|
||
|
||
.iconfont {
|
||
font-size: 12px;
|
||
line-height: 18px;
|
||
}
|
||
|
||
.log-item-txt {
|
||
user-select: text;
|
||
margin-left: 5px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.calibration-footer-btn {
|
||
font-size: 12px;
|
||
padding-top: 5px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
|
||
.btn-box {
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
.log-box-operate-item {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-right: 10px;
|
||
cursor: pointer;
|
||
|
||
.icon-icon_gengxin {
|
||
margin-left: 2px;
|
||
}
|
||
.log-socket-state {
|
||
color: #00a73c;
|
||
display: flex;
|
||
align-items: center;
|
||
cursor: pointer;
|
||
}
|
||
}
|
||
}
|
||
.calibration-footer-btn-right {
|
||
display: flex;
|
||
align-items: center;
|
||
/* 表格暗黑样式 */
|
||
}
|
||
}
|
||
|
||
.version-info {
|
||
margin-left: 10px;
|
||
color: #888;
|
||
}
|
||
|
||
/* Element Plus component overrides if needed */
|
||
.el-header {
|
||
--el-header-padding: 0 20px;
|
||
/* Adjust if necessary */
|
||
--el-header-height: 60px;
|
||
/* Ensure this matches fixed height */
|
||
}
|
||
|
||
.el-footer {
|
||
--el-footer-padding: 0px;
|
||
/* Adjust if necessary */
|
||
--el-footer-height: 200px;
|
||
/* Ensure this matches fixed height */
|
||
}
|
||
|
||
.el-main {
|
||
--el-main-padding: 20px;
|
||
/* Adjust if necessary */
|
||
}
|
||
|
||
.el-select .el-input__inner {
|
||
background-color: #555;
|
||
/* Darker input fields */
|
||
color: white;
|
||
border-color: #666;
|
||
}
|
||
|
||
:deep(.log-content .el-table .cell) {
|
||
padding: 0 !important;
|
||
}
|
||
|
||
:deep(.device-grid .el-card__header) {
|
||
padding: 10px 5px;
|
||
}
|
||
|
||
:deep(.device-grid .el-card__body) {
|
||
padding: 10px 5px;
|
||
}
|
||
|
||
/* Ensure select dropdowns are also styled if needed */
|
||
|
||
.scheme-drawer-content {
|
||
padding: 20px;
|
||
user-select: text;
|
||
.device-info {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
background: #ddd;
|
||
padding: 10px;
|
||
font-size: 13px;
|
||
align-items: center;
|
||
.device-info-item {
|
||
margin-right: 10px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.scheme-list-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.calibrate-params {
|
||
display: grid;
|
||
grid-template-columns: repeat(5, 1fr);
|
||
gap: 10px;
|
||
:deep(.el-form-item) {
|
||
margin-bottom: 0;
|
||
}
|
||
}
|
||
|
||
.accuracy-title {
|
||
font-weight: bold;
|
||
margin-bottom: 10px;
|
||
font-size: 14px;
|
||
color: #000;
|
||
}
|
||
|
||
.device-params,
|
||
.accuracy-params {
|
||
display: grid;
|
||
gap: 10px;
|
||
grid-template-columns: repeat(6, 1fr);
|
||
:deep(.el-form-item) {
|
||
margin-bottom: 0;
|
||
}
|
||
}
|
||
|
||
.calibration-steps {
|
||
margin-top: 20px;
|
||
|
||
.steps-header {
|
||
margin-bottom: 5px;
|
||
}
|
||
}
|
||
|
||
.unit {
|
||
margin-left: 5px;
|
||
}
|
||
|
||
:deep(.el-drawer__body) {
|
||
padding: 0;
|
||
}
|
||
|
||
:deep(.el-dialog__body) {
|
||
padding: 20px;
|
||
max-height: calc(100vh - 152px);
|
||
overflow-y: auto;
|
||
}
|
||
:deep(.el-divider--horizontal) {
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.device-params {
|
||
display: grid;
|
||
grid-template-columns: repeat(6, 1fr);
|
||
gap: 15px;
|
||
padding: 10px;
|
||
background-color: #f5f7fa;
|
||
border-radius: 4px;
|
||
|
||
.device-param-item {
|
||
padding: 8px 12px;
|
||
background-color: #fff;
|
||
border: 1px solid #e4e7ed;
|
||
border-radius: 4px;
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
.param-label {
|
||
font-size: 13px;
|
||
color: #606266;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 浮窗搜索样式
|
||
.floating-search-bar {
|
||
position: absolute;
|
||
top: 20px;
|
||
right: 30px;
|
||
z-index: 1000;
|
||
display: flex;
|
||
align-items: center;
|
||
// padding: 8px;
|
||
// background-color: #2a2a2a;
|
||
// border: 1px solid #444;
|
||
border-radius: 6px;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||
gap: 8px;
|
||
// min-width: 280px;
|
||
|
||
.search-input-wrapper {
|
||
flex: 1;
|
||
width: 150px;
|
||
}
|
||
|
||
.search-stats {
|
||
color: #ccc;
|
||
font-size: 12px;
|
||
// width: 40px;
|
||
text-align: center;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.search-navigation {
|
||
display: flex;
|
||
gap: 8px;
|
||
.el-button {
|
||
margin: 0;
|
||
width: 24px;
|
||
height: 24px;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 搜索高亮样式
|
||
.log-item {
|
||
&.search-highlight {
|
||
background-color: rgba(255, 235, 59, 0.3) !important;
|
||
}
|
||
|
||
&.search-current {
|
||
background-color: rgba(255, 235, 59, 0.6) !important;
|
||
border-left: 3px solid #ffeb3b;
|
||
}
|
||
:deep(.search-highlight-text) {
|
||
background-color: #ffeb3b !important;
|
||
color: #000;
|
||
padding: 1px 2px;
|
||
border-radius: 2px;
|
||
font-weight: bold;
|
||
}
|
||
&.finish-txt {
|
||
margin-bottom: 40px;
|
||
}
|
||
}
|
||
|
||
.dark-table {
|
||
background: #303030 !important;
|
||
color: #dcdcdc !important;
|
||
border: none !important;
|
||
font-size: 13px;
|
||
|
||
:deep(.el-table__header) {
|
||
background: #303030 !important;
|
||
color: #e0e0e0 !important;
|
||
font-weight: bold;
|
||
border-bottom: 1px solid #555555 !important;
|
||
}
|
||
:deep(tr) {
|
||
color: #e0e0e0 !important;
|
||
}
|
||
:deep(.el-table__body) {
|
||
background: #303030 !important;
|
||
}
|
||
:deep(.el-table__row) {
|
||
background: #303030 !important;
|
||
transition: background 0.2s;
|
||
}
|
||
:deep(.el-table__row:hover) {
|
||
background: #2a2a2a !important;
|
||
}
|
||
:deep(.el-table__cell) {
|
||
background: #303030 !important;
|
||
|
||
border-color: #555555 !important;
|
||
color: #dcdcdc !important;
|
||
padding: 6px 10px;
|
||
}
|
||
:deep(.el-table__empty-block) {
|
||
background: #303030 !important;
|
||
color: #888 !important;
|
||
}
|
||
:deep(.el-table__border) {
|
||
border-color: #555555 !important;
|
||
}
|
||
:deep(.el-table__inner-wrapper) {
|
||
box-shadow: none !important;
|
||
}
|
||
:deep(.el-table__inner-wrapper:before) {
|
||
background: #303030 !important;
|
||
}
|
||
}
|
||
.color-red {
|
||
color: #f56c6c !important;
|
||
/* Red for disconnected */
|
||
}
|
||
</style>
|
||
<style>
|
||
.custom-message-box.el-button--primary {
|
||
background-color: #f56c6c !important;
|
||
border-color: #f56c6c !important;
|
||
}
|
||
|
||
.custom-message-box.el-button--primary:hover {
|
||
background-color: #cc0000 !important;
|
||
border-color: #cc0000 !important;
|
||
}
|
||
</style>
|