Compare commits

...

24 Commits

Author SHA1 Message Date
2b5e476fdd Merge pull request 'projects/qhd-line-zhp' (#464) from projects/qhd-line-zhp into projects/qhd-line-test
Reviewed-on: #464
2025-12-12 16:57:01 +08:00
‘937886381’
f95d0473bf Merge branch 'projects/qhd-line-test' into projects/qhd-line-zhp 2025-12-12 16:51:39 +08:00
‘937886381’
25dc76dfbe 修改bug 2025-12-12 16:50:46 +08:00
cdeba3f334 Merge pull request '新增设备总览透光率产线产品监控等' (#462) from projects/qhd-line-zhp into projects/qhd-line-test
Reviewed-on: #462
2025-12-12 14:50:32 +08:00
‘937886381’
ba75500776 新增设备总览透光率产线产品监控等 2025-12-12 14:49:24 +08:00
zwq
1a1a3a9129 更新 2025-10-10 14:41:45 +08:00
e91fcab0c8 Merge pull request 'projects/qhd-line-zhp' (#443) from projects/qhd-line-zhp into projects/qhd-line-test
Reviewed-on: #443
2025-10-10 13:24:37 +08:00
‘937886381’
1a0d786774 xiugai 2025-09-11 08:40:41 +08:00
‘937886381’
1b6e6702ed xiugai 2025-08-20 15:11:27 +08:00
‘937886381’
f23e025974 xiugai 2025-08-20 15:04:09 +08:00
‘937886381’
d77bebd375 修改 2025-07-04 09:14:09 +08:00
4225ff35ae Merge pull request '删除dist' (#437) from projects/qhd-line-zjl into projects/qhd-line-test
Reviewed-on: #437
2025-06-20 16:54:04 +08:00
33fce6cb7b 删除dist 2025-06-20 16:50:17 +08:00
dd85c50077 Merge pull request 'projects/qhd-line-zjl' (#436) from projects/qhd-line-zjl into projects/qhd-line-test
Reviewed-on: #436
2025-06-20 16:43:06 +08:00
c30bc48259 mt 2025-06-20 16:42:30 +08:00
92b0b78c9d 现场看板设备部分 2025-06-20 16:31:22 +08:00
9fec50eccb Merge pull request 'projects/qhd-line-zhp' (#435) from projects/qhd-line-zhp into projects/qhd-line-test
Reviewed-on: #435
2025-06-20 16:23:27 +08:00
‘937886381’
a7406e7867 修改产线图 2025-06-19 11:10:53 +08:00
‘937886381’
f3d88638f5 修改产线图 2025-06-19 11:05:02 +08:00
acff3b68be Merge pull request 'projects/qhd-line-zjl' (#430) from projects/qhd-line-zjl into projects/qhd-line-test
Reviewed-on: #430
2025-04-25 11:01:08 +08:00
f24b769305 生产看板视频修改 2025-04-25 10:59:36 +08:00
446fa9a0d1 生产监控看板 2025-04-25 10:33:07 +08:00
8d741b8d8a Merge pull request '看板页面' (#429) from projects/qhd-line-zjl into projects/qhd-line-test
Reviewed-on: #429
2025-04-17 16:32:09 +08:00
2c1e1845f4 看板页面 2025-04-17 16:23:47 +08:00
99 changed files with 20345 additions and 2450 deletions

View File

@@ -12,8 +12,9 @@ ENV = 'development'
VUE_APP_TITLE = 智能监控分析系统 VUE_APP_TITLE = 智能监控分析系统
# 芋道管理系统/开发环境 # 芋道管理系统/开发环境
# VUE_APP_BASE_API = 'http://192.168.8.22:48080' VUE_APP_BASE_API = 'http://172.16.32.79:48082'
VUE_APP_BASE_API = 'http://172.16.32.40:48080' # VUE_APP_BASE_API = 'http://line.kszny.picaiba.com'
# 路由懒加载 # 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true VUE_CLI_BABEL_TRANSPILE_MODULES = true
@@ -22,7 +23,7 @@ VUE_CLI_BABEL_TRANSPILE_MODULES = true
VUE_APP_TENANT_ENABLE = true VUE_APP_TENANT_ENABLE = true
# 验证码的开关 # 验证码的开关
VUE_APP_CAPTCHA_ENABLE = true VUE_APP_CAPTCHA_ENABLE = true
# 文档的开关 # 文档的开关
VUE_APP_DOC_ENABLE = true VUE_APP_DOC_ENABLE = true

BIN
dist.zip Normal file

Binary file not shown.

View File

@@ -52,6 +52,7 @@
"crypto-js": "^4.0.0", "crypto-js": "^4.0.0",
"diagram-js": "^12.3.0", "diagram-js": "^12.3.0",
"echarts": "5.4.0", "echarts": "5.4.0",
"el-tree-transfer": "^2.4.7",
"element-ui": "2.15.12", "element-ui": "2.15.12",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"fuse.js": "6.6.2", "fuse.js": "6.6.2",

Binary file not shown.

View File

@@ -59,3 +59,33 @@ export function getEquipmentAll() {
method: 'get' method: 'get'
}) })
} }
export function getTree(query) {
return request({
url: '/base/factory/getTreeSimple',
method: 'get',
params: query,
});
}
export function getEquipmentOverall(data) {
return request({
url: '/monitoring/equipment-overall/get',
method: 'post',
data: data,
});
}
export function getParamMonitor(data) {
return request({
url: '/monitoring/equipment-monitor/paramMonitor',
method: 'post',
data: data,
});
}
export function getAlarmDet(data) {
return request({
url: 'monitoring/equipment-overall/alarmDet',
method: 'post',
data: data,
});
}

View File

@@ -39,9 +39,10 @@ export function getNewCTCharts(data) {
}) })
} }
// 获取产线平衡分析数据设备listnew // 获取产线平衡分析数据设备listnew
export function getNewCTDet(id) { export function getNewCTDet(data) {
return request({ return request({
url: '/analysis/production-analysis/getNewCTDet?lineId='+id, url: '/analysis/production-analysis/getNewCTDet',
method: 'get', method: 'post',
}) data: data,
});
} }

View File

@@ -61,9 +61,9 @@ export function exportFactoryExcel(query) {
// 获取产线设备状态 // 获取产线设备状态
export function getLineEqStatus(data) { export function getLineEqStatus(data) {
return request({ return request({
url: '/base/production-line/getLineEqStatus', url: '/monitoring/equipment-monitor/getLineEqStatus',
method: 'post', method: 'post',
data: data data: data,
}) });
} }

View File

@@ -5,55 +5,109 @@
* @LastEditors: zwq * @LastEditors: zwq
* @Description: * @Description:
*/ */
import request from '@/utils/request' import request from '@/utils/request';
// 获得工厂分页 // 获得工厂分页
export function getPdlAutoReport(data) { export function getPdlAutoReport(data) {
return request({ return request({
url: '/monitoring/production-monitor/getPdlAutoReport', url: '/monitoring/production-monitor/getPdlAutoReport',
method: 'post', method: 'post',
data: data data: data,
}) });
} }
// 获得所有工厂产线列表 // 获得所有工厂产线列表
export function getPdList(id) { export function getPdList(id) {
return request({ return request({
url: '/base/production-line/listAll' + (id ? '?id=' + id : ''), url: '/base/production-line/listAll' + (id ? '?id=' + id : ''),
method: 'get' method: 'get',
}) });
} }
// 获得产线自动报表 // 获得产线自动报表
export function getLineAuto(data) { export function getLineAuto(data) {
return request({ return request({
url: '/monitoring/production-monitor/getPdlAutoReportNew', url: '/monitoring/production-monitor/getPdlAutoReportNew',
method: 'post', method: 'post',
data: data data: data,
}) });
} }
// 获得产品自动报表 // 获得产品自动报表
export function getProductAuto(data) { export function getProductAuto(data) {
return request({ return request({
url: '/monitoring/production-monitor/getProcessAutoReportNew', url: '/monitoring/production-monitor/getProcessAutoReportNew',
method: 'post', method: 'post',
data: data data: data,
}) });
}
export function getPdlAutoReportNewSearchNow(data) {
return request({
url: '/monitoring/production-monitor/getPdlAutoReportNewSearchNow',
method: 'post',
data: data,
timeout: 60000,
});
}
export function getPdlAutoReportNewSearchLastGroup(data) {
return request({
url: '/monitoring/production-monitor/getPdlAutoReportNewSearchLastGroup',
method: 'post',
data: data,
timeout: 60000,
});
} }
// 班组自动报表分页 // 班组自动报表分页
export function getTeamReportPage(data) { export function getTeamReportPage(data) {
return request({ return request({
url: '/monitoring/team-auto-report/page', url: '/monitoring/group-off-record/page',
method: 'post', method: 'post',
data: data data: data,
}) });
} }
// 班组自动报表分页详细 // 班组自动报表分页详细
export function getTeamReportPageDet(id) { export function getTeamReportPageDet(id) {
return request({ return request({
url: '/monitoring/team-auto-report/pageDet?id=' + id, url: '/monitoring/group-off-record/get?id=' + id,
method: 'get', method: 'get',
}) })
} }
export function exportGroupProductReportExcel(data) {
return request({
url: '/monitoring/group-off-record/export-det-excel',
method: 'get',
params: data,
responseType: 'blob',
});
}
// 获取产品当班数据
export function getProcessAutoReportGroup(data) {
return request({
url: '/monitoring/production-monitor/getProcessAutoReportGroup',
method: 'post',
data: data,
});
}
// 获取产品当天数据
export function getProcessAutoReportDay(data) {
return request({
url: '/monitoring/production-monitor/getProcessAutoReportDay',
method: 'post',
data: data,
});
}
// 获取产品历史数据
export function getProcessAutoReportNew(data) {
return request({
url: '/monitoring/production-monitor/getProcessAutoReportNew',
method: 'post',
data: data,
});
}

View File

@@ -14,3 +14,78 @@ export function getSectionDataSearch(data) {
data: data data: data
}) })
} }
// 获取下片日志分页数据
export function getDownLogPage(data) {
return request({
url: '/base/down-log/page',
method: 'get',
params: data,
});
}
// 获取下片日志历史数据
export function getDownLogHisData(data) {
return request({
url: '/base/down-log/pagehis',
method: 'get',
params: data,
});
}
// 导出下片日志Excel
export function exportDownLogData(query) {
return request({
url: '/base/down-log/export-excel',
method: 'get',
params: query,
responseType: 'blob',
});
}
// 获得所有工厂产线列表
export function getPdList() {
return request({
url: '/base/production-line/listAll',
method: 'get'
})
}
// 获得玻璃型号列表
export function getThick() {
return request({
url: '/base/down-log/thick',
method: 'get',
});
}
// 获得原片报表
export function getCostOriginRadioHisData(data) {
return request({
url: '/monitoring/cost-origin-ratio-his/page',
method: 'get',
params: data,
});
}
// 修改原片报表
export function editCostOriginRadioHisData(data) {
return request({
url: '/monitoring/cost-origin-ratio-his/update',
method: 'put',
data: data,
});
}
// 导出原片报表
export function exportCostOriginRadioHisData(data) {
return request({
url: '/monitoring/cost-origin-ratio-his/export-excel',
method: 'get',
params: data,
responseType: 'blob',
});
}

View File

