feat(export): 实现生产数据导出功能,解决日志滚动不到底部bug,升级1.0.9
- 添加导出生产数据的接口和相关逻辑 - 新增导出时间范围选择功能 - 优化日志滚动和结果显示 - 调整标定日志结构判断逻辑 - 更新 UI 样式,增加配置密钥功能
This commit is contained in:
parent
95f8374a13
commit
7c8b2477d5
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "gy-calibration",
|
"name": "gy-calibration",
|
||||||
"version": "1.0.7",
|
"version": "1.0.9",
|
||||||
"description": "谷云开发部开发的断路器标定软件",
|
"description": "谷云开发部开发的断路器标定软件",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
"author": "example.com",
|
"author": "example.com",
|
||||||
|
|
|
@ -17,3 +17,5 @@ startAddr: 3
|
||||||
fluctuateRatioU: 90
|
fluctuateRatioU: 90
|
||||||
#测试源电压波动比例
|
#测试源电压波动比例
|
||||||
fluctuateRatioI: 90
|
fluctuateRatioI: 90
|
||||||
|
#配置密钥
|
||||||
|
secretKey: "1dd53c82958d1de56df4035d3ed9fd82"
|
|
@ -2,6 +2,7 @@ import { contextBridge, ipcRenderer } from 'electron'
|
||||||
import { electronAPI } from '@electron-toolkit/preload'
|
import { electronAPI } from '@electron-toolkit/preload'
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const { shell } = require('electron');
|
||||||
|
|
||||||
// Custom APIs for renderer
|
// Custom APIs for renderer
|
||||||
// 自定义的主进程方法API
|
// 自定义的主进程方法API
|
||||||
|
@ -36,6 +37,18 @@ function getConfigPath() {
|
||||||
return configPath;
|
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
|
// Use `contextBridge` APIs to expose Electron APIs to
|
||||||
// renderer only if context isolation is enabled, otherwise
|
// renderer only if context isolation is enabled, otherwise
|
||||||
|
@ -56,7 +69,11 @@ if (process.contextIsolated) {
|
||||||
return require(configPath);
|
return require(configPath);
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
},
|
||||||
|
shell: {
|
||||||
|
openPath: shell.openPath
|
||||||
|
},
|
||||||
|
exePath: getExportPath()
|
||||||
})
|
})
|
||||||
// contextBridge.exposeInMainWorld('api', api)
|
// contextBridge.exposeInMainWorld('api', api)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -76,7 +93,11 @@ if (process.contextIsolated) {
|
||||||
return require(configPath);
|
return require(configPath);
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
},
|
||||||
|
shell: {
|
||||||
|
openPath: shell.openPath
|
||||||
|
},
|
||||||
|
exePath: getExportPath()
|
||||||
}
|
}
|
||||||
// window.api = api
|
// window.api = api
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<el-button icon="RefreshRight" type="primary" :disabled="playState" @click="generateDeviceList">刷新列表</el-button>
|
<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" style="margin-left: 10px" @click="openSettings">配置</el-button>
|
||||||
<el-button icon="Setting" :disabled="playState" @click="openSettings">配置方案</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
</el-header>
|
</el-header>
|
||||||
<div v-if="activeScheme && activeScheme.series" class="scheme-list list-one">
|
<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"
|
<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 }}:
|
>{{ item.unit ? item.name + '(' + item.unit + ')' : item.name }}:
|
||||||
<span v-if="index === 6 || index === 7 || index === 8" class="scheme-value"
|
<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
|
><span v-else class="scheme-value">{{ item.value }}</span></span
|
||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
|
@ -138,7 +137,8 @@
|
||||||
class="log-item"
|
class="log-item"
|
||||||
:class="{
|
:class="{
|
||||||
'search-highlight': isSearchActive && searchResults.some(result => result.originalIndex === index),
|
'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">
|
<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="primary" @click="addScheme">新增方案</el-button>
|
||||||
<el-button type="success" @click="exportSelectedSchemes">导出方案</el-button>
|
<el-button type="success" @click="exportSelectedSchemes">导出方案</el-button>
|
||||||
<el-button type="warning" @click="importSchemes">导入方案</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" />
|
<input ref="importInputRef" type="file" accept="application/json" style="display: none" @change="handleImportFile" />
|
||||||
</div>
|
</div>
|
||||||
<el-button type="primary" @click="loadSchemes">刷新</el-button>
|
<el-button type="primary" @click="loadSchemes">刷新</el-button>
|
||||||
|
@ -519,7 +520,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<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 { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import { Search, ArrowUp, ArrowDown, Top, Bottom, Close } from '@element-plus/icons-vue';
|
import { Search, ArrowUp, ArrowDown, Top, Bottom, Close } from '@element-plus/icons-vue';
|
||||||
import { throttle } from 'lodash-es';
|
import { throttle } from 'lodash-es';
|
||||||
|
@ -624,6 +625,10 @@ const shortcutKeyList = ref([
|
||||||
label: '↓',
|
label: '↓',
|
||||||
action: '下一个日志(查询时)'
|
action: '下一个日志(查询时)'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Enter',
|
||||||
|
action: '下一个日志(查询时)'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Esc',
|
label: 'Esc',
|
||||||
action: '退出查询'
|
action: '退出查询'
|
||||||
|
@ -745,7 +750,20 @@ const processLogData = apiData => {
|
||||||
paramMapRow[item.valueKey] = item;
|
paramMapRow[item.valueKey] = item;
|
||||||
});
|
});
|
||||||
// 结果判定
|
// 结果判定
|
||||||
const allOk = logParamColumns.value.every(col => paramMapRow[col.valueKey]?.result === 1);
|
// 检查是否有任何参数的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 {
|
return {
|
||||||
step,
|
step,
|
||||||
paramMap: paramMapRow,
|
paramMap: paramMapRow,
|
||||||
|
@ -789,6 +807,8 @@ const setDeviceInfo = msg => {
|
||||||
type = 'calibrate_result_error';
|
type = 'calibrate_result_error';
|
||||||
} else if (deviceInfo.result === 1) {
|
} else if (deviceInfo.result === 1) {
|
||||||
type = 'calibrate_result_success';
|
type = 'calibrate_result_success';
|
||||||
|
} else {
|
||||||
|
type = 'calibrate_result_hidden';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -807,8 +827,10 @@ const getSocketMeassage = message => {
|
||||||
|
|
||||||
if (msg.msgType === 'calibrate_result') {
|
if (msg.msgType === 'calibrate_result') {
|
||||||
let resultType = setDeviceInfo(msg.data);
|
let resultType = setDeviceInfo(msg.data);
|
||||||
if (resultType) {
|
if (resultType !== 'calibrate_result_hidden') {
|
||||||
newLog.type = resultType;
|
newLog.type = resultType;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -821,7 +843,7 @@ const getSocketMeassage = message => {
|
||||||
logs.value = [...logs.value.slice(-MAX_LOG_LENGTH + 1), newLog];
|
logs.value = [...logs.value.slice(-MAX_LOG_LENGTH + 1), newLog];
|
||||||
|
|
||||||
// 按需触发滚动
|
// 按需触发滚动
|
||||||
if (isScroll.value) throttledScroll();
|
// if (isScroll.value) throttledScroll();
|
||||||
|
|
||||||
// let msg = JSON.parse(message.data);
|
// let msg = JSON.parse(message.data);
|
||||||
// if (msg.msgType !== undefined) {
|
// if (msg.msgType !== undefined) {
|
||||||
|
@ -965,18 +987,16 @@ const openSettings = () => {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
inputType: 'password',
|
inputType: 'password',
|
||||||
inputValue: 'gy999',
|
inputValue: '',
|
||||||
inputPlaceholder: '请输入密码',
|
inputPlaceholder: '请输入密码',
|
||||||
closeOnClickModal: false
|
inputValidator: value => {
|
||||||
// inputValidator: (value) => {
|
if (!value.trim()) return '请输入密码';
|
||||||
// if (!value) return '请输入密码';
|
return true;
|
||||||
// if (value !== 'gy999') return '密码错误';
|
}
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
})
|
})
|
||||||
.then(async ({ value }) => {
|
.then(async ({ value }) => {
|
||||||
try {
|
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.code === 0) {
|
||||||
if (response.data.data.result) {
|
if (response.data.data.result) {
|
||||||
schemeDrawerVisible.value = true;
|
schemeDrawerVisible.value = true;
|
||||||
|
@ -1063,7 +1083,7 @@ const loadSchemes = async () => {
|
||||||
const saveScheme = async () => {
|
const saveScheme = async () => {
|
||||||
// 新增校验:标定中检测和标定后检测必须至少有一个为true
|
// 新增校验:标定中检测和标定后检测必须至少有一个为true
|
||||||
if (!currentScheme.value.middleDetect && !currentScheme.value.laterDetect) {
|
if (!currentScheme.value.middleDetect && !currentScheme.value.laterDetect) {
|
||||||
ElMessage.error('“标定中检测”和“标定后检测”必须至少选择一个');
|
ElMessage.error('"标定中检测"和"标定后检测"必须至少选择一个');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -1458,6 +1478,15 @@ const exportSelectedSchemes = () => {
|
||||||
const showExportProdDialog = ref(false);
|
const showExportProdDialog = ref(false);
|
||||||
const exportProdRange = ref([]);
|
const exportProdRange = ref([]);
|
||||||
const exportProdPickerOptions = [
|
const exportProdPickerOptions = [
|
||||||
|
{
|
||||||
|
text: '本日',
|
||||||
|
value: () => {
|
||||||
|
const end = new Date();
|
||||||
|
const start = new Date();
|
||||||
|
// start.setTime(start.getTime() - 3600 * 1000 * 24);
|
||||||
|
return [start, end];
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: '最近一周',
|
text: '最近一周',
|
||||||
value: () => {
|
value: () => {
|
||||||
|
@ -1487,16 +1516,49 @@ const exportProdPickerOptions = [
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleExportProd = () => {
|
const handleExportProd = async () => {
|
||||||
|
console.log(window.electron);
|
||||||
if (!exportProdRange.value || exportProdRange.value.length !== 2) {
|
if (!exportProdRange.value || exportProdRange.value.length !== 2) {
|
||||||
ElMessage.warning('请选择导出时间范围');
|
ElMessage.warning('请选择导出时间范围');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO: 调用实际导出生产数据接口
|
const [start, end] = exportProdRange.value;
|
||||||
// exportProdRange.value[0]、exportProdRange.value[1] 为起止时间
|
// 补全时间
|
||||||
|
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;
|
showExportProdDialog.value = false;
|
||||||
ElMessage.success('导出请求已发起(请补充实际导出逻辑)');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 监听日志变化滚到到底部
|
||||||
|
watch(
|
||||||
|
logs,
|
||||||
|
() => {
|
||||||
|
if (isScroll.value) {
|
||||||
|
nextTick(() => {
|
||||||
|
throttledScroll();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
@ -1522,10 +1584,11 @@ const handleExportProd = () => {
|
||||||
}
|
}
|
||||||
.scheme-list {
|
.scheme-list {
|
||||||
background: #ddd;
|
background: #ddd;
|
||||||
padding: 10px;
|
padding: 6px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
user-select: text;
|
||||||
&.list-two {
|
&.list-two {
|
||||||
// justify-content: space-between;
|
// justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
@ -1789,6 +1852,7 @@ const handleExportProd = () => {
|
||||||
|
|
||||||
.scheme-drawer-content {
|
.scheme-drawer-content {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
user-select: text;
|
||||||
.device-info {
|
.device-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
@ -1940,6 +2004,9 @@ const handleExportProd = () => {
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
&.finish-txt {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark-table {
|
.dark-table {
|
||||||
|
|
Loading…
Reference in New Issue