feat(export): 实现生产数据导出功能,解决日志滚动不到底部bug,升级1.0.9

- 添加导出生产数据的接口和相关逻辑
- 新增导出时间范围选择功能
- 优化日志滚动和结果显示
- 调整标定日志结构判断逻辑
- 更新 UI 样式,增加配置密钥功能
This commit is contained in:
fhysy 2025-07-24 09:31:37 +08:00
parent 95f8374a13
commit 7c8b2477d5
4 changed files with 116 additions and 26 deletions

View File

@ -1,6 +1,6 @@
{
"name": "gy-calibration",
"version": "1.0.7",
"version": "1.0.9",
"description": "谷云开发部开发的断路器标定软件",
"main": "./out/main/index.js",
"author": "example.com",

View File

@ -16,4 +16,6 @@ startAddr: 3
#测试源电压波动比例
fluctuateRatioU: 90
#测试源电压波动比例
fluctuateRatioI: 90
fluctuateRatioI: 90
#配置密钥
secretKey: "1dd53c82958d1de56df4035d3ed9fd82"

View File

@ -2,6 +2,7 @@ import { contextBridge, ipcRenderer } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
const fs = require('fs');
const path = require('path');
const { shell } = require('electron');
// Custom APIs for renderer
// 自定义的主进程方法API
@ -36,6 +37,18 @@ function getConfigPath() {
return configPath;
}
function getExportPath() {
// __dirname 是 win-unpacked/resources/app
let configPath = path.resolve('./resources/app.asar.unpacked/resources/service');
//单独配置测试环境
if (process.env.NODE_ENV === 'development') {
configPath = path.resolve('./resources/service');
}
return configPath;
}
// Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise
@ -56,7 +69,11 @@ if (process.contextIsolated) {
return require(configPath);
}
return {};
}
},
shell: {
openPath: shell.openPath
},
exePath: getExportPath()
})
// contextBridge.exposeInMainWorld('api', api)
} catch (error) {
@ -76,7 +93,11 @@ if (process.contextIsolated) {
return require(configPath);
}
return {};
}
},
shell: {
openPath: shell.openPath
},
exePath: getExportPath()
}
// window.api = api
}

View File

@ -12,8 +12,7 @@
</div>
<div class="header-right">
<el-button icon="RefreshRight" type="primary" :disabled="playState" @click="generateDeviceList">刷新列表</el-button>
<el-button icon="Download" type="success" style="margin-left: 10px" @click="showExportProdDialog = true">导出生产数据</el-button>
<el-button icon="Setting" :disabled="playState" @click="openSettings">配置方案</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">
@ -42,7 +41,7 @@
<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
>2<sup style="margin-left: 1px; font-size: 10px">{{ item.value }}</sup></span
><span v-else class="scheme-value">{{ item.value }}</span></span
>
</template>
@ -138,7 +137,8 @@
class="log-item"
:class="{
'search-highlight': isSearchActive && searchResults.some(result => result.originalIndex === index),
'search-current': isSearchActive && searchResults[currentSearchIndex] && searchResults[currentSearchIndex].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">
@ -220,6 +220,7 @@
<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>
@ -519,7 +520,7 @@
</template>
<script setup>
import { ref, onMounted, onUnmounted, computed } from 'vue';
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';
@ -624,6 +625,10 @@ const shortcutKeyList = ref([
label: '↓',
action: '下一个日志(查询时)'
},
{
label: 'Enter',
action: '下一个日志(查询时)'
},
{
label: 'Esc',
action: '退出查询'
@ -745,7 +750,20 @@ const processLogData = apiData => {
paramMapRow[item.valueKey] = item;
});
//
const allOk = logParamColumns.value.every(col => paramMapRow[col.valueKey]?.result === 1);
// result0
// const hasFailure = logParamColumns.value.some(col => paramMapRow[col.valueKey]?.result === 0);
// if (hasFailure) return {
// step,
// paramMap: paramMapRow,
// result: 0
// };
// }
// result1
const allOk = logParamColumns.value.every(col => {
const param = paramMapRow[col.valueKey];
// result
return param?.result === undefined || param?.result === 1;
});
return {
step,
paramMap: paramMapRow,
@ -789,6 +807,8 @@ const setDeviceInfo = msg => {
type = 'calibrate_result_error';
} else if (deviceInfo.result === 1) {
type = 'calibrate_result_success';
} else {
type = 'calibrate_result_hidden';
}
}
});
@ -807,8 +827,10 @@ const getSocketMeassage = message => {
if (msg.msgType === 'calibrate_result') {
let resultType = setDeviceInfo(msg.data);
if (resultType) {
if (resultType !== 'calibrate_result_hidden') {
newLog.type = resultType;
} else {
return;
}
}
@ -821,7 +843,7 @@ const getSocketMeassage = message => {
logs.value = [...logs.value.slice(-MAX_LOG_LENGTH + 1), newLog];
//
if (isScroll.value) throttledScroll();
// if (isScroll.value) throttledScroll();
// let msg = JSON.parse(message.data);
// if (msg.msgType !== undefined) {
@ -965,18 +987,16 @@ const openSettings = () => {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputType: 'password',
inputValue: 'gy999',
inputValue: '',
inputPlaceholder: '请输入密码',
closeOnClickModal: false
// inputValidator: (value) => {
// if (!value) return '';
// if (value !== 'gy999') return '';
// return true;
// }
inputValidator: value => {
if (!value.trim()) return '请输入密码';
return true;
}
})
.then(async ({ value }) => {
try {
const response = await axios.post(config.url + '/master/scheme/auth?secret=' + value);
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;
@ -1063,7 +1083,7 @@ const loadSchemes = async () => {
const saveScheme = async () => {
// true
if (!currentScheme.value.middleDetect && !currentScheme.value.laterDetect) {
ElMessage.error('“标定中检测”和“标定后检测”必须至少选择一个');
ElMessage.error('"标定中检测"和"标定后检测"必须至少选择一个');
return;
}
try {
@ -1458,6 +1478,15 @@ const exportSelectedSchemes = () => {
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: () => {
@ -1487,16 +1516,49 @@ const exportProdPickerOptions = [
}
];
const handleExportProd = () => {
const handleExportProd = async () => {
console.log(window.electron);
if (!exportProdRange.value || exportProdRange.value.length !== 2) {
ElMessage.warning('请选择导出时间范围');
return;
}
// TODO:
// exportProdRange.value[0]exportProdRange.value[1]
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;
ElMessage.success('导出请求已发起(请补充实际导出逻辑)');
};
//
watch(
logs,
() => {
if (isScroll.value) {
nextTick(() => {
throttledScroll();
});
}
},
{
deep: true
}
);
</script>
<style scoped lang="scss">
@ -1522,10 +1584,11 @@ const handleExportProd = () => {
}
.scheme-list {
background: #ddd;
padding: 10px;
padding: 6px;
font-size: 13px;
display: flex;
align-items: center;
user-select: text;
&.list-two {
// justify-content: space-between;
}
@ -1789,6 +1852,7 @@ const handleExportProd = () => {
.scheme-drawer-content {
padding: 20px;
user-select: text;
.device-info {
display: flex;
flex-wrap: wrap;
@ -1940,6 +2004,9 @@ const handleExportProd = () => {
border-radius: 2px;
font-weight: bold;
}
&.finish-txt {
margin-bottom: 40px;
}
}
.dark-table {