@@ -0,0 +1,56 @@
import request from '@/utils/request'
// 创建能源监控配置
export function getDefectSummaryTable(data) {
return request({
url: '/extend/check-gaozhun-record/defectSummaryTable',
method: 'post',
data: data,
});
}
export function getTranslucentPage(data) {
return request({
url: '/monitoring/translucent/page',
method: 'get',
params: data,
});
}
export function exportTranslucent(data) {
return request({
url: '/monitoring/translucent/export-excel',
method: 'get',
params: data,
responseType: 'blob',
});
}
export function getDefectAnalysis(data) {
return request({
url: '/extend/check-gaozhun-record/defectAnalysis',
method: 'post',
data: data,
});
}
export function getSectionDefect(data) {
return request({
url: '/extend/check-gaozhun-record/sectionDefect',
method: 'post',
data: data,
});
}
export function getDefectSummaryChart(data) {
return request({
url: '/extend/check-gaozhun-record/defectSummaryChart',
method: 'post',
data: data,
});
}
export function getDefectSummaryDet(data) {
return request({
url: '/extend/check-gaozhun-record/defectSummaryDet',
method: 'post',
data: data,
});
}

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编组备份</title>
<g id="秦皇岛/北方压延" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="现场看板tc—整体产线" transform="translate(-38.175000, -126.808988)">
<g id="编组备份" transform="translate(38.175000, 126.808988)">
<polygon id="Fill-1" fill="#CAE5DD" opacity="0" points="0 32 32 32 32 0 0 0"></polygon>
<path d="M24.1201333,5.07973333 L22.1468,5.07973333 L22.1468,6.3064 L24.1201333,6.3064 C24.7308,6.3064 25.2268,6.8024 25.2268,7.41306667 L25.2268,25.4530667 C25.2268,26.0650667 24.7308,26.5597333 24.1201333,26.5597333 L7.93346667,26.5597333 C7.32146667,26.5597333 6.8268,26.0650667 6.8268,25.4530667 L6.8268,7.41306667 C6.8268,6.8024 7.32146667,6.3064 7.93346667,6.3064 L10.2001333,6.3064 L10.2001333,5.07973333 L7.93346667,5.07973333 C6.64813333,5.08773333 5.6068,6.12773333 5.60013333,7.41306667 L5.60013333,25.4530667 C5.6068,26.7384 6.64813333,27.7797333 7.93346667,27.7864 L24.1334667,27.7864 C25.4188,27.7797333 26.4588,26.7384 26.4668,25.4530667 L26.4668,7.41306667 C26.4588,6.1224 25.4108,5.07973333 24.1201333,5.07973333" id="Fill-3" fill="#6EF9DE"></path>
<path d="M7.93333333,5.56026667 C6.90933333,5.56026667 6.08,6.3896 6.08,7.4136 L6.08,25.4536 C6.08,26.4776 6.90933333,27.3069333 7.93333333,27.3069333 L24.1333333,27.3069333 C25.156,27.3069333 25.9866667,26.4776 25.9866667,25.4536 L25.9866667,7.4136 C25.9866667,6.3896 25.156,5.56026667 24.1333333,5.56026667 L22.6,5.56026667 L22.6,5.78693333 L24.0933333,5.78693333 C24.9613333,5.78693333 25.6666667,6.49093333 25.6666667,7.36026667 L25.6666667,25.4002667 C25.6666667,26.2682667 24.9613333,26.9736 24.0933333,26.9736 L7.93333333,26.9736 C7.064,26.9736 6.36,26.2682667 6.36,25.4002667 L6.36,7.4136 C6.33066667,6.54426667 7.01066667,5.81626667 7.87866667,5.78693333 C7.89733333,5.78693333 7.91466667,5.78693333 7.93333333,5.78693333 L9.73333333,5.78693333 L9.73333333,5.50693333 L7.93333333,5.56026667 Z M24.8266667,28.2536 L7.21333333,28.2536 C6.05733333,28.2536 5.12,27.3162667 5.12,26.1602667 L5.12,6.70693333 C5.12,5.55093333 6.05733333,4.6136 7.21333333,4.6136 L10.6666667,4.6136 L10.6666667,6.78693333 L8.81333333,6.78693333 C7.97333333,6.78693333 7.29333333,7.46693333 7.29333333,8.30693333 L7.29333333,24.5602667 C7.29333333,25.3989333 7.97333333,26.0802667 8.81333333,26.0802667 L23.2266667,26.0802667 C24.0666667,26.0802667 24.7466667,25.3989333 24.7466667,24.5602667 L24.7466667,8.30693333 C24.7466667,7.46693333 24.0666667,6.78693333 23.2266667,6.78693333 L21.68,6.78693333 L21.68,4.6136 L24.8266667,4.6136 C25.9826667,4.6136 26.92,5.55093333 26.92,6.70693333 L26.92,26.1602667 C26.92,27.3162667 25.9826667,28.2536 24.8266667,28.2536 L24.8266667,28.2536 Z" id="Fill-5" fill="#6EF9DE"></path>
<path d="M11.3468,3.3864 L20.9068,3.3864 C21.7534667,3.3864 22.4401333,4.07306667 22.4401333,4.91973333 L22.4401333,6.1864 C22.4401333,7.03306667 21.7534667,7.71973333 20.9068,7.71973333 L11.3468,7.71973333 C10.5001333,7.71973333 9.81346667,7.03306667 9.81346667,6.1864 L9.81346667,4.91973333 C9.81346667,4.07306667 10.5001333,3.3864 11.3468,3.3864" id="Fill-7" fill="#6EF9DE"></path>
<path d="M21.7732,11.3864 C21.7732,12.0530667 21.5065333,12.3864 20.9598667,12.3864 L13.1065333,12.3864 C12.5732,12.3864 12.2932,12.0530667 12.2932,11.3864 C12.2932,10.7197333 12.5732,10.3997333 13.1065333,10.3997333 L20.9598667,10.3997333 C21.5065333,10.3997333 21.7732,10.7330667 21.7732,11.3864" id="Fill-9" fill="#6EF9E1"></path>
<path d="M10.2666667,10.4130667 L10.28,10.4130667 C10.824,10.4130667 11.2666667,10.8557333 11.2666667,11.3997333 L11.2666667,11.4130667 C11.2666667,11.9584 10.824,12.3997333 10.28,12.3997333 L10.2666667,12.3997333 C9.72133333,12.3997333 9.28,11.9584 9.28,11.4130667 L9.28,11.3997333 C9.28,10.8557333 9.72133333,10.4130667 10.2666667,10.4130667" id="Fill-11" fill="#6DF8E1"></path>
<path d="M21.7732,16.3597333 C21.7732,17.0264 21.3998667,17.3597333 20.6398667,17.3597333 L13.4398667,17.3597333 C12.6798667,17.3597333 12.2932,17.0264 12.2932,16.3597333 C12.2932,15.6930667 12.6798667,15.3597333 13.4398667,15.3597333 L20.6398667,15.3597333 C21.3998667,15.3597333 21.7732,15.6930667 21.7732,16.3597333" id="Fill-13" fill="#6EF9E1"></path>
<path d="M10.2666667,15.4130667 L10.28,15.4130667 C10.824,15.4130667 11.2666667,15.8557333 11.2666667,16.3997333 L11.2666667,16.4130667 C11.2666667,16.9584 10.824,17.3997333 10.28,17.3997333 L10.2666667,17.3997333 C9.72133333,17.3997333 9.28,16.9584 9.28,16.4130667 L9.28,16.3997333 C9.28,15.8557333 9.72133333,15.4130667 10.2666667,15.4130667" id="Fill-15" fill="#6DF8E1"></path>
<path d="M21.7732,21.3597333 C21.7732,22.0264 21.5065333,22.3597333 20.9598667,22.3597333 L13.1065333,22.3597333 C12.5732,22.3597333 12.2932,22.0264 12.2932,21.3597333 C12.2932,20.6930667 12.5732,20.3597333 13.1065333,20.3597333 L20.9598667,20.3597333 C21.5065333,20.3597333 21.7732,20.6930667 21.7732,21.3597333" id="Fill-17" fill="#6EF9E1"></path>
<path d="M10.2666667,20.3864 L10.28,20.3864 C10.824,20.3864 11.2666667,20.8290667 11.2666667,21.3730667 L11.2666667,21.3864 C11.2666667,21.9317333 10.824,22.3730667 10.28,22.3730667 L10.2666667,22.3730667 C9.72133333,22.3730667 9.28,21.9317333 9.28,21.3864 L9.28,21.3730667 C9.28,20.8290667 9.72133333,20.3864 10.2666667,20.3864" id="Fill-19" fill="#6DF8E1"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>切片</title>
<defs>
<linearGradient x1="100%" y1="81.9999999%" x2="20.318998%" y2="18.0000001%" id="linearGradient-1">
<stop stop-color="#4BFFC8" offset="0%"></stop>
<stop stop-color="#45F2EC" offset="100%"></stop>
</linearGradient>
</defs>
<g id="秦皇岛/北方压延" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="现场看板tc—整体产线" transform="translate(-35.000000, -741.000000)" fill-rule="nonzero">
<g id="icon/可视化/柱状图" transform="translate(35.000000, 741.000000)">
<rect id="矩形" fill="#000000" opacity="0" x="0" y="0" width="32" height="32"></rect>
<path d="M6.07816026,6.07816026 L6.07816026,23.4413798 L28.4022997,23.4413798 L28.4022997,25.9218397 L3.5977003,25.9218397 L3.5977003,6.07816026 L6.07816026,6.07816026 Z M13.5195401,6.07816026 L13.5195401,22.2011498 L8.55862018,22.2011498 L8.55862018,6.07816026 L13.5195401,6.07816026 Z M20.9609199,11.0390801 L20.9609199,22.2011498 L16,22.2011498 L16,11.0390801 L20.9609199,11.0390801 Z M28.4022997,16 L28.4022997,22.2011498 L23.4413798,22.2011498 L23.4413798,16 L28.4022997,16 Z" id="形状" fill="url(#linearGradient-1)"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="27px" height="32px" viewBox="0 0 27 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>产线备份 3</title>
<defs>
<linearGradient x1="100%" y1="84.4142661%" x2="20.318998%" y2="15.5857339%" id="linearGradient-1">
<stop stop-color="#4BFFC8" offset="0%"></stop>
<stop stop-color="#45F2EC" offset="100%"></stop>
</linearGradient>
</defs>
<g id="秦皇岛/北方压延" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="现场看板tc—整体产线" transform="translate(-456.000000, -740.000000)" fill-rule="nonzero">
<g id="产线备份-3" transform="translate(456.000000, 740.000000)">
<rect id="矩形" fill="#000000" opacity="0" x="0" y="0" width="27" height="32"></rect>
<path d="M5.9634842,21.3256682 C5.8209113,21.3358884 5.67787363,21.3358884 5.53530073,21.3256682 L5.53152795,21.3256682 C4.70985419,21.2655042 3.9028068,20.8665317 3.27496659,20.1284294 C1.90834447,18.5219016 1.90834447,15.9172034 3.27496659,14.3106756 C3.5291373,14.011848 3.81269745,13.7686207 4.11471142,13.5809616 L4.11389125,13.5765264 L4.1789034,13.5420735 C4.26583759,13.4907514 4.35456706,13.4437443 4.44485723,13.4011765 L16.0336968,7.2612301 L16.0353372,7.27016472 C18.3137697,6.10911436 21.061777,6.62333696 22.9243013,8.8128968 C25.3585662,11.6745456 25.3585662,16.314119 22.9243013,19.1757678 C21.7290674,20.580881 20.1691859,21.2960361 18.6027704,21.3212973 L18.6035632,21.3256682 L5.9634842,21.3256682 Z M5.625,20 C6.86761875,20 7.875,18.8061 7.875,17.3333333 C7.875,15.8605667 6.86761875,14.6666667 5.625,14.6666667 C4.382325,14.6666667 3.375,15.8605667 3.375,17.3333333 C3.375,18.8061 4.382325,20 5.625,20 Z M18.5625,18.6666667 C20.737088,18.6666667 22.5,16.5773202 22.5,14 C22.5,11.4226798 20.737088,9.33333333 18.5625,9.33333333 C16.3878602,9.33333333 14.625,11.4226798 14.625,14 C14.625,16.5773202 16.3878602,18.6666667 18.5625,18.6666667 Z M4.92857143,22.6666667 L22.5,22.6666667 L22.5,24.8888889 C22.5,25.1343611 22.3081339,25.3333333 22.0714286,25.3333333 L4.5,25.3333333 L4.5,23.1111111 C4.5,22.8656389 4.69186607,22.6666667 4.92857143,22.6666667 Z" id="形状" fill="url(#linearGradient-1)"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>切片</title>
<defs>
<linearGradient x1="99.4683184%" y1="100%" x2="20.6346149%" y2="7.84095011e-14%" id="linearGradient-1">
<stop stop-color="#4BFFC8" offset="0%"></stop>
<stop stop-color="#45F2EC" offset="100%"></stop>
</linearGradient>
</defs>
<g id="秦皇岛/北方压延" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="现场看板tc—整体产线" transform="translate(-1514.000000, -127.000000)" fill-rule="nonzero">
<g id="icon/可视化/模块" transform="translate(1514.000000, 127.000000)">
<rect id="矩形" fill="#000000" opacity="0" x="0" y="7.10542736e-14" width="32" height="32"></rect>
<path d="M25.7527221,14.1816202 L24.188579,14.1816202 L24.188579,9.49373791 C24.1860753,8.90287145 23.7063759,8.4252046 23.1155041,8.42520991 L17.3681876,8.42520991 L17.3681876,6.56551655 C17.370851,5.66122185 17.0084214,4.79412914 16.363022,4.1607133 C15.7176226,3.52729745 14.8438903,3.18118084 13.9398043,3.20079015 C12.066652,3.28658042 10.6006443,4.84522051 10.629641,6.72011209 L10.629641,8.42520991 L4.88232453,8.42520991 C4.29145277,8.4252046 3.81175331,8.90287145 3.80921104,9.49373791 L3.80921104,13.0994515 C3.80682438,13.385621 3.9188086,13.6609038 4.12031265,13.8641155 C4.32181669,14.0673272 4.59614473,14.1816305 4.88232453,14.1816202 L5.48251897,14.1816202 C7.35572581,14.1525356 8.91367406,15.6158797 9.00184091,17.4872366 C9.01900932,18.3913465 8.67055578,19.2641187 8.03543236,19.9078018 C7.40030895,20.5514848 6.53227944,20.9115903 5.62802066,20.9065261 L4.88232453,20.9065261 C4.28968163,20.9065261 3.80921104,21.3869581 3.80921104,21.979601 L3.80921104,27.7269175 C3.80921104,28.3195604 4.28968163,28.8000021 4.88232453,28.8000021 L9.55656609,28.8000021 C9.84153405,28.8012076 10.1151815,28.6885421 10.3166861,28.4870375 C10.5181907,28.2855329 10.6308562,28.0118854 10.629641,27.7269175 L10.629641,27.1949269 C10.6006443,25.3200353 12.066652,23.7613953 13.9398043,23.675605 C14.8438903,23.6559957 15.7176226,24.0021123 16.363022,24.6355281 C17.0084214,25.268944 17.370851,26.1360367 17.3681876,27.0403314 L17.3681876,27.7269175 C17.3669784,28.0110957 17.479021,28.2840495 17.6795401,28.4854219 C17.8800592,28.6867943 18.1525348,28.8000021 18.4367156,28.8000021 L23.1155041,28.8000021 C23.708147,28.8000021 24.188579,28.3195604 24.188579,27.7269175 L24.188579,20.9065261 L25.9027707,20.9065261 C26.8070472,20.910389 27.6746078,20.5491212 28.3088772,19.9045802 C28.9431467,19.2600392 29.290432,18.3867874 29.272044,17.4826896 C29.1815049,15.6131286 27.6242468,14.1524446 25.7527221,14.1816202 Z" id="路径" fill="url(#linearGradient-1)"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="驾驶舱" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="运营总览_生产线监控驾驶舱" transform="translate(-1866.000000, -36.000000)">
<g id="编组-54" transform="translate(1866.000000, 36.000000)">
<rect id="矩形" stroke="#979797" fill="#D8D8D8" opacity="0" x="0.5" y="0.5" width="21" height="21"></rect>
<path d="M18.4001211,1 L18.574731,1.00571398 C18.8641421,1.02474925 19.1451559,1.09128771 19.4122178,1.20447947 C19.7221027,1.33395436 19.9991094,1.52058631 20.2392616,1.76073844 C20.4776764,1.99915325 20.6652172,2.27804627 20.7953442,2.58736464 C20.9313506,2.90825642 21,3.24882158 21,3.59987893 L21,3.59987893 L21,18.4001211 L20.994286,18.574731 C20.9752507,18.8641421 20.9087123,19.1451559 20.7955205,19.4122178 C20.6660456,19.7221027 20.4794137,19.9991094 20.2392616,20.2392616 C20.0008468,20.4776764 19.7219537,20.6652172 19.4126354,20.7953442 C19.0917436,20.9313506 18.7511784,21 18.4001211,21 L18.4001211,21 L3.59987893,21 L3.42526905,20.994286 C3.13585794,20.9752507 2.85484405,20.9087123 2.58778224,20.7955205 C2.2778973,20.6660456 2.00089057,20.4794137 1.76073844,20.2392616 C1.52232363,20.0008468 1.33478285,19.7219537 1.20465581,19.4126354 C1.06864936,19.0917436 1,18.7511784 1,18.4001211 L1,18.4001211 L1,3.59987893 L1.00571398,3.42526905 C1.02474925,3.13585794 1.09128771,2.85484405 1.20447947,2.58778224 C1.33395436,2.2778973 1.52058631,2.00089057 1.76073844,1.76073844 C1.99915325,1.52232363 2.27804627,1.33478285 2.58736464,1.20465581 C2.90825642,1.06864936 3.24882158,1 3.59987893,1 L3.59987893,1 L18.4001211,1 Z M18.4001211,2.29539952 L3.59987893,2.29539952 L3.49797651,2.2993263 C2.82542992,2.35136056 2.29539952,2.9140495 2.29539952,3.59987893 L2.29539952,3.59987893 L2.29539952,18.4001211 L2.2993263,18.5020235 C2.35136056,19.1745701 2.9140495,19.7046005 3.59987893,19.7046005 L3.59987893,19.7046005 L18.4001211,19.7046005 L18.5020235,19.7006737 C19.1745701,19.6486394 19.7046005,19.0859505 19.7046005,18.4001211 L19.7046005,18.4001211 L19.7046005,3.59987893 L19.7006737,3.49797651 C19.6486394,2.82542992 19.0859505,2.29539952 18.4001211,2.29539952 L18.4001211,2.29539952 Z M4.56580299,11.8731508 L4.63987359,11.8789244 C4.95620444,11.9219442 5.20096852,12.1943435 5.20096852,12.5208838 L5.20096852,12.5208838 L5.20096852,15.8606113 L8.39814764,12.6634321 L8.45892035,12.6098356 C8.71226443,12.4133149 9.08161636,12.4311804 9.3138681,12.6634321 C9.56547415,12.9150382 9.56547415,13.3275466 9.3138681,13.5791526 L9.3138681,13.5791526 L6.09149511,16.7990315 L9.4155569,16.7990315 L9.49368756,16.8035151 C9.82695229,16.8420028 10.0864105,17.123441 10.081435,17.4601165 C10.0742517,17.8119637 9.78624591,18.094431 9.43371671,18.094431 L9.43371671,18.094431 L4.5691586,18.094431 L4.49163938,18.0899766 C4.16113157,18.051728 3.90556901,17.7718105 3.90556901,17.4308414 L3.90556901,17.4308414 L3.90556901,12.5367736 L3.91007171,12.4587841 C3.94871707,12.1260764 4.23118526,11.8665247 4.56580299,11.8731508 L4.56580299,11.8731508 Z M17.4353814,3.90556901 L17.512257,3.91000958 C17.8400885,3.94813523 18.094431,4.22707005 18.094431,4.56461864 L18.094431,4.56461864 L18.094431,9.46095642 L18.0899283,9.53894589 C18.0512829,9.87165361 17.7688147,10.1312053 17.434197,10.1245792 C17.0828999,10.1173731 16.7990315,9.82850504 16.7990315,9.47684625 L16.7990315,9.47684625 L16.7990315,6.13938874 L13.6018524,9.33656786 L13.5410796,9.39016441 C13.2877356,9.58668512 12.9183836,9.5688196 12.6861319,9.33656786 C12.4345258,9.08496181 12.4345258,8.67245345 12.6861319,8.4208474 L12.6861319,8.4208474 L15.9060108,5.20096852 L12.5821731,5.20096852 L12.5040246,5.19648608 C12.170734,5.1580095 11.9119308,4.876675 11.9185506,4.54071802 C11.9257483,4.18803625 12.2137541,3.90556901 12.5662833,3.90556901 L12.5662833,3.90556901 L17.4353814,3.90556901 Z" id="形状结合" fill="#52FFF1" fill-rule="nonzero" opacity="0.79078311"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="驾驶舱" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="编组-54备份">
<rect id="矩形" stroke="#979797" fill="#D8D8D8" opacity="0" x="0.5" y="0.5" width="21" height="21"></rect>
<path d="M18.4001211,1 L18.574731,1.00571398 C18.8641421,1.02474925 19.1451559,1.09128771 19.4122178,1.20447947 C19.7221027,1.33395436 19.9991094,1.52058631 20.2392616,1.76073844 C20.4776764,1.99915325 20.6652172,2.27804627 20.7953442,2.58736464 C20.9313506,2.90825642 21,3.24882158 21,3.59987893 L21,3.59987893 L21,18.4001211 L20.994286,18.574731 C20.9752507,18.8641421 20.9087123,19.1451559 20.7955205,19.4122178 C20.6660456,19.7221027 20.4794137,19.9991094 20.2392616,20.2392616 C20.0008468,20.4776764 19.7219537,20.6652172 19.4126354,20.7953442 C19.0917436,20.9313506 18.7511784,21 18.4001211,21 L18.4001211,21 L3.59987893,21 L3.42526905,20.994286 C3.13585794,20.9752507 2.85484405,20.9087123 2.58778224,20.7955205 C2.2778973,20.6660456 2.00089057,20.4794137 1.76073844,20.2392616 C1.52232363,20.0008468 1.33478285,19.7219537 1.20465581,19.4126354 C1.06864936,19.0917436 1,18.7511784 1,18.4001211 L1,18.4001211 L1,3.59987893 L1.00571398,3.42526905 C1.02474925,3.13585794 1.09128771,2.85484405 1.20447947,2.58778224 C1.33395436,2.2778973 1.52058631,2.00089057 1.76073844,1.76073844 C1.99915325,1.52232363 2.27804627,1.33478285 2.58736464,1.20465581 C2.90825642,1.06864936 3.24882158,1 3.59987893,1 L3.59987893,1 L18.4001211,1 Z M18.4001211,2.29539952 L3.59987893,2.29539952 L3.49797651,2.2993263 C2.82542992,2.35136056 2.29539952,2.9140495 2.29539952,3.59987893 L2.29539952,3.59987893 L2.29539952,18.4001211 L2.2993263,18.5020235 C2.35136056,19.1745701 2.9140495,19.7046005 3.59987893,19.7046005 L3.59987893,19.7046005 L18.4001211,19.7046005 L18.5020235,19.7006737 C19.1745701,19.6486394 19.7046005,19.0859505 19.7046005,18.4001211 L19.7046005,18.4001211 L19.7046005,3.59987893 L19.7006737,3.49797651 C19.6486394,2.82542992 19.0859505,2.29539952 18.4001211,2.29539952 L18.4001211,2.29539952 Z M9.43538136,11.905569 L9.512257,11.9100096 C9.84008849,11.9481352 10.094431,12.2270701 10.094431,12.5646186 L10.094431,12.5646186 L10.094431,17.4609564 L10.0899283,17.5389459 C10.0512829,17.8716536 9.76881474,18.1312053 9.43419701,18.1245792 C9.08289988,18.1173731 8.79903148,17.828505 8.79903148,17.4768462 L8.79903148,17.4768462 L8.79903148,14.1393887 L5.60185236,17.3365679 L5.54107965,17.3901644 C5.28773557,17.5866851 4.91838364,17.5688196 4.6861319,17.3365679 C4.43452585,17.0849618 4.43452585,16.6724534 4.6861319,16.4208474 L4.6861319,16.4208474 L7.90601077,13.2009685 L4.58217312,13.2009685 L4.50402457,13.1964861 C4.17073404,13.1580095 3.9119308,12.876675 3.91855064,12.540718 C3.92574827,12.1880363 4.21375409,11.905569 4.56628329,11.905569 L4.56628329,11.905569 L9.43538136,11.905569 Z M12.565803,3.8731508 L12.6398736,3.87892442 C12.9562044,3.92194416 13.2009685,4.19434347 13.2009685,4.52088378 L13.2009685,4.52088378 L13.2009685,7.86061126 L16.3981476,4.66343214 L16.4589204,4.60983559 C16.7122644,4.41331488 17.0816164,4.4311804 17.3138681,4.66343214 C17.5654742,4.91503819 17.5654742,5.32754655 17.3138681,5.5791526 L17.3138681,5.5791526 L14.0914951,8.79903148 L17.4155569,8.79903148 L17.4936876,8.80351509 C17.8269523,8.84200281 18.0864105,9.12344101 18.081435,9.46011654 C18.0742517,9.81196375 17.7862459,10.094431 17.4337167,10.094431 L17.4337167,10.094431 L12.5691586,10.094431 L12.4916394,10.0899766 C12.1611316,10.051728 11.905569,9.77181051 11.905569,9.4308414 L11.905569,9.4308414 L11.905569,4.53677361 L11.9100717,4.45878413 C11.9487171,4.12607641 12.2311853,3.86652471 12.565803,3.8731508 L12.565803,3.8731508 Z" id="形状结合" fill="#52FFF1" fill-rule="nonzero" opacity="0.79078311"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@@ -54,7 +54,7 @@ export default {
}; };
}, },
created() { created() {
this.currentMenu = this.menus[0]; // this.currentMenu = this.menus[0];
}, },
watch: { watch: {
currentMenu(val) { currentMenu(val) {

View File

@@ -6,104 +6,48 @@
--> -->
<template> <template>
<el-form <el-form ref="form" :model="form" :label-width="`${labelWidth}px`" :size="size" :label-position="labelPosition"
ref="form" v-loading="formLoading">
:model="form" <el-row :gutter="20" v-for="(row, rindex) in rows" :key="rindex">
:label-width="`${labelWidth}px`" <el-col v-for="col in row" :key="col.label" :span="24 / row.length">
:size="size" <el-form-item :label="col.label" :prop="col.prop" :rules="col.rules">
:label-position="labelPosition" <el-input v-if="col.input" v-model="form[col.prop]" @change="$emit('update', form)"
v-loading="formLoading"> :placeholder="`请输入${col.label}`" v-bind="col.bind" />
<el-row :gutter="20" v-for="(row, rindex) in rows" :key="rindex"> <el-input v-if="col.textarea" type="textarea" v-model="form[col.prop]" @change="$emit('update', form)"
<el-col v-for="col in row" :key="col.label" :span="24 / row.length"> :placeholder="`请输入${col.label}`" v-bind="col.bind" />
<el-form-item :label="col.label" :prop="col.prop" :rules="col.rules"> <el-select v-if="col.select" v-model="form[col.prop]" :placeholder="`请选择${col.label}`"
<el-input @change="$emit('update', form)" v-bind="col.bind">
v-if="col.input" <el-option v-for="opt in optionListOf[col.prop]" :key="opt.value" :label="opt.label" :value="opt.value" />
v-model="form[col.prop]" </el-select>
@change="$emit('update', form)" <el-date-picker v-if="col.datetime" v-model="form[col.prop]" type="datetime" :placeholder="`请选择${col.label}`"
:placeholder="`请输入${col.label}`" value-format="timestamp" v-bind="col.bind"></el-date-picker>
v-bind="col.bind" /> <el-switch v-if="col.switch" v-model="form[col.prop]" active-color="#0b58ff" inactive-color="#e1e1e1"
<el-input v-bind="col.bind" @change="handleSwitchChange(col.prop)"></el-switch>
v-if="col.textarea" <component v-if="col.subcomponent" :key="col.key" :is="col.subcomponent" :inlineStyle="col.style"></component>
type="textarea"
v-model="form[col.prop]"
@change="$emit('update', form)"
:placeholder="`请输入${col.label}`"
v-bind="col.bind" />
<el-select
v-if="col.select"
v-model="form[col.prop]"
:placeholder="`请选择${col.label}`"
@change="$emit('update', form)"
v-bind="col.bind">
<el-option
v-for="opt in optionListOf[col.prop]"
:key="opt.value"
:label="opt.label"
:value="opt.value" />
</el-select>
<el-date-picker
v-if="col.datetime"
v-model="form[col.prop]"
type="datetime"
:placeholder="`请选择${col.label}`"
value-format="timestamp"
v-bind="col.bind"></el-date-picker>
<el-switch
v-if="col.switch"
v-model="form[col.prop]"
active-color="#0b58ff"
inactive-color="#e1e1e1"
v-bind="col.bind"></el-switch>
<component
v-if="col.subcomponent"
:key="col.key"
:is="col.subcomponent"
:inlineStyle="col.style"></component>
<div <div class="upload-area" :class="uploadOpen ? '' : 'height-48'" ref="uploadArea" v-if="col.upload">
class="upload-area" <span class="close-icon" :class="uploadOpen ? 'open' : ''">
:class="uploadOpen ? '' : 'height-48'" <el-button type="text" icon="el-icon-arrow-right" @click="handleFilesOpen" />
ref="uploadArea" </span>
v-if="col.upload"> <!-- :file-list="uploadedFileList" -->
<span class="close-icon" :class="uploadOpen ? 'open' : ''"> <el-upload class="upload-in-dialog" v-if="col.upload" :action="uploadUrl" :headers="uploadHeaders"
<el-button :show-file-list="false" icon="el-icon-upload2" :before-upload="beforeUpload"
type="text" :on-success="handleUploadSuccess" v-bind="col.bind">
icon="el-icon-arrow-right" <el-button size="mini" :disabled="col.bind?.disabled || false">
@click="handleFilesOpen" /> <svg-icon icon-class="icon-upload" style="color: inherit"></svg-icon>
</span> 上传文件
<!-- :file-list="uploadedFileList" --> </el-button>
<el-upload <div class="el-upload__tip" slot="tip" v-if="col.uploadTips">
class="upload-in-dialog" {{ col.uploadTips || '只能上传jpg/png文件, 大小不超过2MB' }}
v-if="col.upload" </div>
:action="uploadUrl" </el-upload>
:headers="uploadHeaders" <uploadedFile class="file" v-for="file in form[col.prop] || []" :file="file" :key="file.fileUrl"
:show-file-list="false" @delete="handleDeleteFile(file)" @Preview="handlePreview(file)" />
icon="el-icon-upload2" </div>
:before-upload="beforeUpload" </el-form-item>
:on-success="handleUploadSuccess" </el-col>
v-bind="col.bind"> </el-row>
<el-button size="mini" :disabled="col.bind?.disabled || false"> </el-form>
<svg-icon
icon-class="icon-upload"
style="color: inherit"></svg-icon>
上传文件
</el-button>
<div class="el-upload__tip" slot="tip" v-if="col.uploadTips">
{{ col.uploadTips || '只能上传jpg/png文件, 大小不超过2MB' }}
</div>
</el-upload>
<uploadedFile
class="file"
v-for="file in form[col.prop] || []"
:file="file"
:key="file.fileUrl"
@delete="handleDeleteFile(file)"
@Preview="handlePreview(file)" />
</div>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template> </template>
<script> <script>
@@ -115,346 +59,353 @@ import tupleImg from '@/assets/images/tuple.png';
* @param {*} options * @param {*} options
*/ */
function findMaxLabelWidth(rows) { function findMaxLabelWidth(rows) {
let max = 0; let max = 0;
rows.forEach((row) => { rows.forEach((row) => {
row.forEach((opt) => { row.forEach((opt) => {
// debugger; // debugger;
if (!opt.label) return 0; if (!opt.label) return 0;
if (opt.label.length > max) { if (opt.label.length > max) {
max = opt.label.length; max = opt.label.length;
} }
}); });
}); });
return max; return max;
} }
const uploadedFile = { const uploadedFile = {
name: 'UploadedFile', name: 'UploadedFile',
props: ['file'], props: ['file'],
data() { data() {
return {}; return {};
}, },
methods: { methods: {
handleDelete() {
this.$emit('delete', this.file); handleDelete() {
}, this.$emit('delete', this.file);
handlePreview() { },
this.$emit('Preview', this.file); handlePreview() {
}, this.$emit('Preview', this.file);
}, },
mounted() {}, },
render: function (h) { mounted() { },
return ( render: function (h) {
<div return (
title={this.file.fileName} <div
style={{ title={this.file.fileName}
background: `url(${tupleImg}) no-repeat`, style={{
backgroundSize: '14px', background: `url(${tupleImg}) no-repeat`,
backgroundPosition: '0 55%', backgroundSize: '14px',
paddingLeft: '20px', backgroundPosition: '0 55%',
paddingRight: '24px', paddingLeft: '20px',
textOverflow: 'ellipsis', paddingRight: '24px',
whiteSpace: 'nowrap', textOverflow: 'ellipsis',
overflow: 'hidden', whiteSpace: 'nowrap',
cursor: 'pointer', overflow: 'hidden',
display: 'inline-block', cursor: 'pointer',
}}> display: 'inline-block',
<el-button onClick={this.handlePreview}>{this.file.fileName}</el-button> }}>
<el-button <el-button onClick={this.handlePreview}>{this.file.fileName}</el-button>
type="text" <el-button
icon="el-icon-close" type="text"
style="float: right; position: relative; top: 2px; left: 8px; z-index: 100" icon="el-icon-close"
class="dialog__upload_component__close" style="float: right; position: relative; top: 2px; left: 8px; z-index: 100"
onClick={this.handleDelete} class="dialog__upload_component__close"
/> onClick={this.handleDelete}
</div> />
); </div>
}, );
},
}; };
export default { export default {
name: 'DialogForm', name: 'DialogForm',
model: { model: {
prop: 'dataForm', prop: 'dataForm',
event: 'update', event: 'update',
}, },
emits: ['update'], emits: ['update'],
components: { uploadedFile }, components: { uploadedFile },
props: { props: {
rows: { rows: {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
dataForm: { dataForm: {
type: Object, type: Object,
default: () => ({}), default: () => ({}),
}, },
disabled: { disabled: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
hasFile: { hasFile: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
labelPosition: { labelPosition: {
type: String, type: String,
default: 'right', default: 'right',
}, },
size: { size: {
type: String, type: String,
default: '', default: '',
}, },
}, },
data() { data() {
return { return {
uploadOpen: false, uploadOpen: false,
form: {}, form: {},
formLoading: true, formLoading: true,
optionListOf: {}, optionListOf: {},
uploadedFileList: [], uploadedFileList: [],
dataLoaded: false, dataLoaded: false,
uploadHeaders: { Authorization: 'Bearer ' + getAccessToken() }, uploadHeaders: { Authorization: 'Bearer ' + getAccessToken() },
uploadUrl: process.env.VUE_APP_BASE_API + '/admin-api/infra/file/upload', // 上传有关的headersurl都是固定的 uploadUrl: process.env.VUE_APP_BASE_API + '/admin-api/infra/file/upload', // 上传有关的headersurl都是固定的
}; };
}, },
computed: { computed: {
labelWidth() { labelWidth() {
let max = findMaxLabelWidth(this.rows); let max = findMaxLabelWidth(this.rows);
// 每个汉字占20px // 每个汉字占20px
return max * 20; return max * 20;
// return max * 20 + 'px'; // return max * 20 + 'px';
}, },
}, },
watch: { watch: {
rows: { rows: {
handler() { handler() {
this.$nextTick(() => { this.$nextTick(() => {
this.handleOptions('watch'); this.handleOptions('watch');
}); });
}, },
deep: true, deep: true,
immediate: false, immediate: false,
}, },
dataForm: { dataForm: {
handler(val) { handler(val) {
this.form = JSON.parse(JSON.stringify(val)); this.form = JSON.parse(JSON.stringify(val));
if (this.hasFile) { if (this.hasFile) {
this.form.files = this.form.files ?? []; this.form.files = this.form.files ?? [];
} }
}, },
deep: true, deep: true,
immediate: true, immediate: true,
}, },
}, },
mounted() { mounted() {
// 处理 options // 处理 options
this.handleOptions(); this.handleOptions();
}, },
methods: { methods: {
/** 模拟透传 ref */ handleSwitchChange(prop) {
validate(cb) { // 触发 update 事件,将最新的 form 数据传递给父组件
return this.$refs.form.validate(cb); this.$emit('update', { ...this.form });
}, console.log(`switch ${prop} 变化:`, this.form[prop]);
resetFields(args) { },
return this.$refs.form.resetFields(args); /** 模拟透传 ref */
}, validate(cb) {
async handlePreview(file) { return this.$refs.form.validate(cb);
const data = await this.$axios({ },
url: file.fileUrl, resetFields(args) {
method: 'get', return this.$refs.form.resetFields(args);
responseType: 'blob', },
}); async handlePreview(file) {
const link = document.createElement('a'); const data = await this.$axios({
link.href = window.URL.createObjectURL(new Blob([data])); url: file.fileUrl,
link.download = file.fileName; method: 'get',
document.body.appendChild(link); responseType: 'blob',
link.click(); });
document.body.removeChild(link); const link = document.createElement('a');
window.URL.revokeObjectURL(link.href); link.href = window.URL.createObjectURL(new Blob([data]));
}, link.download = file.fileName;
// getCode document.body.appendChild(link);
async getCode(url) { link.click();
const response = await this.$axios(url); document.body.removeChild(link);
return response.data; window.URL.revokeObjectURL(link.href);
}, },
async handleOptions(trigger = 'monuted') { // getCode
console.log('[dialogForm:handleOptions]'); async getCode(url) {
const promiseList = []; const response = await this.$axios(url);
this.rows.forEach((cols) => { return response.data;
cols.forEach((opt) => { },
if (opt.value && !this.form[opt.prop]) { async handleOptions(trigger = 'monuted') {
// 默认值 console.log('[dialogForm:handleOptions]');
this.form[opt.prop] = opt.value; const promiseList = [];
} this.rows.forEach((cols) => {
cols.forEach((opt) => {
if (opt.value && !this.form[opt.prop]) {
// 默认值
this.form[opt.prop] = opt.value;
}
if (opt.options) { if (opt.options) {
this.$set(this.optionListOf, opt.prop, opt.options); this.$set(this.optionListOf, opt.prop, opt.options);
} else if (opt.url) { } else if (opt.url) {
// 如果有 depends则暂时先不获取注册一个watcher // 如果有 depends则暂时先不获取注册一个watcher
if (opt.depends) { if (opt.depends) {
console.log('[handleOptions] setting watch'); console.log('[handleOptions] setting watch');
this.$watch( this.$watch(
() => this.form[opt.depends], () => this.form[opt.depends],
(id) => { (id) => {
console.log('<', opt.depends, '>', 'changed', id); console.log('<', opt.depends, '>', 'changed', id);
if (id == null) return; if (id == null) return;
// 清空原有选项 // 清空原有选项
this.form[opt.prop] = null; this.form[opt.prop] = null;
// 获取新的选项 // 获取新的选项
this.$axios({ this.$axios({
url: `${opt.url}?id=${id}`, url: `${opt.url}?id=${id}`,
}).then((res) => { }).then((res) => {
this.$set( this.$set(
this.optionListOf, this.optionListOf,
opt.prop, opt.prop,
res.data.map((item) => ({ res.data.map((item) => ({
label: item[opt.labelKey ?? 'name'], label: item[opt.labelKey ?? 'name'],
value: item[opt.valueKey ?? 'id'], value: item[opt.valueKey ?? 'id'],
})) }))
); );
}); });
}, },
{ {
immediate: true, immediate: true,
} }
); );
return; return;
} }
// 如果是下拉框,或者新增模式下的输入框,才去请求 // 如果是下拉框,或者新增模式下的输入框,才去请求
if (opt.select || (opt.input && !this.form?.id)) { if (opt.select || (opt.input && !this.form?.id)) {
promiseList.push(async () => { promiseList.push(async () => {
const response = await this.$axios(opt.url, { const response = await this.$axios(opt.url, {
method: opt.method ?? 'get', method: opt.method ?? 'get',
}); });
console.log('[dialogForm:handleOptions:response]', response); console.log('[dialogForm:handleOptions:response]', response);
if (opt.select) { if (opt.select) {
// 处理下拉框选项 // 处理下拉框选项
const list = const list =
'list' in response.data 'list' in response.data
? response.data.list ? response.data.list
: response.data; : response.data;
this.$set( this.$set(
this.optionListOf, this.optionListOf,
opt.prop, opt.prop,
list.map((item) => ({ list.map((item) => ({
label: item[opt.labelKey ?? 'name'], label: item[opt.labelKey ?? 'name'],
value: item[opt.valueKey ?? 'id'], value: item[opt.valueKey ?? 'id'],
})) }))
); );
} else if (opt.input) { } else if (opt.input) {
console.log('setting code: ', response.data); console.log('setting code: ', response.data);
// 处理输入框数据 // 处理输入框数据
this.form[opt.prop] = response.data; this.form[opt.prop] = response.data;
} }
}); });
} }
} }
}); });
}); });
console.log('[dialogForm:handleOptions] done!'); console.log('[dialogForm:handleOptions] done!');
// 如果是 watch 触发的,不需要执行进一步的请求 // 如果是 watch 触发的,不需要执行进一步的请求
if (trigger == 'watch') { if (trigger == 'watch') {
this.formLoading = false; this.formLoading = false;
return; return;
} }
try { try {
await Promise.all(promiseList.map((fn) => fn())); await Promise.all(promiseList.map((fn) => fn()));
this.formLoading = false; this.formLoading = false;
this.dataLoaded = true; this.dataLoaded = true;
// console.log("[dialogForm:handleOptions:optionListOf]", this.optionListOf) // console.log("[dialogForm:handleOptions:optionListOf]", this.optionListOf)
} catch (error) { } catch (error) {
console.log('[dialogForm:handleOptions:error]', error); console.log('[dialogForm:handleOptions:error]', error);
this.formLoading = false; this.formLoading = false;
} }
if (!promiseList.length) this.formLoading = false; if (!promiseList.length) this.formLoading = false;
}, },
// 上传成功的特殊处理 // 上传成功的特殊处理
beforeUpload(file) { beforeUpload(file) {
console.log(file) console.log(file)
}, },
// 上传前的验证规则可通过 bind 属性传入 // 上传前的验证规则可通过 bind 属性传入
handleUploadSuccess(response, file, fileList) { handleUploadSuccess(response, file, fileList) {
this.form.files.push({ this.form.files.push({
fileName: file.name, fileName: file.name,
fileUrl: response.data, fileUrl: response.data,
fileType: 2, fileType: 2,
}); });
this.$modal.msgSuccess('上传成功'); this.$modal.msgSuccess('上传成功');
this.$emit('update', this.form); this.$emit('update', this.form);
}, },
getFileName(fileUrl) { getFileName(fileUrl) {
return fileUrl.split('/').pop(); return fileUrl.split('/').pop();
}, },
handleFilesOpen() { handleFilesOpen() {
this.uploadOpen = !this.uploadOpen; this.uploadOpen = !this.uploadOpen;
}, },
handleDeleteFile(file) { handleDeleteFile(file) {
this.form.files = this.form.files.filter( this.form.files = this.form.files.filter(
(item) => item.fileUrl != file.fileUrl (item) => item.fileUrl != file.fileUrl
); );
this.$emit('update', this.form); this.$emit('update', this.form);
}, },
}, },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.el-date-editor, .el-date-editor,
.el-select { .el-select {
width: 100%; width: 100%;
} }
.upload-area { .upload-area {
// background: #ccc; // background: #ccc;
// display: grid; // display: grid;
// grid-auto-rows: 34px; // grid-auto-rows: 34px;
// grid-template-columns: repeat(6, minmax(32px, max-content)); // grid-template-columns: repeat(6, minmax(32px, max-content));
// gap: 8px; // gap: 8px;
// align-items: center; // align-items: center;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
transition: height 0.3s ease-out; transition: height 0.3s ease-out;
} }
.upload-in-dialog { .upload-in-dialog {
// display: inline-block; // display: inline-block;
margin-right: 24px; margin-right: 24px;
// background: #ccc; // background: #ccc;
position: relative; position: relative;
// top: -13px; // top: -13px;
float: left; float: left;
} }
.close-icon { .close-icon {
// background: #ccc; // background: #ccc;
position: absolute; position: absolute;
top: 0; top: 0;
right: 12px; right: 12px;
z-index: 100; z-index: 100;
transition: transform 0.3s ease-out; transition: transform 0.3s ease-out;
} }
.close-icon.open { .close-icon.open {
transform: rotateZ(90deg); transform: rotateZ(90deg);
} }
</style> </style>
<style> <style>
.dialog__upload_component__close { .dialog__upload_component__close {
color: #ccc; color: #ccc;
} }
.dialog__upload_component__close:hover { .dialog__upload_component__close:hover {
/* color: #777; */ /* color: #777; */
color: red; color: red;
} }
.height-48 { .height-48 {
height: 35px !important; height: 35px !important;
} }
</style> </style>

View File

@@ -91,4 +91,4 @@ export default function (dictTable) {
return function (val) { return function (val) {
return table?.[dictTable]?.[val] return table?.[dictTable]?.[val]
} }
} }

View File

@@ -21,6 +21,7 @@ import {
handleTree, handleTree,
addBeginAndEndTime, addBeginAndEndTime,
divide, divide,
formatThousands
} from '@/utils/ruoyi'; } from '@/utils/ruoyi';
import Pagination from '@/components/Pagination'; import Pagination from '@/components/Pagination';
// 自定义表格工具扩展 // 自定义表格工具扩展
@@ -45,6 +46,7 @@ Vue.prototype.resetForm = resetForm;
Vue.prototype.getDictDatas = getDictDatas; Vue.prototype.getDictDatas = getDictDatas;
Vue.prototype.getDictDatas2 = getDictDatas2; Vue.prototype.getDictDatas2 = getDictDatas2;
Vue.prototype.getDictDataLabel = getDictDataLabel; Vue.prototype.getDictDataLabel = getDictDataLabel;
Vue.prototype.formatThousands = formatThousands;
Vue.prototype.DICT_TYPE = DICT_TYPE; Vue.prototype.DICT_TYPE = DICT_TYPE;
Vue.prototype.handleTree = handleTree; Vue.prototype.handleTree = handleTree;
Vue.prototype.addBeginAndEndTime = addBeginAndEndTime; Vue.prototype.addBeginAndEndTime = addBeginAndEndTime;

View File

@@ -295,3 +295,15 @@ export function getPath(path) {
} }
return Math.floor(divisor/dividend*100)/100; return Math.floor(divisor/dividend*100)/100;
} }
// 通用千分位格式化函数
export function formatThousands(value) {
if (value === null || value === undefined) return '0'
// 清理已有逗号并转为数字
const numValue = Number(String(value).replace(/,/g, ''))
if (isNaN(numValue)) return '0'
// 支持小数处理
return numValue.toLocaleString('en-US')
}

View File

@@ -0,0 +1,116 @@
<template>
<div class='centerBottomL'>
<div class='title'>
<svg-icon icon-class="dataBoard3" class='icon'/>
<span>本月数据</span>
</div>
<div class='dataBox' style='top:50px'>
<p>
<span class='text'>总投入片数</span>
<span class='precent' :class='{precentR:monthData?.inputNumChange>=0,precentG:monthData?.inputNumChange<0}'>{{monthData?.inputNumChange || '-'}}%</span>
<img v-show='monthData?.inputNumChange<0' src="../../../../assets/images/dataBoard/arrDown.png" alt="" width='5' height='15'>
<img v-show='monthData?.inputNumChange>=0' src="../../../../assets/images/dataBoard/arrUp.png" alt="" width='5' height='15'>
</p>
<p class='num'>{{monthData?.inputNum ? formatThousands(monthData.inputNum) : '-'}}</p>
</div>
<div class='dataBox'style='top:180px'>
<p>
<span class='text'>总生产片数</span>
<span class='precent' :class='{precentR:monthData?.outputNumChange>=0,precentG:monthData?.outputNumChange<0}'>{{monthData?.outputNumChange || '-'}}%</span>
<img v-show='monthData?.outputNumChange<0' src="../../../../assets/images/dataBoard/arrDown.png" alt="" width='5' height='15'>
<img v-show='monthData?.outputNumChange>=0' src="../../../../assets/images/dataBoard/arrUp.png" alt="" width='5' height='15'>
</p>
<p class='num'>{{monthData?.outputNum ? formatThousands(monthData.outputNum) : '-'}}</p>
</div>
</div>
</template>
<script>
export default {
name: 'CenterBottomL',
props: {
dataObj: {
type: Object,
default: () => {}
}
},
watch: {
dataObj(val) {
val.monthAndLastMonth && val.monthAndLastMonth.forEach(item => {
if (item.dataType === "本月") {
this.monthData = item
}
})
}
},
data() {
return {
monthData:{}
}
},
methods: {}
}
</script>
<style lang="scss" scoped>
.centerBottomL {
width: 337px;
height: 332px;
background: url('../../../../assets/images/dataBoard/center-bottom.png') no-repeat;
background-size: 100%;
position: absolute;
left: 440px;
bottom:23px;
.title {
margin: 7px 0 0 15px;
.icon {
width: 33px;
height: 33px;
margin-right: 5px;
vertical-align:middle;
margin-top: 5px;
}
span {
font-size: 24px;
color: #52FFF1;
line-height: 24px;
vertical-align:middle;
}
}
.dataBox {
width: 290px;
height: 135px;
background: url('../../../../assets/images/dataBoard/numberBox.png') no-repeat;
background-size: 100%;
position: absolute;
padding:22px 0 0 16px;
left: 24px;
p{
margin: 0;
}
.text {
font-weight: 500;
font-size: 20px;
color: #FFFFFF;
letter-spacing: 2px;
margin-right: 8px;
}
.precent {
font-size: 18px;
margin-right: 5px;
}
.precentR {
color: #FF271D;
}
.precentG {
color: #34F716;
}
.num {
font-weight: 500;
font-size: 38px;
color: #FFFFFF;
margin-top: 5px;
}
}
}
</style>

View File

@@ -0,0 +1,100 @@
<template>
<div class='centerBottomR'>
<div class='title'>
<svg-icon icon-class="dataBoard3" class='icon'/>
<span>上月数据</span>
</div>
<div class='dataBox' style='top:50px'>
<p>
<span class='text'>总投入片数</span>
</p>
<p class='num'>{{lastMonthData?.inputNum ? formatThousands(lastMonthData.inputNum) : '-'}}</p>
</div>
<div class='dataBox'style='top:180px'>
<p>
<span class='text'>总生产片数</span>
</p>
<p class='num'>{{lastMonthData?.outputNum ? formatThousands(lastMonthData.outputNum) : '-'}}</p>
</div>
</div>
</template>
<script>
export default {
name: 'CenterBottomR',
props: {
dataObj: {
type: Object,
default: () => {}
}
},
watch: {
dataObj(val) {
val.monthAndLastMonth && val.monthAndLastMonth.forEach(item => {
if (item.dataType === "上月") {
this.lastMonthData = item
}
})
}
},
data() {
return {
lastMonthData:{}
}
},
methods: {}
}
</script>
<style lang="scss" scoped>
.centerBottomR {
width: 337px;
height: 332px;
background: url('../../../../assets/images/dataBoard/center-bottom.png') no-repeat;
background-size: 100%;
position: absolute;
left: 791px;
bottom:23px;
.title {
margin: 7px 0 0 15px;
.icon {
width: 33px;
height: 33px;
margin-right: 5px;
vertical-align:middle;
margin-top: 5px;
}
span {
font-size: 24px;
color: #52FFF1;
line-height: 24px;
vertical-align:middle;
}
}
.dataBox {
width: 290px;
height: 135px;
background: url('../../../../assets/images/dataBoard/numberBox.png') no-repeat;
background-size: 100%;
position: absolute;
padding:22px 0 0 16px;
left: 24px;
p{
margin: 0;
}
.text {
font-weight: 500;
font-size: 20px;
color: #FFFFFF;
letter-spacing: 2px;
margin-right: 8px;
}
.num {
font-weight: 500;
font-size: 38px;
color: #FFFFFF;
margin-top: 5px;
}
}
}
</style>

View File

@@ -0,0 +1,352 @@
<template>
<div ref="centerTopBox" class='centerTopBox'>
<div>
<video src="/static/videos/01.webm" muted autoplay loop class='videoStyle'></video>
<div
class='eqTipBox'
v-show='showTooltip'
:style="'left:'+tooltipStyle.left+'px;top:'+tooltipStyle.top+'px;'"
>
<p><span class='eqTipTitle'>设备名称:</span><span class='eqTipNum'>{{eqTipMsg.name}}</span></p>
<p><span class='eqTipTitle'>进口数量:</span><span class='eqTipNum'>{{eqTipMsg.input}}</span></p>
<p><span class='eqTipTitle'>出口数量:</span><span class='eqTipNum'>{{eqTipMsg.output}}</span></p>
<p><span class='eqTipTitle'>报警状态:</span>
<span class='eqTipNum'>
<img v-show='eqTipMsg.alarm' :src='dotY' width='16'/>
<img v-show='!eqTipMsg.alarm' :src='dotG' width='16'/>
{{eqTipMsg.alarm?'报警':'未报警'}}
</span>
</p>
<p><span class='eqTipTitle'>在线状态:</span>
<span class='eqTipNum'>
<img v-show='eqTipMsg.status' :src='dotG' width='16'/>
<img v-show='!eqTipMsg.status' :src='dotR' width='16'/>
{{eqTipMsg.status?'在线':'离线'}}
</span>
</p>
</div>
<!-- 设备 -->
<div class='eqBox'>
<!-- line-从上到下-从右往左 -->
<span
v-for='(item,index) in eqList'
:key='index'
:style="'width:'+item.w+'px;height:'+item.h+'px;left:'+item.l+'px;top:'+item.t+'px'"
@mouseenter="handleMouseEnter(item, $event)"
@mouseleave="handleMouseLeave"
></span>
</div>
</div>
<div class='centerTopTopBox'>
<div style='display: inline-block;'>
<img src="../../../../assets/images/dataBoard/centerNumB.png" alt="" width='203'>
<p class='num'>{{dataObj?.productRate || '-'}}%</p>
<p class='title'>成品率</p>
</div>
<div style='display: inline-block;'>
<img src="../../../../assets/images/dataBoard/centerNumY.png" alt="" width='261'>
<p class='num' style='width: 260px;padding-left: 20px;'>{{dataObj?.todayProduct ? formatThousands(dataObj.todayProduct) : '-'}}</p>
<p class='title' style='color:#FFB625'>今日产量</p>
</div>
<div style='display: inline-block;'>
<img src="../../../../assets/images/dataBoard/centerNumY.png" alt="" width='261'>
<p class='num' style='width: 260px;padding-left: 20px;'>{{dataObj?.monthProduct ? formatThousands(dataObj.monthProduct) : '-'}}</p>
<p class='title' style='color:#FFB625'>本月产量</p>
</div>
<div style='display: inline-block;'>
<img src="../../../../assets/images/dataBoard/centerNumB.png" alt="" width='203'>
<p class='num'>{{dataObj?.alarmNum ? formatThousands(dataObj.alarmNum) : '-'}}</p>
<p class='title'>设备报警数</p>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'CenterTop',
data() {
return {
showTooltip:false,
tooltipStyle: {
left: 0,
top: 0
},
dotY:require('../../../../assets/images/dataBoard/dotY.png'),
dotR:require('../../../../assets/images/dataBoard/dotR.png'),
dotG:require('../../../../assets/images/dataBoard/dotG.png'),
eqList:[
{name:'A1-磨边机-1',id:100301,w:70,h:18,l:830,t:160},
{name:'A1-磨边机-2',id:100302,w:70,h:18,l:830,t:183},
{name:'A1-磨边机-3',id:100303,w:70,h:18,l:832,t:206},
{name:'A1-磨边后清洗机-1',id:100401,w:30,h:15,l:798,t:160},
{name:'A1-磨边后清洗机-2',id:100402,w:30,h:15,l:798,t:183},
{name:'A1-磨边后清洗机-3',id:100403,w:30,h:15,l:800,t:206},
{name:'A1-打孔机-1',id:100501,w:13,h:12,l:730,t:163},
{name:'A1-打孔机-2',id:100502,w:13,h:12,l:730,t:187},
{name:'A1-打孔机-3',id:100503,w:13,h:12,l:730,t:210},
{name:'A1-打孔后清洗机-1',id:100601,w:32,h:15,l:665,t:160},
{name:'A1-打孔后清洗机-2',id:100602,w:32,h:15,l:665,t:183},
{name:'A1-打孔后清洗机-3',id:100603,w:32,h:15,l:665,t:206},
{name:'A1-丝印机-1',id:100701,w:30,h:12,l:605,t:163},
{name:'A1-丝印机-2',id:100702,w:30,h:12,l:605,t:187},
{name:'A1-丝印机-3',id:100703,w:30,h:12,l:605,t:210},
{name:'A1-丝印后固化机-1',id:101301,w:37,h:12,l:558,t:163},
{name:'A1-丝印后固化机-2',id:101302,w:37,h:12,l:558,t:187},
{name:'A1-丝印后固化机-3',id:101303,w:37,h:12,l:558,t:210},
{name:'A1-钢化炉',id:101401,w:75,h:15,l:382,t:209},
{name:'A1-包装清洗机-1',id:101501,w:30,h:15,l:228,t:173},
{name:'A1-包装清洗机-2',id:101502,w:30,h:15,l:228,t:206},
{name:'A1-铺纸机-1',id:101601,w:18,h:15,l:188,t:175},
{name:'A1-铺纸机-2',id:101602,w:18,h:15,l:188,t:206},
{name:'A2-磨边机-1',id:200301,w:70,h:18,l:832,t:233},
{name:'A2-磨边机-2',id:200302,w:70,h:18,l:833,t:257},
{name:'A2-磨边机-3',id:200303,w:70,h:18,l:834,t:281},
{name:'A2-磨边后清洗机-1',id:200401,w:30,h:15,l:800,t:233},
{name:'A2-磨边后清洗机-2',id:200402,w:30,h:15,l:801,t:257},
{name:'A2-磨边后清洗机-3',id:200403,w:30,h:15,l:802,t:281},
{name:'A2-打孔机-1',id:200501,w:13,h:12,l:731,t:240},
{name:'A2-打孔机-2',id:200502,w:13,h:12,l:731,t:262},
{name:'A2-打孔机-3',id:200503,w:13,h:12,l:731,t:285},
{name:'A2-打孔后清洗机-1',id:200601,w:32,h:15,l:666,t:234},
{name:'A2-打孔后清洗机-2',id:200602,w:32,h:15,l:666,t:258},
{name:'A2-打孔后清洗机-3',id:200603,w:32,h:15,l:666,t:282},
{name:'A2-丝印机-1',id:200701,w:30,h:12,l:605,t:238},
{name:'A2-丝印机-2',id:200702,w:30,h:12,l:605,t:262},
{name:'A2-丝印机-3',id:200703,w:30,h:12,l:605,t:286},
{name:'A2-丝印后固化机-1',id:201301,w:37,h:12,l:558,t:238},
{name:'A2-丝印后固化机-2',id:201302,w:37,h:12,l:558,t:262},
{name:'A2-丝印后固化机-3',id:201303,w:37,h:12,l:558,t:286},
{name:'A2-钢化炉',id:201401,w:75,h:15,l:382,t:238},
{name:'A2-包装清洗机-1',id:201501,w:30,h:15,l:228,t:234},
{name:'A2-包装清洗机-2',id:201502,w:30,h:15,l:228,t:267},
{name:'A2-铺纸机-1',id:201601,w:18,h:15,l:187,t:234},
{name:'A2-铺纸机-2',id:201602,w:18,h:15,l:186,t:267},
{name:'A3-磨边机-1',id:300301,w:70,h:18,l:834,t:318},
{name:'A3-磨边机-2',id:300302,w:70,h:18,l:834,t:342},
{name:'A3-磨边后清洗机-1',id:300401,w:30,h:15,l:802,t:319},
{name:'A3-磨边后清洗机-2',id:300402,w:30,h:15,l:802,t:342},
{name:'A3-打孔机-1',id:300501,w:13,h:12,l:731,t:324},
{name:'A3-打孔机-2',id:300502,w:13,h:12,l:731,t:348},
{name:'A3-打孔后清洗机-1',id:300601,w:32,h:15,l:666,t:320},
{name:'A3-打孔后清洗机-2',id:300602,w:32,h:15,l:666,t:342},
{name:'A3-丝印机-1',id:300701,w:30,h:12,l:605,t:324},
{name:'A3-丝印机-2',id:300702,w:30,h:12,l:605,t:348},
{name:'A3-一次镀膜机-1',id:300801,w:20,h:15,l:553,t:322},
{name:'A3-一次镀膜机-2',id:300802,w:20,h:15,l:553,t:346},
{name:'A3-丝印后固化机-1',id:301301,w:37,h:12,l:506,t:324},
{name:'A3-丝印后固化机-2',id:301302,w:37,h:12,l:506,t:347},
{name:'A3-钢化炉',id:301401,w:75,h:15,l:340,t:346},
{name:'A3-包装清洗机-1',id:301501,w:30,h:15,l:240,t:343},
{name:'A3-铺纸机-1',id:301601,w:18,h:15,l:200,t:343},
{name:'A3-铺纸机-2',id:301602,w:18,h:15,l:170,t:343},
{name:'A4-磨边机-1',id:400301,w:70,h:18,l:838,t:380},
{name:'A4-磨边机-2',id:400302,w:70,h:18,l:838,t:405},
{name:'A4-磨边机-3',id:400303,w:70,h:18,l:839,t:428},
{name:'A4-磨边后清洗机-1',id:400401,w:30,h:15,l:804,t:380},
{name:'A4-磨边后清洗机-2',id:400402,w:30,h:15,l:805,t:405},
{name:'A4-磨边后清洗机-3',id:400403,w:30,h:15,l:806,t:427},
{name:'A4-一次镀膜机-1',id:400801,w:20,h:15,l:707,t:381},
{name:'A4-一次镀膜机-2',id:400802,w:20,h:15,l:707,t:407},
{name:'A4-一次镀膜机-3',id:400803,w:20,h:15,l:707,t:429},
{name:'A4-一次固化机-1',id:401001,w:37,h:12,l:660,t:383},
{name:'A4-一次固化机-2',id:401002,w:37,h:12,l:660,t:409},
{name:'A4-一次固化机-3',id:401003,w:37,h:12,l:660,t:431},
{name:'A4-二次镀膜机-1',id:401101,w:20,h:15,l:605,t:382},
{name:'A4-二次镀膜机-2',id:401102,w:20,h:15,l:605,t:408},
{name:'A4-二次镀膜机-3',id:401103,w:20,h:15,l:605,t:430},
{name:'A4-二次固化机-1',id:401201,w:37,h:12,l:557,t:383},
{name:'A4-二次固化机-2',id:401202,w:37,h:12,l:557,t:409},
{name:'A4-二次固化机-3',id:401203,w:37,h:12,l:557,t:431},
{name:'A4-钢化炉',id:401401,w:75,h:15,l:379,t:381},
{name:'A4-包装清洗机-1',id:401501,w:30,h:15,l:220,t:379},
{name:'A4-包装清洗机-2',id:401502,w:30,h:18,l:220,t:410},
{name:'A4-铺纸机-1',id:401601,w:18,h:15,l:180,t:381},
{name:'A4-铺纸机-2',id:401602,w:18,h:15,l:180,t:414},
{name:'A5-磨边机-1',id:500301,w:70,h:18,l:817,t:465},
{name:'A5-磨边机-2',id:500302,w:70,h:18,l:817,t:488},
{name:'A5-磨边机-3',id:500303,w:70,h:18,l:819,t:512},
{name:'A5-磨边后清洗机-1',id:500401,w:30,h:15,l:784,t:462},
{name:'A5-磨边后清洗机-2',id:500402,w:30,h:15,l:784,t:488},
{name:'A5-磨边后清洗机-3',id:500403,w:30,h:15,l:785,t:512},
{name:'A5-一次镀膜机-1',id:500801,w:20,h:15,l:685,t:466},
{name:'A5-一次镀膜机-2',id:500802,w:20,h:15,l:685,t:490},
{name:'A5-一次镀膜机-3',id:500803,w:20,h:15,l:685,t:513},
{name:'A5-一次固化机-1',id:501001,w:37,h:12,l:638,t:468},
{name:'A5-一次固化机-2',id:501002,w:37,h:12,l:638,t:491},
{name:'A5-一次固化机-3',id:501003,w:37,h:12,l:638,t:514},
{name:'A5-二次镀膜机-1',id:501101,w:20,h:15,l:584,t:468},
{name:'A5-二次镀膜机-2',id:501102,w:20,h:15,l:584,t:490},
{name:'A5-二次镀膜机-3',id:501103,w:20,h:15,l:584,t:513},
{name:'A5-二次固化机-1',id:501201,w:37,h:12,l:535,t:468},
{name:'A5-二次固化机-2',id:501202,w:37,h:12,l:535,t:492},
{name:'A5-二次固化机-3',id:501203,w:37,h:12,l:535,t:514},
{name:'A5-钢化炉',id:501401,w:75,h:15,l:344,t:513},
{name:'A5-包装清洗机-1',id:501501,w:30,h:18,l:185,t:473},
{name:'A5-包装清洗机-2',id:501502,w:30,h:18,l:185,t:511},
{name:'A5-铺纸机-1',id:501601,w:18,h:15,l:144,t:475},
{name:'A5-铺纸机-2',id:501602,w:18,h:15,l:144,t:511}
],
eqTipMsg:{
name:'',
input:null,
output:null,
alarm:'未报警',
status:'在线'
}
}
},
props: {
scaleNum: {
type: Number,
default: 1
},
dataObj: {
type: Object,
default: () => {}
}
},
watch: {
scaleNum(val) {
this.scaleNum = val
},
dataObj(val) {
val.equipmentDets && val.equipmentDets.forEach(item => {
this.eqList.forEach(eq => {
if (eq.id == item.id) {
eq.name = item.name
eq.input = item.input
eq.output = item.output
eq.alarm = item.isError
eq.status = item.isRun
}
})
})
}
},
mounted() {},
methods: {
// 鼠标hover事件
handleMouseEnter(item, event) {
this.showTooltip = true;
this.eqTipMsg.name = item.name;
this.eqTipMsg.input = item.input;
this.eqTipMsg.output = item.output;
this.eqTipMsg.alarm = item.alarm;
this.eqTipMsg.status = item.status;
this.updateTooltipPosition(event);
},
handleMouseLeave() {
this.showTooltip = false;
},
updateTooltipPosition(event) {
const rect = this.$refs.centerTopBox.getBoundingClientRect()
const offset = 15;
const tooltipWidth = 315*this.scaleNum;
const tooltipHeight = 170*this.scaleNum;
const startX = rect.left;
const startY = rect.top;
const endX = rect.width;
const endY = rect.height;
let posX = event.clientX-startX + offset;
let posY = event.clientY-startY + offset;
if (posX + tooltipWidth > endX) {
posX = event.clientX-startX - tooltipWidth - offset;
}
if (posY + tooltipHeight > endY) {
posY = event.clientY -startY - tooltipHeight - offset;
}
this.tooltipStyle.left = posX/this.scaleNum;
this.tooltipStyle.top = posY/this.scaleNum;
}
}
}
</script>
<style lang="scss" scoped>
p{
margin: 0;
}
.centerTopBox {
width: 1041px;
height: 600px;
background: url('../../../../assets/images/dataBoard/center-top.png') no-repeat;
background-size: 100%;
position: absolute;
left: 440px;
top:113px;
overflow: hidden;
.videoStyle {
width: 1920px;
height: 1080px;
position: absolute;
left:-440px;
top:-113px;
}
.eqTipBox {
width: 315px;
height: 170px;
background: url('../../../../assets/images/dataBoard/eq-tip.png') no-repeat;
background-size: 100%;
position: absolute;
padding-top: 10px;
z-index: 1;
span{
display: inline-block;
font-size: 20px;
color: #FFFFFF;
letter-spacing: 1px;
vertical-align: middle;
}
.eqTipTitle {
width: 112px;
height: 30px;
padding-left: 15px;
}
.eqTipNum {
width: 190px;
height: 30px;
line-height: 30px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.eqBox {
span{
display: inline-block;
// border: 1px solid red;
position: absolute;
}
}
.centerTopTopBox {
padding-left: 48px;
position: absolute;
top:30px;
.num {
width: 203px;
height: 65px;
line-height: 65px;
padding-left: 10px;
box-sizing: border-box;
font-weight: 500;
font-size: 38px;
color: #FFFFFF;
text-shadow: 0px 5px 2px rgba(0,0,0,0.62);
text-align: center;
position: absolute;
top:0px;
}
.title {
font-weight: 500;
font-size: 20px;
color: #00C8F7;
letter-spacing: 2px;
text-shadow: 0px 5px 2px rgba(0,0,0,0.62);
text-align: center;
}
}
}
</style>

View File

@@ -0,0 +1,108 @@
<template>
<header class="dataBoardHeader">
<div class='topTitle'></div>
<!-- <div class='time-chsoose'>
<span class='title'>时间选择</span>
<span class='time-show'>{{time}}</span>
<el-date-picker
v-model="time"
type="date"
value-format='yyyy-MM-dd'
:clearable='false'
style='position: absolute;right: 0px;width: 119px;top:-2px;opacity: 0;'
>
</el-date-picker>
</div> -->
<div
type="text"
class="screen-btn"
:title="isFullScreen?'退出全屏':'全屏'"
@click="changeFullScreen"
>
<svg-icon v-if="isFullScreen" icon-class="unFullScreenView" />
<svg-icon v-else icon-class="fullScreenView" />
</div>
</header>
</template>
<script>
// import moment from 'moment'
export default {
name: 'DataBoardHeader',
props: {
isFullScreen: {
type: Boolean,
default: () => {
return false
}
},
},
data() {
return {
// time: moment().format('YYYY-MM-DD')
}
},
computed: {},
methods: {
changeFullScreen() {
this.$emit('screenfullChange')
},
exportPDF() {
this.$emit('exportPDF')
}
}
}
</script>
<style scoped lang="scss">
.dataBoardHeader {
background: url('../../../../assets/images/dataBoard/head.png') no-repeat;
height: 99px;
background-size: 100%;
background-position: 0 0;
position: relative;
.topTitle{
width: 465px;
height: 77px;
background: url('../../../../assets/images/dataBoard/data-board-title.png') no-repeat;
background-size: 100%;
position: absolute;
left: 731px;
top:19px;
}
.time-chsoose {
width: 212px;
height: 33px;
background: url('../../../../assets/images/dataBoard/time-choose.png') no-repeat;
background-size: 100%;
position: absolute;
right: 63px;
top:49px;
.title {
font-size: 18px;
color: #082049;
letter-spacing: 1px;
position: relative;
top: 4px;
left: 8px
}
.time-show {
font-size: 18px;
color: #FFFFFF;
line-height: 18px;
position: relative;
top: 4px;
left: 28px;
}
}
.screen-btn{
color: #00fff0;
height: 32px;
line-height: 32px;
font-size: 32px;
position: absolute;
right: 22px;
top:47px;
}
}
</style>

View File

@@ -0,0 +1,286 @@
<template>
<div class='leftBottomBox'>
<div class='title'>
<svg-icon icon-class="dataBoard2" class='icon'/>
<span>投入产出及良品率</span>
</div>
<div v-if='xData.length === 0' class='noData'>暂无数据</div>
<div v-else>
<div class="top_legend">
<span class="chart_legend_icon1">投入</span>
<span class="chart_legend_icon2">产出</span>
<span><span class="chart_legend_icon3"></span>良品率</span>
</div>
<div id='inOutputChart' style='width: 400px;height: 290px;'></div>
</div>
</div>
</template>
<script>
import * as echarts from 'echarts';
export default {
name: 'LeftBottom',
props: {
dataObj: {
type: Object,
default: () => {}
}
},
watch: {
dataObj(val) {
this.xData = []
this.inputData = []
this.outputData = []
this.goodRateData = []
val.monthBar && val.monthBar.forEach(item => {
this.xData.push(item.dataType)
this.inputData.push(item.inputNum)
this.outputData.push(item.outputNum)
this.goodRateData.push(item.goodRate)
})
this.$nextTick(()=>{
this.initChart();
})
}
},
data() {
return {
chartDom: '',
chart: '',
xData:[],
inputData:[],
outputData:[],
goodRateData:[],
}
},
mounted() {},
methods: {
initChart() {
if (
this.chart !== null &&
this.chart !== '' &&
this.chart !== undefined
) {
this.chart.dispose() // 页面多次刷新会出现警告Dom已经初始化了一个实例这是销毁实例
}
this.chartDom = document.getElementById('inOutputChart')
this.chart = echarts.init(this.chartDom)
var option;
option = {
grid: { top: 40, right: 10, bottom: 5, left: 10, containLabel: true },
legend: {
show: false,
},
xAxis: {
type: "category",
data: this.xData,
axisLabel: {
color: "#fff",
fontSize: 10,
interval: 0,
rotate:30
},
axisTick: { show: false },
axisLine: {
lineStyle: {
width: 2,
color: "#5982B2",
},
},
},
yAxis: [{
name: "单位/片",
nameTextStyle: {
color: "#DFF1FE",
fontSize: 12,
},
type: "value",
axisLabel: {
color: "#DFF1FE",
fontSize: 12,
formatter: "{value}",
},
axisLine: {
show: true,
lineStyle: {
width: 2,
color: "#5982B2",
},
},
splitLine: {
lineStyle: {
width: 2,
color: "#5982B2",
},
},
},{
name: "良品率/%",
nameTextStyle: {
color: "#DFF1FE",
fontSize: 12,
},
type: "value",
axisLabel: {
color: "#DFF1FE",
fontSize: 12,
formatter: "{value}",
},
axisLine: {
show: true,
lineStyle: {
width: 2,
color: "#5982B2",
},
},
splitLine: {
lineStyle: {
width: 2,
color: "#5982B2",
},
},
}],
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow",
},
className: "qhd-chart-tooltip",
show: true,
},
series: [
{
data: this.inputData,
type: "bar",
barWidth: 10,
barGap:0,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(157, 234, 245, 1)' },
{ offset: 1, color: 'rgba(110, 249, 222, 1)' },
]),
},
},
{
data:this.outputData,
type: "bar",
barWidth: 10,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(92, 183, 255, 1)' },
{ offset: 1, color: 'rgba(54, 75, 254, 1)' },
]),
},
},
{
data:this.goodRateData,
type: "line",
yAxisIndex: 1,
symbol:'circle',
symbolSize: 7,
color:'rgba(18, 255, 245, 1)',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(18, 255, 245, 0.8)' },
{ offset: 0.2, color: 'rgba(18, 255, 245, 0.2)' },
{ offset: 0.4, color: 'rgba(18, 255, 245, 0)' },
]),
}
}
],
}
option && this.chart.setOption(option);
}
}
}
</script>
<style lang='scss' scoped>
.leftBottomBox {
width: 402px;
height: 332px;
background: url('../../../../assets/images/dataBoard/left-bottom.png') no-repeat;
background-size: 100%;
position: absolute;
left: 23px;
bottom:23px;
.title {
margin: 7px 0 0 15px;
.icon {
width: 32px;
height: 32px;
margin-right: 5px;
vertical-align:middle;
margin-top: 5px;
}
span {
font-size: 24px;
color: #52FFF1;
line-height: 24px;
vertical-align:middle;
}
}
.top_legend {
color: #fff;
font-size: 14px;
position: absolute;
left:120px;
top:50px;
.chart_legend_icon1{
margin-right: 10px;
}
.chart_legend_icon2{
margin-right: 14px;
}
.chart_legend_icon1:before {
display: inline-block;
content: "";
width: 10px;
height: 10px;
margin-right: 5px;
border-radius: 2px;
background: #73F8E0;
}
.chart_legend_icon2:before {
display: inline-block;
content: "";
width: 10px;
height: 10px;
margin-right: 5px;
border-radius: 2px;
background: #497EFF;
}
.chart_legend_icon3 {
display: inline-block;
width: 8px;
height: 8px;
margin-right: 8px;
border-radius: 4px;
background: #73F8E0;
position:relative;
}
.chart_legend_icon3:before {
display: inline-block;
content: "";
width: 16px;
height:2px;
background: #73F8E0;
position:absolute;
top:3px;
left:-4px;
}
}
}
.noData {
font-size: 24px;
text-align: center;
padding-top: 100px;
}
</style>
<style>
.qhd-chart-tooltip {
background: #0a2b4f77 !important;
border: none !important;
backdrop-filter: blur(12px);
}
.qhd-chart-tooltip * {
color: #fff !important;
}
</style>

View File

@@ -0,0 +1,162 @@
<template>
<div class='leftTopBox'>
<div class='title'>
<svg-icon icon-class="dataBoard1" class='icon'/>
<span>近期数据对比</span>
</div>
<div class='title-split'>
<img src="../../../../assets//images/dataBoard/leftbar.png" alt="">
<span class='text'>本日</span>
<img src="../../../../assets//images/dataBoard/rightbar.png" alt="">
</div>
<div class='data-box'>
<div class='left-icon' style="top:19px;">
<img src="../../../../assets//images/dataBoard/leftTopIcon1.png" alt="" width='54'>
<div>总投入</div>
</div>
<div class='left-icon' style="top:139px;">
<img src="../../../../assets//images/dataBoard/leftTopIcon2.png" alt="" width='48'>
<div>总生产</div>
</div>
<div class='right-data' style="top:15px;">
<p><span class='num'>{{todayData?.inputNum ? formatThousands(todayData.inputNum) : '-'}}</span><span class='text'>片数</span></p>
<p><span class='num'>{{todayData?.inputArea ? formatThousands(todayData.inputArea) : '-'}}</span><span class='text'>面积/</span></p>
</div>
<div class='right-data' style="top:132px;">
<p><span class='num'>{{todayData?.outputNum ? formatThousands(todayData.outputNum) : '-'}}</span><span class='text'>片数</span></p>
<p><span class='num'>{{todayData?.outputArea ? formatThousands(todayData.outputArea) : '-'}}</span><span class='text'>面积/</span></p>
</div>
</div>
<div class='title-split'>
<img src="../../../../assets//images/dataBoard/leftbar.png" alt="">
<span class='text'>昨日</span>
<img src="../../../../assets//images/dataBoard/rightbar.png" alt="">
</div>
<div class='data-box'>
<div class='left-icon' style="top:19px;">
<img src="../../../../assets//images/dataBoard/leftTopIcon1.png" alt="" width='54'>
<div>总投入</div>
</div>
<div class='left-icon' style="top:139px;">
<img src="../../../../assets//images/dataBoard/leftTopIcon2.png" alt="" width='48'>
<div>总生产</div>
</div>
<div class='right-data' style="top:15px;">
<p><span class='num'>{{yesterdayData?.inputNum ? formatThousands(yesterdayData.inputNum) : '-'}}</span><span class='text'>片数</span></p>
<p><span class='num'>{{yesterdayData?.inputArea ? formatThousands(yesterdayData.inputArea) : '-'}}</span><span class='text'>面积/</span></p>
</div>
<div class='right-data' style="top:132px;">
<p><span class='num'>{{yesterdayData?.outputNum ? formatThousands(yesterdayData.outputNum) : '-'}}</span><span class='text'>片数</span></p>
<p><span class='num'>{{yesterdayData?.outputArea ? formatThousands(yesterdayData.outputArea) : '-'}}</span><span class='text'>面积/</span></p>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'LeftTop',
props: {
dataObj: {
type: Object,
default: () => {}
}
},
watch: {
dataObj(val) {
val.todayAndYesterday && val.todayAndYesterday.forEach(item => {
if (item.dataType === "今日") {
this.todayData = item
} else {
this.yesterdayData = item
}
})
}
},
data() {
return {
todayData:{},
yesterdayData:{}
}
},
methods: {}
}
</script>
<style lang='scss' scoped>
.leftTopBox {
width: 402px;
height: 600px;
background: url('../../../../assets/images/dataBoard/left-top.png') no-repeat;
background-size: 100%;
position: absolute;
left: 23px;
top:113px;
.title {
margin: 10px 0 0 15px;
.icon {
width: 33px;
height: 33px;
margin-right: 5px;
vertical-align:middle;
}
span {
font-size: 24px;
color: #52FFF1;
line-height: 24px;
vertical-align:middle;
}
}
.title-split {
margin-left: 95px;
margin-top: 10px;
.text{
display: inline-block;
width: 100px;
font-size: 22px;
color: #01CFCC;
line-height: 24px;
letter-spacing: 7px;
text-align: center;
}
}
.data-box {
width: 358px;
height: 230px;
background: url('../../../../assets/images/dataBoard/leftTopDataBox.png') no-repeat;
background-size: 100%;
margin-top: 8px;
position: relative;
left: 24px;
.left-icon {
font-weight: 500;
font-size: 16px;
color: #9DEAF5;
line-height: 22px;
letter-spacing: 1px;
text-shadow: 0px 4px 2px rgba(0,0,0,0.62);
position: absolute;
left: 30px;
}
.right-data {
position: absolute;
left: 100px;
p{
margin: 0;
};
.num {
font-weight: 500;
font-size: 32px;
color: #FFFFFF;
text-shadow: 0px 4px 2px rgba(0,0,0,0.62);
margin-right: 5px;
}
.text {
font-weight: 500;
font-size: 16px;
color: #FFFFFF;
letter-spacing: 1px;
text-shadow: 0px 4px 2px rgba(0,0,0,0.62);
}
}
}
}
</style>

View File

@@ -0,0 +1,198 @@
<template>
<div class='rightBottomBox'>
<div class='title'>
<svg-icon icon-class="dataBoard3" class='icon'/>
<span>本月<span class='dotted'></span>班组生产排名</span>
</div>
<div class='rankingLeft'>
<div class='rankingLeftTitle'>产量</div>
<div class='rankingTypeBox'>
<div style='margin-bottom: 10px;' v-for='(item,index) in teamProductionRank' :key='index'>
<img :src="require(`../../../../assets/images/dataBoard/ranking${index+1}.png`)" alt="" width="50">
<div class='rankingTextBox'>
<p class='text1'>{{item.name}}/</p>
<p class='text2'>{{item?.value ? formatThousands(item.value) : '-'}}</p>
</div>
</div>
</div>
</div>
<div class='rankingRight'>
<div class='rankingRightTitle'>
成品率
</div>
<div class='rankingTypeBox' style='left: 120px;'>
<div style='margin-bottom: 10px;' v-for='(item,index) in teamRateRank' :key='index'>
<img :src="require(`../../../../assets/images/dataBoard/ranking${index+1}.png`)" alt="" width="50">
<div class='rankingTextBox'>
<p class='text1'>{{item.name}}</p>
<p class='text2'>{{Math.trunc(item.value)}}<span style='font-size: 20px;'>.{{item.value1}}%</span></p>
</div>
</div>
<!-- <div>
<img src="../../../../assets//images//dataBoard/ranking2.png" alt="" width='50'>
<div class='rankingTextBox'>
<p class='text1'>班组1</p>
<p class='text2'>94<span style='font-size: 20px;'>.13%</span></p>
</div>
</div>
<div>
<img src="../../../../assets//images//dataBoard/ranking3.png" alt="" width='50'>
<div class='rankingTextBox'>
<p class='text1'>班组1</p>
<p class='text2'>90<span style='font-size: 20px;'>.42%</span></p>
</div>
</div> -->
</div>
</div>
</div>
</template>
<script>
export default {
name: 'RightBottom',
props: {
dataObj: {
type: Object,
default: () => {}
}
},
watch: {
dataObj(val) {
this.teamProductionRank = []
this.teamRateRank = []
val.teamProductionRank && val.teamProductionRank.forEach(item => {
let obj = {
name: item.name,
value: item.value,
rank: item.rank,
}
this.teamProductionRank.push(obj)
})
val.teamRateRank && val.teamRateRank.forEach(item => {
let obj = {
name: item.name,
value: item.value,
rank: item.rank,
value1: this.getPositiveDecimal(item.value)
}
this.teamRateRank.push(obj)
})
}
},
data() {
return {
teamProductionRank:[],
teamRateRank:[]
}
},
mounted() {},
methods: {
getPositiveDecimal(num) {
const str = Math.abs(num).toString();
const [, decimal] = str.split('.');
return decimal || '00';
}
}
}
</script>
<style lang='scss' scoped>
p{
margin: 0;
}
.rightBottomBox {
width: 755px;
height: 332px;
background: url('../../../../assets/images/dataBoard/right-bottom.png') no-repeat;
background-size: 100%;
position: absolute;
right: 23px;
bottom:23px;
.title {
margin: 10px 0 0 15px;
.icon {
width: 33px;
height: 33px;
margin-right: 5px;
vertical-align:middle;
}
span {
font-size: 24px;
color: #52FFF1;
line-height: 24px;
vertical-align:middle;
.dotted {
display: inline-block;
width: 4px;
height: 4px;
border-radius: 2px;
background-color: #52FFF1;
margin: 0 8px;
}
}
}
.rankingLeft {
width: 405px;
height: 265px;
background: url('../../../../assets/images/dataBoard/rankingBg1.png') no-repeat;
background-size: 100%;
position: absolute;
left: 12px;
top: 48px;
.rankingLeftTitle {
width: 24px;
height: 73px;
font-weight: 500;
font-size: 24px;
color: #52FFF1;
line-height: 55px;
position: relative;
left: 37px;
top: 63px;
}
}
.rankingRight {
width: 338px;
height: 265px;
background: url('../../../../assets/images/dataBoard/rankingBg2.png') no-repeat;
background-size: 100%;
position: absolute;
right: 12px;
top: 48px;
.rankingRightTitle {
width: 24px;
height: 132px;
font-weight: 500;
font-size: 24px;
color: #52FFF1;
line-height: 55px;
position: relative;
left: 37px;
top: 43px;
}
}
.rankingTypeBox {
position: absolute;
left: 100px;
top:13px;
.rankingTextBox {
display: inline-block;
margin-left: 16px;
vertical-align: top;
.text1 {
font-weight: 500;
font-size: 17px;
color: #FFFFFF;
line-height: 22px;
letter-spacing: 1px;
padding-top: 5px;
}
.text2 {
font-weight: 500;
font-size: 32px;
color: #FFFFFF;
line-height: 22px;
margin-top: 8px;
}
}
}
}
</style>

View File

@@ -0,0 +1,352 @@
<template>
<div class='rightTopBox'>
<div class='title'>
<svg-icon icon-class="dataBoard4" class='icon'/>
<span>缺陷情况</span>
</div>
<div class='title-split'>
<img src="../../../../assets//images/dataBoard/leftbar.png" alt="">
<span class='text'>总数</span>
<img src="../../../../assets//images/dataBoard/rightbar.png" alt="">
</div>
<div class='dataBox'>
<div class='row'>
<div class='box' style='width: 105px;'>
<p class='name'>
<span style='margin-right: 3px;'>当日</span>
<img v-show='nokSumDet.today >= nokSumDet.yesterday' src="../../../../assets/images/dataBoard/arrUp.png" alt="" width='5' height='15'>
<img v-show='nokSumDet.today < nokSumDet.yesterday' src="../../../../assets/images/dataBoard/arrDown.png" alt="" width='5' height='15'>
</p>
<p class='num'>{{nokSumDet?.today ? formatThousands(nokSumDet.today) : '-'}}</p>
</div>
<div class='box' style='width: 115px;'>
<p class='name'>
<span style='margin-right: 3px;'>本月</span>
<img v-show='nokSumDet.month >= nokSumDet.lastMonth' src="../../../../assets/images/dataBoard/arrUp.png" alt="" width='5' height='15'>
<img v-show='nokSumDet.month < nokSumDet.lastMonth' src="../../../../assets/images/dataBoard/arrDown.png" alt="" width='5' height='15'>
</p>
<p class='num'>{{nokSumDet?.month ? formatThousands(nokSumDet.month) : '-'}}</p>
</div>
<div class='box' style='width: 110px;'>
<p class='name'>
<span style='margin-right: 3px;'>本年</span>
<img v-show='nokSumDet.year >= nokSumDet.lastYear' src="../../../../assets/images/dataBoard/arrUp.png" alt="" width='5' height='15'>
<img v-show='nokSumDet.year < nokSumDet.lastYear' src="../../../../assets/images/dataBoard/arrDown.png" alt="" width='5' height='15'>
</p>
<p class='num'>{{nokSumDet?.year ? formatThousands(nokSumDet.year) : '-'}}</p>
</div>
</div>
<div class='row' style='padding-top: 5px;'>
<div class='box' style='width: 105px;'>
<p class='name'>
<span>昨日</span>
</p>
<p class='num'>{{nokSumDet?.yesterday ? formatThousands(nokSumDet.yesterday) : '-'}}</p>
</div>
<div class='box' style='width: 115px;'>
<p class='name'>
<span>上月</span>
</p>
<p class='num'>{{nokSumDet?.lastMonth ? formatThousands(nokSumDet.lastMonth) : '-'}}</p>
</div>
<div class='box' style='width: 110px;'>
<p class='name'>
<span>上年</span>
</p>
<p class='num'>{{nokSumDet?.lastYear ? formatThousands(nokSumDet.lastYear) : '-'}}</p>
</div>
</div>
</div>
<div class='title-split2'>
<img src="../../../../assets//images/dataBoard/leftbar.png" alt="">
<span class='text'>全厂缺陷汇总</span>
<img src="../../../../assets//images/dataBoard/rightbar.png" alt="">
</div>
<div id='defectSumChart' style='width: 400px;height: 315px;'></div>
</div>
</template>
<script>
import * as echarts from 'echarts';
const colors = [
"rgb(39, 96, 255)",
"rgb(91, 155, 255)",
"rgb(153, 214, 108)",
"rgb(18, 255, 245)",
"rgb(221, 177, 18)",
];
export default {
name: 'RightTop',
props: {
dataObj: {
type: Object,
default: () => {}
}
},
watch: {
dataObj(val) {
this.nokSumDet = val.nokSumDet
this.dataProps = []
val.nokPieDet && val.nokPieDet.forEach(item => {
let obj = {
value: item.num,
name: item.type
}
this.dataProps.push(obj)
})
this.initChart()
}
},
data() {
return {
nokSumDet:{},
chartDom: '',
chart: '',
dataProps:[],
rangArrValue:[],
dataList:[],
totalValue:0
}
},
mounted() {},
methods: {
initChart() {
if (
this.chart !== null &&
this.chart !== '' &&
this.chart !== undefined
) {
this.chart.dispose() // 页面多次刷新会出现警告Dom已经初始化了一个实例这是销毁实例
}
this.chartDom = document.getElementById('defectSumChart')
this.chart = echarts.init(this.chartDom)
var option;
this.getPersonnelList(this.dataProps)
option = {
color: colors,
graphic: [
{
type: "text",
left: "center",
top: "44%",
style: {
text: this.totalValue,
fill: "#fff",
width: 150,
height: 44,
fontSize: 31,
fontWeight: 400,
},
},
{
type: "text",
left: "center",
top: "55%",
style: {
text: "总数/件",
fill: "rgba(255, 255, 255, 0.70)",
width: 32,
height: 16,
fontSize: 16,
fontWeight: 400,
},
},
],
series: [
{
name: "产线缺陷分类",
type: "pie",
radius: ["45%", "60%"],
center: ["50%", "50%"],
label:{
formatter:function(params){
return `{color${params.dataIndex}|${params.percent}%}\n{style2|${params.name}}`
},
rich:{
color0:{color:'rgb(39, 96, 255)',fontSize:22},
color1:{color:'rgb(91, 155, 255)',fontSize:22},
color2:{color:'rgb(153, 214, 108)',fontSize:22},
color3:{color:'rgb(18, 255, 245)',fontSize:22},
color4:{color:'rgb(221, 177, 18)',fontSize:22},
color5:{color:'rgb(39, 96, 255)',fontSize:22},
color6:{color:'rgb(91, 155, 255)',fontSize:22},
color7:{color:'rgb(153, 214, 108)',fontSize:22},
color8:{color:'rgb(18, 255, 245)',fontSize:22},
color9:{color:'rgb(221, 177, 18)',fontSize:22},
style2:{
color:'#fff',
fontSize:14
}
}
},
data: this.dataList,
},
],
}
option && this.chart.setOption(option);
},
getCoordinates(startArc, endArc) {
const posi = [
Math.sin(startArc),
-Math.cos(startArc),
Math.sin(endArc),
-Math.cos(endArc),
];
const dx = posi[2] - posi[0];
const dy = posi[3] - posi[1];
return this.getLocation(dx, dy);
},
getLocation(dx,dy) {
const tanV = dx / dy;
const directSign = Math.abs(tanV) < 1;
const t = directSign ? tanV : 1 / tanV;
const sign1 = t > 0 ? 1 : -1;
const sign2 = dx > 0 ? 1 : -1;
const sign = directSign ? sign1 * sign2 : sign2;
const group1 = [0.5 - (sign * t) / 2, 0.5 + (sign * t) / 2];
const group2 = sign > 0 ? [0, 1] : [1, 0];
const group = [...group1, ...group2];
const keys = directSign ? ["x", "x2", "y", "y2"] : ["y", "y2", "x", "x2"];
let res = {};
keys.forEach((k, idx) => {
res[k] = group[idx];
});
return res;
},
async getPersonnelList(dataProps){
this.rangArrValue = [];
this.totalValue = 0;
this.dataList = [];
this.totalValue = dataProps.reduce(
(total, value) => total + value.value,
0
);
let cacheNum = 0;
for (let i = 0; i < dataProps.length; i++) {
const endNum = cacheNum + dataProps[i].value;
this.rangArrValue.push([cacheNum, endNum]);
cacheNum = endNum;
}
const angleArr = this.rangArrValue.map((arr) =>
arr.map((num) => (num / this.totalValue) * Math.PI * 2)
);
this.dataList = dataProps.map((item, index) => {
const range = this.getCoordinates(angleArr[index][0], angleArr[index][1]);
const startColor = colors[index%5];
const color = {
type: "linear",
x: range.x,
x2: range.x2,
y: range.y,
y2: range.y2,
colorStops: [
{
offset: 0,
color: startColor, // 起始颜色
},
{
offset: 1,
color: `${startColor.substring(0, startColor.length - 1)}, 0.2)`, // 终点颜色
},
],
global: false,
};
return {
name: item.name,
value: item.value,
itemStyle: {
color: color,
},
};
});
}
}
}
</script>
<style lang='scss' scoped>
p{
margin: 0;
}
.rightTopBox {
width: 402px;
height: 600px;
background: url('../../../../assets/images/dataBoard/left-top.png') no-repeat;
background-size: 100%;
position: absolute;
right: 23px;
top:113px;
.title {
margin: 10px 0 0 15px;
.icon {
width: 33px;
height: 33px;
margin-right: 5px;
vertical-align:middle;
}
span {
font-size: 24px;
color: #52FFF1;
line-height: 24px;
vertical-align:middle;
}
}
.title-split {
margin-left: 95px;
margin-top: 10px;
.text{
display: inline-block;
width: 100px;
font-size: 22px;
color: #01CFCC;
line-height: 24px;
letter-spacing: 7px;
text-align: center;
}
}
.title-split2 {
margin-left: 66px;
margin-top: 20px;
.text{
display: inline-block;
width: 160px;
font-size: 22px;
color: #01CFCC;
line-height: 24px;
letter-spacing: 2px;
text-align: center;
}
}
.dataBox {
width: 358px;
height: 160px;
background: url('../../../../assets/images/dataBoard/defectNum.png') no-repeat;
background-size: 100%;
position: relative;
left: 21px;
top:8px;
padding-left: 24px;
.row {
.box{
display: inline-block;
padding-top: 10px;
.name {
font-weight: 500;
font-size: 20px;
color: #FFFFFF;
letter-spacing: 2px;
}
.num {
font-weight: 500;
font-size: 28px;
color: #FFFFFF;
text-shadow: 0px 5px 2px rgba(0,0,0,0.62);
}
}
}
}
}
</style>

View File

@@ -1,100 +1,279 @@
<!--
filename: index.vue
author: liubin
date: 2023-10-11 09:32:04
description: 设备看板
-->
<template> <template>
<div ref="dataBoard" class="data-board"> <div ref="dataBoardBoxB" class="dataBoardBoxB">
<el-button type="text" @click="goback" class="go-back--btn">返回</el-button> <div
id="dataBoardBox"
class="dataBoardBox"
style="width: 1920px; height: 1080px"
:style="{ transform: 'scale(' + scaleNum + ')' }">
<DataBoardHeader
:is-full-screen="isFullScreen"
@screenfullChange="screenfullChange" />
<LeftTop :dataObj="dataObj" />
<LeftBottom :dataObj="dataObj" />
<CenterTop :scaleNum="scaleNum" :dataObj="dataObj" />
<CenterBottomL :dataObj="dataObj" />
<CenterBottomR :dataObj="dataObj" />
<RightTop :dataObj="dataObj" />
<RightBottom :dataObj="dataObj" />
</div>
</div> </div>
</template> </template>
<script> <script>
import DataBoardHeader from './components/Header.vue';
import LeftTop from './components/LeftTop.vue';
import LeftBottom from './components/LeftBottom.vue';
import CenterTop from './components/CenterTop.vue';
import CenterBottomL from './components/CenterBottomL.vue';
import CenterBottomR from './components/CenterBottomR.vue';
import RightTop from './components/RightTop.vue';
import RightBottom from './components/RightBottom.vue';
import { debounce } from '@/utils/debounce';
import screenfull from 'screenfull';
import { getAccessToken } from '@/utils/auth';
import store from '@/store';
export default { export default {
name: 'DataBoard', name: 'DataBoard',
components: {}, components: {
DataBoardHeader,
LeftTop,
LeftBottom,
CenterTop,
CenterBottomL,
CenterBottomR,
RightTop,
RightBottom,
},
props: {}, props: {},
data() { data() {
return { return {
appMain: null, // dom isFullScreen: false,
parentStyle: { scaleNum: 0.8,
margin: '8px 14px 0px 16px', dataObj: {},
minHeight: 'calc(100vh - 120px - 8px)', sseReader: null, // 保存流读取器
}, // object abortController: null, // 用于中止 fetch 请求
mainFooter: null, // dom retryCount: 0, // 当前重试次数
isDestroyed: false, // 标记组件是否已销毁
}; };
}, },
// mounted() { created() {
// this.$nextTick(() => { this.init();
// this.modify(); },
// }); mounted() {
// }, this.boxReset = debounce(() => {
// activated() { this.resetSize();
// this.modify(); }, 300);
// }, this.boxReset();
// deactivated() { window.addEventListener('resize', () => {
// this.recover(); this.boxReset();
// }, });
// beforeDestroy() { this.getData();
// this.recover(); },
// }, beforeDestroy() {
this.closeSSE();
},
destroyed() {
window.removeEventListener('resize', this.boxReset);
},
computed: {
sidebarOpened() {
return this.$store.state.app.sidebar.opened;
},
},
watch: {
sidebarOpened(newVal, oldVal) {
this.boxReset();
},
},
methods: { methods: {
// modify() { async getData() {
// // 在这个页面临时修改下父类的margin结束时需还原 let _this = this;
// this.appMain = document.querySelector('.app-main'); if (_this.isDestroyed) return;
// // this.appMain.style.minHeight = 'calc(100vh - 90px)'; const url =
// this.appMain.style.margin = 0; process.env.VUE_APP_BASE_API +
// // 在这个页面临时删除 main-footer 元素,结束时需还原 '/admin-api/monitoring/message/subscribe/' +
// // this.mainFooter = document.querySelector('.main-footer').cloneNode(true); store.getters.userId +
// // document.querySelector('.main-footer').remove(); '-' +
// this.$refs.dataBoard.classList.add('data-board'); Date.now();
// }, const token = getAccessToken();
// recover() { const headers = new Headers({
// this.$refs.dataBoard.classList.remove('data-board'); Authorization: `Bearer ${token}`,
// this.$nextTick(() => { 'Content-Type': 'text/event-stream',
// this.appMain.style.margin = this.parentStyle.margin; });
// // this.appMain.style.minHeight = this.parentStyle.minHeight; try {
// // this.appMain.insertAdjacentElement('afterend', this.mainFooter); // 创建中止控制器
// }); this.abortController = new AbortController();
// }, // 发起 fetch 请求(替换为你的接口地址)
goback() { const response = await fetch(url, {
this.$router.go(-1); method: 'GET',
} headers: headers,
signal: _this.abortController.signal, // 绑定中止信号
});
// 获取流读取器
_this.sseReader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let receivedBytes = 0;
// 持续读取流数据
while (true) {
const { done, value } = await _this.sseReader.read();
if (done) {
console.log('SSE 连接正常关闭');
_this.handleReconnect(); // 触发重连
break;
}
// const data = decoder.decode(value);
// console.log('收到消息:', data);
receivedBytes += value.length;
console.log(
`收到数据块: ${value.length} 字节, 累计: ${receivedBytes} 字节`
);
const chunk = decoder.decode(value, { stream: true });
buffer += chunk;
// 处理完整的消息
const messages = buffer.split('\n\n'); // SSE 消息以双换行分隔
buffer = messages.pop() || ''; // 保留最后一个不完整的消息
for (const message of messages) {
if (_this.isValidData(message)) {
// console.log('完整 SSE 消息:', message);
_this.upDateMsg(message);
}
}
// 处理 SSE 事件数据
// const data = decoder.decode(value);
// console.log('收到消息:', data);
// if (_this.isValidData(data)){
// _this.upDateMsg(data);
// }
}
} catch (error) {
// 主动中止的请求不报错
if (error.name === 'AbortError') return;
console.error('SSE 连接异常:', error);
_this.handleReconnect(); // 触发重连
}
},
closeSSE() {
this.isDestroyed = true; // 标记销毁
if (this.abortController) {
this.abortController.abort(); // 中止 fetch 请求
}
if (this.sseReader) {
this.sseReader.cancel(); // 关闭流读取器
this.sseReader = null;
}
console.log('SSE 连接已强制关闭');
},
handleReconnect() {
if (this.isDestroyed) return;
// 指数退避策略(最大重试 5 次)
const maxRetries = 5;
if (this.retryCount < maxRetries) {
const delay = Math.pow(2, this.retryCount) * 1000;
setTimeout(() => {
this.retryCount++;
this.initSSE();
}, delay);
} else {
console.error('SSE 重连次数已达上限');
}
},
isValidData(data) {
return data.trim().startsWith('data:{') && !data.includes('heartbeat');
},
upDateMsg(data) {
const jsonStr = data.replace(/^data:/, '').trim();
console.log('jsonStr', jsonStr);
try {
const dataObj = JSON.parse(jsonStr);
this.dataObj = dataObj;
console.log('dataObj', this.dataObj);
} catch (e) {
console.error('JSON 解析失败:', e);
}
},
change() {
this.isFullScreen = screenfull.isFullscreen;
},
init() {
if (!screenfull.isEnabled) {
this.$message({
message: 'you browser can not work',
type: 'warning',
});
return false;
}
screenfull.on('change', this.change);
},
destroy() {
if (!screenfull.isEnabled) {
this.$message({
message: 'you browser can not work',
type: 'warning',
});
return false;
}
screenfull.off('change', this.change);
},
// 全屏
screenfullChange() {
if (!screenfull.isEnabled) {
this.$message({
message: 'you browser can not work',
type: 'warning',
});
return false;
}
screenfull.toggle(this.$refs.dataBoardBoxB);
},
resetSize() {
const dataBoardBox = document.getElementById('dataBoardBox');
const rw = parseFloat(window.innerWidth);
const rh = parseFloat(window.innerHeight);
const bw = parseFloat(dataBoardBox.style.width);
const bh = parseFloat(dataBoardBox.style.height);
let wx = 0;
let hy = 0;
if (screenfull.isFullscreen) {
wx = rw / bw;
hy = rh / bh;
} else {
if (this.$store.state.app.sidebar.opened) {
wx = (rw - 283) / bw;
} else {
wx = (rw - 88) / bw;
}
hy = (rh - 150) / bh;
}
this.scaleNum = wx < hy ? wx : hy;
},
}, },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.data-board { .dataBoardBoxB {
position: absolute;
top: 0;
left: 0;
width: 100%; width: 100%;
height: 100%; height: calc(100vh - 150px);
background: url('../../../assets/images/DataBoard.png') 100% 100% / contain position: relative;
no-repeat; overflow: auto;
} .dataBoardBox {
.go-back--btn {
position: fixed;
top: 28px;
left: 24px;
color: #fff;
font-size: 18px;
letter-spacing: 6px;
&:hover {
text-decoration: underline;
}
&::after {
content: "\2BAA";
position: absolute; position: absolute;
top: 6px; transform-origin: 16px 8px;
right: -26px; font-size: 16px;
font-size: 24px; top: 0px;
left: 0px;
background: url('../../../assets/images/dataBoard/background.png') no-repeat;
background-size: cover;
background-position: 0 0;
overflow: auto;
} }
} }
</style> </style>

File diff suppressed because it is too large Load Diff

View File

@@ -6,77 +6,47 @@
* @Description: * @Description:
--> -->
<template> <template>
<el-form <el-form :model="dataForm" :rules="dataRule" ref="dataForm" v-if="visible" @keyup.enter.native="dataFormSubmit()"
:model="dataForm" label-width="100px" label-position="top">
:rules="dataRule" <el-row :gutter="20">
ref="dataForm" <el-col :span="12">
v-if="visible" <el-form-item label="原料名称" prop="materialId">
@keyup.enter.native="dataFormSubmit()" <el-select v-model="dataForm.materialId" filterable @change="setCode" :style="{ width: '100%' }"
label-width="100px" placeholder="请选择原料名称">
label-position="top"> <el-option v-for="item in MaterialList" :key="item.id" :label="item.name" :value="item.id"></el-option>
<el-row :gutter="20"> </el-select>
<el-col :span="12"> </el-form-item>
<el-form-item label="原料名称" prop="materialId"> </el-col>
<el-select <el-col :span="12">
v-model="dataForm.materialId" <el-form-item label="原料编码" prop="code">
filterable <el-input v-model="dataForm.code" clearable readonly />
@change="setCode" </el-form-item>
:style="{ width: '100%' }" </el-col>
placeholder="请选择原料名称"> <el-col :span="12">
<el-option <el-form-item label="生效开始时间" prop="startTime">
v-for="item in MaterialList" <el-date-picker v-model="dataForm.startTime" type="date" value-format="timestamp" :style="{ width: '100%' }"
:key="item.id" placeholder="选择开始时间"></el-date-picker>
:label="item.name" </el-form-item>
:value="item.id"></el-option> </el-col>
</el-select> <el-col :span="12">
</el-form-item> <el-form-item label="生效结束时间" prop="endTime">
</el-col> <el-date-picker v-model="dataForm.endTime" type="date" value-format="timestamp" :style="{ width: '100%' }"
<el-col :span="12"> placeholder="选择结束时间"></el-date-picker>
<el-form-item label="原料编码" prop="code"> </el-form-item>
<el-input v-model="dataForm.code" clearable readonly /> </el-col>
</el-form-item> <el-col :span="12">
</el-col> <el-form-item label="单价" prop="price">
<el-col :span="12"> <el-input-number :min="0" style="width: 75%" v-model="dataForm.price" clearable placeholder="请输入单价" />
<el-form-item label="生效开始时间" prop="startTime"> {{ unit }}
<el-date-picker </el-form-item>
v-model="dataForm.startTime" </el-col>
type="date" <el-col :span="12">
value-format="timestamp" <el-form-item label="备注" prop="remark">
:style="{ width: '100%' }" <el-input v-model="dataForm.remark" clearable placeholder="请输入备注" />
placeholder="选择开始时间"></el-date-picker> </el-form-item>
</el-form-item> </el-col>
</el-col> </el-row>
<el-col :span="12"> </el-form>
<el-form-item label="生效结束时间" prop="endTime">
<el-date-picker
v-model="dataForm.endTime"
type="date"
value-format="timestamp"
:style="{ width: '100%' }"
placeholder="选择结束时间"></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="单价" prop="price">
<el-input-number
:min="0"
style="width: 75%"
v-model="dataForm.price"
clearable
placeholder="请输入允许留存时间" />
{{ unit }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="备注" prop="remark">
<el-input
v-model="dataForm.remark"
clearable
placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</template> </template>
<script> <script>
@@ -146,7 +116,7 @@ export default {
this.dataForm.code = item.code; this.dataForm.code = item.code;
this.unit = this.unit =
'元/' + '元/' +
this.urlOptions.dictArr.dict0.find((d) => d.value === item.unit) this.urlOptions.dictArr.dict0.find((d) => d.value == item.unit)
.label; .label;
} }
}); });

View File

@@ -47,15 +47,15 @@ const tableProps = [
prop: 'equipmentName', prop: 'equipmentName',
label: '设备', label: '设备',
}, },
{ // {
prop: 'size', // prop: 'size',
label: '规格', // label: '规格',
showOverflowtooltip: true, // showOverflowtooltip: true,
}, // },
{ // {
prop: 'process', // prop: 'process',
label: '产品工艺', // label: '产品工艺',
}, // },
{ {
prop: 'standardCt', prop: 'standardCt',
label: '标准节拍pcs/min', label: '标准节拍pcs/min',
@@ -119,16 +119,16 @@ export default {
}, },
methods: { methods: {
// 获取数据列表 // 获取数据列表
init(lineId, startTime, endTime) { init(lId, startTime, endTime) {
this.eqChartData = []; this.eqChartData = [];
this.time.startTime = startTime; this.time.startTime = startTime;
this.time.endTime = endTime; this.time.endTime = endTime;
this.dataListLoading = true; this.dataListLoading = true;
getNewCTDet(lineId).then((response) => { getNewCTDet({ lineId: [lId], startTime, endTime }).then((response) => {
this.tableData = response.data; this.tableData = response.data;
this.dataListLoading = false; this.dataListLoading = false;
}); });
}, },
handleClick(val) { handleClick(val) {
const data = { const data = {
...this.time, ...this.time,

View File

@@ -92,7 +92,7 @@ export default {
filter: (val) => moment(val).format('yyyy-MM-DD HH:mm:ss'), filter: (val) => moment(val).format('yyyy-MM-DD HH:mm:ss'),
}, },
{ prop: 'name', label: '设备类型名称' }, { prop: 'name', label: '设备类型名称' },
{ prop: 'code', label: '检测类型编码' }, { prop: 'code', label: '设备类型编码' },
{ prop: 'remark', label: '备注' }, { prop: 'remark', label: '备注' },
], ],
searchBarFormConfig: [ searchBarFormConfig: [

View File

@@ -1,157 +1,114 @@
<template> <template>
<el-drawer <el-drawer :visible.sync="visible" :show-close="false" :wrapper-closable="false" class="drawer" size="60%">
:visible.sync="visible" <small-title slot="title" :no-padding="true">
:show-close="false" {{ isdetail ? '详情' : !dataForm.id ? '新增' : '编辑' }}
:wrapper-closable="false" </small-title>
class="drawer"
size="60%">
<small-title slot="title" :no-padding="true">
{{ isdetail ? '详情' : !dataForm.id ? '新增' : '编辑' }}
</small-title>
<div class="content"> <div class="content">
<div class="visual-part"> <div class="visual-part">
<el-form <el-form ref="dataForm" :model="dataForm" :rules="dataRule" label-width="100px" label-position="top"
ref="dataForm" @keyup.enter.native="dataFormSubmit">
:model="dataForm" <el-row :gutter="20">
:rules="dataRule" <el-col :span="12">
label-width="100px" <el-form-item label="产品编码" prop="code">
label-position="top" <el-input v-model="dataForm.code" clearable :disabled="isdetail" placeholder="请输入产品编码" />
@keyup.enter.native="dataFormSubmit"> </el-form-item>
<el-row :gutter="20"> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="产品编码" prop="code"> <el-form-item label="产品名称" prop="name">
<el-input <el-input v-model="dataForm.name" clearable :disabled="isdetail" placeholder="请输入产品名称" />
v-model="dataForm.code" </el-form-item>
clearable </el-col>
:disabled="isdetail" </el-row>
placeholder="请输入产品编码" /> <el-row :gutter="20">
</el-form-item> <el-col :span="12">
</el-col> <el-form-item label="产品类型" prop="typeDictValue">
<el-col :span="12"> <el-select v-model="dataForm.typeDictValue" style="width: 100%" :disabled="isdetail"
<el-form-item label="产品名称" prop="name"> placeholder="请选择产品类型">
<el-input <el-option v-for="dict in getDictDatas(DICT_TYPE.PRODUCT_TYPE)" :key="dict.value" :label="dict.label"
v-model="dataForm.name" :value="dict.value" />
clearable </el-select>
:disabled="isdetail" </el-form-item>
placeholder="请输入产品名称" /> </el-col>
</el-form-item> <el-col :span="12">
</el-col> <el-form-item label="单位" prop="unitDictValue">
</el-row> <el-select v-model="dataForm.unitDictValue" style="width: 100%" :disabled="isdetail"
<el-row :gutter="20"> placeholder="请选择单位">
<el-col :span="12"> <el-option v-for="dict in getDictDatas(DICT_TYPE.UNIT_DICT)" :key="dict.value" :label="dict.label"
<el-form-item label="产品类型" prop="typeDictValue"> :value="dict.value" />
<el-select </el-select>
v-model="dataForm.typeDictValue" </el-form-item>
style="width: 100%" </el-col>
:disabled="isdetail" </el-row>
placeholder="请选择产品类型"> <el-row :gutter="20">
<el-option <el-col :span="12">
v-for="dict in getDictDatas(DICT_TYPE.PRODUCT_TYPE)" <el-form-item label="原片规格" prop="originalSpecifications">
:key="dict.value" <el-input :disabled="isdetail" v-model="dataForm.originalSpecifications" placeholder="请输入原片规格" />
:label="dict.label" </el-form-item>
:value="dict.value" /> </el-col>
</el-select> <el-col :span="12">
</el-form-item> <el-form-item label="原片单位平方数" prop="originalArea">
</el-col> <el-input :disabled="isdetail" v-model="dataForm.originalArea" placeholder="请输入原片单位平方数" />
<el-col :span="12"> </el-form-item>
<el-form-item label="单位" prop="unitDictValue"> </el-col>
<el-select </el-row>
v-model="dataForm.unitDictValue" <el-row :gutter="20">
style="width: 100%" <el-col :span="12">
:disabled="isdetail" <el-form-item label="深加工规格" prop="specifications">
placeholder="请选择单位"> <el-input :disabled="isdetail" v-model="dataForm.specifications" placeholder="请输入深加工规格" />
<el-option </el-form-item>
v-for="dict in getDictDatas(DICT_TYPE.UNIT_DICT)" </el-col>
:key="dict.value" <el-col :span="12">
:label="dict.label" <el-form-item label="深加工单位平方数" prop="area">
:value="dict.value" /> <el-input :disabled="isdetail" v-model="dataForm.area" placeholder="请输入深加工单位平方数" />
</el-select> </el-form-item>
</el-form-item> </el-col>
</el-col> </el-row>
</el-row> <el-row :gutter="20">
<el-row :gutter="20"> <el-col :span="12">
<el-col :span="12"> <el-form-item label="完成单位产品用时(S)" prop="processTime">
<el-form-item label="原片规格" prop="originalSpecifications"> <el-input :disabled="isdetail" v-model="dataForm.processTime" placeholder="请输入完成单位产品用时(S)" />
<el-input </el-form-item>
:disabled="isdetail" </el-col>
v-model="dataForm.originalSpecifications" <el-col :span="12">
placeholder="请输入原片规格" /> <el-form-item label=" 产品工艺" prop="processTypes">
</el-form-item> <el-select :disabled="isdetail" collapse-tags multiple v-model="dataForm.processTypes" clearable
</el-col> style="width: 100%" placeholder="请选择产品工艺">
<el-col :span="12"> <el-option v-for="dict in processTypeList" :key="dict.value" :label="dict.label"
<el-form-item label="原片单位平方数" prop="originalArea"> :value="dict.value" />
<el-input </el-select>
:disabled="isdetail" </el-form-item>
v-model="dataForm.originalArea" </el-col>
placeholder="请输入原片单位平方数" /> </el-row>
</el-form-item> <el-row :gutter="20">
</el-col> <el-col :span="12">
</el-row> <el-form-item label=" 基板类型" prop="typeDictValue">
<el-row :gutter="20"> <el-select :disabled="isdetail" v-model="dataForm.typeDictValue" clearable style="width: 100%"
<el-col :span="12"> placeholder="请选择基板类型">
<el-form-item label="深加工规格" prop="specifications"> <el-option v-for="dict in typeList" :key="dict.value" :label="dict.label" :value="dict.value" />
<el-input </el-select>
:disabled="isdetail" </el-form-item>
v-model="dataForm.specifications" </el-col>
placeholder="请输入深加工规格" /> </el-row>
</el-form-item> </el-form>
</el-col>
<el-col :span="12">
<el-form-item label="深加工单位平方数" prop="area">
<el-input
:disabled="isdetail"
v-model="dataForm.area"
placeholder="请输入深加工单位平方数" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="完成单位产品用时" prop="processTime">
<el-input
:disabled="isdetail"
v-model="dataForm.processTime"
placeholder="请输入完成单位产品用时" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<small-title <small-title style="margin: 16px 0; padding-left: 8px" :no-padding="true">
style="margin: 16px 0; padding-left: 8px" 产品属性列表
:no-padding="true"> </small-title>
产品属性列表
</small-title>
<div class="attr-list"> <div class="attr-list">
<base-table <base-table :table-props="tableProps" :page="listQuery.pageNo" :limit="listQuery.pageSize"
:table-props="tableProps" :add-button-show="isdetail ? null : '添加属性'" @emitButtonClick="addNew()" :table-data="productAttributeList">
:page="listQuery.pageNo" <method-btn v-if="!isdetail" slot="handleBtn" :width="120" label="操作" :method-list="tableBtn"
:limit="listQuery.pageSize" @clickBtn="handleClick" />
:add-button-show="isdetail ? null : '添加属性'" </base-table>
@emitButtonClick="addNew()" <pagination v-show="listQuery.total > 0" :total="listQuery.total" :page.sync="listQuery.pageNo"
:table-data="productAttributeList"> :limit.sync="listQuery.pageSize" :page-sizes="[5, 10, 15]" @pagination="getList" />
<method-btn </div>
v-if="!isdetail" </div>
slot="handleBtn" </div>
:width="120"
label="操作"
:method-list="tableBtn"
@clickBtn="handleClick" />
</base-table>
<pagination
v-show="listQuery.total > 0"
:total="listQuery.total"
:page.sync="listQuery.pageNo"
:limit.sync="listQuery.pageSize"
:page-sizes="[5, 10, 15]"
@pagination="getList" />
</div>
</div>
</div>
<!-- <div style="position: absolute; bottom: 24px; right: 24px"> <!-- <div style="position: absolute; bottom: 24px; right: 24px">
<el-button style="margin-right: 10px" @click="goback()">返回</el-button> <el-button style="margin-right: 10px" @click="goback()">返回</el-button>
<el-button v-if="isdetail" type="primary" @click="goEdit()"> <el-button v-if="isdetail" type="primary" @click="goEdit()">
编辑 编辑
@@ -167,319 +124,378 @@
</span> </span>
</div> --> </div> -->
<div class="drawer-body__footer"> <div class="drawer-body__footer">
<el-button style="" @click="goback()">取消</el-button> <el-button style="" @click="goback()">取消</el-button>
<el-button v-if="isdetail" type="primary" @click="goEdit()"> <el-button v-if="isdetail" type="primary" @click="goEdit()">
编辑 编辑
</el-button> </el-button>
<el-button v-else type="primary" @click="dataFormSubmit()"> <el-button v-else type="primary" @click="dataFormSubmit()">
确定 确定
</el-button> </el-button>
</div> </div>
<product-attr-add <product-attr-add v-if="addOrUpdateVisible" ref="addOrUpdate" :product-id="dataForm.id"
v-if="addOrUpdateVisible" @refreshDataList="getList" />
ref="addOrUpdate" </el-drawer>
:product-id="dataForm.id"
@refreshDataList="getList" />
</el-drawer>
</template> </template>
<script> <script>
import { import {
deleteProductAttr, deleteProductAttr,
getProductAttrPage, getProductAttrPage,
} from '@/api/core/base/productAttr'; } from '@/api/core/base/productAttr';
import { import {
createProduct, createProduct,
updateProduct, updateProduct,
getProduct, getProduct,
getCode, getCode,
} from '@/api/core/base/product'; } from '@/api/core/base/product';
import productAttrAdd from './attr-add'; import productAttrAdd from './attr-add';
import { parseTime } from '../../mixins/code-filter'; import { parseTime } from '../../mixins/code-filter';
import SmallTitle from './SmallTitle'; import SmallTitle from './SmallTitle';
import { listData } from "@/api/system/dict/data"; //数据字典接口
const tableBtn = [ const tableBtn = [
{ {
type: 'edit', type: 'edit',
btnName: '编辑', btnName: '编辑',
}, },
{ {
type: 'delete', type: 'delete',
btnName: '删除', btnName: '删除',
}, },
]; ];
const tableProps = [ const tableProps = [
{ {
prop: 'createTime', prop: 'createTime',
label: '添加时间', label: '添加时间',
filter: parseTime, filter: parseTime,
}, },
{ {
prop: 'name', prop: 'name',
label: '属性名', label: '属性名',
}, },
{ {
prop: 'value', prop: 'value',
label: '属性值', label: '属性值',
}, },
]; ];
export default { export default {
components: { productAttrAdd, SmallTitle }, components: { productAttrAdd, SmallTitle },
data() { data() {
return { return {
visible: false, visible: false,
addOrUpdateVisible: false, addOrUpdateVisible: false,
tableBtn, tableBtn,
tableProps, tableProps,
productAttributeList: [], productAttributeList: [],
dataForm: { dataForm: {
id: null, id: null,
name: '', // 产品名称 name: '', // 产品名称
code: '', // 产品编码 code: '', // 产品编码
area: 0, // 深加工单位平方数(float only) area: 0, // 深加工单位平方数(float only)
typeDictValue: null, // 产品类型id typeDictValue: null, // 产品类型id
processTime: null, // 单位产品用时 (s) processTime: null, // 单位产品用时 (s)
specifications: '', // 深加工规格 specifications: '', // 深加工规格
unitDictValue: '', // 单位id unitDictValue: '', // 单位id
originalSpecifications: '', // 原片规格 originalSpecifications: '', // 原片规格
originalArea: 0, // 原片单位平方数 originalArea: 0, // 原片单位平方数
}, processTypes: [],
listQuery: { typeDictValue: null,
pageSize: 10, },
pageNo: 1, typeList: [],
total: 0, listQuery: {
}, pageSize: 10,
dataRule: { pageNo: 1,
code: [ total: 0,
{ },
required: true, processTypeList: [
message: '产品编码不能为空', // {
trigger: 'blur', // value: '1',
}, // label: '压花丝印'
// { // },
// type: 'number', // {
// message: '产品编码为数字类型', // value: '2',
// trigger: 'blur', // label: '无印打孔'
// transfom: 'val => Number(val)', // },
// }, // {
], // value: '3',
name: [ // label: '单层镀膜'
{ // }, {
required: true, // value: '4',
message: '产品名称不能为空', // label: '双层镀膜'
trigger: 'blur', // }
}, ],
], dataRule: {
typeDictValue: [ code: [
{ {
required: true, required: true,
message: '产品类型不能为空', message: '产品编码不能为空',
trigger: 'blur', trigger: 'blur',
}, },
], // {
area: [ // type: 'number',
{ // message: '产品编码为数字类型',
type: 'number', // trigger: 'blur',
message: '请输入正确的数值', // transfom: 'val => Number(val)',
trigger: 'change', // },
transform: (val) => Number(val), ],
}, name: [
], {
processTime: [ required: true,
{ message: '产品名称不能为空',
required: true, trigger: 'blur',
message: '完成单位产品用时不能为空', },
trigger: 'blur', ],
}, typeDictValue: [
{ {
type: 'number', required: true,
message: '请输入正确的数值', message: '产品类型不能为空',
trigger: 'blur', trigger: 'blur',
transform: (val) => Number(val), },
}, ],
], area: [
}, {
isdetail: false, type: 'number',
}; message: '请输入正确的数值',
}, trigger: 'change',
methods: { transform: (val) => Number(val),
initData() { },
this.productAttributeList.splice(0); ],
this.listQuery.total = 0; processTime: [
}, {
init(id, isdetail) { required: true,
this.initData(); message: '完成单位产品用时不能为空',
this.isdetail = isdetail || false; trigger: 'blur',
this.dataForm.id = id || null; },
this.visible = true; {
type: 'number',
message: '请输入正确的数值',
trigger: 'blur',
transform: (val) => Number(val),
},
],
},
isdetail: false,
};
},
methods: {
async initData() {
this.productAttributeList.splice(0);
this.listQuery.total = 0;
const typeRes = await listData({
pageNo:
1,
pageSize
: 10,
dictType
: 'product_type'
})
this.$nextTick(() => { this.typeList = typeRes.data.list
this.$refs['dataForm'].resetFields(); console.log('typeRes', this.typeList);
const processTypeRes = await listData({
pageNo:
1,
pageSize
: 10,
dictType
: 'process_type'
})
if (this.dataForm.id) { this.processTypeList = processTypeRes.data.list
// 获取产品详情 console.log('typeRes', this.typeList);
getProduct(id).then((response) => { },
this.dataForm = response.data; init(id, isdetail) {
}); this.initData();
// 获取产品的属性列表 this.isdetail = isdetail || false;
this.getList(); this.dataForm.id = id || null;
} else { // this.dataForm.processTypes = [] // 清空工艺选择
getCode().then((res) => { this.visible = true;
this.dataForm.code = res.data;
});
}
});
},
getList() { this.$nextTick(() => {
// 获取产品的属性列表 this.$refs['dataForm'].resetFields();
getProductAttrPage({
...this.listQuery, if (this.dataForm.id) {
productId: this.dataForm.id, // 获取产品详情
}).then((response) => { getProduct(id).then((res) => {
this.productAttributeList = response.data.list; const resData = res.data || {};
this.listQuery.total = response.data.total; // 逐个字段赋值(保留响应式)
}); this.dataForm.name = resData.name || '';
}, this.dataForm.code = resData.code || '';
handleClick(raw) { this.dataForm.area = resData.area || 0;
if (raw.type === 'delete') { this.dataForm.typeDictValue = resData.typeDictValue || null;
this.$confirm( this.dataForm.processTime = resData.processTime || null;
`确定对${ this.dataForm.specifications = resData.specifications || '';
raw.data.name this.dataForm.unitDictValue = resData.unitDictValue || '';
? '[名称=' + raw.data.name + ']' this.dataForm.originalSpecifications = resData.originalSpecifications || '';
: '[序号=' + raw.data._pageIndex + ']' this.dataForm.originalArea = resData.originalArea || 0;
}进行删除操作?`,
'提示', // 处理工艺列表:确保是数组,过滤空值
{ this.dataForm.processTypes = resData.processType
confirmButtonText: '确定', ? resData.processType.split(',').filter(Boolean)
cancelButtonText: '取消', : [];
type: 'warning',
} console.log('工艺列表(编辑时):', this.dataForm.processTypes); // 验证是否为 ["1","2"] 格式
) });
.then(() => { // 获取产品的属性列表
deleteProductAttr(raw.data.id).then(({ data }) => { this.getList();
this.$message({ } else {
message: '操作成功', getCode().then((res) => {
type: 'success', this.dataForm.code = res.data;
duration: 1500, });
onClose: () => { }
this.getList(); });
}, },
});
}); getList() {
}) // 获取产品的属性列表
.catch(() => {}); getProductAttrPage({
} else { ...this.listQuery,
this.addNew(raw.data.id); productId: this.dataForm.id,
} }).then((response) => {
}, this.productAttributeList = response.data.list;
// 表单提交 this.listQuery.total = response.data.total;
dataFormSubmit() { });
this.$refs['dataForm'].validate((valid) => { },
if (valid) { handleClick(raw) {
// 修改的提交 if (raw.type === 'delete') {
if (this.dataForm.id) { this.$confirm(
updateProduct(this.dataForm).then((response) => { `确定对${raw.data.name
this.$modal.msgSuccess('修改成功'); ? '[名称=' + raw.data.name + ']'
this.visible = false; : '[序号=' + raw.data._pageIndex + ']'
this.$emit('refreshDataList'); }进行删除操作?`,
}); '提示',
return; {
} confirmButtonText: '确定',
// 添加的提交 cancelButtonText: '取消',
createProduct(this.dataForm).then((response) => { type: 'warning',
this.$modal.msgSuccess('新增成功'); }
this.$confirm(`是否新增产品属性?`, '系统提示', { )
confirmButtonText: '确定', .then(() => {
cancelButtonText: '取消', deleteProductAttr(raw.data.id).then(({ data }) => {
type: 'warning', this.$message({
}) message: '操作成功',
.then(() => { type: 'success',
duration: 1500,
onClose: () => {
this.getList();
},
});
});
})
.catch(() => { });
} else {
this.addNew(raw.data.id);
}
},
// 表单提交
dataFormSubmit() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
// 修改的提交
if (this.dataForm.id) {
updateProduct(this.dataForm).then((response) => {
this.$modal.msgSuccess('修改成功');
this.visible = false;
this.$emit('refreshDataList');
});
return;
}
// 添加的提交
createProduct(this.dataForm).then((response) => {
this.$modal.msgSuccess('新增成功');
this.$confirm(`是否新增产品属性?`, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
this.dataForm.id = response.data this.dataForm.id = response.data
this.addNew(); this.addNew();
}) })
.catch(() => { .catch(() => {
this.visible = false; this.visible = false;
this.$emit('refreshDataList'); this.$emit('refreshDataList');
}); });
}); });
} }
}); });
}, },
goEdit() { goEdit() {
this.isdetail = false; this.isdetail = false;
}, },
// 新增 / 修改 // 新增 / 修改
addNew(id) { addNew(id) {
if (this.dataForm.id) { if (this.dataForm.id) {
this.addOrUpdateVisible = true; this.addOrUpdateVisible = true;
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.addOrUpdate.init(id); this.$refs.addOrUpdate.init(id);
}); });
} else { } else {
this.$message('请先创建产品!'); this.$message('请先创建产品!');
} }
}, },
goback() { goback() {
this.$emit('refreshDataList'); this.$emit('refreshDataList');
this.visible = false; this.visible = false;
this.initData(); this.initData();
}, },
}, },
}; };
</script> </script>
<style scoped> <style scoped>
.drawer >>> .el-drawer { .drawer>>>.el-drawer {
border-radius: 8px 0 0 8px; border-radius: 8px 0 0 8px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.drawer >>> .el-form-item__label { .drawer>>>.el-form-item__label {
padding: 0; padding: 0;
} }
.drawer >>> .el-drawer__header { .drawer>>>.el-drawer__header {
margin: 0; margin: 0;
padding: 32px 32px 24px; padding: 32px 32px 24px;
border-bottom: 1px solid #dcdfe6; border-bottom: 1px solid #dcdfe6;
}
.drawer >>> .el-drawer__body {
flex: 1;
height: 1px;
display: flex;
flex-direction: column;
} }
.drawer >>> .content { .drawer>>>.el-drawer__body {
padding: 30px 24px; flex: 1;
flex: 1; height: 1px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
/* height: 100%; */
} }
.drawer >>> .visual-part { .drawer>>>.content {
flex: 1 auto; padding: 30px 24px;
max-height: 76vh; flex: 1;
overflow: hidden; display: flex;
overflow-y: scroll; flex-direction: column;
padding-right: 10px; /* 调整滚动条样式 */ /* height: 100%; */
} }
.drawer >>> .el-form, .drawer>>>.visual-part {
.drawer >>> .attr-list { flex: 1 auto;
padding: 0 16px; max-height: 76vh;
overflow: hidden;
overflow-y: scroll;
padding-right: 10px;
/* 调整滚动条样式 */
}
.drawer>>>.el-form,
.drawer>>>.attr-list {
padding: 0 16px;
} }
.drawer-body__footer { .drawer-body__footer {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
padding: 18px; padding: 18px;
} }
</style> </style>

View File

@@ -0,0 +1,278 @@
<template>
<div class="baseTable">
<el-table
:ref="id"
:data="renderData"
v-bind="$attrs"
:border="cancelBorder ? false : true"
@current-change="currentChange"
@selection-change="handleSelectionChange"
style="width: 100%"
:header-cell-style="{
background: '#F2F4F9',
color: '#606266',
}">
<!-- 多选 -->
<el-table-column
v-if="selectWidth"
type="selection"
:width="selectWidth" />
<!-- 序号 -->
<el-table-column
v-if="page && limit"
prop="_pageIndex"
:width="pageWidth"
align="center"
:fixed="cancelPageFixed ? false : true">
<template slot="header">
<el-popover placement="bottom-start" width="300" trigger="click">
<div
class="setting-box"
style="max-height: 400px; overflow-y: auto">
<el-checkbox
v-for="(item, index) in tableProps"
:key="'cb' + index"
v-model="selectedBox[index]"
:label="item.label" />
</div>
<i slot="reference" class="el-icon-s-tools" />
</el-popover>
</template>
</el-table-column>
<el-table-column
v-for="item in renderTableHeadList"
:key="item.prop"
v-bind="item"
:label="item.label"
:prop="item.prop"
:fixed="item.fixed || false"
:show-overflow-tooltip="item.showOverflowtooltip || false"
:sortable="item.sortable || false">
<template slot="header">
<span>{{ item.label }}</span>
</template>
<!-- 多表头 -->
<template v-if="item.children">
<el-table-column
v-for="sub in item.children"
:prop="sub.prop"
:key="sub.prop"
v-bind="sub"
:label="sub.label">
<template v-if="sub.children">
<el-table-column
v-for="ssub in sub.children"
:prop="ssub.prop"
:key="ssub.prop"
v-bind="ssub"
:label="ssub.label">
<template slot-scope="sscopeInner">
<component
:is="ssub.subcomponent"
v-if="ssub.subcomponent"
:key="sscopeInner.row.id"
:inject-data="{ ...sscopeInner.row, ...ssub }"
@emitData="emitData" />
<span v-else>
{{ sscopeInner.row[ssub.prop] | commonFilter(ssub.filter) }}
</span>
</template>
</el-table-column>
</template>
<template slot-scope="scopeInner">
<component
:is="sub.subcomponent"
v-if="sub.subcomponent"
:key="scopeInner.row.id"
:inject-data="{ ...scopeInner.row, ...sub }"
@emitData="emitData" />
<span v-else>
{{ scopeInner.row[sub.prop] | commonFilter(sub.filter) }}
</span>
</template>
</el-table-column>
</template>
<template slot-scope="scope">
<component
:is="item.subcomponent"
v-if="item.subcomponent"
:key="scope.row.id"
:itemProp="item.prop"
:inject-data="{ ...scope.row, ...item }"
@emitData="emitData" />
<span v-else>
{{ scope.row[item.prop] | commonFilter(item.filter) }}
</span>
</template>
</el-table-column>
<slot name="handleBtn" />
</el-table>
<!-- 表格底部加号 -->
<el-button
v-if="addButtonShow"
class="addButton"
icon="el-icon-plus"
@click="emitButtonClick">
{{ addButtonShow }}
</el-button>
</div>
</template>
<script>
export default {
name: 'BaseTable',
filters: {
commonFilter: (source, filterType = (a) => a) => {
return filterType(source);
},
},
props: {
cancelBorder: {
type: Boolean,
default: false,
},
cancelPageFixed: {
type: Boolean,
default: false,
},
tableData: {
type: Array,
required: true,
default: () => {
return [];
},
},
tableProps: {
type: Array,
default: () => {
return [];
},
},
id: {
type: String,
required: false,
default: '',
},
page: {
type: Number,
required: false,
default: 0,
},
pageWidth: {
type: Number,
required: false,
default: 70,
},
limit: {
type: Number,
required: false,
default: 0,
},
selectWidth: {
type: Number,
required: false,
default: 0,
},
addButtonShow: {
type: String,
required: false,
default: '',
},
},
data() {
return {
selectedBox: new Array(100).fill(true),
};
},
computed: {
renderTableHeadList() {
return this.tableProps.filter((item, index) => {
return this.selectedBox[index];
});
},
renderData() {
return this.tableData.map((item, index) => {
return {
...item,
_pageIndex: (this.page - 1) * this.limit + index + 1,
};
});
},
},
beforeMount() {
this.selectedBox = new Array(100).fill(true);
},
methods: {
currentChange(newVal, oldVal) {
this.$emit('current-change', { newVal, oldVal });
},
handleSelectionChange(val) {
this.$emit('selection-change', val);
},
emitData(val) {
this.$emit('emitFun', val);
},
emitButtonClick() {
this.$emit('emitButtonClick');
},
setCurrent(name, index) {
let _this = this;
let obj = _this.$refs[name].data[index];
_this.$refs[name].setCurrentRow(obj);
},
doLayout(name) {
this.$refs[name].doLayout();
},
},
};
</script>
<!-- <style scoped>
.baseTable .show-col-btn {
margin-right: 5px;
line-height: inherit;
cursor: pointer;
}
.baseTable .el-icon-refresh {
cursor: pointer;
}
.baseTable >>> .el-table .el-table__cell {
padding: 0;
height: 35px;
border: 1px solid rgb(220, 220, 220);
}
</style>
<style>
.baseTable .el-table__body tr.current-row > td.el-table__cell {
background-color: #eaf1fc;
}
.baseTable .el-table .el-table__cell {
padding: 0;
height: 35px;
}
.baseTable .addButton {
width: 100%;
height: 35px;
border-top: none;
color: #0b58ff;
border-color: #ebeef5;
border-radius: 0;
}
.baseTable .addButton:hover {
color: #0b58ff;
border-color: #ebeef5;
background-color: #fff;
}
.baseTable .addButton:focus {
border-color: #ebeef5;
background-color: #fff;
}
.el-tooltip__popper.is-dark {
background: rgba(0, 0, 0, 0.6) !important;
}
.el-tooltip__popper .popper__arrow,
.el-tooltip__popper .popper__arrow::after {
border-top-color: rgba(0, 0, 0, 0.4) !important;
}
</style> -->

View File

@@ -0,0 +1,331 @@
<!--
* @Author: zwq
* @Date: 2023-08-24 14:47:58
* @LastEditors: zwq
* @LastEditTime: 2025-02-25 14:03:40
* @Description:
-->
<template>
<div>
<search-bar :formConfigs="formConfig" ref="searchBarForm" @headBtnClick="buttonClick" />
<el-table id="detail" :data="tableData" :header-cell-style="{
background: '#F2F4F9',
color: '#606266',
}" border v-loading="dataListLoading" style="width: 100%" ref="dataList">
<el-table-column prop="lineName" label="产线" align="center" />
<!-- <el-table-column prop="sizes" width="105" showOverflowtooltip align="center" label="规格" /> -->
<!-- <el-table-column prop="process" label="产品工艺" align="center" /> -->
<el-table-column prop="inputN" label="磨边" align="center">
<el-table-column prop="edgeNum" label="投入数量/片" />
<el-table-column prop="edgeTime" label="数据上报时间">
<template v-slot="scope">
<span>
{{
scope.row.reportType === 0
? parseTime(scope.row.edgeTime)
: '-'
}}
</span>
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="outputN" label="打孔/丝印" align="center">
<el-table-column prop="drillCoating" label="投入数量">
<template v-slot="scope">
<span>
{{
(scope.row.drillNum ?? '-')
+ '/'
+ (scope.row.coatingNum ?? '-')
}}
</span>
</template>
</el-table-column>
<el-table-column prop="coatingTime" label="数据上报时间">
<template v-slot="scope">
<span>
{{
scope.row.reportType === 0
? parseTime(scope.row.coatingTime)
: '-'
}}
</span>
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="lossN" label="镀膜" align="center">
<el-table-column prop="silkNum" label="投入数量" />
<el-table-column prop="silkTime" label="数据上报时间">
<template v-slot="scope">
<span>
{{
scope.row.reportType === 0
? parseTime(scope.row.silkTime)
: '-'
}}
</span>
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="lossN" label="钢化" align="center">
<el-table-column prop="temperingNum" label="投入数量" />
<el-table-column prop="temperingTime" label="数据上报时间">
<template v-slot="scope">
<span>
{{
scope.row.reportType === 0
? parseTime(scope.row.temperingTime)
: '-'
}}
</span>
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="lossN" label="包装" align="center">
<el-table-column prop="packingNum" label="投入数量" />
<el-table-column prop="packingTime" label="数据上报时间">
<template v-slot="scope">
<span>
{{
scope.row.reportType === 0
? parseTime(scope.row.packingTime)
: '-'
}}
</span>
</template>
</el-table-column>
</el-table-column>
<!-- <el-table-column prop="lossRatio" label="不良率/%">
<template v-slot="scope">
<span>
{{
scope.row.lossRatio != null ? scope.row.lossRatio.toFixed(2) : '-'
}}
</span>
</template>
</el-table-column>
<el-table-column prop="outputRatio" label="投入产出率/%">
<template v-slot="scope">
<span>
{{
scope.row.outputRatio != null
? scope.row.outputRatio.toFixed(2)
: '-'
}}
</span>
</template>
</el-table-column>
<el-table-column prop="processingRatio" label="加工成品率/%">
<template v-slot="scope">
<span>
{{
scope.row.processingRatio != null
? scope.row.processingRatio.toFixed(2)
: '-'
}}
</span>
</template>
</el-table-column>
<el-table-column prop="lossD" label="不良详情" align="center">
<el-table-column prop="original" label="原片" align="center">
<el-table-column prop="originalLossNum" label="原片不良/片" />
<el-table-column prop="originalLossArea" label="原片不良/m²">
<template v-slot="scope">
<span>
{{
scope.row.originalLossArea != null
? scope.row.originalLossArea.toFixed(2)
: '-'
}}
</span>
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="edge" label="磨边" align="center">
<el-table-column prop="edgeLossNum" label="磨边不良/片" />
<el-table-column prop="edgeLossArea" label="磨边不良/m²">
<template v-slot="scope">
<span>
{{
scope.row.edgeLossArea != null
? scope.row.edgeLossArea.toFixed(2)
: '-'
}}
</span>
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="drill" label="打孔" align="center">
<el-table-column prop="drillLossNum" label="打孔不良/片" />
<el-table-column prop="drillLossArea" label="打孔不良/m²">
<template v-slot="scope">
<span>
{{
scope.row.drillLossArea != null
? scope.row.drillLossArea.toFixed(2)
: '-'
}}
</span>
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="coating" label="镀膜" align="center">
<el-table-column prop="coatingLossNum" label="镀膜不良/片" />
<el-table-column prop="coatingLossArea" label="镀膜不良/m²">
<template v-slot="scope">
<span>
{{
scope.row.coatingLossArea != null
? scope.row.coatingLossArea.toFixed(2)
: '-'
}}
</span>
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="silk" label="丝印" align="center">
<el-table-column prop="silkLossNum" label="丝印不良/片" />
<el-table-column prop="silkLossArea" label="丝印不良/m²">
<template v-slot="scope">
<span>
{{
scope.row.silkLossArea != null
? scope.row.silkLossArea.toFixed(2)
: '-'
}}
</span>
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="tempering" label="钢化" align="center">
<el-table-column prop="temperingLossNum" label="钢化不良/片" />
<el-table-column prop="temperingLossArea" label="钢化不良/m²">
<template v-slot="scope">
<span>
{{
scope.row.temperingLossArea != null
? scope.row.temperingLossArea.toFixed(2)
: '-'
}}
</span>
</template>
</el-table-column>
</el-table-column> -->
<el-table-column prop="down" label="下片" align="center">
<el-table-column prop="downNum" label="成品数量" />
<el-table-column prop="scrapNum" label="废片数量" />
<el-table-column prop="inputOutputRate" label="投入产出率" />
<el-table-column prop="yieldRate" label="加工成品率" />
</el-table-column>
</el-table-column>
</el-table>
</div>
</template>
<script>
import { getTeamReportPageDet, exportGroupProductReportExcel } from '@/api/core/monitoring/auto';
import * as XLSX from 'xlsx';
import FileSaver from 'file-saver';
export default {
components: {},
data() {
return {
tableData: [],
id:null,
dataListLoading: false,
formConfig: [
{
// type: this.$auth.hasPermi('base:factory:export') ? 'button' : '',
type: 'button',
btnName: '导出',
name: 'export',
color: 'warning',
},
],
};
},
components: {},
created() {},
mounted() {},
methods: {
// 获取数据列表
init(id) {
this.id = id
this.dataListLoading = true;
getTeamReportPageDet(id).then((response) => {
this.tableData = response.data?.map((item, index) => {
item.originalLossNum = item.original?.lossNum;
item.originalLossArea = item.original?.lossArea;
item.edgeLossNum = item.edge?.lossNum;
item.edgeLossArea = item.edge?.lossArea;
item.drillLossNum = item.drill?.lossNum;
item.drillLossArea = item.drill?.lossArea;
item.coatingLossNum = item.coating?.lossNum;
item.coatingLossArea = item.coating?.lossArea;
item.silkLossNum = item.silk?.lossNum;
item.silkLossArea = item.silk?.lossArea;
item.temperingLossNum = item.tempering?.lossNum;
item.temperingLossArea = item.tempering?.lossArea;
item.packingLossNum = item.packing?.lossNum;
item.packingLossArea = item.packing?.lossArea;
if (item.isSummaryReport) {
item.lineName = '合计';
}
return item;
});
this.dataListLoading = false;
});
},
// arraySpanMethod({ row, column, rowIndex, columnIndex }) {
// if (row.isSummaryReport) {
// if (columnIndex === 0) {
// return [1, 3];
// } else if (columnIndex === 1) {
// return [0, 0];
// } else if (columnIndex === 2) {
// return [0, 0];
// }
// }
// },
buttonClick(val) {
switch (val.btnName) {
case 'export':
this.handleExport();
break;
default:
console.log(val);
}
},
handleExport() {
this.$modal.confirm('是否确认导出').then(() => {
return exportGroupProductReportExcel({id:this.id});
}).then(response => {
console.log(response)
this.$download.excel(response, '班组生产报表-详情.xls');
}).catch(() => {})
// let tables = document.querySelector('#detail').cloneNode(true);
// let exportTable = XLSX.utils.table_to_book(tables);
// var exportTableOut = XLSX.write(exportTable, {
// bookType: 'xlsx',
// bookSST: true,
// type: 'array',
// });
// // sheetjs.xlsx为导出表格的标题名称
// try {
// FileSaver.saveAs(
// new Blob([exportTableOut], {
// type: 'application/octet-stream',
// }),
// '班组生产报表-详情.xlsx'
// );
// } catch (e) {
// if (typeof console !== 'undefined') console.log(e, exportTableOut);
// }
// return exportTableOut;
},
},
};
</script>

View File

@@ -0,0 +1,594 @@
<!--
* @Author: Do not edit
* @Date: 2023-08-29 14:59:29
* @LastEditTime: 2025-02-25 14:26:04
* @LastEditors: zwq
* @Description:
-->
<template>
<div class="app-container">
<search-bar
:formConfigs="formConfig"
ref="searchBarForm"
@headBtnClick="buttonClick" />
<base-table-s
v-if="showData.length"
class="right-aside"
v-loading="dataListLoading"
:table-props="tableProps"
:page="listQuery.pageNo"
:limit="listQuery.pageSize"
:table-data="showData">
<method-btn
v-if="showData.length"
slot="handleBtn"
:width="80"
label="操作"
:method-list="tableBtn"
@clickBtn="handleClick" />
</base-table-s>
<div v-else class="no-data-bg"></div>
<pagination
:limit.sync="listQuery.pageSize"
:page.sync="listQuery.pageNo"
:total="listQuery.total"
@pagination="getDataList" />
<base-dialog
:dialogTitle="addOrEditTitle"
:dialogVisible="addOrUpdateVisible"
@cancel="handleCancel"
@confirm="handleConfirm"
:before-close="handleCancel"
close-on-click-modal
top="0"
width="80%">
<gr-detail ref="grDetail" />
<slot name="footer">
<el-row slot="footer" type="flex" justify="end">
<el-col :span="24">
<el-button size="small" class="btnTextStyle" @click="handleCancel">
取消
</el-button>
</el-col>
</el-row>
</slot>
</base-dialog>
</div>
</template>
<script>
import grDetail from './gr-detail';
import { getTeamReportPage } from '@/api/core/monitoring/auto';
import { getFactoryPage } from '@/api/core/base/factory';
import { getGroupTeamPage } from '@/api/base/groupTeam';
// import codeFilter from '../../mixins/code-filter'
import * as XLSX from 'xlsx';
import FileSaver from 'file-saver';
import baseTableS from './baseTable.vue';
import { parseTime } from '@/filter/code-filter';
const tableProps = [
{
prop: 'reportType',
label: '报表类型',
fixed: true
},
{
prop: 'reportDate',
label: '日期',
width: 180,
fixed: true
},
{
prop: 'factoryName',
label: '工厂',
fixed: true
},
{
prop: 'teamName',
label: '班组',
fixed: true
},
{
prop: 'edgeNum',
label: '磨边',
fixed: true
}, {
prop: 'drillOrCoating',
label: '打孔/丝印',
fixed: true
}, {
prop: 'silkNum',
label: '镀膜',
fixed: true
}, {
prop: 'temperingNum',
label: '钢化',
fixed: true
}, {
prop: 'packingNum',
label: '包装',
fixed: true
},
{
prop: 'inputN',
label: '下片',
align: 'center',
children: [
{
prop: 'downNum',
label: '成品数量',
width:100
},
{
prop: 'scrapNum',
label: '废片数量',
width:100
},
{
prop: 'inputOutputRate',
label: '投入产出率',
width: 100
},
{
prop: 'yieldRate',
label: '加工成品率',
width: 100
},
],
},
// {
// prop: 'outputN',
// label: '产出',
// align: 'center',
// children: [
// {
// prop: 'outputNum',
// label: '产出数量/片',
// width:100
// },
// {
// prop: 'outputArea',
// label: '产出面积/㎡',
// filter: (val) => (val != null ? val.toFixed(2) : '-'),
// width:100
// },
// ],
// },
// {
// prop: 'lossN',
// label: '不良',
// align: 'center',
// children: [
// {
// prop: 'lossNum',
// label: '不良数量/片',
// width:100
// },
// {
// prop: 'lossArea',
// label: '不良面积/㎡',
// filter: (val) => (val != null ? val.toFixed(2) : '-'),
// width:100
// },
// ],
// },
// {
// prop: 'lossRatio',
// label: '不良率/%',
// filter: (val) => (val != null ? val.toFixed(2) : '-'),
// width:100
// },
// {
// prop: 'outputRatio',
// label: '投入产出率/%',
// filter: (val) => (val != null ? val.toFixed(2) : '-'),
// width:110
// },
// {
// prop: 'processingRatio',
// label: '加工成品率/%',
// filter: (val) => (val != null ? val.toFixed(2) : '-'),
// width:110
// },
// {
// prop: 'lossD',
// label: '不良详情',
// align: 'center',
// children: [
// {
// prop: 'original',
// label: '原片',
// align: 'center',
// children: [
// {
// prop: 'originalLossNum',
// label: '原片不良/片',
// width:100
// },
// {
// prop: 'originalLossArea',
// label: '原片不良/㎡',
// filter: (val) => (val != null ? val.toFixed(2) : '-'),
// width:100
// },
// ],
// },
// {
// prop: 'edge',
// label: '磨边',
// align: 'center',
// children: [
// {
// prop: 'edgeLossNum',
// label: '磨边不良/片',
// width:100
// },
// {
// prop: 'edgeLossArea',
// label: '磨边不良/㎡',
// filter: (val) => (val != null ? val.toFixed(2) : '-'),
// width:100
// },
// ],
// },
// {
// prop: 'drill',
// label: '打孔',
// align: 'center',
// children: [
// {
// prop: 'drillLossNum',
// label: '打孔不良/片',
// width:100
// },
// {
// prop: 'drillLossArea',
// label: '打孔不良/㎡',
// filter: (val) => (val != null ? val.toFixed(2) : '-'),
// width:100
// },
// ],
// },
// {
// prop: 'coating',
// label: '镀膜',
// align: 'center',
// children: [
// {
// prop: 'coatingLossNum',
// label: '镀膜不良/片',
// width:100
// },
// {
// prop: 'coatingLossArea',
// label: '镀膜不良/㎡',
// filter: (val) => (val != null ? val.toFixed(2) : '-'),
// width:100
// },
// ],
// },
// {
// prop: 'silk',
// label: '丝印',
// align: 'center',
// children: [
// {
// prop: 'silkLossNum',
// label: '丝印不良/片',
// width:100
// },
// {
// prop: 'silkLossArea',
// label: '丝印不良/㎡',
// filter: (val) => (val != null ? val.toFixed(2) : '-'),
// width:100
// },
// ],
// },
// {
// prop: 'tempering',
// label: '钢化',
// align: 'center',
// children: [
// {
// prop: 'temperingLossNum',
// label: '钢化不良/片',
// width:100
// },
// {
// prop: 'temperingLossArea',
// label: '钢化不良/㎡',
// filter: (val) => (val != null ? val.toFixed(2) : '-'),
// width:100
// },
// ],
// },
// {
// prop: 'packing',
// label: '包装',
// align: 'center',
// children: [
// {
// prop: 'packingLossNum',
// label: '包装不良/片',
// width:100
// },
// {
// prop: 'packingLossArea',
// label: '包装不良/㎡',
// filter: (val) => (val != null ? val.toFixed(2) : '-'),
// width:100
// },
// ],
// },
// ],
// },
];
export default {
components: {
baseTableS,
grDetail,
},
data() {
return {
urlOptions: {
getDataListURL: getTeamReportPage,
},
listQuery: {
reportType: 1,
pageSize: 10,
pageNo: 1,
startTime: undefined,
endTime: undefined,
total: 1,
},
fileName: '',
dataListLoading: false,
tableProps,
tableBtn: [
this.$auth.hasPermi(`monitoring:group-off:update`)
? {
type: 'eq',
btnName: '详情',
}
: undefined,
].filter((v) => v),
showData: [],
tableData: [],
formConfig: [
{
type: 'select',
label: '工厂',
selectOptions: [],
param: 'factoryId',
},
{
type: 'select',
label: '班组',
selectOptions: [],
param: 'teamId',
},
{
type: 'select',
label: '报表类型',
selectOptions: [
{
id: 0,
name: '班',
},
{
id: 1,
name: '日',
},
// {
// id: 2,
// name: '周',
// },
{
id: 3,
name: '月',
},
{
id: 4,
name: '年',
},
],
defaultSelect: 1,
param: 'reportType',
},
{
type: 'datePicker',
label: '时间范围',
dateType: 'daterange',
format: 'yyyy-MM-dd',
valueFormat: 'timestamp',
rangeSeparator: '-',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
param: 'timeVal',
},
{
type: this.$auth.hasPermi('monitoring:group-off:query') ? 'button' : '',
btnName: '查询',
name: 'search',
color: 'primary',
},
{
type: 'separate',
},
{
type: this.$auth.hasPermi('monitoring:group-off:export') ? 'button' : '',
// type: 'button',
btnName: '导出',
name: 'export',
color: 'warning',
},
],
addOrEditTitle: '',
addOrUpdateVisible: false,
};
},
created() {
// 获取当前时间
const now = new Date();
// 获取前一天的同一时间
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
// 设置为00:00:00
yesterday.setHours(0, 0, 0, 0);
// 设置为23:59:59
const end = new Date(yesterday.getTime());
end.setHours(23, 59, 59, 59);
this.listQuery.startTime = yesterday.getTime(), [end.getTime()];
this.listQuery.endTime = end.getTime()
this.$nextTick(() => {
this.$refs.searchBarForm.formInline.timeVal = [yesterday.getTime(),end.getTime()];
});
this.getDataList();
this.getPdLineList();
},
methods: {
handleExport() {
let tables = document.querySelector('.el-table').cloneNode(true);
const fix = tables.querySelector('.el-table__fixed');
const fixRight = tables.querySelector('.el-table__fixed-right');
if (fix) {
tables.removeChild(tables.querySelector('.el-table__fixed'));
}
if (fixRight) {
tables.removeChild(tables.querySelector('.el-table__fixed-right'));
}
let exportTable = XLSX.utils.table_to_book(tables);
exportTable.Sheets.Sheet1.A1.v = '序号' //导出表格第一列表头为序号
var exportTableOut = XLSX.write(exportTable, {
bookType: 'xlsx',
bookSST: true,
type: 'array',
});
// sheetjs.xlsx为导出表格的标题名称
try {
FileSaver.saveAs(
new Blob([exportTableOut], {
type: 'application/octet-stream',
}),
this.fileName + '班组生产报表.xlsx'
);
} catch (e) {
if (typeof console !== 'undefined') console.log(e, exportTableOut);
}
return exportTableOut;
},
getPdLineList() {
const params = {
pageSize: 100,
pageNo: 1,
};
getGroupTeamPage(params).then((res) => {
this.formConfig[1].selectOptions = res.data.list || [];
});
getFactoryPage(params).then((res) => {
this.formConfig[0].selectOptions = res.data.list || [];
});
},
buttonClick(val) {
switch (val.btnName) {
case 'search':
this.listQuery.pageNo = 1;
this.listQuery.pageSize = 10;
this.listQuery.factoryId = val.factoryId || undefined;
this.listQuery.teamId = val.teamId || undefined;
this.listQuery.reportType = val.reportType || undefined;
this.listQuery.startTime = val.timeVal
? val.timeVal[0]
: undefined;
this.listQuery.endTime = val.timeVal
? val.timeVal[1]
: undefined;
this.getDataList();
break;
case 'export':
this.handleExport();
break;
default:
console.log(val);
}
},
// 获取数据列表
getDataList() {
this.dataListLoading = true;
console.log(this.listQuery);
const arr = ['班', '日', '', '月', '年']; // 索引0对应班1对应日3对应月4对应年索引2留空
this.urlOptions.getDataListURL(this.listQuery).then((response) => {
if (!response.data.list) {
this.showData = [];
this.dataListLoading = false;
return;
}
this.tableData = response.data?.list.map((item, index) => {
item.reportType = arr[item.reportType] || item.reportType;
item.drillOrCoating = item.drillNum + "/" + item.coatingNum
item.originalLossNum = item.original?.lossNum;
item.originalLossArea = item.original?.lossArea;
item.edgeLossNum = item.edge?.lossNum;
item.edgeLossArea = item.edge?.lossArea;
item.drillLossNum = item.drill?.lossNum;
item.drillLossArea = item.drill?.lossArea;
item.coatingLossNum = item.coating?.lossNum;
item.coatingLossArea = item.coating?.lossArea;
item.silkLossNum = item.silk?.lossNum;
item.silkLossArea = item.silk?.lossArea;
item.temperingLossNum = item.tempering?.lossNum;
item.temperingLossArea = item.tempering?.lossArea;
item.packingLossNum = item.packing?.lossNum;
item.packingLossArea = item.packing?.lossArea;
return item;
});
this.listQuery.total = response.data?.total;
this.dataListLoading = false;
this.showData = this.tableData;
});
},
handleClick(val) {
this.addOrUpdateVisible = true;
const time = val.data.timeVal ? parseTime(val.data.timeVal[0]) + '-' + parseTime(val.data.timeVal[1]) : '- '
const teamName = val.data.teamName?val.data.teamName:'- '
const teamLeader = val.data.teamLeader?val.data.teamLeader:'- '
this.addOrEditTitle =
'时间:' +
time +
' 班组:' +
teamName +
' 组长:' +
teamLeader +
' 详情';
this.$nextTick(() => {
this.$refs.grDetail.init(val.data.id);
});
},
handleCancel() {
this.addOrUpdateVisible = false;
this.addOrEditTitle = '';
},
handleConfirm() {
this.handleCancel();
},
// 每页数
sizeChangeHandle(val) {
this.listQuery.pageSize = val;
this.listQuery.pageNo = 1;
this.getDataList();
},
// 当前页
currentChangeHandle(val) {
this.listQuery.pageNo = val;
this.getDataList();
},
},
};
</script>

View File

@@ -0,0 +1,280 @@
<template>
<div :class="className" :style="{ height: height, width: width, marginLeft: '10px' }" />
</template>
<script>
import * as echarts from 'echarts';
require('echarts/theme/macarons'); // 引入主题
import resize from '@/utils/chartMixins/resize';
const animationDuration = 1000;
export default {
mixins: [resize], // 混入 resize 逻辑(自适应窗口)
props: {
className: {
type: String,
default: 'chart',
},
title: {
type: String,
default: '',
},
width: {
type: String,
default: '100%',
},
height: {
type: String,
default: '300px',
},
barData: {
type: Array,
default: () => [],
},
},
data() {
return {
chart: null, // 图表实例
};
},
watch: {
// 监听 barData 变化(深度监听数组内部元素)
barData: {
deep: true,
handler: 'handleBarDataChange', // 调用处理方法
},
},
mounted() {
// 组件挂载后初始化图表(确保 DOM 已就绪)
this.$nextTick(() => {
this.initChart();
});
},
beforeDestroy() {
// 组件销毁前清理图表实例
if (this.chart) {
this.chart.dispose();
this.chart = null;
}
},
methods: {
// barData 变化时的处理方法
handleBarDataChange() {
// 确保 DOM 存在再更新图表
this.$nextTick(() => {
// 如果图表未初始化,先初始化;否则直接更新数据
if (!this.chart) {
this.initChart();
} else {
this.updateChart();
}
});
},
// 初始化图表
initChart() {
// 避免重复初始化(先销毁旧实例)
if (this.chart) {
this.chart.dispose();
}
// 确保 DOM 元素存在
if (!this.$el) {
console.error('图表容器 DOM 元素不存在');
return;
}
// 初始化图表实例
this.chart = echarts.init(this.$el, 'macarons');
// 设置图表配置
this.setChartOption();
},
// 更新图表数据(复用配置逻辑)
updateChart() {
if (!this.chart) return;
this.setChartOption();
},
// 图表配置项(抽离为单独方法,方便初始化和更新复用)
setChartOption() {
const dataValues = this.barData.flatMap(item => [item.inputNum || 0, item.outputNum || 0]);
const maxData = Math.max(...dataValues, 0); // 加 0 确保无数据时 maxData 为 0
// 2. 计算 Y 轴最大值(留 10% 余量,避免数据顶到顶部)
let yMax = 0;
if (maxData > 0) {
yMax = Math.ceil(maxData * 1.1); // 向上取整,确保刻度为整数
} else {
yMax = 100; // 无数据时默认最大值为 100避免 Y 轴消失
}
// 3. 计算 interval5 个刻度对应 4 个间隔,向上取整确保间隔为整数)
const yInterval = Math.ceil((yMax - 0) / 4); // min 固定为 0直接减 0
this.chart.setOption({
title: {
text: this.title
? '{space|}{tip|}{space|}{value|' + this.title + '}'
: '',
textStyle: {
rich: {
tip: {
width: 6,
height: 6,
borderRadius: 50,
backgroundColor: '#288AFF',
},
space: {
width: 8,
},
value: {
fontSize: 14,
color: 'black',
},
},
},
},
color: ['#288AFF', '#8EF0AB', '#FFDC94'],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
crossStyle: {
color: '#999',
},
},
},
legend: {
data: ['投入', '产出', '加工成品率'],
},
grid: {
left: 20,
right: 30,
top:40,
bottom: 10,
containLabel: true,
splitArea: {
show: false // 关键:关闭网格背景分区(去掉间隔色块)
}
},
xAxis: {
type: 'category',
data: this.barData.map((item) => item.lineName),
axisLine: {
lineStyle: {
color: 'rgba(0, 0, 0, 0.45)',
width: 1 // 轴线宽度(可选,默认 1可按需调整
}
},
// 2. 控制 X 轴刻度线颜色(与轴线颜色保持一致,视觉统一)
axisTick: {
lineStyle: {
color: 'rgba(0, 0, 0, 0.45)', // 刻度线颜色,需与轴线颜色匹配
width: 1 // 刻度线宽度(可选)
},
alignWithLabel: true // 可选:让刻度线与文字对齐(避免文字偏移时刻度线错位)
},
// 3. 控制 X 轴文字颜色(如:深灰色 rgba(0, 0, 0, 0.45)
axisLabel: {
color: 'rgba(0, 0, 0, 0.45)6', // 文字颜色,可自定义
fontSize: 12, // 可选:调整文字大小(默认 12按需修改
// 可选:文字过长时换行/省略(避免文字重叠,按需开启)
formatter: (value) => {
// 示例:文字超过 6 个字符时换行(可根据需求调整字符数)
if (value.length > 6) {
return value.slice(0, 6) + '\n' + value.slice(6);
}
return value;
}
},
// 原有配置(若需保留可解开注释)
// axisPointer: {
// type: 'shadow',
// }
},
yAxis: [
{
type: 'value',
name: '投入/产出 片',
min: 0, // 最小值固定为 0
max: yMax,
interval: yInterval,
splitArea: {
show: false
},
axisLabel: {
formatter: '{value}',
color: 'rgba(0, 0, 0, 0.45)'
},
// 可选:修改 Y 轴名称颜色(与文字颜色保持一致)
nameTextStyle: {
color: 'rgba(0, 0, 0, 0.45)'
}
},
{
type: 'value',
name: '加工成品率',
min: 0,
max: 100, // 成品率固定 0-100%
interval: 25, // 100 / 4 = 25刚好 5 个刻度0、25、50、75、100
axisLabel: {
formatter: '{value} %',
color: 'rgba(0, 0, 0, 0.45)'
},
splitArea: {
show: false
},
// 可选:修改 Y 轴名称颜色
nameTextStyle: {
color: 'rgba(0, 0, 0, 0.45)'
}
},
],
series: [
{
name: '投入',
type: 'bar',
barWidth: '20',
data: this.barData.map((item) => item.inputNum),
tooltip: {
valueFormatter: (value) => `${value}`,
},
animationDuration,
},
{
name: '产出',
type: 'bar',
barWidth: '20',
data: this.barData.map((item) => item.outputNum),
tooltip: {
valueFormatter: (value) => `${value}`,
},
animationDuration,
},
{
name: '加工成品率',
type: 'line',
yAxisIndex: 1,
tooltip: {
valueFormatter: (value) => `${value} %`,
},
label: {
show: true,
position: 'top',
distance: 6,
fontSize: 11,
color: '#333333',
formatter: (params) => `${params.value}` // 显示单位
},
data: this.barData.map((item) => item.processingRatio),
},
],
});
},
},
};
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,350 @@
<!--
* @Author: Do not edit
* @Date: 2023-08-29 14:59:29
* @LastEditTime: 2024-12-02 13:44:47
* @LastEditors: zwq
* @Description:
-->
<template>
<div class="app-container">
<!-- :isFold="true" 控制展开 -->
<search-bar
:formConfigs="formConfig"
ref="searchBarForm"
@headBtnClick="buttonClick" />
<base-table
v-if="showData.length"
class="right-aside"
v-loading="dataListLoading"
:table-props="tableProps"
:page="listQuery.pageNo"
:limit="listQuery.pageSize"
:table-data="showData"
>
<!-- <method-btn
v-if="tableBtn.length"
slot="handleBtn"
:width="120"
label="操作"
:method-list="tableBtn"
@clickBtn="handleClick" /> -->
</base-table>
<div v-else class="no-data-bg"></div>
<pagination
:limit.sync="listQuery.pageSize"
:page.sync="listQuery.pageNo"
:total="listQuery.total"
@pagination="getDataList" />
<!-- <el-dialog
title="提示"
:visible.sync="dialogVisible"
width="30%"
:before-close="handleClose">
<el-button type="primary" @click="exportXlsx">xlsx</el-button>
<el-button type="success" @click="exportPdf">pdf</el-button>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="dialogVisible = false"> </el-button>
</span>
</el-dialog> -->
</div>
</template>
<script>
import { parseTime } from '../../mixins/code-filter';
import { getDownLogHisData, getPdList, getThick } from '@/api/core/monitoring/index'
import * as XLSX from 'xlsx'
import FileSaver from 'file-saver'
import jsPDF from 'jspdf'
import html2canvas from 'html2canvas'
const tableProps = [
{
prop: 'productionLineName',
label: '产线'
},
{
prop: 'eqName',
label: '下片机械手编号'
},
{
prop: 'pos',
label: '工位编号'
},
{
prop: 'pallet',
label: '托数/托'
},
{
prop: 'palletNum',
label: '一托玻璃数量/片'
},
{
prop: 'startTime',
label: '开始时间',
filter: parseTime,
width: 160
},
{
prop: 'endTime',
label: '结束时间',
filter: parseTime,
width: 160
},
{
prop: 'length',
label: '玻璃长度/mm'
},
{
prop: 'width',
label: '玻璃宽度/mm',
},
{
prop: 'thick',
label: '玻璃长度/mm'
},
];
export default {
data() {
return {
urlOptions: {
getDataListURL: getDownLogHisData
},
tableData: [],
listQuery: {
pageSize: 10,
pageNo: 1,
total: 1,
eqName: undefined,
productionLineId: undefined,
thick:undefined
},
exportLoading: false,
dataListLoading: false,
selectedList: [],
dialogVisible: false,
addOrEditTitle: '',
addOrUpdateVisible: false,
tableProps,
tableBtn: [
{
type: 'his',
btnName: '历史',
},
].filter((v) => v),
tableData: [],
showData: [],
fileName: '',
formConfig: [
{
type: 'select',
label: '产线',
selectOptions: [],
param: 'productionLineId'
},
{
type: 'select',
label: '玻璃型号',
selectOptions: [],
param: 'thick'
},
{
type: 'datePicker',
label: '统计开始时间',
dateType: 'daterange',
format: 'yyyy-MM-dd',
valueFormat: "yyyy-MM-dd HH:mm:ss",
rangeSeparator: '-',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
param: 'timeVal',
defaultTime: ['00:00:00', '23:59:59'],
defaultSelect: []
},
{
type: 'button',
btnName: '查询',
name: 'search',
color: 'primary',
},
{
type: 'separate',
},
{
// type: this.$auth.hasPermi('base:factory:export') ? 'button' : '',
type: 'button',
btnName: '导出',
name: 'export',
color: 'warning',
}
],
};
},
mounted() {
this.$refs.searchBarForm.formInline.productionLineId = this.$route.query.productionLineId
this.$refs.searchBarForm.formInline.thick = this.$route.query.thick
this.listQuery.productionLineId = this.$route.query.productionLineId
this.listQuery.thick = this.$route.query.thick
this.getDataList()
this.getPdLineList()
console.log('this.$route.query', this.$route.query);
},
methods: {
handleClick(val) {
this.addOrUpdateVisible = true;
this.addOrEditTitle =
val.data?.factoryName + '-' + val.data?.lineName + ' 详情';
this.$nextTick(() => {
this.$refs.eqDetail.init(
val.data.lineId,
this.listQuery.startTime,
this.listQuery.endTime
);
});
},
exportXlsx() {
if (!this.showData.length) {
this.$message.warning('暂无数据可导出');
return;
}
this.exportLoading = true;
try {
// 1. 处理导出数据(格式化时间字段)
const exportData = this.showData.map(item => {
const formatItem = { ...item };
// 格式化时间字段
if (formatItem.startTime) formatItem.startTime = parseTime(formatItem.startTime);
if (formatItem.endTime) formatItem.endTime = parseTime(formatItem.endTime);
return formatItem;
});
// 2. 构建表头映射:{ prop: label },只保留表格配置中存在的列
const headerMap = {};
this.tableProps.forEach(col => {
if (col.prop && col.label) {
headerMap[col.prop] = col.label;
}
});
// 3. 转换数据将prop键名替换为label按tableProps顺序排列列
const formattedData = exportData.map(item => {
const newItem = {};
// 按表格配置顺序遍历列确保Excel列顺序与表格一致
this.tableProps.forEach(col => {
const prop = col.prop;
const label = col.label;
if (prop && label) {
// 处理可能的undefined值避免导出为空字符串
newItem[label] = item[prop] ?? '';
}
});
return newItem;
});
// 4. 创建工作表使用处理后的带label表头的数据
const worksheet = XLSX.utils.json_to_sheet(formattedData);
// 5. 创建工作簿并添加工作表
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, '下片日志数据');
// 6. 生成Excel文件
const excelBuffer = XLSX.write(workbook, {
bookType: 'xlsx',
type: 'array'
});
// 7. 保存文件
const blob = new Blob([excelBuffer], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'
});
// 8. 生成文件名(包含查询条件信息)
let fileName = '下片历史';
if (this.listQuery.productionLineId) {
const lineItem = this.formConfig[0].selectOptions.find(
item => item.id === this.listQuery.productionLineId
);
if (lineItem) fileName += lineItem.name + '_';
}
if (this.listQuery.thick) {
fileName += this.listQuery.thick + '_';
}
// 添加时间戳避免文件名重复
fileName += + '.xlsx';
FileSaver.saveAs(blob, fileName);
this.$message.success('导出成功');
} catch (error) {
console.error('导出失败:', error);
this.$message.error('导出失败,请重试');
} finally {
this.exportLoading = false;
}
},
getPdLineList() {
getPdList().then((res) => {
this.formConfig[0].selectOptions = res.data || []
})
getThick().then((res) => {
this.formConfig[1].selectOptions = res.data.map((item) => {
return {
id: item.thick + 'mm',
name: item.thick + 'mm'
}
})
})
},
selectChange(val) {
console.log(val)
this.selectedList = val
},
buttonClick(val) {
switch (val.btnName) {
case 'search':
this.listQuery.pageNo = 1;
this.listQuery.pageSize = 10;
this.listQuery.productionLineId = val.productionLineId ? val.productionLineId : undefined;
this.listQuery.thick = val.thick ? val.thick : undefined;
this.listQuery.startTime = val.timeVal ? val.timeVal[0] : undefined;
this.listQuery.endTime = val.timeVal ? val.timeVal[1] : undefined;
//this.listQuery.reportEndTime = val.timeVal ? [new Date(val.timeVal[1]).getTime()] : undefined;
this.getDataList();
break;
case 'export':
this.exportXlsx();
break;
default:
console.log(val);
}
},
// 获取数据列表
getDataList() {
this.listQuery.eqName = this.$route.query.eqName
this.dataListLoading = true;
this.urlOptions.getDataListURL(this.listQuery).then(response => {
this.tableData = response.data.list
this.showData = this.tableData
this.listQuery.total = response.data.total;
this.dataListLoading = false;
});
},
// 每页数
sizeChangeHandle(val) {
this.listQuery.pageSize = val;
this.listQuery.pageNo = 1;
this.getDataList();
},
// 当前页
currentChangeHandle(val) {
this.listQuery.pageNo = val;
this.getDataList();
},
},
};
</script>

View File

@@ -0,0 +1,373 @@
<!--
* @Author: Do not edit
* @Date: 2023-08-29 14:59:29
* @LastEditTime: 2024-12-02 13:44:47
* @LastEditors: zwq
* @Description:
-->
<template>
<div class="app-container">
<!-- :isFold="true" 控制展开 -->
<search-bar
:formConfigs="formConfig"
ref="searchBarForm"
@headBtnClick="buttonClick" />
<base-table
v-if="showData.length"
class="right-aside"
v-loading="dataListLoading"
:table-props="tableProps"
:page="listQuery.pageNo"
:limit="listQuery.pageSize"
:table-data="showData"
>
<method-btn
v-if="tableBtn.length"
slot="handleBtn"
:width="120"
label="操作"
:method-list="tableBtn"
@clickBtn="handleClick" />
</base-table>
<div v-else class="no-data-bg"></div>
<pagination
:limit.sync="listQuery.pageSize"
:page.sync="listQuery.pageNo"
:total="listQuery.total"
@pagination="getDataList" />
<!-- <el-dialog
title="提示"
:visible.sync="dialogVisible"
width="30%"
:before-close="handleClose">
<el-button type="primary" @click="exportXlsx">xlsx</el-button>
<el-button type="success" @click="exportPdf">pdf</el-button>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="dialogVisible = false"> </el-button>
</span>
</el-dialog> -->
</div>
</template>
<script>
import { parseTime } from '../../mixins/code-filter';
import { getDownLogPage, getPdList, getThick, exportDownLogData } from '@/api/core/monitoring/index'
import * as XLSX from 'xlsx'
import FileSaver from 'file-saver'
import jsPDF from 'jspdf'
import html2canvas from 'html2canvas'
const tableProps = [
{
prop: 'productionLineName',
label: '产线'
},
{
prop: 'eqName',
label: '下片机械手编号'
},
{
prop: 'pos',
label: '工位编号'
},
{
prop: 'pallet',
label: '托数/托'
},
{
prop: 'palletNum',
label: '一托玻璃数量/片'
},
{
prop: 'startTime',
label: '开始时间',
filter: parseTime,
width: 160
},
{
prop: 'endTime',
label: '结束时间',
filter: parseTime,
width: 160
},
{
prop: 'length',
label: '玻璃长度/mm'
},
{
prop: 'width',
label: '玻璃宽度/mm',
},
{
prop: 'thick',
label: '玻璃厚度/mm'
},
];
export default {
data() {
return {
urlOptions: {
getDataListURL: getDownLogPage
},
tableData: [],
listQuery: {
pageSize: 10,
pageNo: 1,
total: 1,
productionLineId: undefined,
thick: undefined,
},
exportLoading: false,
dataListLoading: false,
selectedList: [],
dialogVisible: false,
addOrEditTitle: '',
addOrUpdateVisible: false,
tableProps,
tableBtn: [
{
type: 'his',
btnName: '历史',
},
].filter((v) => v),
tableData: [],
showData: [],
fileName: '',
formConfig: [
{
type: 'select',
label: '产线',
selectOptions: [],
param: 'productionLineId'
},
{
type: 'select',
label: '玻璃型号',
selectOptions: [],
param: 'thick'
},
{
type: 'datePicker',
label: '统计开始时间',
dateType: 'daterange',
format: 'yyyy-MM-dd',
valueFormat: "yyyy-MM-dd HH:mm:ss",
rangeSeparator: '-',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
param: 'timeVal',
defaultTime: ['00:00:00', '23:59:59'],
defaultSelect: []
},
{
type: 'button',
btnName: '查询',
name: 'search',
color: 'primary',
},
{
type: 'separate',
},
{
// type: this.$auth.hasPermi('base:factory:export') ? 'button' : '',
type: 'button',
btnName: '导出',
name: 'export',
color: 'warning',
}
],
};
},
created() {
this.getDataList()
this.getPdLineList()
},
methods: {
handleClick(val) {
console.log(val);
if (val.type === 'his') {
this.$router.push({
path: 'nextClipHis',
query: {
eqName: val.data.eqName,
productionLineId: this.listQuery.productionLineId,
thick: this.listQuery.thick,
},
});
}
// this.addOrUpdateVisible = true;
// this.addOrEditTitle =
// val.data?.factoryName + '-' + val.data?.lineName + ' 详情';
// this.$nextTick(() => {
// this.$refs.eqDetail.init(
// val.data.lineId,
// this.listQuery.startTime,
// this.listQuery.endTime
// );
// });
},
test() {
var target = document.getElementsByClassName("right-aside")[0]
target.style.background = '#FFFFFF'
var that = this
setTimeout(() => {
html2canvas(target).then(function(canvas) {
var contentWidth = canvas.width
var contentHeight = canvas.height
// 一页pdf显示html页面生成的canvas高度
var pageHeight = contentHeight / 592.28 * 841.89
// 未生成pdf的html页面高度
var leftHeight = contentHeight
// 页面偏移
var position = 0
// a4纸的尺寸[595.28,841.89]html页面生成的canvas在pdf中图片的高度
var imgWidth = 595.28
var imgHeight = 592.28 / contentWidth * contentHeight
var pageData = canvas.toDataURL('image/jpeg', 1.0)
console.log('nihc URL', leftHeight, pageHeight)
var pdf = new jsPDF('', 'pt', 'a4')
if (leftHeight < pageHeight) {
pdf.addImage(pageData, 'JPEG', 0, 20, imgWidth, imgHeight)
} else {
while(leftHeight > 0) {
pdf.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)
leftHeight -= pageHeight
position -= 841.89
// 避免空白页
if (leftHeight > 0) {
pdf.addPage()
}
}
}
pdf.save(that.fileName + '工段统计.pdf')
})
}, 300)
},
exportECL() {
let tables = document.querySelector('.el-table').cloneNode(true)
const fix = tables.querySelector('.el-table__fixed')
const fixRight = tables.querySelector('.el-table__fixed-right')
if (fix) {
tables.removeChild(tables.querySelector('.el-table__fixed'))
}
if (fixRight) {
tables.removeChild(tables.querySelector('.el-table__fixed-right'))
}
let exportTable = XLSX.utils.table_to_book(tables)
var exportTableOut = XLSX.write(exportTable, {
bookType: 'xlsx', bookSST: true, type: 'array'
})
// sheetjs.xlsx为导出表格的标题名称
try {
FileSaver.saveAs(new Blob([exportTableOut], {
type: 'application/octet-stream'
}), this.fileName + '工段统计.xlsx')
} catch (e) {
if (typeof console !== 'undefined') console.log(e, exportTableOut)
}
return exportTableOut
},
exportPdf() {
this.test()
setTimeout(() =>{
this.dialogVisible = false
this.showData = this.tableData
}, 600)
},
exportXlsx() {
this.exportECL()
this.dialogVisible = false
this.showData = this.tableData
},
handleClose(done) {
this.$confirm('确认关闭?')
.then(_ => {
done();
})
.catch(_ => {});
},
getPdLineList() {
getPdList().then((res) => {
this.formConfig[0].selectOptions = res.data || []
})
getThick().then((res) => {
this.formConfig[1].selectOptions = res.data.map((item) => {
return {
id: item.thick + 'mm',
name: item.thick + 'mm'
}
})
})
},
selectChange(val) {
console.log(val)
this.selectedList = val
},
buttonClick(val) {
switch (val.btnName) {
case 'search':
this.listQuery.pageNo = 1;
this.listQuery.pageSize = 10;
this.listQuery.productionLineId = val.productionLineId ? val.productionLineId : undefined;
this.listQuery.thick = val.thick ? val.thick : undefined;
this.listQuery.startTime = val.timeVal ? val.timeVal[0]: undefined;
this.listQuery.endTime = val.timeVal ? val.timeVal[1]: undefined;
//this.listQuery.reportEndTime = val.timeVal ? [new Date(val.timeVal[1]).getTime()] : undefined;
this.getDataList();
break;
case 'export':
this.handleExport();
break;
default:
console.log(val);
}
},
// 获取数据列表
getDataList() {
this.dataListLoading = true;
this.urlOptions.getDataListURL(this.listQuery).then(response => {
this.tableData = response.data.list
this.showData = this.tableData
this.listQuery.total = response.data.total;
this.dataListLoading = false;
});
},
// 每页数
sizeChangeHandle(val) {
this.listQuery.pageSize = val;
this.listQuery.pageNo = 1;
this.getDataList();
},
// 当前页
currentChangeHandle(val) {
this.listQuery.pageNo = val;
this.getDataList();
},
handleExport() {
// 处理查询参数
let params = { ...this.listQuery };
params.pageNo = undefined;
params.pageSize = undefined;
this.$modal.confirm('是否确认导出下片日志?').then(() => {
this.exportLoading = true;
return exportDownLogData(params);
}).then(response => {
this.$download.excel(response, '下片日志.xls');
this.exportLoading = false;
}).catch(() => { });
}
},
};
</script>

View File

@@ -0,0 +1,280 @@
<template>
<div :class="className" :style="{ height: height, width: width, marginLeft: '10px' }" />
</template>
<script>
import * as echarts from 'echarts';
require('echarts/theme/macarons'); // 引入主题
import resize from '@/utils/chartMixins/resize';
const animationDuration = 1000;
export default {
mixins: [resize], // 混入 resize 逻辑(自适应窗口)
props: {
className: {
type: String,
default: 'chart',
},
title: {
type: String,
default: '',
},
width: {
type: String,
default: '100%',
},
height: {
type: String,
default: '300px',
},
barData: {
type: Array,
default: () => [],
},
},
data() {
return {
chart: null, // 图表实例
};
},
watch: {
// 监听 barData 变化(深度监听数组内部元素)
barData: {
deep: true,
handler: 'handleBarDataChange', // 调用处理方法
},
},
mounted() {
// 组件挂载后初始化图表(确保 DOM 已就绪)
this.$nextTick(() => {
this.initChart();
});
},
beforeDestroy() {
// 组件销毁前清理图表实例
if (this.chart) {
this.chart.dispose();
this.chart = null;
}
},
methods: {
// barData 变化时的处理方法
handleBarDataChange() {
// 确保 DOM 存在再更新图表
this.$nextTick(() => {
// 如果图表未初始化,先初始化;否则直接更新数据
if (!this.chart) {
this.initChart();
} else {
this.updateChart();
}
});
},
// 初始化图表
initChart() {
// 避免重复初始化(先销毁旧实例)
if (this.chart) {
this.chart.dispose();
}
// 确保 DOM 元素存在
if (!this.$el) {
console.error('图表容器 DOM 元素不存在');
return;
}
// 初始化图表实例
this.chart = echarts.init(this.$el, 'macarons');
// 设置图表配置
this.setChartOption();
},
// 更新图表数据(复用配置逻辑)
updateChart() {
if (!this.chart) return;
this.setChartOption();
},
// 图表配置项(抽离为单独方法,方便初始化和更新复用)
setChartOption() {
const dataValues = this.barData.flatMap(item => [item.inputNum || 0, item.outputNum || 0]);
const maxData = Math.max(...dataValues, 0); // 加 0 确保无数据时 maxData 为 0
// 2. 计算 Y 轴最大值(留 10% 余量,避免数据顶到顶部)
let yMax = 0;
if (maxData > 0) {
yMax = Math.ceil(maxData * 1.1); // 向上取整,确保刻度为整数
} else {
yMax = 100; // 无数据时默认最大值为 100避免 Y 轴消失
}
// 3. 计算 interval5 个刻度对应 4 个间隔,向上取整确保间隔为整数)
const yInterval = Math.ceil((yMax - 0) / 4); // min 固定为 0直接减 0
this.chart.setOption({
title: {
text: this.title
? '{space|}{tip|}{space|}{value|' + this.title + '}'
: '',
textStyle: {
rich: {
tip: {
width: 6,
height: 6,
borderRadius: 50,
backgroundColor: '#288AFF',
},
space: {
width: 8,
},
value: {
fontSize: 14,
color: 'black',
},
},
},
},
color: ['#288AFF', '#8EF0AB', '#FFDC94'],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
crossStyle: {
color: '#999',
},
},
},
legend: {
data: ['投入', '产出', '加工成品率'],
},
grid: {
left: 20,
right: 30,
top:40,
bottom: 10,
containLabel: true,
splitArea: {
show: false // 关键:关闭网格背景分区(去掉间隔色块)
}
},
xAxis: {
type: 'category',
data: this.barData.map((item) => item.lineName),
axisLine: {
lineStyle: {
color: 'rgba(0, 0, 0, 0.45)',
width: 1 // 轴线宽度(可选,默认 1可按需调整
}
},
// 2. 控制 X 轴刻度线颜色(与轴线颜色保持一致,视觉统一)
axisTick: {
lineStyle: {
color: 'rgba(0, 0, 0, 0.45)', // 刻度线颜色,需与轴线颜色匹配
width: 1 // 刻度线宽度(可选)
},
alignWithLabel: true // 可选:让刻度线与文字对齐(避免文字偏移时刻度线错位)
},
// 3. 控制 X 轴文字颜色(如:深灰色 rgba(0, 0, 0, 0.45)
axisLabel: {
color: 'rgba(0, 0, 0, 0.45)6', // 文字颜色,可自定义
fontSize: 12, // 可选:调整文字大小(默认 12按需修改
// 可选:文字过长时换行/省略(避免文字重叠,按需开启)
formatter: (value) => {
// 示例:文字超过 6 个字符时换行(可根据需求调整字符数)
if (value.length > 6) {
return value.slice(0, 6) + '\n' + value.slice(6);
}
return value;
}
},
// 原有配置(若需保留可解开注释)
// axisPointer: {
// type: 'shadow',
// }
},
yAxis: [
{
type: 'value',
name: '投入/产出 片',
min: 0, // 最小值固定为 0
max: yMax,
interval: yInterval,
splitArea: {
show: false
},
axisLabel: {
formatter: '{value}',
color: 'rgba(0, 0, 0, 0.45)'
},
// 可选:修改 Y 轴名称颜色(与文字颜色保持一致)
nameTextStyle: {
color: 'rgba(0, 0, 0, 0.45)'
}
},
{
type: 'value',
name: '加工成品率',
min: 0,
max: 100, // 成品率固定 0-100%
interval: 25, // 100 / 4 = 25刚好 5 个刻度0、25、50、75、100
axisLabel: {
formatter: '{value} %',
color: 'rgba(0, 0, 0, 0.45)'
},
splitArea: {
show: false
},
// 可选:修改 Y 轴名称颜色
nameTextStyle: {
color: 'rgba(0, 0, 0, 0.45)'
}
},
],
series: [
{
name: '投入',
type: 'bar',
barWidth: '20',
data: this.barData.map((item) => item.inputNum),
tooltip: {
valueFormatter: (value) => `${value}`,
},
animationDuration,
},
{
name: '产出',
type: 'bar',
barWidth: '20',
data: this.barData.map((item) => item.outputNum),
tooltip: {
valueFormatter: (value) => `${value}`,
},
animationDuration,
},
{
name: '加工成品率',
type: 'line',
yAxisIndex: 1,
tooltip: {
valueFormatter: (value) => `${value} %`,
},
label: {
show: true,
position: 'top',
distance: 6,
fontSize: 11,
color: '#333333',
formatter: (params) => `${params.value}` // 显示单位
},
data: this.barData.map((item) => item.processingRatio),
},
],
});
},
},
};
</script>

View File

@@ -0,0 +1,273 @@
<template>
<div class="baseTable">
<el-table
:ref="id"
:data="renderData"
v-bind="$attrs"
:border="cancelBorder ? false : true"
@current-change="currentChange"
@selection-change="handleSelectionChange"
style="width: 100%"
:header-cell-style="{
background: '#F2F4F9',
color: '#606266',
}">
<!-- 多选 -->
<el-table-column
v-if="selectWidth"
type="selection"
:width="selectWidth" />
<!-- 序号 -->
<el-table-column
v-if="page && limit"
prop="_pageIndex"
:width="pageWidth"
align="center"
:fixed="cancelPageFixed ? false : true">
<template slot="header">
<el-popover placement="bottom-start" width="300" trigger="click">
<div
class="setting-box"
style="max-height: 400px; overflow-y: auto">
<el-checkbox
v-for="(item, index) in tableProps"
:key="'cb' + index"
v-model="selectedBox[index]"
:label="item.label" />
</div>
<i slot="reference" class="el-icon-s-tools" />
</el-popover>
</template>
</el-table-column>
<el-table-column
v-for="item in renderTableHeadList"
:key="item.prop"
v-bind="item"
:label="item.label"
:prop="item.prop"
:fixed="item.fixed || false"
:show-overflow-tooltip="item.showOverflowtooltip || false"
:sortable="item.sortable || false">
<template slot="header">
<span>{{ item.label }}</span>
</template>
<!-- 多表头 -->
<template v-if="item.children">
<el-table-column
v-for="sub in item.children"
:prop="sub.prop"
:key="sub.prop"
v-bind="sub"
:label="sub.label">
<template v-if="sub.children">
<el-table-column
v-for="ssub in sub.children"
:prop="ssub.prop"
:key="ssub.prop"
v-bind="ssub"
:label="ssub.label">
<template slot-scope="sscopeInner">
<component
:is="ssub.subcomponent"
v-if="ssub.subcomponent"
:key="sscopeInner.row.id"
:inject-data="{ ...sscopeInner.row, ...ssub }"
@emitData="emitData" />
<span v-else>
{{ sscopeInner.row[ssub.prop] | commonFilter(ssub.filter) }}
</span>
</template>
</el-table-column>
</template>
<template slot-scope="scopeInner">
<component
:is="sub.subcomponent"
v-if="sub.subcomponent"
:key="scopeInner.row.id"
:inject-data="{ ...scopeInner.row, ...sub }"
@emitData="emitData" />
<span v-else>
{{ scopeInner.row[sub.prop] | commonFilter(sub.filter) }}
</span>
</template>
</el-table-column>
</template>
<template slot-scope="scope">
<component
:is="item.subcomponent"
v-if="item.subcomponent"
:key="scope.row.id"
:itemProp="item.prop"
:inject-data="{ ...scope.row, ...item }"
@emitData="emitData" />
<span v-else>
{{ scope.row[item.prop] | commonFilter(item.filter) }}
</span>
</template>
</el-table-column>
<slot name="handleBtn" />
</el-table>
<!-- 表格底部加号 -->
<el-button
v-if="addButtonShow"
class="addButton"
icon="el-icon-plus"
@click="emitButtonClick">
{{ addButtonShow }}
</el-button>
</div>
</template>
<script>
export default {
name: 'BaseTable',
filters: {
commonFilter: (source, filterType = (a) => a) => {
return filterType(source);
},
},
props: {
cancelBorder: {
type: Boolean,
default: false,
},
cancelPageFixed: {
type: Boolean,
default: false,
},
tableData: {
type: Array,
required: true,
default: () => {
return [];
},
},
tableProps: {
type: Array,
default: () => {
return [];
},
},
id: {
type: String,
required: false,
default: '',
},
page: {
type: Number,
required: false,
default: 0,
},
pageWidth: {
type: Number,
required: false,
default: 70,
},
limit: {
type: Number,
required: false,
default: 0,
},
selectWidth: {
type: Number,
required: false,
default: 0,
},
addButtonShow: {
type: String,
required: false,
default: '',
},
},
data() {
return {
selectedBox: new Array(100).fill(true),
};
},
computed: {
renderTableHeadList() {
return this.tableProps.filter((item, index) => {
return this.selectedBox[index];
});
},
renderData() {
return this.tableData.map((item, index) => {
return {
...item,
_pageIndex: (this.page - 1) * this.limit + index + 1,
};
});
},
},
beforeMount() {
this.selectedBox = new Array(100).fill(true);
},
methods: {
currentChange(newVal, oldVal) {
this.$emit('current-change', { newVal, oldVal });
},
handleSelectionChange(val) {
this.$emit('selection-change', val);
},
emitData(val) {
this.$emit('emitFun', val);
},
emitButtonClick() {
this.$emit('emitButtonClick');
},
setCurrent(name, index) {
let _this = this;
let obj = _this.$refs[name].data[index];
_this.$refs[name].setCurrentRow(obj);
},
doLayout(name) {
this.$refs[name].doLayout();
},
},
};
</script>
<style scoped>
.baseTable .show-col-btn {
margin-right: 5px;
line-height: inherit;
cursor: pointer;
}
.baseTable .el-icon-refresh {
cursor: pointer;
}
</style>
<style>
.baseTable .el-table__body tr.current-row > td.el-table__cell {
background-color: #eaf1fc;
}
.baseTable .el-table .el-table__cell {
padding: 0;
height: 35px;
}
.baseTable .addButton {
width: 100%;
height: 35px;
border-top: none;
color: #0b58ff;
border-color: #ebeef5;
border-radius: 0;
}
.baseTable .addButton:hover {
color: #0b58ff;
border-color: #ebeef5;
background-color: #fff;
}
.baseTable .addButton:focus {
border-color: #ebeef5;
background-color: #fff;
}
.el-tooltip__popper.is-dark {
background: rgba(0, 0, 0, 0.6) !important;
}
.el-tooltip__popper .popper__arrow,
.el-tooltip__popper .popper__arrow::after {
border-top-color: rgba(0, 0, 0, 0.4) !important;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,65 @@
<!--
* @Author: zwq
* @Date: 2023-08-01 15:27:31
* @LastEditors: zwq
* @LastEditTime: 2023-08-01 16:25:54
* @Description:
-->
<template>
<div :class="[className, { 'p-0': noPadding }]">
<slot />
</div>
</template>
<script>
export default {
props: {
size: {
// 取值范围: xl lg md sm
type: String,
default: 'de',
validator: function (val) {
return ['xl', 'lg', 'de', 'md', 'sm'].indexOf(val) !== -1;
},
},
noPadding: {
type: Boolean,
default: false,
},
},
computed: {
className: function () {
return `${this.size}-title`;
},
},
};
</script>
<style lang="scss" scoped>
$pxls: (xl, 28px) (lg, 24px) (de, 20px) (md, 18px) (sm, 16px);
$mgr: 8px;
@each $size, $height in $pxls {
.#{$size}-title {
font-size: 18px;
line-height: $height;
color: #000;
font-weight: 500;
font-family: '微软雅黑', 'Microsoft YaHei', Arial, Helvetica, sans-serif;
&::before {
content: '';
display: inline-block;
vertical-align: top;
width: 4px;
height: $height + 2px;
border-radius: 1px;
margin-right: $mgr;
background-color: #0b58ff;
}
}
}
.p-0 {
padding: 0;
}
</style>

View File

@@ -0,0 +1,221 @@
<template>
<el-dialog :visible.sync="visible" width="40%">
<small-title slot="title" :no-padding="true">
{{ !dataForm.id ? '新增' : '编辑' }}
</small-title>
<div class="content">
<div class="visual-part">
<el-form ref="dataForm" :model="dataForm" :rules="dataRule" label-width="100px"
@keyup.enter.native="dataFormSubmit">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="维度" prop="statisticType">
<el-select v-model="dataForm.statisticType" style="width: 100%" placeholder="请选择维度">
<el-option v-for="item in statisticTypeList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="厚度" prop="modifyThick">
<el-input v-model="dataForm.modifyThick" placeholder="请输入厚度" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="在线速度" prop="modifySpeed">
<el-input v-model="dataForm.modifySpeed" placeholder="请输入在线速度" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="宽度" prop="modifyWidth">
<el-input v-model="dataForm.modifyWidth" placeholder="请输入宽度" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="拉引量" prop="modifyInArea">
<el-input v-model="dataForm.modifyInArea" placeholder="请输入拉引量" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="下片面积" prop="modifyOutArea">
<el-input v-model="dataForm.modifyOutArea" placeholder="请输入下片面积" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="良品率" prop="modifyRatio">
<el-input v-model="dataForm.modifyRatio" placeholder="请输入良品率" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button style="" @click="goback()">取消</el-button>
<el-button type="primary" @click="dataFormSubmit()">
确定
</el-button>
</div>
</el-dialog>
</template>
<script>
import { editCostOriginRadioHisData } from '@/api/core/monitoring/index'
import { parseTime } from '../../mixins/code-filter';
import SmallTitle from './SmallTitle';
export default {
components: { SmallTitle },
data() {
return {
visible: false,
addOrUpdateVisible: false,
statisticTypeList: [
{
id: '0',
name: '班组'
},
{
id: '1',
name: '日'
},
{
id: '2',
name: '周'
},
{
id: '3',
name: '月'
},
{
id: '4',
name: '年'
}
],
dataForm: {
id: null,
statisticType:undefined,
modifyThick: undefined,
modifySpeed: undefined,
modifyWidth: undefined,
modifyInArea: undefined,
modifyOutArea: undefined,
modifyRatio: undefined,
},
dataRule: {
statisticType: [
{
required: true,
message: '维度不能为空',
trigger: 'blur',
},
// {
// type: 'number',
// message: '产品编码为数字类型',
// trigger: 'blur',
// transfom: 'val => Number(val)',
// },
],
},
};
},
methods: {
init(data) {
console.log(data,'data');
this.dataForm.id = data.id || null;
this.visible = true;
this.$nextTick(() => {
this.$refs['dataForm'].resetFields();
this.dataForm = {
id: data.id || null,
statisticType: data.statisticType || undefined,
modifyThick: data.thick || undefined, // 厚度对应
modifySpeed: data.speed || undefined, // 在线速度对应
modifyWidth: data.width || undefined, // 掰边宽度对应
modifyInArea: data.inArea || undefined, // 拉引量对应
modifyOutArea: data.outArea || undefined, // 下片面积对应
modifyRatio: data.ratio || undefined, // 良品率对应
};
});
},
// 表单提交
dataFormSubmit() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
// 修改的提交
if (this.dataForm.id) {
editCostOriginRadioHisData(this.dataForm).then((response) => {
this.$modal.msgSuccess('修改成功');
this.visible = false;
this.$emit('refreshDataList');
});
return;
}
}
});
},
goback() {
this.$emit('refreshDataList');
this.visible = false;
this.initData();
},
},
};
</script>
<style scoped>
.drawer >>> .el-drawer {
border-radius: 8px 0 0 8px;
display: flex;
flex-direction: column;
}
.drawer >>> .el-form-item__label {
padding: 0;
}
.drawer >>> .el-drawer__header {
margin: 0;
padding: 32px 32px 24px;
border-bottom: 1px solid #dcdfe6;
}
.drawer >>> .el-drawer__body {
flex: 1;
height: 1px;
display: flex;
flex-direction: column;
}
.drawer >>> .content {
padding: 30px 24px;
flex: 1;
display: flex;
flex-direction: column;
/* height: 100%; */
}
.drawer >>> .visual-part {
flex: 1 auto;
max-height: 76vh;
overflow: hidden;
overflow-y: scroll;
padding-right: 10px; /* 调整滚动条样式 */
}
.drawer >>> .el-form,
.drawer >>> .attr-list {
padding: 0 16px;
}
.drawer-body__footer {
display: flex;
justify-content: flex-end;
padding: 18px;
}
</style>

View File

@@ -0,0 +1,536 @@
<template>
<div class="app-container">
<!-- :isFold="true" 控制展开 -->
<search-bar :formConfigs="formConfig" ref="searchBarForm" @headBtnClick="buttonClick"
@select-changed="selectType" />
<base-table v-if="tableData.length" class="right-aside" v-loading="dataListLoading" :table-props="tableProps"
:page="listQuery.pageNo" :limit="listQuery.pageSize" :table-data="tableData">
<method-btn v-if="tableBtn.length" slot="handleBtn" :width="120" label="操作" :method-list="tableBtn"
@clickBtn="handleClick" />
</base-table>
<div v-else class="no-data-bg"></div>
<pagination :limit.sync="listQuery.pageSize" :page.sync="listQuery.pageNo" :total="listQuery.total"
@pagination="getDataList" />
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList" />
</div>
</template>
<script>
import Vue from 'vue';
import AddOrUpdate from './add-or-updata';
import { parseTime } from '../../mixins/code-filter';
import { getCostOriginRadioHisData, getPdList } from '@/api/core/monitoring/index';
import { exportCostOriginRadioHisData } from '../../../../api/core/monitoring';
// Vue2 中注册全局方法(如果需要)
Vue.prototype.$download = Vue.prototype.$download || {
excel: (response, fileName) => {
const blob = new Blob([response.data], { type: 'application/vnd.ms-excel' });
const url = window.URL.createObjectURL(blob);
const aLink = document.createElement('a');
aLink.style.display = 'none';
aLink.href = url;
aLink.setAttribute('download', fileName);
document.body.appendChild(aLink);
aLink.click();
document.body.removeChild(aLink);
window.URL.revokeObjectURL(url);
}
};
Vue.prototype.$modal = Vue.prototype.$modal || {
confirm: (message) => {
return new Promise((resolve, reject) => {
if (window.confirm(message)) {
resolve();
} else {
reject();
}
});
}
};
const tableProps = [
{
prop: 'reportType',
label: '报表类型'
},
{
prop: 'time',
label: '日期',
width: 160
},
{
prop: 'bindObjectName',
label: '产线'
},
{
prop: 'thick',
label: '厚度'
},
{
prop: 'speed',
label: '在线速度'
},
{
prop: 'width',
label: '掰边宽度'
},
{
prop: 'inArea',
label: '拉引量/㎡'
},
{
prop: 'outArea',
label: '下片面积/㎡'
},
{
prop: 'ratio',
label: '良品率/%'
},
];
/**
* 工具函数获取选择时间所在周的起始和结束时间Vue2 兼容)
* @param {String|Date} selectTime - 选择的时间支持格式yyyy-MM-dd、yyyy-MM-dd HH:mm:ss 或 Date 对象)
* @returns {Array} [startDate, endDate] - 所在周周一 00:00:00 至 周日 23:59:59Date 对象)
*/
function getSelectedWeekRange(selectTime) {
// 兼容 String 类型时间和 Date 对象,统一转为 Date 实例
const targetDate = new Date(selectTime);
// 处理无效日期(若传入非法时间,返回当前时间的本周范围)
// if (isNaN(targetDate.getTime())) {
// console.warn('传入的时间格式无效,将使用当前时间计算本周范围');
// return getCurrentWeekRange(); // 可根据需求改为抛出错误或返回空
// }
const day = targetDate.getDay() || 7; // 周日为 7避免周日 -0 天仍为周日)
const start = new Date(targetDate);
start.setDate(targetDate.getDate() - day + 1); // 计算所在周的周一
start.setHours(0, 0, 0, 0); // 重置时分秒为 00:00:00.000
const end = new Date(start);
end.setDate(start.getDate() + 6); // 周一 +6 天 = 周日
end.setHours(23, 59, 59, 999); // 重置时分秒为 23:59:59.999
return [start, end];
}
/**
* 工具函数获取选择时间所在年的起始和结束时间Vue2 兼容)
* @param {String|Date} selectTime - 选择的时间支持格式yyyy-MM-dd、yyyy-MM-dd HH:mm:ss 或 Date 对象)
* @returns {Array} [startDate, endDate] - 所在年 1月1日 00:00:00 至 12月31日 23:59:59Date 对象)
*/
function getSelectedYearRange(selectTime) {
// 兼容 String 类型时间和 Date 对象,统一转为 Date 实例
const targetDate = new Date(selectTime);
// 处理无效日期(若传入非法时间,返回当前时间的本年范围)
// if (isNaN(targetDate.getTime())) {
// console.warn('传入的时间格式无效,将使用当前时间计算本年范围');
// return getCurrentYearRange(); // 可根据需求改为抛出错误或返回空
// }
const year = targetDate.getFullYear(); // 获取选择时间的年份
const start = new Date(year, 0, 1); // 所在年 1月1日月份从 0 开始)
start.setHours(0, 0, 0, 0); // 重置时分秒为 00:00:00.000
const end = new Date(year, 11, 31); // 所在年 12月31日11 代表 12 月)
end.setHours(23, 59, 59, 999); // 重置时分秒为 23:59:59.999
return [start, end];
}
// 格式化时间为 yyyy-MM-dd HH:mm:ssVue2 兼容)
function formatDateTime(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
export default {
components: {
AddOrUpdate
},
data() {
return {
urlOptions: {
getDataListURL: getCostOriginRadioHisData
},
tableData: [],
listQuery: {
pageSize: 10,
pageNo: 1,
total: 1,
bindObjectId: undefined,
statisticType: undefined,
startTime: undefined,
endTime: undefined
},
pdLineList: [],
exportLoading: false,
dataListLoading: false,
selectedList: [],
dialogVisible: false,
addOrEditTitle: '',
addOrUpdateVisible: false,
tableProps,
tableBtn: [
{
type: 'edit',
btnName: '编辑',
},
].filter(v => v),
fileName: '',
formConfig: [
{
type: 'select',
label: '报表类型',
onchange: true,
selectOptions: [
{ id: '0', name: '班组' },
{ id: '1', name: '日' },
{ id: '2', name: '周' },
{ id: '3', name: '月' },
{ id: '4', name: '年' }
],
param: 'statisticType',
index: 1,
extraOptions: [
{
parent: 'statisticType',
type: 'datePicker',
label: '统计时间',
dateType: 'daterange',
format: 'yyyy-MM-dd',
valueFormat: 'yyyy-MM-dd HH:mm:ss',
rangeSeparator: '-',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
param: 'timeVal',
defaultTime: ['00:00:00', '23:59:59'],
defaultSelect: [],
width: 250,
key: 'datePicker-0', // 唯一 key触发重新渲染
appendToBody: true // 优化定位:挂载到 body 下
},
// 日 - 日期范围选择
{
parent: 'statisticType',
type: 'datePicker',
label: '统计时间',
dateType: 'daterange',
format: 'yyyy-MM-dd',
valueFormat: 'yyyy-MM-dd HH:mm:ss',
rangeSeparator: '-',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
param: 'timeValDay',
defaultTime: ['00:00:00', '23:59:59'],
defaultSelect: [],
width: 250,
key: 'datePicker-1', // 唯一 key触发重新渲染
appendToBody: true // 优化定位:挂载到 body 下
},
// 周 - 单个日期选择(自动获取本周范围)
{
parent: 'statisticType',
type: 'datePicker',
label: '统计时间',
dateType: 'week',
placeholder: '选择日期',
format: 'yyyy 第 WW 周',
pickerOptions: {
firstDayOfWeek: 1 // 数字1表示周一作为周的第一天0=周日1=周一,依此类推)
},
valueFormat: 'yyyy-MM-dd',
param: 'timeValWeek',
width: 250,
key: 'datePicker-2', // 唯一 key触发重新渲染
appendToBody: true // 优化定位:挂载到 body 下
},
// 月 - 日期范围选择
{
parent: 'statisticType',
type: 'datePicker',
label: '统计时间',
dateType: 'monthrange',
format: 'yyyy-MM-dd',
valueFormat: 'yyyy-MM-dd HH:mm:ss',
rangeSeparator: '-',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
param: 'timeValMonth',
defaultTime: ['00:00:00', '23:59:59'],
width: 250,
key: 'datePicker-3', // 唯一 key触发重新渲染
appendToBody: true // 优化定位:挂载到 body 下
},
// 年 - 单个日期选择(自动获取本年范围)
{
parent: 'statisticType',
type: 'datePicker',
label: '统计时间',
dateType: 'year',
placeholder: '选择年份',
format: 'yyyy-MM-dd',
valueFormat: 'yyyy-MM-dd',
param: 'timeValYear',
width: 250,
key: 'datePicker-4', // 唯一 key触发重新渲染
appendToBody: true // 优化定位:挂载到 body 下
}
]
},
{
type: 'select',
label: '产线',
selectOptions: [],
param: 'bindObjectId'
},
{
type: 'button',
btnName: '查询',
name: 'search',
color: 'primary',
},
{
type: 'separate',
},
{
type: 'button',
btnName: '导出',
name: 'export',
color: 'warning',
}
],
};
},
watch: {
// 监听报表类型变化,强制刷新日期选择器
'listQuery.statisticType'(newVal, oldVal) {
if (newVal !== oldVal && this.$refs.searchBarForm) {
// 触发 search-bar 组件重新渲染(如果 search-bar 支持)
if (this.$refs.searchBarForm.$forceUpdate) {
this.$refs.searchBarForm.$forceUpdate();
}
// 延迟重置定位,确保 DOM 已更新
setTimeout(() => {
const datePickerEl = this.$refs.searchBarForm.$el.querySelector('.el-date-picker');
if (datePickerEl) {
// 触发 Element UI 日期选择器重新计算定位(内部方法)
const datePickerInstance = datePickerEl.__vue__;
if (datePickerInstance && datePickerInstance.updatePopper) {
datePickerInstance.updatePopper();
}
}
}, 100);
}
}
},
mounted() {
// Vue2 中 $refs 需在 $nextTick 中访问(确保 DOM 渲染完成)
this.$nextTick(() => {
if (this.$refs.searchBarForm) {
this.$refs.searchBarForm.formInline.statisticType = '1';
}
});
this.listQuery.statisticType = '1';
this.getDataList();
this.getPdLineList();
},
methods: {
selectType(val) {
// 报表类型切换时的回调(如需扩展可在此添加逻辑)
console.log('报表类型切换:', val);
},
handleClick(val) {
console.log('操作按钮点击:', val);
if (val.type === 'edit') {
this.addOrUpdateVisible = true;
// Vue2 中 $nextTick 确保子组件已渲染
this.$nextTick(() => {
this.$refs.addOrUpdate.init(val.data);
});
}
},
getPdLineList() {
getPdList().then(res => {
// Vue2 中数组赋值需确保响应式
this.$set(this.formConfig[1], 'selectOptions', res.data || []);
this.pdLineList = res.data || [];
}).catch(err => {
console.error('获取产线列表失败:', err);
});
},
selectChange(val) {
console.log('选择变更:', val);
this.selectedList = val;
},
buttonClick(val) {
console.log('头部按钮点击:', val);
switch (val.btnName) {
case 'search':
this.listQuery.pageNo = 1;
this.listQuery.pageSize = 10;
this.listQuery.bindObjectId = val.bindObjectId ? val.bindObjectId : undefined;
this.listQuery.statisticType = val.statisticType ? val.statisticType : undefined;
// 处理不同时间维度的时间范围
this.handleTimeRange(val);
this.getDataList();
break;
case 'export':
this.handleExport();
break;
default:
console.log('未知按钮:', val);
}
},
// 处理不同时间维度的时间范围
handleTimeRange(val) {
const statisticType = val.statisticType;
const timeVal = val.timeVal;
const timeValDay = val.timeValDay;
const timeValWeek = val.timeValWeek;
const timeValMonth = val.timeValMonth;
const timeValYear = val.timeValYear;
// 重置时间参数
this.listQuery.startTime = undefined;
this.listQuery.endTime = undefined;
switch (statisticType) {
case '0': // 班组 - 沿用原时间范围
if (timeVal && timeVal.length === 2) {
this.listQuery.startTime = timeVal[0];
this.listQuery.endTime = timeVal[1];
}
break;
case '1': // 日 - 沿用原时间范围
if (timeValDay && timeValDay.length === 2) {
this.listQuery.startTime = timeValDay[0];
this.listQuery.endTime = timeValDay[1];
}
break;
case '3': // 月 - 沿用原时间范围
if (timeValMonth && timeValMonth.length === 2) {
this.listQuery.startTime = timeValMonth[0];
this.listQuery.endTime = timeValMonth[1];
}
break;
case '2': // 周 - 自动计算本周范围
if (timeValWeek) {
const [start, end] = getSelectedWeekRange(timeValWeek);
this.listQuery.startTime = formatDateTime(start);
this.listQuery.endTime = formatDateTime(end);
}
break;
case '4': // 年 - 自动计算本年范围
if (timeValYear) {
const [start, end] = getSelectedYearRange(timeValYear);
this.listQuery.startTime = formatDateTime(start);
this.listQuery.endTime = formatDateTime(end);
}
break;
}
},
// 获取数据列表
getDataList() {
this.dataListLoading = true;
this.urlOptions.getDataListURL(this.listQuery)
.then(response => {
const arr = ['班组', '日', '周', '月', '年'];
// Vue2 中数组赋值确保响应式
this.tableData = (response.data?.list || []).map(item => {
item.reportType = arr[this.listQuery.statisticType] || '';
item.statisticType = this.listQuery.statisticType;
// 匹配产线名称
// const targetLine = this.pdLineList.find(line => line.id === item.bindObjectId);
// item.bindObjectName = targetLine ? targetLine.name : ''
return item;
})
this.listQuery.total = response.data?.total || 0;
})
.catch(err => {
console.error('获取数据失败:', err);
this.tableData = [];
this.listQuery.total = 0;
})
.finally(() => {
this.dataListLoading = false;
});
},
// 每页数变更
sizeChangeHandle(val) {
this.listQuery.pageSize = val;
this.listQuery.pageNo = 1;
this.getDataList();
},
// 当前页变更
currentChangeHandle(val) {
this.listQuery.pageNo = val;
this.getDataList();
},
// 导出处理
handleExport() {
const params = { ...this.listQuery };
// 移除分页参数
delete params.pageNo;
delete params.pageSize;
delete params.total;
this.$modal.confirm('是否确认导出原片报表?')
.then(() => {
this.exportLoading = true;
return exportCostOriginRadioHisData(params);
})
.then(response => {
this.$download.excel(response, '原片报表.xls');
})
.catch(err => {
console.error('导出失败:', err);
})
.finally(() => {
this.exportLoading = false;
});
}
},
// Vue2 中监听数据变化(如需)
watch: {
'listQuery.statisticType'(newVal) {
console.log('报表类型变更:', newVal);
// 可添加类型变更后的额外逻辑
}
}
};
</script>
<style scoped>
.app-container {
padding: 16px;
}
.no-data-bg {
height: 400px;
background-color: #f5f7fa;
display: flex;
align-items: center;
justify-content: center;
color: #999;
}
.right-aside {
margin-bottom: 16px;
}
</style>

View File

@@ -0,0 +1,274 @@
<template>
<div class="searchBarBox divHeight" ref="searchBarRef" :style="{ paddingRight: isFold ? '55px' : '0px' }">
<el-form :inline="true" ref="searchBarForm" :model="formInline" class="searchBar">
<span class="blue-block" v-if="removeBlue ? false : true"></span>
<template v-for="item in formConfig">
<el-form-item v-if="item.type !== ''" :key="item.param" :label="item.label ? item.label : ''"
:required="item.required ? item.required : false">
<el-input v-if="item.type === 'input'" v-model="formInline[item.param]"
:size="item.size ? item.size : 'small'" clearable :disabled="item.disabled ? item.disabled : false"
:style="item.width ? 'width:' + item.width + 'px' : 'width:200px'"
:placeholder="item.placeholder ? item.placeholder : ''" />
<el-select v-if="item.type === 'select'" v-model="formInline[item.param]"
:size="item.size ? item.size : 'small'" :filterable="item.filterable ? item.filterable : false"
:multiple="item.multiple ? item.multiple : false" :clearable="item.clearable === false ? false : true"
:style="item.width ? 'width:' + item.width + 'px' : 'width:200px'" :placeholder="item.label" @change="
item.onchange
? $emit('select-changed', {
param: item.param,
value: formInline[item.param]
})
: null
">
<el-option v-for="(sub, i) in item.selectOptions" :key="i"
:label="item.labelField ? sub[item.labelField] : sub['name']"
:value="item.valueField ? sub[item.valueField] : sub['id']" />
</el-select>
<el-date-picker v-if="item.type === 'datePicker'" :key="item.param" :size="item.size ? item.size : 'small'"
v-model="formInline[item.param]" :type="item.dateType" :format="item.format ? item.format : 'yyyy-MM-dd'"
:value-format="item.valueFormat ? item.valueFormat : null" :default-time="item.defaultTime || null"
:range-separator="item.rangeSeparator || null" :start-placeholder="item.startPlaceholder || null"
:end-placeholder="item.endPlaceholder || null" :placeholder="item.placeholder"
:picker-options="item.pickerOptions ? item.pickerOptions : null"
:clearable="item.clearable === false ? false : true"
:style="item.width ? 'width:' + item.width + 'px' : (item.dateType === 'datetimerange' ? 'width:340px' : (item.dateType === 'daterange' ? 'width:220px' : 'width:140px'))" />
<el-autocomplete v-if="item.type === 'autocomplete'" v-model="formInline[item.param]"
:value-key="item.valueKey ? item.valueKey : 'value'" :size="item.size ? item.size : 'small'"
:fetch-suggestions="item.querySearch" :placeholder="item.placeholder"
:clearable="item.clearable === false ? false : true"
:style="item.width ? 'width:' + item.width + 'px' : 'width:200px'" filterable />
<el-cascader v-if="item.type === 'cascader'" v-model="formInline[item.param]" :options="item.selectOptions"
:props="item.cascaderProps" :size="item.size ? item.size : 'small'"
:clearable="item.clearable === false ? false : true"
:show-all-levels="item.showAllLevels === false ? false : true"
:collapse-tags="item.collapseTags === true ? true : false"
:style="item.width ? 'width:' + item.width + 'px' : 'width:200px'" @change="
item.onChange
? $emit('cascader-change', {
param: item.param,
value: formInline[item.param]
})
: null
"></el-cascader>
<el-button v-if="item.type === 'button'" :type="item.color" :size="item.size ? item.size : 'small'"
:plain="item.plain ? item.plain : false" :round="item.round ? item.round : false"
@click="headBtnClick(item.name)">{{ item.btnName }}</el-button>
<span v-if="item.type === 'separate'" class="separateStyle"></span>
<!-- 可用于显示其他按钮 -->
</el-form-item>
</template>
<el-form-item>
<slot></slot>
</el-form-item>
</el-form>
<span v-if="isFold" class="foldClass" @click='switchMode'>
{{ isExpand ? '收起' : '展开' }}
<i class="iconfont" :class="isExpand ? 'icon-upward' : 'icon-downward'"></i>
</span>
</div>
</template>
<script>
export default {
name: 'SearchBar',
props: {
formConfigs: {
type: Array,
default: () => {
return []
}
},
removeBlue: {
type: Boolean,
default: false
},
isFold: {// 多行模式(默认否)
type: Boolean,
default: false
}
},
data() {
const formInline = {}
const formConfig = this.formConfigs
let hasExtraOptions = false
for (const obj of formConfig) {
if (obj.type !== 'button') {
if (obj.defaultSelect === false || obj.defaultSelect === 0) {
formInline[obj.param] = obj.defaultSelect
} else {
formInline[obj.param] = obj.defaultSelect || '' // defaultSelect下拉框默认选中项
}
}
if (obj.extraOptions) {
hasExtraOptions = true
}
}
return {
formInline,
formConfig,
hasExtraOptions,
isExpand: false // 是否展开(默认否)
}
},
watch: {
formConfig: {
handler() {
for (const obj of this.formConfig) {
if (obj.defaultSelect) {
this.formInline[obj.param] = obj.defaultSelect
} else if (obj.defaultSelect === null) {
// 需要手动从外部清除选项缓存的情况确保在外部配置项中可直接设置null
this.formInline[obj.param] = ''
}
}
},
deep: true,
immediate: true
},
formInline: {
handler: function () {
this.$forceUpdate()
},
deep: true,
immediate: true
}
},
mounted() {
this.$nextTick(() => {
this.init()
})
},
methods: {
init() {
if (this.hasExtraOptions) {
// 如果有额外参数就处理,如果没有就算了
for (const obj of this.formConfig) {
if (obj.extraOptions) {
// 注: 对obj.extraOptions的选择是互斥的!
this.$watch(
`formInline.${obj.param}`,
function (newVal) {
let deleteCount = 0
if (obj.index + 1 < this.formConfig.length) {
// 如果obj不是最后一个配置
const nextConfig = this.formConfig[obj.index + 1]
if (nextConfig.parent && nextConfig.parent === obj.param)
deleteCount = 1
}
const currentConfig = Object.assign(
{},
obj.extraOptions[newVal]
)
this.formConfig.splice(
obj.index + 1,
deleteCount,
currentConfig
)
// 修改 formInline
this.$set(this.formInline, currentConfig.param, '')
},
{ immediate: true }
)
}
}
}
},
headBtnClick(btnName) {
this.formInline.btnName = btnName
this.$emit('headBtnClick', this.formInline)
},
resetForm() {
this.$refs.searchBarForm.resetFields()
const formInline = {}
const formConfig = this.formConfigs
for (const obj of formConfig) {
if (obj.type !== 'button') {
if (obj.defaultSelect === false || obj.defaultSelect === 0) {
formInline[obj.param] = obj.defaultSelect
} else {
formInline[obj.param] = obj.defaultSelect || '' // defaultSelect下拉框默认选中项
}
}
}
this.formInline = formInline
},
switchMode() {// 展开和收起切换
this.isExpand = !this.isExpand
const element = this.$refs.searchBarRef
if (this.isExpand) {
element.classList.remove('divHeight')
} else {
element.classList.add('divHeight')
}
}
}
}
</script>
<style>
.searchBarBox {
width: 100%;
position: relative;
margin-bottom: 8px;
}
.searchBarBox::after {
content: "";
display: block;
clear: both;
}
.divHeight {
height: 45px;
overflow: hidden;
}
.searchBar .blue-block {
display: inline-block;
float: left;
width: 4px;
height: 16px;
background-color: #0B58FF;
border-radius: 1px;
margin-right: 8px;
margin-top: 12px;
}
.searchBar .el-form-item {
margin-bottom: 4px;
}
.searchBar .el-date-editor .el-range__icon {
font-size: 16px;
color: #0B58FF;
}
.searchBar .el-input__prefix .el-icon-date {
font-size: 16px;
color: #0B58FF;
}
.searchBar .el-input__prefix .el-icon-time {
font-size: 16px;
color: #0B58FF;
}
.searchBar .separateStyle {
display: inline-block;
width: 1px;
height: 24px;
background: #E8E8E8;
vertical-align: middle;
}
.searchBarBox .foldClass {
position: absolute;
top: 14px;
right: 0;
cursor: pointer;
font-size: 12px;
color: #0B58FF;
}
.searchBarBox .foldClass .iconfont {
font-size: 14px;
}
</style>

View File

@@ -0,0 +1,65 @@
<!--
* @Author: zwq
* @Date: 2023-08-01 15:27:31
* @LastEditors: zwq
* @LastEditTime: 2023-08-01 16:25:54
* @Description:
-->
<template>
<div :class="[className, { 'p-0': noPadding }]">
<slot />
</div>
</template>
<script>
export default {
props: {
size: {
// 取值范围: xl lg md sm
type: String,
default: 'de',
validator: function (val) {
return ['xl', 'lg', 'de', 'md', 'sm'].indexOf(val) !== -1;
},
},
noPadding: {
type: Boolean,
default: false,
},
},
computed: {
className: function () {
return `${this.size}-title`;
},
},
};
</script>
<style lang="scss" scoped>
$pxls: (xl, 28px) (lg, 24px) (de, 20px) (md, 18px) (sm, 16px);
$mgr: 8px;
@each $size, $height in $pxls {
.#{$size}-title {
font-size: 18px;
line-height: $height;
color: #000;
font-weight: 500;
font-family: '微软雅黑', 'Microsoft YaHei', Arial, Helvetica, sans-serif;
&::before {
content: '';
display: inline-block;
vertical-align: top;
width: 4px;
height: $height + 2px;
border-radius: 1px;
margin-right: $mgr;
background-color: #0b58ff;
}
}
}
.p-0 {
padding: 0;
}
</style>

View File

@@ -0,0 +1,134 @@
<template>
<el-dialog :visible.sync="visible" width="40%">
<small-title slot="title" :no-padding="true">
{{ this.dataForm.lineId + '·' + this.dataForm.equipmentName }}
</small-title>
<div class="content">
<div class="visual-part">
<base-table :table-props="tableProps"
:page="listQuery.pageNo" :limit="listQuery.pageSize" :table-data="tableData">
<!-- <method-btn v-if="tableBtn.length" slot="handleBtn" :width="120" label="操作" :method-list="tableBtn"
@clickBtn="handleClick" /> -->
</base-table>
</div>
</div>
<!-- <div slot="footer" class="dialog-footer">
<el-button style="" @click="goback()">取消</el-button>
<el-button type="primary" @click="dataFormSubmit()">
确定
</el-button>
</div> -->
</el-dialog>
</template>
<script>
const tableProps = [
{
prop: 'paramName',
label: '参数名称'
},
{
prop: 'paramValue',
label: '当前值',
// filter: parseTime,
// width: 160
},
];
import { getParamMonitor } from '@/api/base/equipment';
// import { parseTime } from '../../mixins/code-filter';
import SmallTitle from './SmallTitle';
export default {
components: { SmallTitle },
data() {
return {
visible: false,
tableProps,
tableData:[],
listQuery: {
pageNo: 1,
pageSize:100,
},
addOrUpdateVisible: false,
dataForm: {
equipmentId:undefined,
equipmentName: undefined,
lineId: undefined,
},
};
},
methods: {
init(data) {
console.log(data.paramMonitors,'data');
this.dataForm.equipmentId = data.equipmentId || '';
this.dataForm.equipmentName = data.equipmentName || '';
this.dataForm.lineId = data.lineId || '';
this.visible = true;
this.$nextTick(() => {
// this.$refs['dataForm'].resetFields();
// getParamMonitor({
// equipmentId:this.dataForm.equipmentId
// }).then((res) => {
this.tableData = data.paramMonitors
// })
});
}
},
};
</script>
<style scoped>
.drawer >>> .el-drawer {
border-radius: 8px 0 0 8px;
display: flex;
flex-direction: column;
}
.drawer >>> .el-form-item__label {
padding: 0;
}
.drawer >>> .el-drawer__header {
margin: 0;
padding: 32px 32px 24px;
border-bottom: 1px solid #dcdfe6;
}
.drawer >>> .el-drawer__body {
flex: 1;
height: 1px;
display: flex;
flex-direction: column;
}
.drawer >>> .content {
padding: 30px 24px;
flex: 1;
display: flex;
flex-direction: column;
/* height: 100%; */
}
.drawer >>> .visual-part {
flex: 1 auto;
max-height: 76vh;
overflow: hidden;
overflow-y: scroll;
padding-right: 10px; /* 调整滚动条样式 */
}
.drawer >>> .el-form,
.drawer >>> .attr-list {
padding: 0 16px;
}
.drawer-body__footer {
display: flex;
justify-content: flex-end;
padding: 18px;
}
</style>

View File

@@ -0,0 +1,563 @@
<template>
<el-dialog :visible.sync="visible" width="80%" @close="handleClose" title-class="dialog-title">
<small-title slot="title" :no-padding="true">
{{ dataForm.lineId + '·' + dataForm.equipmentName }}
</small-title>
<search-bar removeBlue :formConfigs="formConfig" ref="searchBarForm" @headBtnClick="buttonClick" />
<el-tabs class="custom-tabs" v-model="activeLabel" :stretch="true" @tab-click="handleTabClick">
<el-tab-pane :label="'\u3000报警时长\u3000'" name="duration"></el-tab-pane>
<el-tab-pane :label="'\u3000报警次数\u3000'" name="times"></el-tab-pane>
</el-tabs>
<div class="content">
<div class="visual-part">
<div v-if="hasData" style="display: flex; justify-content: space-around; gap: 20px; padding: 10px 0;">
<!-- 移除 v-if始终渲染两个图表容器 -->
<div id="barChart" style="width: 48%; height: 400px;"></div>
<div id="pieChart" style="width: 48%; height: 400px;"></div>
</div>
<div v-if="!hasData" class="no-data">
<el-empty description="暂无相关报警数据"></el-empty>
</div>
</div>
</div>
</el-dialog>
</template>
<script>
import { getAlarmDet } from '@/api/base/equipment';
import * as echarts from 'echarts';
import SmallTitle from './SmallTitle';
const CHART_CONFIG = {
barColor: '#288AFF',
pieColors: [
'#288AFF', '#4096FF', '#69B1FF', '#91CFFF', '#B8E0FF',
'#E0F2FF', '#1890FF', '#096DD9', '#0050B3', '#003A8C'
],
fontColor: '#333',
lightFontColor: '#666',
borderRadius: 4
};
export default {
components: { SmallTitle },
data() {
return {
visible: false,
hasData: false,
listQuery: {
pageNo: 1,
pageSize: 100,
equipmentId: undefined,
startTime: undefined,
endTime: undefined
},
formConfig: [
{
type: 'datePicker',
label: '时间段',
dateType: 'daterange',
format: 'yyyy-MM-dd',
valueFormat: 'timestamp',
rangeSeparator: '-',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
param: 'timeVal',
defaultTime: ['00:00:00', '23:59:59'],
defaultSelect: []
},
{
type: 'button',
btnName: '查询',
name: 'search',
color: 'primary'
}
],
activeLabel: 'duration', // 默认选中「报警时长」
dataForm: {
equipmentId: undefined,
equipmentName: undefined,
lineId: undefined
},
chartInstances: {
bar: null,
pie: null
},
isDomReady: false,
originData: null // 存储原始数据
};
},
mounted() {
this.$nextTick(() => {
this.isDomReady = true;
if (this.listQuery.equipmentId) {
this.getDataList();
}
});
},
watch: {
// Tab 切换时自动刷新图表(无需额外操作,依赖 handleTabClick 触发查询)
activeLabel() {
if (this.isDomReady && this.originData) {
this.$nextTick(() => {
this.renderBothCharts(); // 切换 Tab 后重新渲染两个图表
});
}
}
},
methods: {
initDefaultDate() {
const today = new Date();
const start = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0, 0).getTime();
const end = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 23, 59, 59, 0).getTime();
this.formConfig[0].defaultSelect = [start, end];
this.listQuery.startTime = start;
this.listQuery.endTime = end;
// 修复点1增加多层判断确保对象存在后再赋值
if (this.$refs.searchBarForm && this.$refs.searchBarForm.form) {
this.$refs.searchBarForm.form.timeVal = [start, end];
}
},
handleTabClick() {
// 切换 Tab 时重新查询数据(或直接复用已有数据渲染)
this.getDataList();
},
buttonClick(val) {
switch (val.btnName) {
case 'search':
this.listQuery.startTime = val.timeVal?.[0];
this.listQuery.endTime = val.timeVal?.[1];
this.getDataList();
break;
default:
}
},
async getDataList() {
try {
if (!this.listQuery.equipmentId) {
console.warn('设备ID不能为空');
this.hasData = false;
return;
}
const queryParams = {
equipmentId: this.listQuery.equipmentId,
startTime: this.listQuery.startTime,
endTime: this.listQuery.endTime,
};
const res = await getAlarmDet(queryParams);
const originData = res.data || [];
this.originData = originData;
this.hasData = originData.length > 0;
if (this.hasData && this.isDomReady) {
this.$nextTick(() => {
this.renderBothCharts(); // 数据查询成功后,同时渲染两个图表
});
} else {
this.destroyAllCharts();
}
} catch (error) {
console.error('获取报警数据失败:', error);
this.hasData = false;
this.destroyAllCharts();
}
},
// 核心方法:同时渲染柱状图和饼图(根据当前 Tab 类型)
renderBothCharts() {
if (this.activeLabel === 'duration') {
// 报警时长:柱状图(时长排序)+ 饼图(时长占比)
this.renderBarChart('duration');
this.renderPieChart('duration');
} else {
// 报警次数:柱状图(次数排序)+ 饼图(次数占比)
this.renderBarChart('times');
this.renderPieChart('times');
}
},
// 渲染柱状图(支持两种数据类型)
renderBarChart(type) {
this.destroyChart('bar');
const chartDom = document.getElementById('barChart');
if (!chartDom || !this.originData.length) return;
// 根据类型排序和提取数据
let sortedData, xData, seriesData, yAxisName;
if (type === 'duration') {
// 报警时长:按时长降序
sortedData = [...this.originData].sort((a, b) => b.alarmDuration - a.alarmDuration);
seriesData = sortedData.map(item => item.alarmDuration);
yAxisName = '报警时长';
} else {
// 报警次数:按次数降序
sortedData = [...this.originData].sort((a, b) => b.alarmCount - a.alarmCount);
seriesData = sortedData.map(item => item.alarmCount);
yAxisName = '报警次数';
}
xData = sortedData.map(item => this.truncateText(item.alarmContent, 8));
try {
this.chartInstances.bar = echarts.init(chartDom);
const option = {
title: {
text: `${yAxisName}统计(柱状图)`,
left: 'center',
textStyle: { fontSize: 14, color: CHART_CONFIG.fontColor }
},
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
padding: 10,
textStyle: { fontSize: 11 },
formatter: (params) => {
const index = params[0].dataIndex;
const item = sortedData[index];
return `
<div style="text-align: left;">
<div>${item.alarmContent}</div>
<div>${yAxisName}${type === 'duration' ? item.alarmDuration : item.alarmCount}</div>
<div>占比:${type === 'duration' ? item.alarmDurationRatio.toFixed(2) : item.alarmCountRatio.toFixed(2)}%</div>
</div>
`;
}
},
grid: {
left: '5%',
right: '5%',
bottom: '18%',
top: '15%',
containLabel: true
},
xAxis: [
{
type: 'category',
data: xData,
axisTick: { alignWithLabel: true },
axisLabel: {
interval: 0,
fontSize: 12,
color: CHART_CONFIG.lightFontColor
},
axisLine: { lineStyle: { color: '#e8e8e8' } }
}
],
yAxis: [
{
type: 'value',
name: yAxisName,
nameTextStyle: { fontSize: 11, color: CHART_CONFIG.lightFontColor },
axisLabel: {
fontSize: 11,
color: CHART_CONFIG.lightFontColor,
},
axisLine: { lineStyle: { color: '#e8e8e8' } },
splitLine: { lineStyle: { color: '#f5f5f5' } },
max: (value) => value.max * 1.2
}
],
series: [
{
name: yAxisName,
type: 'bar',
itemStyle: {
color: CHART_CONFIG.barColor,
borderRadius: [CHART_CONFIG.borderRadius, CHART_CONFIG.borderRadius, 0, 0],
shadowBlur: 3,
shadowColor: 'rgba(40, 138, 255, 0.2)',
shadowOffsetY: 2
},
barWidth: '16',
data: seriesData,
label: {
show: true,
position: 'top',
distance: 6,
fontSize: 11,
color: CHART_CONFIG.fontColor,
formatter: (params) => `${params.value}`
}
}
]
};
this.chartInstances.bar.setOption(option);
this.addResizeListener('bar');
} catch (error) {
console.error(`${yAxisName}柱状图初始化失败:`, error);
setTimeout(() => this.renderBarChart(type), 200);
}
},
// 渲染饼图(支持两种数据类型)
renderPieChart(type) {
this.destroyChart('pie');
const chartDom = document.getElementById('pieChart');
if (!chartDom || !this.originData.length) return;
// 根据类型处理饼图数据
let pieData, seriesName;
if (type === 'duration') {
// 报警时长:按时长占比处理
seriesName = '报警时长';
pieData = this.handlePieData(this.originData, 'alarmDuration', 'alarmDurationRatio');
} else {
// 报警次数:按次数占比处理
seriesName = '报警次数';
pieData = this.handlePieData(this.originData, 'alarmCount', 'alarmCountRatio');
}
try {
this.chartInstances.pie = echarts.init(chartDom);
const option = {
title: {
text: `${seriesName}统计(饼图)`,
left: 'center',
textStyle: { fontSize: 14, color: CHART_CONFIG.fontColor }
},
tooltip: {
trigger: 'item',
padding: 10,
textStyle: { fontSize: 11 },
formatter: (params) => {
return `
<div style="text-align: left;">
<div>${params.name}</div>
<div>${seriesName}${params.value}${type === 'duration' ? '' : '次'}</div>
<div>占比:${params.percent.toFixed(2)}%</div>
</div>
`;
}
},
series: [
{
name: seriesName,
type: 'pie',
radius: ['50%', '70%'],
center: ['50%', '55%'],
color: CHART_CONFIG.pieColors,
label: {
show: true,
position: 'outside',
distance: 15,
fontSize: 11,
color: CHART_CONFIG.lightFontColor,
formatter: (params) => {
const truncatedName = this.truncateText(params.name, 8);
return `${truncatedName}(${params.value}${type === 'duration' ? '' : '次'}, ${params.percent.toFixed(1)}%)`;
},
align: 'center',
baseline: 'middle'
},
labelLine: {
show: true,
length: 15,
length2: 20,
lineStyle: {
color: '#ccc',
width: 1,
type: 'solid'
},
smooth: 0.2
},
data: pieData,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowColor: 'rgba(0, 0, 0, 0.1)'
},
label: {
color: CHART_CONFIG.fontColor,
fontSize: 12,
fontWeight: 500
},
labelLine: {
lineStyle: {
color: CHART_CONFIG.barColor,
width: 1.5
}
}
}
}
]
};
this.chartInstances.pie.setOption(option);
this.addResizeListener('pie');
} catch (error) {
console.error(`${seriesName}饼图初始化失败:`, error);
setTimeout(() => this.renderPieChart(type), 200);
}
},
// 通用饼图数据处理(支持动态字段)
handlePieData(data, valueKey, ratioKey) {
const threshold = 5; // 占比低于5%合并为「其他」
let otherCount = 0;
const mainData = data.filter(item => {
if (item[ratioKey] >= threshold) {
return true;
} else {
otherCount += item[valueKey];
return false;
}
}).map(item => ({
name: item.alarmContent,
value: item[valueKey],
ratio: item[ratioKey]
}));
if (otherCount > 0) {
mainData.push({
name: '其他',
value: otherCount,
ratio: 100 - mainData.reduce((sum, item) => sum + item.ratio, 0)
});
}
return mainData;
},
truncateText(text, maxLength) {
if (!text) return '';
return text.length > maxLength ? text.slice(0, maxLength) + '...' : text;
},
addResizeListener(type) {
const chart = this.chartInstances[type];
if (chart) {
const resizeHandler = () => chart.resize();
window.addEventListener('resize', resizeHandler);
chart.resizeHandler = resizeHandler;
}
},
destroyChart(type) {
const chart = this.chartInstances[type];
if (chart) {
window.removeEventListener('resize', chart.resizeHandler);
chart.dispose();
this.chartInstances[type] = null;
}
},
destroyAllCharts() {
Object.keys(this.chartInstances).forEach(type => {
this.destroyChart(type);
});
},
handleClose() {
this.destroyAllCharts();
this.formConfig[0].defaultSelect = [];
this.listQuery.startTime = undefined;
this.listQuery.endTime = undefined;
this.originData = null;
this.hasData = true;
// 修复点2增加多层判断确保对象存在后再赋值
if (this.$refs.searchBarForm && this.$refs.searchBarForm.form) {
this.$refs.searchBarForm.form.timeVal = [];
}
},
init(data) {
this.dataForm = {
equipmentId: data.equipmentId || '',
equipmentName: data.equipmentName || '',
lineId: data.lineId || ''
};
this.activeLabel = 'duration'
this.listQuery.equipmentId = data.equipmentId || undefined;
this.visible = true;
this.originData = null;
this.hasData = false;
this.initDefaultDate();
this.$nextTick(() => {
this.$nextTick(() => {
this.isDomReady = true;
this.getDataList();
});
});
}
},
beforeDestroy() {
this.destroyAllCharts();
}
};
</script>
<style scoped>
/* 保持原有样式,优化图表容器布局 */
.drawer>>>.el-drawer {
border-radius: 8px 0 0 8px;
display: flex;
flex-direction: column;
}
.drawer>>>.el-form-item__label {
padding: 0;
}
.drawer>>>.el-drawer__header {
margin: 0;
padding: 32px 32px 24px;
border-bottom: 1px solid #dcdfe6;
}
.drawer>>>.el-drawer__body {
flex: 1;
height: 1px;
display: flex;
flex-direction: column;
}
.drawer>>>.content {
padding: 30px 24px;
flex: 1;
display: flex;
flex-direction: column;
}
.drawer>>>.visual-part {
flex: 1 auto;
max-height: 76vh;
overflow: hidden;
padding: 10px 0;
}
/* 优化图表容器响应式布局 */
@media (max-width: 1200px) {
.visual-part>div {
flex-direction: column;
}
#barChart,
#pieChart {
width: 100% !important;
height: 350px !important;
margin-bottom: 20px;
}
}
.drawer>>>.el-form,
.drawer>>>.attr-list {
padding: 0 16px;
}
.drawer-body__footer {
display: flex;
justify-content: flex-end;
padding: 18px;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -305,7 +305,7 @@ export default {
this.startTime = new Date(payload.recordTime) this.startTime = new Date(payload.recordTime)
this.queryParams.lineId = payload.lineId || null; this.queryParams.lineId = payload.lineId || null;
this.queryParams.sectionId = payload.sectionId || null; this.queryParams.sectionId = payload.sectionId || null;
this.queryParams.equipmentId = payload.equipmentId || null; this.queryParams.equipmentId = payload.equipmentId ? Number(payload.equipmentId) : undefined
this.queryParams.recordTime = payload.recordTime this.queryParams.recordTime = payload.recordTime
? [ ? [
payload.recordTime, payload.recordTime,

View File

@@ -132,7 +132,7 @@ export default {
}, },
], ],
queryParams: { queryParams: {
equipmentId: null, equipmentId: undefined,
recordTime: null, recordTime: null,
}, },
graphList: [], graphList: [],
@@ -153,7 +153,7 @@ export default {
this.$nextTick(() => { this.$nextTick(() => {
this.$refs['search-bar'].formInline.recordTime = formattedDate; this.$refs['search-bar'].formInline.recordTime = formattedDate;
}); });
this.queryParams.equipmentId = Number(this.$route.query.eqid); this.queryParams.equipmentId = this.$route.query.eqid? Number(this.$route.query.eqid): undefined
this.startTime = new Date(formattedDate); this.startTime = new Date(formattedDate);
this.queryParams.recordTime = formattedDate this.queryParams.recordTime = formattedDate
? [ ? [
@@ -207,7 +207,7 @@ export default {
/** 重置查询条件 */ /** 重置查询条件 */
initQuery() { initQuery() {
this.queryParams.equipmentId = null; this.queryParams.equipmentId = undefined;
this.queryParams.recordTime = null; this.queryParams.recordTime = null;
}, },
@@ -251,7 +251,7 @@ export default {
}, },
handleSearchBarBtnClick({ btnName, ...payload }) { handleSearchBarBtnClick({ btnName, ...payload }) {
this.queryParams.equipmentId = Number(this.$route.query.eqid); this.queryParams.equipmentId = this.$route.query.eqid ? Number(this.$route.query.eqid) :undefined
switch (btnName) { switch (btnName) {
case 'search': case 'search':
if (!payload.recordTime || payload.recordTime.length <= 0) { if (!payload.recordTime || payload.recordTime.length <= 0) {

View File

@@ -278,8 +278,8 @@ export default {
mobileCodeTimer: 0, mobileCodeTimer: 0,
loginForm: { loginForm: {
loginType: 'uname', loginType: 'uname',
username: 'admin', username: '',
password: 'admin123', password: '',
captchaVerification: '', captchaVerification: '',
mobile: '', mobile: '',
mobileCode: '', mobileCode: '',

View File

@@ -248,12 +248,11 @@ export default {
this.lineArr = response.data; this.lineArr = response.data;
this.lineArr.forEach((item, index) => { this.lineArr.forEach((item, index) => {
const num = [ const num = [
'', '20001',
'1672847052717821953', '20002',
'1672847052717821954', '20003',
'1686260211054157825', '20004',
'1701892552632770122', '20005',
'1714562503683465331',
].indexOf(item.id); ].indexOf(item.id);
if (num > 0) { if (num > 0) {
item.num = num; item.num = num;

View File

@@ -0,0 +1,339 @@
<!--
* @Author: zwq
* @Date: 2025-02-10 09:15:55
* @LastEditors: zwq
* @LastEditTime: 2025-02-25 15:48:59
* @Description:
-->
<template>
<div class="app-container" style="overflow: auto">
<search-bar :formConfigs="formConfig" ref="searchBarForm" @select-changed="handleSearchBarChanged"
@headBtnClick="buttonClick" />
<div class="top-tip">
<div class="block" style="background: #5b8ff9" />
运行
<div class="block" style="background: #f4b183" />
停机
<div class="block" style="background: #ff4f3e" />
故障
</div>
<div class="canvas-line" v-for="(item, index) in lineArr" :key="index">
<div class="title">{{ item.name }}</div>
<canvas style="
box-shadow: 1px 1px 2px 2px #909399;
padding: 0;
height: 350px;
width: 2500px;
" @click="getclient($event, index + 1, item.num)" :id="'mycanvas' + (index + 1)"></canvas>
</div>
</div>
</template>
<script>
import { getPdList } from '@/api/core/monitoring/auto';
import { getFactoryPage, getLineEqStatus } from '@/api/core/base/factory';
import linePot from './line-pot';
export default {
data() {
return {
listQuery: {
factoryId: null,
lineId: null,
},
ctx: '', //canvas
lineArr: [],
formConfig: [
{
type: 'select',
label: '工厂',
selectOptions: [],
param: 'factoryId',
onchange: true,
},
{
type: 'select',
label: '产线',
selectOptions: [],
param: 'lineId',
multiple: true,
width: 410
},
{
type: 'button',
btnName: '搜索',
name: 'search',
color: 'primary',
},
],
linePot,
};
},
components: {},
created() {
this.getPdLineList();
},
methods: {
initCanvas(name) {
let canvas = document.getElementById(name);
canvas.width = canvas.clientWidth;
canvas.width = canvas.clientWidth;
console.log(canvas.width);
canvas.height = 350;
this.ctx = canvas.getContext('2d');
this.ctx.lineWidth = 1;
this.ctx.strokeStyle = '#909399';
this.ctx.fillStyle = '#5b8ff9';
this.ctx.font = '12px 宋体'; //文字大小 字体样式
this.ctx.clearRect(0, 0, canvas.clientWidth, 350); //清空画布
},
//画长方形
fillRect(x, y, width, height, name, color) {
this.ctx.beginPath(); //开始
this.ctx.fillStyle = color ? color : '#5b8ff9'; //填充颜色
this.ctx.fillRect(x, y, width, height); //左上角点的坐标(x,y) 宽 高
this.ctx.strokeRect(x, y, width, height);
this.ctx.fillStyle = '#fff'; //填充颜色
if (height === 50) {
//判断是否特殊机器 机器文字y轴定位加10
this.ctx.fillText(name, x + 5, y + 30);
} else {
this.ctx.fillText(name, x + 5, y + 20);
}
this.ctx.closePath(); //结束
},
//画特殊形
fillLRect(LArr, name, color) {
this.ctx.beginPath(); //开始
LArr.forEach((item, index) => {
if (index === 0) {
this.ctx.moveTo(item[0], item[1]);
} else {
this.ctx.lineTo(item[0], item[1]);
}
});
this.ctx.closePath(); //结束 // 闭合路径
this.ctx.fillStyle = color ? color : '#5b8ff9'; //填充颜色
this.ctx.fill();
this.ctx.stroke();
this.ctx.fillStyle = '#fff'; //填充颜色
if (LArr[0][0] === LArr[1][0]) {
//判断L形第一截是竖着还是横着
this.ctx.fillText(name, LArr[1][0] + 5, LArr[1][1]-10); //竖着
} else {
this.ctx.fillText(name, LArr[0][0] + 25, LArr[0][1] + 20);
}
},
isEven(num) {
return (num & 1) === 0;
},
setRect(item, index) {
console.log(index)
let identifier = ''
if (this.isEven(index + 1)) {
identifier = 'line' + item.num + 'canvasSecondHalf';
} else {
identifier = 'line' + item.num + 'canvasFirstHalf';
}
// const identifier = 'line' + item.num + 'canvas';
this.$nextTick(() => {
this.initCanvas('mycanvas' + (index + 1));
});
this.$nextTick(() => {
this.linePot[identifier].forEach((idItem) => {
item.children.forEach((eqItem) => {
if (eqItem.id === idItem.id) {
idItem.color = ['#5b8ff9', '#f4b183', '#ff4f3e'][
eqItem.equipmentStatus
];
}
});
if (idItem.isL) {
this.fillLRect(idItem.line, idItem.name, idItem.color);
} else {
this.fillRect(
idItem.x,
idItem.y,
idItem.width,
idItem.height,
idItem.name,
idItem.color
);
}
});
});
},
getclient(e, index, num) {
let canvas = document.getElementById('mycanvas' + index);
const rect = canvas.getBoundingClientRect();
let identifier = ''
if (this.isEven(index + 1)) {
identifier = 'line' + num + 'canvasSecondHalf';
} else {
identifier = 'line' + num + 'canvasFirstHalf';
}
this.isEQ(e.clientX - rect.left, e.clientY - rect.top, identifier);
},
// 检查点击位置是否在设备内
isEQ(x, y, identifier) {
this.linePot[identifier].forEach((idItem) => {
if (idItem.isEQ) {
// 检查点击位置是否在矩形内
if (
x >= idItem.x &&
x <= idItem.x + idItem.width &&
y >= idItem.y &&
y <= idItem.y + idItem.height
) {
console.log(idItem.name, idItem.id);
this.$router.push({
path: '//base/production-line/getLineEqStatus',
query: { eqid: idItem.id },
});
}
}
});
},
getPdLineList() {
const params = {
pageSize: 100,
pageNo: 1,
};
getFactoryPage(params).then((res) => {
this.formConfig[0].selectOptions = res.data.list || [];
});
getPdList().then((res) => {
this.formConfig[1].selectOptions = res.data || [];
});
},
buttonClick(val) {
switch (val.btnName) {
case 'search':
this.listQuery.pageNo = 1;
this.listQuery.pageSize = 100;
this.listQuery.factoryId = val.factoryId || undefined;
this.listQuery.lineId = val.lineId ? val.lineId : [];
this.getDataList();
break;
default:
console.log(val);
}
},
handleSearchBarChanged({ param, value }) {
this.listQuery.lineId = [];
this.$refs.searchBarForm.formInline.lineId = undefined;
getPdList(value).then((res) => {
this.formConfig[1].selectOptions = res.data || [];
});
},
getDataList() {
// this.lineArr = [
// {
// num: 1,
// name: 'A1',
// children: [],
// },
// {
// num: 2,
// name: 'A2',
// children: [],
// },
// {
// num: 3,
// name: 'A3',
// children: [],
// },
// {
// num: 4,
// name: 'A4',
// children: [],
// },
// {
// num: 5,
// name: 'A5',
// children: [],
// },
// ];
getLineEqStatus(this.listQuery).then((response) => {
this.lineArr = response.data;
// Create a new array by duplicating each item
const duplicatedArr = [];
this.lineArr.forEach((item, index) => {
console.log(item,'item');
// Process original item
const num = [
'',
20001,
20002,
20003,
20004,
20005,
].indexOf(item.id);
if (num > 0) {
// Original item with copy=1
const originalItem = {
...item,
num: num,
name: item.lineName +'前半段',
copy: 1
};
duplicatedArr.push(originalItem);
this.setRect(originalItem, index * 2); // Adjust index for original
// Create copy with copy=2
const copiedItem = {
...item,
num: num,
name: item.lineName + '后半段',
copy: 2
};
duplicatedArr.push(copiedItem);
this.setRect(copiedItem, index * 2 + 1); // Adjust index for copy
} else {
this.$message.warning('没有对应的产线!');
}
});
console.log(duplicatedArr);
// Replace the original array with the duplicated one
this.lineArr = duplicatedArr;
});
},
},
};
</script>
<style scoped lang="scss">
.top-tip {
.block {
display: inline-block;
width: 20px;
height: 10px;
margin-left: 20px;
margin-right: 3px;
}
}
.canvas-line {
margin-top: 20px;
min-width: 2000px;
overflow: auto;
// height: 300px;
// overflow-x: hidden;
.title {
font-size: 20px;
font-weight: bolder;
&::before {
content: '';
background: rgb(39, 240, 17);
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 3px;
transform: translateY(-50%);
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,310 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<SearchBar :formConfigs="searchBarFormConfig" ref="search-bar" @headBtnClick="handleSearchBarBtnClick" />
<!-- 列表 -->
<base-table :table-props="tableProps" :page="queryParams.pageNo" :limit="queryParams.pageSize" :table-data="list"
@emitFun="handleEmitFun">
<!-- <method-btn v-if="tableBtn.length" slot="handleBtn" label="操作" :width="120" :method-list="tableBtn"
@clickBtn="handleTableBtnClick" /> -->
</base-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList" />
<!-- 对话框(添加 / 修改) -->
</div>
</template>
<script>
import moment from 'moment';
import {
getPdList,
} from '@/api/core/monitoring/auto';
import { getFactoryPage } from '@/api/core/base/factory';
import basicPageMixin from '@/mixins/lb/basicPageMixin';
import { getDefectAnalysis } from '@/api/monitoring/defectSummary';
import * as XLSX from 'xlsx'
import FileSaver from 'file-saver'
export default {
name: 'QualityInspectionType',
mixins: [basicPageMixin],
data() {
return {
// tableBtn: [
// this.$auth.hasPermi('base:quality-inspection-type:update')
// ? {
// type: 'edit',
// btnName: '修改',
// }
// : undefined,
// this.$auth.hasPermi('base:quality-inspection-type:delete')
// ? {
// type: 'delete',
// btnName: '删除',
// }
// : undefined,
// ].filter((v) => v),
tableProps: [
// {
// prop: 'createTime',
// label: '添加时间',
// fixed: true,
// width: 180,
// filter: (val) => moment(val).format('yyyy-MM-DD HH:mm:ss'),
// },
{ prop: 'factoryName', label: '工厂' },
{ prop: 'lineName', label: '产线' },
{ prop: 'remark', label: '玻璃编号' },
{
prop: 'checkTime',
label: '检测时间',
fixed: true,
width: 180,
filter: (val) => moment(val).format('yyyy-MM-DD HH:mm:ss'),
},
{ prop: 'checkNum', label: '缺陷数' },
{
prop: 'glassGrade',
label: '等级',
filter: (val) => val === 0 ? '合格' : val === 1 ? '提示' : val === 2 ? '返修' : val === 3 ? '报废' : ''
},
{ prop: 'reason', label: '判等原因' },
{ prop: 'specifications', label: '规格' },
// {
// label: '操作',
// alignt: 'center',
// subcomponent: {
// render: function (h) {
// return h('div', null, [
// h(
// 'el-button',
// {
// props: {
// icon: 'el-icon-edit',
// size: 'mini',
// type: 'text',
// },
// },
// ' 修改'
// ),
// h(
// 'el-button',
// {
// props: {
// icon: 'el-icon-edit',
// size: 'mini',
// type: 'text',
// },
// },
// ' 修改'
// ),
// ]);
// },
// },
// },
],
//
searchBarFormConfig: [
{
type: 'select',
label: '工厂',
selectOptions: [],
param: 'factoryId',
},
{
type: 'select',
label: '产线',
selectOptions: [],
multiple: true,
param: 'lineId',
},
{
type: 'input',
label: '缺陷数>',
// selectOptions: [],
param: 'checkNum',
},
{
type: 'datePicker',
label: '时间范围',
dateType: 'datetimerange',
format: 'yyyy-MM-dd HH:mm:ss',
// valueFormat: 'timestamp',
valueFormat: 'yyyy-MM-dd HH:mm:ss',
rangeSeparator: '-',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
param: 'timeVal',
width: 350
},
{
type: 'button',
btnName: '查询',
name: 'search',
color: 'primary',
},
// {
// type: 'button',
// btnName: '重置',
// name: 'reset',
// },
{
type: 'separate',
},
// {
// type: this.$auth.hasPermi('base:quality-inspection-type:create')
// ? 'button'
// : '',
// btnName: '新增',
// name: 'add',
// plain: true,
// color: 'success',
// },
{
type: this.$auth.hasPermi('base:quality-inspection-type:export')
? 'button'
: '',
btnName: '导出',
name: 'export',
color: 'warning',
},
],
// 表单配置
// formRows: [
// [
// {
// input: true,
// label: '检测类型名称',
// prop: 'name',
// rules: [{ required: true, message: '不能为空', trigger: 'blur' }],
// // bind: {
// // disabled: true, // some condition, like detail mode...
// // }
// },
// ],
// [{ input: true, label: '检测类型编码', prop: 'code' }],
// [{ input: true, label: '备注', prop: 'remark' }],
// ],
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
lineId: undefined,
factoryId: undefined,
checkNum: undefined,
startTime: undefined,
endTime: undefined,
},
// 表单参数
form: {},
};
},
// watch: {
// form: {
// handler: (val) => {
// console.log('form changed', val);
// },
// deep: true
// },
// },
created() {
this.getList();
this.getDict()
},
methods: {
getDict() {
getPdList().then(res => {
this.searchBarFormConfig[1].selectOptions = res.data || [];
});
getFactoryPage({ pageSize: 100, pageNo: 1 }).then(res => {
this.searchBarFormConfig[0].selectOptions = res.data.list || [];
});
},
/** base table related */
handleTableBtnClick({ data, type }) {
switch (type) {
case 'edit':
this.handleUpdate(data);
break;
case 'delete':
this.handleDelete(data);
break;
}
},
/** search bar related */
handleSearchBarBtnClick(btn) {
// const keys = ['name'];
switch (btn.btnName) {
case 'search':
this.queryParams.lineId = btn.lineId ? btn.lineId : undefined
this.queryParams.factoryId = btn.factoryId ? btn.factoryId : undefined
this.queryParams.startTime = btn.timeVal ? btn.timeVal[0] : undefined
this.queryParams.endTime = btn.timeVal ? btn.timeVal[1] : undefined
this.queryParams.checkNum = btn.checkNum ? btn.checkNum : undefined
// keys.forEach((key) => {
// this.queryParams[key] = btn[key] || null;
// });
this.getList();
break;
case 'add':
this.handleAdd();
break;
case 'export':
this.handleExport();
break;
// case 'reset':
// this.$refs['search-bar'].resetForm();
// this.resetQuery();
// break;
}
},
/** 查询列表 */
getList() {
this.loading = true;
// 执行查询
getDefectAnalysis(this.queryParams).then((response) => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
handleExport() {
// 处理查询参数
let tables = document.querySelector('.el-table').cloneNode(true)
const fix = tables.querySelector('.el-table__fixed')
const fixRight = tables.querySelector('.el-table__fixed-right')
if (fix) {
tables.removeChild(tables.querySelector('.el-table__fixed'))
}
if (fixRight) {
tables.removeChild(tables.querySelector('.el-table__fixed-right'))
}
let exportTable = XLSX.utils.table_to_book(tables)
var exportTableOut = XLSX.write(exportTable, {
bookType: 'xlsx', bookSST: true, type: 'array'
})
// sheetjs.xlsx为导出表格的标题名称
try {
FileSaver.saveAs(new Blob([exportTableOut], {
type: 'application/octet-stream'
}),'缺陷分析.xlsx')
} catch (e) {
if (typeof console !== 'undefined') console.log(e, exportTableOut)
}
return exportTableOut
},
},
};
</script>

View File

@@ -0,0 +1,560 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<SearchBar :formConfigs="searchBarFormConfig" ref="search-bar" @headBtnClick="handleSearchBarBtnClick" />
<!-- 列表 -->
<base-table :table-props="tableProps" :page="queryParams.pageNo" :limit="queryParams.pageSize" :table-data="list"
@emitFun="handleEmitFun">
<method-btn v-if="tableBtn.length" slot="handleBtn" label="操作" :width="120" :method-list="tableBtn"
@clickBtn="handleTableBtnClick" />
</base-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList" />
<div v-if="chartData" class="charts-container">
<div v-for="(lineData, lineName) in chartData" :key="lineName" class="chart-wrapper">
<div class="blue-block"></div>
<h3 class="chart-title">{{ lineName }}</h3>
<div :id="`chart-${lineName}`" class="chart" style="width: 100%; height: 300px;"></div>
</div>
</div>
<defect-summary-det ref="defectSummaryDetRef" v-if="defectVis" />
</div>
</template>
<script>
import moment from 'moment';
import {
getPdList,
} from '@/api/core/monitoring/auto';
import { getFactoryPage } from '@/api/core/base/factory';
import { getDefectSummaryTable, getDefectSummaryChart } from '@/api/monitoring/defectSummary';
import * as echarts from 'echarts';
import basicPageMixin from '@/mixins/lb/basicPageMixin';
import defectSummaryDet from './defectSummaryDet.vue'
export default {
name: 'QualityInspectionType',
mixins: [basicPageMixin],
components: {
defectSummaryDet
},
data() {
return {
tableBtn: [
this.$auth.hasPermi('base:quality-inspection-type:update')
? {
type: 'detail',
btnName: '缺陷详情',
}
: undefined,
// this.$auth.hasPermi('base:quality-inspection-type:delete')
// ? {
// type: 'delete',
// btnName: '删除',
// }
// : undefined,
].filter((v) => v),
tableProps: [
// {
// prop: 'createTime',
// label: '添加时间',
// fixed: true,
// width: 180,
// filter: (val) => moment(val).format('yyyy-MM-DD HH:mm:ss'),
// },
{ prop: 'factoryName', label: '工厂' },
{ prop: 'lineName', label: '产线' },
{ prop: 'glassNum', label: '玻璃总数' },
{
prop: 'okNum',
label: '合格品数',
},
{ prop: 'okRate', label: '合格百分比' },
{ prop: 'repairNum', label: '返修品数' },
{ prop: 'repairRate', label: '返修百分比' },
{ prop: 'ngNum', label: '废片数' },
{ prop: 'ngRate', label: '废片百分比' },
{ prop: 'oneNgNum', label: '1类缺陷玻璃数量' },
{ prop: 'twoNgNum', label: '2类缺陷玻璃数量' },
{ prop: 'threeNgNum', label: '3类缺陷玻璃数量' },
// {
// label: '操作',
// alignt: 'center',
// subcomponent: {
// render: function (h) {
// return h('div', null, [
// h(
// 'el-button',
// {
// props: {
// icon: 'el-icon-edit',
// size: 'mini',
// type: 'text',
// },
// },
// ' 修改'
// ),
// h(
// 'el-button',
// {
// props: {
// icon: 'el-icon-edit',
// size: 'mini',
// type: 'text',
// },
// },
// ' 修改'
// ),
// ]);
// },
// },
// },
],
//
searchBarFormConfig: [
{
type: 'select',
label: '工厂',
selectOptions: [],
param: 'factoryId',
},
{
type: 'select',
label: '产线',
selectOptions: [],
multiple: true,
param: 'lineId',
},
// {
// type: 'input',
// label: '缺陷数>',
// // selectOptions: [],
// param: 'number',
// },
{
type: 'datePicker',
label: '时间范围',
dateType: 'datetimerange',
format: 'yyyy-MM-dd HH:mm:ss',
// valueFormat: 'yyyy-MM-dd HH:mm:ss',
valueFormat: 'timestamp',
defaultTime: ['00:00:00', '23:59:59'],
rangeSeparator: '-',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
param: 'timeVal',
width: 350
},
{
type: 'button',
btnName: '查询',
name: 'search',
color: 'primary',
},
// {
// type: 'button',
// btnName: '重置',
// name: 'reset',
// },
{
type: 'separate',
},
// {
// type: this.$auth.hasPermi('base:quality-inspection-type:create')
// ? 'button'
// : '',
// btnName: '新增',
// name: 'add',
// plain: true,
// color: 'success',
// },
{
type: this.$auth.hasPermi('base:quality-inspection-type:export')
? 'button'
: '',
btnName: '导出',
name: 'export',
color: 'warning',
},
],
// 表单配置
// formRows: [
// [
// {
// input: true,
// label: '检测类型名称',
// prop: 'name',
// rules: [{ required: true, message: '不能为空', trigger: 'blur' }],
// // bind: {
// // disabled: true, // some condition, like detail mode...
// // }
// },
// ],
// [{ input: true, label: '检测类型编码', prop: 'code' }],
// [{ input: true, label: '备注', prop: 'remark' }],
// ],
// 是否显示弹出层
open: false,
chartData: {},
charts: {},
defectVis:false,
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
lineId: undefined,
factoryId: undefined,
checkNum: undefined,
startTime: undefined,
},
// 表单参数
form: {},
};
},
// watch: {
// form: {
// handler: (val) => {
// console.log('form changed', val);
// },
// deep: true
// },
// },
created() {
this.getList();
this.getDict()
},
methods: {
getDict() {
getPdList().then(res => {
this.searchBarFormConfig[1].selectOptions = res.data || [];
});
getFactoryPage({ pageSize: 100, pageNo: 1 }).then(res => {
this.searchBarFormConfig[0].selectOptions = res.data.list || [];
});
},
/** base table related */
handleTableBtnClick({ data, type }) {
switch (type) {
case 'detail':
this.defectVis = true
this.$nextTick(() => {
this.$refs.defectSummaryDetRef.init({
factoryId:data.factoryId,
lineId: [data.lineId],
factoryName: data.factoryName,
lineName: data.lineName,
startTime: this.queryParams.startTime,
endTime: this.queryParams.endTime,
})
})
break;
case 'delete':
this.handleDelete(data);
break;
}
},
beforeDestroy() {
// 销毁所有 ECharts 实例
Object.values(this.charts).forEach(chart => {
if (chart && chart.dispose) {
chart.dispose();
}
});
this.charts = {};
},
/**
* 初始化单个图表
* @param {string} lineName - 产线名称
* @param {Array} lineData - 该产线的原始数据
*/
initSingleChart(lineName, lineData) {
// 如果已有实例,先销毁
if (this.charts[lineName]) {
this.charts[lineName].dispose();
}
const chartDom = document.getElementById(`chart-${lineName}`);
if (!chartDom || !lineData || lineData.length === 0) {
console.warn(`图表容器 chart-${lineName} 未找到或数据为空`);
return;
}
const myChart = echarts.init(chartDom);
// --- 动态处理数据逻辑 ---
const startTime = this.queryParams.startTime;
const endTime = this.queryParams.endTime;
if (!startTime || !endTime) {
console.error("起始时间或结束时间为空,无法计算时间差。");
return;
}
const durationDays = (endTime - startTime) / (1000 * 60 * 60 * 24);
const isHourly = durationDays < 7;
// 使用Map来保证顺序并能存储聚合后的数据和原始显示标签
const aggregatedMap = new Map();
lineData.forEach(item => {
let groupKey; // 用于分组的键
let displayLabel; // 用于显示的标签
if (isHourly) {
// --- 方法一:使用字符串分割 ---
const timePart = item.timePoint.split('T')[1];
groupKey = timePart ? timePart : item.timePoint; // 安全处理
displayLabel = item.timePoint.replace('T', ' ');
} else {
// 按天聚合:分组键和显示标签都到天
groupKey = item.timePoint.split('T')[0];
displayLabel = groupKey;
}
if (!aggregatedMap.has(groupKey)) {
aggregatedMap.set(groupKey, {
displayLabel: displayLabel,
okNum: 0,
repairNum: 0,
ngNum: 0
});
}
const aggregatedItem = aggregatedMap.get(groupKey);
aggregatedItem.okNum += item.okNum || 0;
aggregatedItem.repairNum += item.repairNum || 0;
aggregatedItem.ngNum += item.ngNum || 0;
});
// 准备 ECharts 所需的数据格式
const xAxisData = [];
const okNumData = [];
const repairNumData = [];
const ngNumData = [];
aggregatedMap.forEach(item => {
xAxisData.push(item.displayLabel);
okNumData.push(item.okNum);
repairNumData.push(item.repairNum);
ngNumData.push(item.ngNum);
});
// --- ECharts 配置项 ---
const option = {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'cross' },
},
legend: {
data: ['合格品数', '返修品数', '废片数'],
itemHeight: 2,
itemWidth: 20,
},
grid: {
left: '3%',
right: '4%',
bottom: '20%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: xAxisData,
axisLabel: {
rotate: 45,
interval: 0, // 强制显示所有标签
formatter: function (value) {
if (isHourly) {
// 从 "2025-10-30 00:00" 中提取 "00:00" 进行显示
return value.split(' ')[1];
} else {
// 天级别,显示 "MM-DD"
const dateParts = value.split('-');
return `${dateParts[1]}-${dateParts[2]}`;
}
}
}
},
yAxis: {
type: 'value',
min: 0
},
series: [
{
name: '合格品数',
type: 'line',
showSymbol: false,
data: okNumData,
itemStyle: { color: 'rgba(40, 138, 255, 1)' },
},
{
name: '返修品数',
type: 'line',
showSymbol: false,
data: repairNumData,
itemStyle: { color: 'rgba(115, 222, 147, 1)' },
},
{
name: '废片数',
type: 'line',
showSymbol: false,
data: ngNumData,
itemStyle: { color: 'rgba(255, 206, 106, 1)' },
}
]
};
myChart.setOption(option);
this.charts[lineName] = myChart;
// 监听容器大小变化
const resizeObserver = new ResizeObserver(() => {
myChart.resize();
});
resizeObserver.observe(chartDom);
myChart._resizeObserver = resizeObserver;
},
/**
* 根据获取到的 chartData 更新所有图表
*/
updateAllCharts() {
if (!this.chartData) {
return;
}
// 使用 nextTick 确保 v-for 生成的 DOM 已存在
this.$nextTick(() => {
const lineNames = Object.keys(this.chartData);
lineNames.forEach(lineName => {
this.initSingleChart(lineName, this.chartData[lineName]);
});
});
},
/** search bar related */
handleSearchBarBtnClick(btn) {
switch (btn.btnName) {
case 'search':
this.queryParams.lineId = btn.lineId ? btn.lineId : undefined
this.queryParams.factoryId = btn.factoryId ? btn.factoryId : undefined
this.queryParams.startTime = btn.timeVal ? btn.timeVal[0] : undefined
this.queryParams.endTime = btn.timeVal ? btn.timeVal[1] : undefined
this.getList();
break;
case 'add':
this.handleAdd();
break;
case 'export':
this.handleExport();
break;
// case 'reset':
// this.$refs['search-bar'].resetForm();
// this.resetQuery();
// break;
}
},
/** 查询列表 */
getList() {
this.loading = true;
// 执行查询
getDefectSummaryTable(this.queryParams).then((response) => {
this.list = response.data;
// this.total = response.data.total;
this.loading = false;
})
getDefectSummaryChart(this.queryParams).then((res) => {
console.log('res.data', res.data);
this.chartData = res.data || {};
// 手动调用更新图表的方法
this.updateAllCharts();
// // this.total = response.data.total;
// this.loading = false;
})
},
/** 表单重置 */
reset() {
this.form = {
id: undefined,
name: undefined,
code: undefined,
remark: undefined,
};
this.resetForm('form');
},
/** 导出按钮操作 */
handleExport() {
// 处理查询参数
let tables = document.querySelector('.el-table').cloneNode(true)
const fix = tables.querySelector('.el-table__fixed')
const fixRight = tables.querySelector('.el-table__fixed-right')
if (fix) {
tables.removeChild(tables.querySelector('.el-table__fixed'))
}
if (fixRight) {
tables.removeChild(tables.querySelector('.el-table__fixed-right'))
}
let exportTable = XLSX.utils.table_to_book(tables)
var exportTableOut = XLSX.write(exportTable, {
bookType: 'xlsx', bookSST: true, type: 'array'
})
// sheetjs.xlsx为导出表格的标题名称
try {
FileSaver.saveAs(new Blob([exportTableOut], {
type: 'application/octet-stream'
}), '缺陷汇总.xlsx')
} catch (e) {
if (typeof console !== 'undefined') console.log(e, exportTableOut)
}
return exportTableOut
},
},
};
</script>
<style scoped>
.charts-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 20px;
}
.chart-wrapper {
flex: 1;
min-width: 300px;
margin-top: 10px;
/* 最小宽度,防止缩得太小 */
/* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
border-radius: 4px; */
/* padding: 10px; */
}
.blue-block {
display: inline-block;
float: left;
width: 4px;
height: 16px;
background-color: #0B58FF;
border-radius: 1px;
margin-right: 8px;
margin-top: 16px;
}
.chart-title {
text-align: left;
color: #333;
font-size: 16px;
margin-bottom: 10px;
}
</style>

View File

@@ -0,0 +1,224 @@
<template>
<div>
<el-drawer title="详情" :visible.sync="visible" size="70%" @close='closeD'>
<div class="box">
<!-- 顶部信息展示区域 -->
<div class="info-header">
<div class="info-item">
<span class="label">工厂</span>
<span class="value">{{ factoryName }}</span>
</div>
<div class="info-item">
<span class="label">产线</span>
<span class="value">{{ lineName }}</span>
</div>
<div class="info-item">
<span class="label">时间范围</span>
<span class="value">{{ timeRange }}</span>
</div>
<div class="info-item">
<el-button type="primary" @click="handleExport">导出</el-button>
</div>
</div>
<!-- 表格 -->
<base-table :page="queryParams.pageNo" :limit="queryParams.pageSize" :table-props="tableProps"
:table-data="tableData" :span-method="spanMethod" :max-height="tableH">
</base-table>
</div>
</el-drawer>
</div>
</template>
<script>
import moment from 'moment';
import * as XLSX from 'xlsx';
import { getDefectSummaryDet } from '@/api/monitoring/defectSummary';
// import { publicFormatter } from '@/utils/dict'
const tableProps = [
{ prop: 'defectLevel', label: '缺陷等级' },
{ prop: 'defectName', label: '缺陷类型' },
{ prop: 'defectNum', label: '玻璃数量' }
]
export default {
name: 'EnergyStatisticsDet',
props: {},
data() {
return {
visible: false,
tableProps,
tableData: [],
tableBtn: [],
tableH: this.tableHeight(115),
total: 0,
queryParams: { pageNo: 1, pageSize: 30 },
factoryName: '',
lineName: '',
timeRange: '',
name: '',
energyType: '',
energyTypeId: '',
addOrEditTitle: "",
centervisible: false,
collectionList: [
{ value: 0, label: '否' },
{ value: 1, label: '是' }
],
showBtn: true,
selectedList: []
}
},
created() {
window.addEventListener('resize', () => {
this.tableH = this.tableHeight(115)
})
},
methods: {
spanMethod({ row, column, rowIndex, columnIndex }) {
const fields = ['defectLevel']
const cellValue = row[column.property]
if (cellValue && fields.includes(column.property)) {
const prevRow = this.tableData[rowIndex - 1]
let nextRow = this.tableData[rowIndex + 1]
if (prevRow && prevRow[column.property] === cellValue) {
return { rowspan: 0, colspan: 0 }
} else {
let countRowspan = 1
while (nextRow && nextRow[column.property] === cellValue) {
nextRow = this.tableData[++countRowspan + rowIndex]
}
if (countRowspan > 1) {
return { rowspan: countRowspan, colspan: 1 }
}
}
}
},
init(data) {
this.visible = true;
this.factoryName = data.factoryName || '';
this.lineName = data.lineName || '';
if (data.startTime && data.endTime) {
const start = moment(data.startTime).format('yyyy-MM-DD HH:mm:ss');
const end = moment(data.endTime).format('yyyy-MM-DD HH:mm:ss');
this.timeRange = `${start} - ${end}`;
} else {
this.timeRange = '';
}
this.queryParams.factoryId = data.factoryId;
this.queryParams.lineId = data.lineId;
this.queryParams.startTime = data.startTime;
this.queryParams.endTime = data.endTime;
this.getList();
},
getList() {
getDefectSummaryDet({ ...this.queryParams }).then((res) => {
this.tableData = res.data || [];
})
},
closeD() {
this.$emit('closeDrawer');
},
closeDet() {
this.getList();
},
// --- 重写的导出方法 ---
// --- 修改后的导出方法 ---
handleExport() {
console.log("开始执行导出...");
this.$modal.confirm('确定要导出表格数据吗?').then(() => {
console.log("用户确认导出");
// 1. 准备数据
console.log("tableProps:", this.tableProps);
console.log("tableData:", this.tableData);
// 1.1 准备表头 (新增了 '工厂', '产线', '时间范围')
const headers = ['工厂', '产线', '时间范围', ...this.tableProps.map(prop => prop.label)];
console.log("生成的表头:", headers);
// 1.2 准备表格数据
const exportData = [];
this.tableData.forEach((row) => {
const newRow = [];
// 先添加顶部信息列
newRow.push(this.factoryName);
newRow.push(this.lineName);
newRow.push(this.timeRange);
// 再添加表格数据列
this.tableProps.forEach((prop) => {
// 直接填入所有数据,不再对 defectLevel 进行合并判断
newRow.push(row[prop.prop] !== undefined ? row[prop.prop] : '');
});
exportData.push(newRow);
});
console.log("处理后的数据:", exportData);
// 2. 创建工作簿和工作表
try {
const worksheet = XLSX.utils.aoa_to_sheet([headers, ...exportData]);
console.log("工作表创建成功");
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, '缺陷详情数据');
console.log("工作簿创建成功");
// 3. 触发下载
const fileName = `${this.factoryName}-${this.lineName}-缺陷汇总详情-${moment().format('YYYYMMDDHHmmss')}.xlsx`;
console.log("准备下载文件:", fileName);
XLSX.writeFile(workbook, fileName);
console.log("下载触发成功");
} catch (error) {
console.error("生成Excel文件时出错:", error);
this.$modal.msgError('导出失败,生成文件时出错!');
}
}).catch((error) => {
console.log("用户取消导出或发生错误:", error);
});
},
}
}
</script>
<style lang="scss" scoped>
.box {
padding: 0 32px;
}
.info-header {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
padding: 16px 0;
border-bottom: 1px solid #ebeef5;
margin-bottom: 16px;
.info-item {
display: flex;
align-items: center;
margin-right: 32px;
font-size: 16px;
margin-bottom: 8px;
.label {
color: #606266;
margin-right: 8px;
font-weight: 500;
}
.value {
color: #303133;
}
}
}
</style>

View File

@@ -0,0 +1,416 @@
<template>
<div style="background: #f2f4f9; flex: 1; display: flex; flex-direction: column">
<el-row class="" style="
margin-bottom: 12px;
background: #fff;
padding: 16px 16px 0;
border-radius: 8px;
">
<SearchBar :formConfigs="searchBarFormConfig" ref="search-bar" @headBtnClick="handleSearchBarBtnClick" />
<base-table :max-height="tableH" :table-props="tableProps" :page="queryParams.pageNo"
:limit="queryParams.pageSize" :table-data="list" @emitFun="handleEmitFun" />
</el-row>
<!-- 搜索工作栏 -->
<!-- 用一个 div 包裹表格和图表并使用 flex 布局 -->
<el-row class="" style="
height: 1px;
flex: 1;
margin-bottom: 12px;
background: #fff;
padding: 16px 16px 32px;
border-radius: 8px;
display: flex;
flex-direction: column;
">
<el-row :gutter="20">
<el-col :span="24">
<div id="chart" style="width: 100%; height: 300px;"></div>
</el-col>
</el-row>
</el-row>
</div>
</template>
<script>
import {
getPdList,
} from '@/api/core/monitoring/auto';
import { getFactoryPage } from '@/api/core/base/factory';
import basicPageMixin from '@/mixins/lb/basicPageMixin';
import { getSectionDefect } from '@/api/monitoring/defectSummary';
import * as echarts from 'echarts';
import tableHeightMixin from '@/mixins/lb/tableHeightMixin';
export default {
name: 'QualityInspectionType',
mixins: [basicPageMixin, tableHeightMixin],
data() {
return {
tableBtn: [
this.$auth.hasPermi('base:quality-inspection-type:update')
? {
type: 'edit',
btnName: '修改',
}
: undefined,
this.$auth.hasPermi('base:quality-inspection-type:delete')
? {
type: 'delete',
btnName: '删除',
}
: undefined,
].filter((v) => v),
tableProps: [
// {
// prop: 'createTime',
// label: '添加时间',
// fixed: true,
// width: 180,
// filter: (val) => moment(val).format('yyyy-MM-DD HH:mm:ss'),
// },
{ prop: 'factoryName', label: '工厂' },
{ prop: 'lineName', label: '产线' },
{ prop: 'checkCount', label: '检测片数' },
{
prop: 'ngCount',
label: '不良合计',
},
{
prop: 'ngRate',
label: '不良率',
},
{ prop: 'originalNgCount', label: '原片不良' },
{ prop: 'edgeNgCount', label: '磨边不良' },
{ prop: 'silkNgCount', label: '丝印不良' },
{ prop: 'drillNgCount', label: '打孔不良' },
{ prop: 'coatingNgCount', label: '镀膜不良' },
{ prop: 'packNgCount', label: '钢包不良' },
// {
// label: '操作',
// alignt: 'center',
// subcomponent: {
// render: function (h) {
// return h('div', null, [
// h(
// 'el-button',
// {
// props: {
// icon: 'el-icon-edit',
// size: 'mini',
// type: 'text',
// },
// },
// ' 修改'
// ),
// h(
// 'el-button',
// {
// props: {
// icon: 'el-icon-edit',
// size: 'mini',
// type: 'text',
// },
// },
// ' 修改'
// ),
// ]);
// },
// },
// },
],
//
searchBarFormConfig: [
{
type: 'select',
label: '工厂',
selectOptions: [],
param: 'factoryId',
},
{
type: 'select',
label: '产线',
selectOptions: [],
multiple: true,
param: 'lineId',
},
// {
// type: 'input',
// label: '缺陷数>',
// // selectOptions: [],
// param: 'number',
// },
{
type: 'datePicker',
label: '时间范围',
dateType: 'datetimerange',
format: 'yyyy-MM-dd HH:mm:ss',
valueFormat: 'timestamp',
rangeSeparator: '-',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
param: 'timeVal',
width: 350
},
{
type: 'button',
btnName: '查询',
name: 'search',
color: 'primary',
},
// {
// type: 'button',
// btnName: '重置',
// name: 'reset',
// },
{
type: 'separate',
},
// {
// type: this.$auth.hasPermi('base:quality-inspection-type:create')
// ? 'button'
// : '',
// btnName: '新增',
// name: 'add',
// plain: true,
// color: 'success',
// },
{
type: this.$auth.hasPermi('base:quality-inspection-type:export')
? 'button'
: '',
btnName: '导出',
name: 'export',
color: 'warning',
},
],
// 表单配置
// formRows: [
// [
// {
// input: true,
// label: '检测类型名称',
// prop: 'name',
// rules: [{ required: true, message: '不能为空', trigger: 'blur' }],
// // bind: {
// // disabled: true, // some condition, like detail mode...
// // }
// },
// ],
// [{ input: true, label: '检测类型编码', prop: 'code' }],
// [{ input: true, label: '备注', prop: 'remark' }],
// ],
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
lineId: undefined,
factoryId: undefined,
startTime: undefined,
endTime: undefined,
},
// 表单参数
form: {},
};
},
// watch: {
// form: {
// handler: (val) => {
// console.log('form changed', val);
// },
// deep: true
// },
// },
created() {
this.getList();
this.getDict()
},
methods: {
getDict() {
getPdList().then(res => {
this.searchBarFormConfig[1].selectOptions = res.data || [];
});
getFactoryPage({ pageSize: 100, pageNo: 1 }).then(res => {
this.searchBarFormConfig[0].selectOptions = res.data.list || [];
});
},
/** base table related */
handleTableBtnClick({ data, type }) {
switch (type) {
case 'edit':
this.handleUpdate(data);
break;
case 'delete':
this.handleDelete(data);
break;
}
},
/** search bar related */
handleSearchBarBtnClick(btn) {
switch (btn.btnName) {
case 'search':
this.queryParams.lineId = btn.lineId ? btn.lineId : undefined
this.queryParams.factoryId = btn.factoryId ? btn.factoryId : undefined
this.queryParams.startTime = btn.timeVal ? btn.timeVal[0] : undefined
this.queryParams.endTime = btn.timeVal ? btn.timeVal[1] : undefined
this.getList();
break;
case 'add':
this.handleAdd();
break;
case 'export':
this.handleExport();
break;
case 'reset':
this.$refs['search-bar'].resetForm();
this.resetQuery();
break;
}
},
/** 查询列表 */
getList() {
this.loading = true;
// 执行查询
getSectionDefect(this.queryParams).then((response) => {
this.list = response.data;
// 定义颜色数组,与你提供的顺序一致
const colors = [
'rgba(99, 189, 255, 1)',
'rgba(113, 100, 255, 1)',
'rgba(255, 104, 96, 1)',
'rgba(255, 151, 71, 1)',
'rgba(176, 235, 66, 1)',
'rgba(214, 128, 255, 1)',
'rgba(0, 67, 210, 1)'
];
const series = response.data.map((item, index) => {
// 为每个系列分配颜色index 从 0 开始,依次对应 colors 数组
const color = colors[index % colors.length]; // 使用取模确保不越界
return {
name: item.lineName,
type: 'bar',
stack: 'Ad',
barWidth: '20',
emphasis: {
focus: 'series'
},
itemStyle: {
color: color // 指定当前系列的颜色
},
data: [
item.originalNgCount,
item.edgeNgCount,
item.silkNgCount,
item.drillNgCount,
item.coatingNgCount,
item.packNgCount
]
};
});
this.loading = false;
this.getChart(series);
});
},
getChart(series) {
var chartDom = document.getElementById('chart');
var myChart = echarts.init(chartDom);
var option;
option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
grid: {
left: 30,
top: 20,
right: 30,
bottom:20
},
legend: {},
xAxis: [
{
type: 'category',
data: ['原片不良', '磨边不良', '丝印不良', '打孔不良', '镀膜不良', ' 钢包不良']
}
],
yAxis: [
{
type: 'value'
}
],
series: series
};
option && myChart.setOption(option);
},
/** 导出按钮操作 */
handleExport() {
// 处理查询参数
let tables = document.querySelector('.el-table').cloneNode(true)
const fix = tables.querySelector('.el-table__fixed')
const fixRight = tables.querySelector('.el-table__fixed-right')
if (fix) {
tables.removeChild(tables.querySelector('.el-table__fixed'))
}
if (fixRight) {
tables.removeChild(tables.querySelector('.el-table__fixed-right'))
}
let exportTable = XLSX.utils.table_to_book(tables)
var exportTableOut = XLSX.write(exportTable, {
bookType: 'xlsx', bookSST: true, type: 'array'
})
// sheetjs.xlsx为导出表格的标题名称
try {
FileSaver.saveAs(new Blob([exportTableOut], {
type: 'application/octet-stream'
}), '工段不良.xlsx')
} catch (e) {
if (typeof console !== 'undefined') console.log(e, exportTableOut)
}
return exportTableOut
},
},
};
</script>
<style scoped>
/* 添加 scoped 以避免样式污染 */
.app-container {
padding: 20px;
}
.content-wrapper {
display: flex;
flex-direction: column;
/* 垂直排列 */
gap: 20px;
}
.table-container,
.chart-container {
flex: 1;
/* 让两个容器平分父容器的空间 */
min-height: 300px;
/* 设置最小高度,防止内容过少时变形 */
border: 1px solid #ebeef5;
/* 添加一个边框,方便看清分隔 */
border-radius: 4px;
padding: 10px;
background-color: #fff;
}
/* 确保图表容器的父元素也有高度ECharts 才能正确渲染 */
.chart-container {
display: flex;
flex-direction: column;
}
</style>

View File

@@ -0,0 +1,305 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<SearchBar :formConfigs="searchBarFormConfig" ref="search-bar" @headBtnClick="handleSearchBarBtnClick" />
<!-- 列表 -->
<base-table :max-height="tableH" :table-props="tableProps" :page="queryParams.pageNo" :limit="queryParams.pageSize"
:table-data="list" @emitFun="handleEmitFun">
<!-- <method-btn v-if="tableBtn.length" slot="handleBtn" label="操作" :width="120" :method-list="tableBtn"
@clickBtn="handleTableBtnClick" /> -->
</base-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList" />
<!-- 对话框(添加 / 修改) -->
</div>
</template>
<script>
import moment from 'moment';
import { getPdList,} from '@/api/core/monitoring/auto';
import { getFactoryPage } from '@/api/core/base/factory';
import basicPageMixin from '@/mixins/lb/basicPageMixin';
import { getTranslucentPage, exportTranslucent } from '@/api/monitoring/defectSummary';
import tableHeightMixin from '@/mixins/lb/tableHeightMixin';
export default {
name: 'QualityInspectionType',
mixins: [basicPageMixin, tableHeightMixin],
data() {
return {
// tableBtn: [
// this.$auth.hasPermi('base:quality-inspection-type:update')
// ? {
// type: 'edit',
// btnName: '修改',
// }
// : undefined,
// this.$auth.hasPermi('base:quality-inspection-type:delete')
// ? {
// type: 'delete',
// btnName: '删除',
// }
// : undefined,
// ].filter((v) => v),
tableProps: [
// {
// prop: 'createTime',
// label: '添加时间',
// fixed: true,
// width: 180,
// filter: (val) => moment(val).format('yyyy-MM-DD HH:mm:ss'),
// },
{ prop: 'factoryName', label: '工厂' },
{ prop: 'lineName', label: '产线' },
{ prop: 'timeVal', label: '时间段' },
{ prop: 'totalNum', label: '玻璃总数' },
{ prop: 'goodNum', label: '一等品数量' },
{ prop: 'passNum', label: '二等品数量' },
{ prop: 'scrapNum', label: '废片数' },
{ prop: 'passRate', label: '合格率' },
],
//
searchBarFormConfig: [
{
type: 'select',
label: '工厂',
selectOptions: [],
param: 'factoryId',
},
{
type: 'select',
label: '产线',
selectOptions: [],
param: 'lineId',
},
{
type: 'datePicker',
label: '时间范围',
dateType: 'datetimerange',
format: 'yyyy-MM-dd HH:mm:ss',
valueFormat: 'yyyy-MM-dd HH:mm:ss',
rangeSeparator: '-',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
param: 'timeVal',
width: 350
},
{
type: 'button',
btnName: '查询',
name: 'search',
color: 'primary',
},
// {
// type: 'button',
// btnName: '重置',
// name: 'reset',
// },
{
type: 'separate',
},
// {
// type: this.$auth.hasPermi('base:quality-inspection-type:create')
// ? 'button'
// : '',
// btnName: '新增',
// name: 'add',
// plain: true,
// color: 'success',
// },
{
type: this.$auth.hasPermi('monitoring:translucent:export')
? 'button'
: '',
btnName: '导出',
name: 'export',
color: 'warning',
},
],
// 表单配置
// formRows: [
// [
// {
// input: true,
// label: '检测类型名称',
// prop: 'name',
// rules: [{ required: true, message: '不能为空', trigger: 'blur' }],
// // bind: {
// // disabled: true, // some condition, like detail mode...
// // }
// },
// ],
// [{ input: true, label: '检测类型编码', prop: 'code' }],
// [{ input: true, label: '备注', prop: 'remark' }],
// ],
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
lineId: undefined,
factoryId: undefined,
startTime: undefined,
endTime: undefined,
},
// 表单参数
form: {},
};
},
// watch: {
// form: {
// handler: (val) => {
// console.log('form changed', val);
// },
// deep: true
// },
// },
created() {
this.getList();
this.getDict()
},
methods: {
getDict() {
getPdList().then(res => {
this.searchBarFormConfig[1].selectOptions = res.data || [];
});
getFactoryPage({ pageSize: 100, pageNo: 1 }).then(res => {
this.searchBarFormConfig[0].selectOptions = res.data.list || [];
});
},
/** base table related */
handleTableBtnClick({ data, type }) {
switch (type) {
case 'edit':
this.handleUpdate(data);
break;
case 'delete':
this.handleDelete(data);
break;
}
},
/** search bar related */
handleSearchBarBtnClick(btn) {
console.log('btn',btn);
// const keys = ['name'];
switch (btn.btnName) {
case 'search':
this.queryParams.lineId = btn.lineId ? btn.lineId : undefined
this.queryParams.factoryId = btn.factoryId ? btn.factoryId : undefined
this.queryParams.startTime = btn.timeVal ? btn.timeVal[0] : undefined
this.queryParams.endTime = btn.timeVal ? btn.timeVal[1] : undefined
// keys.forEach((key) => {
// this.queryParams[key] = btn[key] || null;
// });
this.getList();
break;
case 'add':
this.handleAdd();
break;
case 'export':
this.handleExport();
break;
case 'reset':
this.$refs['search-bar'].resetForm();
this.resetQuery();
break;
}
},
/** 查询列表 */
getList() {
this.loading = true;
// 执行查询
getTranslucentPage(this.queryParams).then((res) => {
this.list = res.data.list ? res.data.list.map((item) => {
const startTime = item.startTime ? moment(item.startTime).format('YYYY-MM-DD HH:mm:ss') : '';
const endTime = item.endTime ? moment(item.endTime).format('YYYY-MM-DD HH:mm:ss') : '';
// 拼接开始时间和结束时间,中间用“至”连接
const timeVal = startTime && endTime ? `${startTime}${endTime}` : '';
return {
...item,
timeVal: timeVal
};
}) :[]
this.total = res.data.total;
this.loading = false;
});
},
/** 表单重置 */
// reset() {
// this.form = {
// id: undefined,
// name: undefined,
// code: undefined,
// remark: undefined,
// };
// this.resetForm('form');
// },
/** 新增按钮操作 */
// handleAdd() {
// this.reset();
// this.open = true;
// this.title = '添加质量检测类型基础';
// },
/** 修改按钮操作 */
// handleUpdate(row) {
// this.reset();
// const id = row.id;
// getQualityInspectionType(id).then((response) => {
// this.form = response.data;
// this.open = true;
// this.title = '修改质量检测类型基础';
// });
// },
/** 提交按钮 */
// submitForm() {
// // console.log('this.$refs.form', this.$refs.form);
// // return;
// this.$refs['form'].validate((valid) => {
// if (!valid) {
// return;
// }
// console.log('final form', JSON.stringify(this.form));
// // 修改的提交
// if (this.form.id != null) {
// updateQualityInspectionType(this.form).then((response) => {
// this.$modal.msgSuccess('修改成功');
// this.open = false;
// this.getList();
// });
// return;
// }
// // 添加的提交
// createQualityInspectionType(this.form).then((response) => {
// this.$modal.msgSuccess('新增成功');
// this.open = false;
// this.getList();
// });
// });
// },
handleExport() {
// 处理查询参数
let params = { ...this.queryParams };
// params.pageNo = undefined;
// params.pageSize = undefined;
this.$modal
.confirm('是否确认导出透光率检测?')
.then(() => {
this.exportLoading = true;
return exportTranslucent(params);
})
.then((response) => {
this.$download.excel(response, '透光率检测.xls');
this.exportLoading = false;
})
.catch(() => { });
},
},
};
</script>