Merge pull request 'projects/line-new-zhp' (#457) from projects/line-new-zhp into projects/line-new

Reviewed-on: #457
This commit is contained in:
2025-12-09 13:11:32 +08:00
43 changed files with 7142 additions and 729 deletions

View File

@@ -12,8 +12,10 @@ ENV = 'development'
VUE_APP_TITLE = 智能监控分析系统
# 芋道管理系统/开发环境
# VUE_APP_BASE_API = 'http://192.168.8.22:48080'
VUE_APP_BASE_API = 'http://172.16.33.65:48082'
# VUE_APP_BASE_API = 'http://172.16.33.187:48082'
# VUE_APP_BASE_API = 'http://line.kszny.picaiba.com'
VUE_APP_BASE_API = 'http://172.16.32.79:48082'
# 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true

View File

@@ -59,3 +59,34 @@ export function getEquipmentAll() {
method: 'get'
})
}
export function getTree(query) {
return request({
url: '/base/factory/getTree',
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

@@ -52,3 +52,5 @@ export function exportEquipmentTypeExcel(query) {
responseType: 'blob'
})
}

View File

@@ -50,6 +50,15 @@ export function getPdlAutoReportNewSearchNow(data) {
});
}
export function getPdlAutoReportNewSearchLastGroup(data) {
return request({
url: '/monitoring/production-monitor/getPdlAutoReportNewSearchLastGroup',
method: 'post',
data: data,
timeout: 60000,
});
}
// 班组自动报表分页
export function getTeamReportPage(data) {

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,63 @@
/*
* @Author: zhp
* @Date: 2023-12-08 15:26:59
* @LastEditTime: 2023-12-11 15:21:44
* @LastEditors: zhp
* @Description:
*/
import request from '@/utils/request'
export function getQualityIsraPage(query) {
return request({
url: '/extend/check-isra-statistics/getIsraData',
method: 'get',
params: query,
});
}
export function getQualityIsraDayMap(query) {
return request({
url: '/base/quality-isra-statistics/dayMap',
method: 'get',
params: query
})
}
export function getQualityIsraWeekMap(query) {
return request({
url: '/base/quality-isra-statistics/weekMap',
method: 'get',
params: query
})
}
export function getQualityIsraMonthMap(query) {
return request({
url: '/base/quality-isra-statistics/monthMap',
method: 'get',
params: query
})
}
export function getQualityIsraDayList(query) {
return request({
url: '/base/quality-isra-statistics/dayList',
method: 'get',
params: query
})
}
export function getQualityIsraWeekList(query) {
return request({
url: '/base/quality-isra-statistics/weekList',
method: 'get',
params: query
})
}
export function getQualityIsraMonthList(query) {
return request({
url: '/base/quality-isra-statistics/monthList',
method: 'get',
params: query
})
}

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

@@ -6,104 +6,48 @@
-->
<template>
<el-form
ref="form"
:model="form"
:label-width="`${labelWidth}px`"
:size="size"
:label-position="labelPosition"
v-loading="formLoading">
<el-row :gutter="20" v-for="(row, rindex) in rows" :key="rindex">
<el-col v-for="col in row" :key="col.label" :span="24 / row.length">
<el-form-item :label="col.label" :prop="col.prop" :rules="col.rules">
<el-input
v-if="col.input"
v-model="form[col.prop]"
@change="$emit('update', form)"
:placeholder="`请输入${col.label}`"
v-bind="col.bind" />
<el-input
v-if="col.textarea"
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>
<el-form ref="form" :model="form" :label-width="`${labelWidth}px`" :size="size" :label-position="labelPosition"
v-loading="formLoading">
<el-row :gutter="20" v-for="(row, rindex) in rows" :key="rindex">
<el-col v-for="col in row" :key="col.label" :span="24 / row.length">
<el-form-item :label="col.label" :prop="col.prop" :rules="col.rules">
<el-input v-if="col.input" v-model="form[col.prop]" @change="$emit('update', form)"
:placeholder="`请输入${col.label}`" v-bind="col.bind" />
<el-input v-if="col.textarea" 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" @change="handleSwitchChange(col.prop)"></el-switch>
<component v-if="col.subcomponent" :key="col.key" :is="col.subcomponent" :inlineStyle="col.style"></component>
<div
class="upload-area"
:class="uploadOpen ? '' : 'height-48'"
ref="uploadArea"
v-if="col.upload">
<span class="close-icon" :class="uploadOpen ? 'open' : ''">
<el-button
type="text"
icon="el-icon-arrow-right"
@click="handleFilesOpen" />
</span>
<!-- :file-list="uploadedFileList" -->
<el-upload
class="upload-in-dialog"
v-if="col.upload"
:action="uploadUrl"
:headers="uploadHeaders"
:show-file-list="false"
icon="el-icon-upload2"
:before-upload="beforeUpload"
:on-success="handleUploadSuccess"
v-bind="col.bind">
<el-button size="mini" :disabled="col.bind?.disabled || false">
<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>
<div class="upload-area" :class="uploadOpen ? '' : 'height-48'" ref="uploadArea" v-if="col.upload">
<span class="close-icon" :class="uploadOpen ? 'open' : ''">
<el-button type="text" icon="el-icon-arrow-right" @click="handleFilesOpen" />
</span>
<!-- :file-list="uploadedFileList" -->
<el-upload class="upload-in-dialog" v-if="col.upload" :action="uploadUrl" :headers="uploadHeaders"
:show-file-list="false" icon="el-icon-upload2" :before-upload="beforeUpload"
:on-success="handleUploadSuccess" v-bind="col.bind">
<el-button size="mini" :disabled="col.bind?.disabled || false">
<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>
<script>
@@ -134,7 +78,8 @@ const uploadedFile = {
data() {
return {};
},
methods: {
methods: {
handleDelete() {
this.$emit('delete', this.file);
},
@@ -251,7 +196,12 @@ export default {
// 处理 options
this.handleOptions();
},
methods: {
methods: {
handleSwitchChange(prop) {
// 触发 update 事件,将最新的 form 数据传递给父组件
this.$emit('update', { ...this.form });
console.log(`switch ${prop} 变化:`, this.form[prop]);
},
/** 模拟透传 ref */
validate(cb) {
return this.$refs.form.validate(cb);

View File

@@ -6,35 +6,27 @@
-->
<template>
<el-drawer
:visible="visible"
:show-close="false"
:wrapper-closable="false"
class="drawer"
custom-class="mes-drawer"
:size="size || '50%'"
@closed="$emit('destroy')">
<SmallTitle slot="title">
{{
mode.includes('detail')
? '详情'
: mode.includes('edit')
? '编辑'
: '新增'
}}
</SmallTitle>
<el-drawer :visible="visible" :show-close="false" :wrapper-closable="false" class="drawer" custom-class="mes-drawer"
:size="size || '50%'" @closed="$emit('destroy')">
<SmallTitle slot="title">
{{
mode.includes('detail')
? '详情'
: mode.includes('edit')
? '编辑'
: '新增'
}}
</SmallTitle>
<div class="drawer-body flex">
<div class="drawer-body__content">
<section v-for="(section, index) in sections" :key="section.key">
<SmallTitle v-if="index != 0">{{ section.name }}</SmallTitle>
<div class="drawer-body flex">
<div class="drawer-body__content">
<section v-for="(section, index) in sections" :key="section.key">
<SmallTitle v-if="index != 0">{{ section.name }}</SmallTitle>
<div
class="form-part"
v-if="section.key == 'base'"
style="border-bottom: 1px solid #dfdfdf; margin-bottom: 24px">
<el-skeleton v-if="!showForm" animated />
<!-- <BaseInfoForm
<div class="form-part" v-if="section.key == 'base'"
style="border-bottom: 1px solid #dfdfdf; margin-bottom: 24px">
<el-skeleton v-if="!showForm" animated />
<!-- <BaseInfoForm
key="drawer-dialog-form"
v-if="showForm"
ref="form"
@@ -42,141 +34,87 @@
v-model="form"
:rows="formRows" /> -->
<!-- if -->
<el-row v-if="mode.includes('detail')" style="margin-bottom: 24px">
<el-col :span="8">
<div
class="title"
style="font-weight: 700; font-size: 16px; margin: 8px 0">
设备名
</div>
<div class="value" style="font-size: 14px">
{{ form.equipmentName }}
</div>
</el-col>
<el-col :span="8">
<div
class="title"
style="font-weight: 700; font-size: 16px; margin: 8px 0">
关联表名
</div>
<div class="value" style="font-size: 14px">
{{ form.plcName }}
</div>
</el-col>
</el-row>
<!-- else -->
<el-row v-else style="margin-bottom: 24px" :gutter="20">
<el-form ref="form" :model="form">
<el-col :span="8">
<el-form-item
class="title"
label="设备名"
style="font-size: 16px; margin: 8px 0">
<el-select
v-model="form.equipmentId"
filterable
clearable
placeholder="请选择设备">
<el-option
v-for="eq in eqList"
:key="eq.id"
:label="eq.name"
:value="eq.id"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
class="title"
label="设备关联表名"
style="font-size: 16px; margin: 8px 0">
<el-select
v-model="form.plcId"
filterable
clearable
placeholder="请选择关联表">
<el-option
v-for="plc in plcList"
:key="plc.id"
:label="plc.plcTableName"
:value="plc.id"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-form>
</el-row>
</div>
<!-- if -->
<el-row v-if="mode.includes('detail')" style="margin-bottom: 24px">
<el-col :span="8">
<div class="title" style="font-weight: 700; font-size: 16px; margin: 8px 0">
设备名
</div>
<div class="value" style="font-size: 14px">
{{ form.equipmentName }}
</div>
</el-col>
<el-col :span="8">
<div class="title" style="font-weight: 700; font-size: 16px; margin: 8px 0">
关联表名
</div>
<div class="value" style="font-size: 14px">
{{ form.plcName }}
</div>
</el-col>
</el-row>
<!-- else -->
<el-row v-else style="margin-bottom: 24px" :gutter="20">
<el-form ref="form" :model="form">
<el-col :span="8">
<el-form-item class="title" label="设备名" style="font-size: 16px; margin: 8px 0">
<el-select v-model="form.equipmentId" filterable clearable placeholder="请选择设备">
<el-option v-for="eq in eqList" :key="eq.id" :label="eq.name" :value="eq.id"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item class="title" label="设备关联表名" style="font-size: 16px; margin: 8px 0">
<el-select v-model="form.plcId" filterable clearable placeholder="请选择关联表">
<el-option v-for="plc in plcList" :key="plc.id" :label="plc.plcTableName"
:value="plc.id"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-form>
</el-row>
</div>
<div
v-if="section.key == 'attrs'"
style="position: relative; margin-top: 12px">
<div
v-if="!mode.includes('detail')"
style="position: absolute; top: -40px; right: 0">
<el-button @click="handleAddAttr" type="text">
<i class="el-icon-plus"></i>
添加参数
</el-button>
</div>
<base-table
v-loading="attrListLoading"
:table-props="section.props"
:page="attrQuery?.params.pageNo || 1"
:limit="attrQuery?.params.pageSize || 10"
:table-data="list"
@emitFun="handleEmitFun">
<!-- :add-button-show="mode.includes('detail') ? null : '添加属性'"
<div v-if="section.key == 'attrs'" style="position: relative; margin-top: 12px">
<div v-if="!mode.includes('detail')" style="position: absolute; top: -40px; right: 0">
<el-button @click="handleAddAttr" type="text">
<i class="el-icon-plus"></i>
添加参数
</el-button>
</div>
<base-table v-loading="attrListLoading" :table-props="section.props" :page="attrQuery?.params.pageNo || 1"
:limit="attrQuery?.params.pageSize || 10" :table-data="list" @emitFun="handleEmitFun">
<!-- :add-button-show="mode.includes('detail') ? null : '添加属性'"
@emitButtonClick="handleAddAttr" -->
<method-btn
v-if="section.tableBtn && !mode.includes('detail')"
slot="handleBtn"
label="操作"
:method-list="tableBtn"
@clickBtn="handleTableBtnClick" />
</base-table>
<method-btn v-if="section.tableBtn && !mode.includes('detail')" slot="handleBtn" label="操作"
:method-list="tableBtn" @clickBtn="handleTableBtnClick" />
</base-table>
<!-- 分页组件 -->
<pagination
v-show="total > 0"
:total="total"
:page.sync="attrQuery.params.pageNo"
:limit.sync="attrQuery.params.pageSize"
@pagination="getAttrList" />
</div>
</section>
</div>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="attrQuery.params.pageNo"
:limit.sync="attrQuery.params.pageSize" @pagination="getAttrList" />
</div>
</section>
</div>
<div class="drawer-body__footer">
<el-button style="" @click="handleCancel">取消</el-button>
<el-button
type="primary"
v-if="!mode.includes('detail')"
@click="handleSave">
保存
</el-button>
</div>
</div>
<div class="drawer-body__footer">
<el-button style="" @click="handleCancel">取消</el-button>
<el-button type="primary" v-if="!mode.includes('detail')" @click="handleSave">
保存
</el-button>
</div>
</div>
<!-- 属性对话框 -->
<base-dialog
v-if="sections[1].allowAdd"
:dialogTitle="attrTitle"
:dialogVisible="attrFormVisible"
width="45%"
:append-to-body="true"
custom-class="baseDialog"
@close="closeAttrForm"
@cancel="closeAttrForm"
@confirm="submitAttrForm">
<!-- :disabled="mode.includes('detail')" -->
<DialogForm
v-if="attrFormVisible"
ref="attrForm"
v-model="attrForm"
:rows="attrRows" />
</base-dialog>
</el-drawer>
<!-- 属性对话框 -->
<base-dialog v-if="sections[1].allowAdd" :dialogTitle="attrTitle" :dialogVisible="attrFormVisible" width="45%"
:append-to-body="true" custom-class="baseDialog" @close="closeAttrForm" @cancel="closeAttrForm"
@confirm="submitAttrForm">
<!-- :disabled="mode.includes('detail')" -->
<DialogForm v-if="attrFormVisible" ref="attrForm" v-model="attrForm" :data-form="attrForm"
:rows="attrRows"
@update="handleAttrFormUpdate"/>
</base-dialog>
</el-drawer>
</template>
<script>
@@ -232,7 +170,9 @@ export default {
defaultValue: '',
description: '',
remark: '',
alarmContent: '',
alarmContent: '',
displayTip: false,
alarmTip: false,
},
attrFormVisible: false,
attrRows: [
@@ -342,7 +282,27 @@ export default {
},
],
},
],
],
[
{
switch: true,
label: '是否展示',
prop: 'displayTip',
bind: {
'active-value': true,
'inactive-value': false,
},
},
{
switch: true,
label: '超出阈值是否报警',
prop: 'alarmTip',
bind: {
'active-value': true,
'inactive-value': false,
},
},
],
[
{
input: true,
@@ -436,7 +396,24 @@ export default {
}
}
},
methods: {
methods: {
handleAttrFormUpdate(updatedForm) {
console.log('updatedForm', updatedForm);
// 只同步需要的字段,避免覆盖其他数据
this.attrForm = {
...this.attrForm,
...updatedForm,
// 确保开关值是布尔类型(双重保险)
displayTip: Boolean(updatedForm.displayTip),
alarmTip: Boolean(updatedForm.alarmTip),
collection: Number(updatedForm.collection) // 采集开关保持数字类型
};
console.log('开关值同步:', {
displayTip: this.attrForm.displayTip,
alarmTip: this.attrForm.alarmTip
});
},
handleTableBtnClick({ type, data }) {
switch (type) {
case 'edit':
@@ -502,7 +479,8 @@ export default {
name: '',
plcParamName: '',
unit: '',
collection: 1,
collection: 1,
minValue: '',
maxValue: '',
defaultValue: '',
@@ -510,7 +488,9 @@ export default {
remark: '',
equipmentParamType: '',
productionParamType: '',
alarmContent: '',
alarmContent: '',
displayTip: false,
alarmTip: false,
};
this.attrTitle = '添加参数绑定信息';
this.attrFormVisible = true;
@@ -523,8 +503,15 @@ export default {
method: 'get',
params: { id: attrId },
});
if (res.code == 0) {
this.attrForm = res.data;
if (res.code == 0) {
console.log('res.data', res.data);
this.attrForm = {
...res.data,
// // 强制转为数字类型,避免字符串类型导致绑定失败
// displayTip: Number(res.data.displayTip) || 0,
// alarmTip: Number(res.data.alarmTip) || 0
};
this.attrTitle = '编辑参数绑定信息';
this.attrFormVisible = true;
}
@@ -563,7 +550,8 @@ export default {
this.$refs['attrForm'].validate(async (valid) => {
if (!valid) {
return;
}
}
console.log('this.attrForm', this.attrForm);
const isEdit = this.attrForm.id != null;
this.attrFormSubmitting = true;
@@ -583,8 +571,9 @@ export default {
type: 'success',
duration: 1500,
onClose: () => {
this.getAttrList();
this.shouldRefreshPageView = true;
this.shouldRefreshPageView = true;
this.getAttrList();
},
});
}

View File

@@ -103,7 +103,7 @@ export default {
id: null,
code: '',
materialId: '',
price: '',
price: undefined,
startTime: new Date().getTime(),
endTime: null,
remark: '',

View File

@@ -47,15 +47,15 @@ const tableProps = [
prop: 'equipmentName',
label: '设备',
},
{
prop: 'size',
label: '规格',
showOverflowtooltip: true,
},
{
prop: 'process',
label: '产品工艺',
},
// {
// prop: 'size',
// label: '规格',
// showOverflowtooltip: true,
// },
// {
// prop: 'process',
// label: '产品工艺',
// },
{
prop: 'standardCt',
label: '标准节拍pcs/min',

View File

@@ -72,16 +72,6 @@ export default {
dateLabelList: [],
optionArrUrl: [getProductionLinePage],
formConfig: [
{
type: 'select',
label: '产线',
selectOptions: [],
param: 'lineIds',
defaultSelect: [],
multiple: true,
filterable: true,
width: 200,
},
{
type: 'datePicker',
label: '时间',
@@ -92,7 +82,17 @@ export default {
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
param: 'time',
},
},
{
type: 'select',
label: '产线',
selectOptions: [],
param: 'lineIds',
defaultSelect: [],
multiple: true,
filterable: true,
width: 300,
},
{
type: 'button',
btnName: '查询',
@@ -113,7 +113,7 @@ export default {
};
this.optionArrUrl.forEach((item, index) => {
item(params).then((response) => {
this.formConfig[index].selectOptions = response.data.list;
this.formConfig[1].selectOptions = response.data.list;
// this.formConfig[0].defaultSelect = response.data.list[0].id
// this.$set(this.formConfig[0], 'defaultSelect', response.data.list[0].id)
});

View File

@@ -279,7 +279,8 @@ export default {
filterable: true,
clearable: true,
},
},
},
// {
// select: true,
// label: '设备分组',

View File

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

View File

@@ -66,16 +66,15 @@
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="完成单位产品用时" prop="processTime">
<el-input :disabled="isdetail" v-model="dataForm.processTime" placeholder="请输入完成单位产品用时" />
<el-form-item label="完成单位产品用时(S)" prop="processTime">
<el-input :disabled="isdetail" v-model="dataForm.processTime" placeholder="请输入完成单位产品用时(S)" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label=" 加工属性" prop="processType">
<el-select v-model="dataForm.processType" clearable style="width: 100%" :disabled="isdetail"
placeholder="请选择加工属性">
<el-option v-for="dict in processTypeList" :key="dict.id" :label="dict.label"
:value="dict.id" />
<el-form-item label=" 产品工艺" prop="processType">
<el-select multiple v-model="dataForm.processType" clearable style="width: 100%" :disabled="isdetail"
placeholder="请选择产品工艺">
<el-option v-for="dict in processTypeList" :key="dict.id" :label="dict.label" :value="dict.id" />
</el-select>
</el-form-item>
</el-col>

View File

@@ -1,139 +1,142 @@
<template>
<el-dialog
:visible.sync="visible"
:width="'35%'"
:append-to-body="true"
:close-on-click-modal="false"
class="dialog">
<template #title>
<slot name="title">
<div class="titleStyle">
{{ !dataForm.id ? '新增' : '编辑' }}
</div>
</slot>
</template>
<el-dialog v-loading="isLoading" loading-text="处理中..." loading-spinner="el-icon-loading"
loading-background="rgba(255, 255, 255, 0.7)" :visible.sync="visible" :width="'35%'" :append-to-body="true"
:close-on-click-modal="false" class="dialog">
<template #title>
<slot name="title">
<div class="titleStyle">
{{ !dataForm.id ? '新增' : '编辑' }}
</div>
</slot>
</template>
<el-form
ref="dataForm"
:model="dataForm"
:rules="dataRule"
label-width="100px"
@keyup.enter.native="dataFormSubmit()">
<el-form-item label="属性" prop="name">
<el-input
v-model="dataForm.name"
placeholder="请输入属性名"
clearable />
</el-form-item>
<el-form-item label="属性值" prop="value">
<el-input
v-model="dataForm.value"
placeholder="请输入属性值"
clearable />
</el-form-item>
</el-form>
<el-form ref="dataForm" :model="dataForm" :rules="dataRule" label-width="100px"
@keyup.enter.native="dataFormSubmit()">
<el-form-item label="属性名" prop="name">
<el-input v-model="dataForm.name" placeholder="请输入属性名" clearable />
</el-form-item>
<el-form-item label="属性值" prop="value">
<el-input v-model="dataForm.value" placeholder="请输入属性" clearable />
</el-form-item>
</el-form>
<el-row style="text-align: right">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
</el-row>
</el-dialog>
<el-row style="text-align: right">
<el-button @click="visible = false">取消</el-button>
<el-button :loading="isLoading" type="primary" @click="dataFormSubmit()">确定</el-button>
</el-row>
</el-dialog>
</template>
<script>
import {
createProductAttr,
updateProductAttr,
getProductAttr,
createProductAttr,
updateProductAttr,
getProductAttr,
} from '@/api/core/base/productAttr';
export default {
props: {
productId: {
type: String,
default: '',
},
},
data() {
return {
visible: false,
dataForm: {
id: 0,
name: '',
value: '',
},
dataRule: {
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
},
};
},
methods: {
init(id) {
this.dataForm.id = id || '';
this.visible = true;
this.$nextTick(() => {
this.$refs['dataForm'].resetFields();
if (this.dataForm.id) {
getProductAttr(this.dataForm.id).then((res) => {
const { name, value } = res.data;
this.dataForm.name = name;
this.dataForm.value = value;
});
}
});
},
// 表单提交
dataFormSubmit() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
// 修改的提交
if (this.dataForm.id) {
updateProductAttr({
...this.dataForm,
productId: this.productId,
}).then((response) => {
this.$modal.msgSuccess('修改成功');
this.visible = false;
this.$emit('refreshDataList');
});
return;
}
// 添加的提交
createProductAttr({
...this.dataForm,
productId: this.productId,
}).then((response) => {
this.$modal.msgSuccess('新增成功');
this.visible = false;
this.$emit('refreshDataList');
});
}
});
},
},
props: {
productId: {
type: String,
default: '',
},
},
data() {
return {
visible: false,
isLoading: false,
dataForm: {
id: 0,
name: '',
value: '',
},
dataRule: {
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
},
};
},
methods: {
init(id) {
this.dataForm.id = id || '';
this.visible = true;
this.$nextTick(() => {
this.$refs['dataForm'].resetFields();
if (this.dataForm.id) {
getProductAttr(this.dataForm.id).then((res) => {
const { name, value } = res.data;
this.dataForm.name = name;
this.dataForm.value = value;
}).catch(err => {
console.error('获取属性详情失败:', err);
this.$modal.msgError('获取数据失败,请重试');
});
}
});
},
// 表单提交(优化加载状态和错误处理)
async dataFormSubmit() {
// 先验证表单
const valid = await new Promise((resolve) => {
this.$refs['dataForm'].validate((isValid) => resolve(isValid));
});
if (!valid) return;
this.isLoading = true; // 开始加载
try {
if (this.dataForm.id) {
// 编辑操作
await updateProductAttr({
...this.dataForm,
productId: this.productId,
});
this.$modal.msgSuccess('修改成功');
} else {
// 新增操作
await createProductAttr({
...this.dataForm,
productId: this.productId,
});
this.$modal.msgSuccess('新增成功');
}
this.visible = false;
this.$emit('refreshDataList');
} catch (error) {
// 错误处理
console.error('提交失败:', error);
this.$modal.msgError('操作失败,请重试');
} finally {
// 无论成功失败,都关闭加载状态
this.isLoading = false;
}
},
},
};
</script>
<style scoped>
.dialog >>> .el-dialog__body {
padding: 30px 24px;
.dialog>>>.el-dialog__body {
padding: 30px 24px;
}
.dialog >>> .el-dialog__header {
font-size: 16px;
color: rgba(0, 0, 0, 0.85);
font-weight: 500;
padding: 13px 24px;
border-bottom: 1px solid #e9e9e9;
.dialog>>>.el-dialog__header {
font-size: 16px;
color: rgba(0, 0, 0, 0.85);
font-weight: 500;
padding: 13px 24px;
border-bottom: 1px solid #e9e9e9;
}
.dialog >>> .el-dialog__header .titleStyle::before {
content: '';
display: inline-block;
width: 4px;
height: 16px;
background-color: #0b58ff;
border-radius: 1px;
margin-right: 8px;
position: relative;
top: 2px;
.dialog>>>.el-dialog__header .titleStyle::before {
content: '';
display: inline-block;
width: 4px;
height: 16px;
background-color: #0b58ff;
border-radius: 1px;
margin-right: 8px;
position: relative;
top: 2px;
}
</style>

View File

@@ -262,6 +262,14 @@ export default {
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),
},
],

View File

@@ -34,7 +34,7 @@
<el-tabs v-model="activeLabel" :stretch="true" @tab-click="handleTabClick">
<el-tab-pane :label="'全部数据'" name="table">
<base-table-s ref="lineCurrentShiftTable" style="margin-bottom: 16px;" v-if="activeLabel == 'table'"
:page="1" :limit="100" :table-props="tableProps" :table-data="tableData" :max-height="210" />
:page="1" :limit="100" :table-props="tableProps" :table-data="tableData" :max-height="300" />
</el-tab-pane>
<el-tab-pane :label="'\u3000当天生产折线图\u3000'" name="graph">
<div style="height: 230px;" v-if="activeLabel == 'graph'" class="graph">
@@ -57,7 +57,7 @@
<el-tabs v-model="activeLabelDay" :stretch="true" @tab-click="handleTabClick">
<el-tab-pane :label="'全部数据'" name="table">
<base-table-s ref="lineTodayTable" style="margin-bottom: 16px;" v-if="activeLabelDay == 'table'"
:page="1" :limit="100" :table-props="tableProps" :table-data="tableData2" :max-height="210" />
:page="1" :limit="100" :table-props="tableProps" :table-data="tableData2" :max-height="300" />
</el-tab-pane>
<el-tab-pane :label="'\u3000当天生产折线图\u3000'" name="graph">
<div style="height: 230px;" v-if="activeLabelDay == 'graph'" class="graph">
@@ -71,9 +71,55 @@
</el-row>
</div>
</div>
<div class="content-inner" v-if="listQuery.timeType === 3">
<!-- 当班数据占1/2剩余高度 -->
<div class="content-card energyOverlimitLog">
<span class="blue-block"></span>
<span class="tip">上一班数据</span>
<el-row style="margin-top: 10px;">
<el-col class="custom-tabs">
<el-tabs v-model="activeLabel" :stretch="true" @tab-click="handleTabClick">
<el-tab-pane :label="'全部数据'" name="table">
<base-table-s ref="lineCurrentShiftTable" style="margin-bottom: 16px;" v-if="activeLabel == 'table'"
:page="1" :limit="100" :table-props="tableProps" :table-data="tableData" :max-height="300" />
</el-tab-pane>
<el-tab-pane :label="'\u3000当天生产折线图\u3000'" name="graph">
<div style="height: 230px;" v-if="activeLabel == 'graph'" class="graph">
<barChart v-if="tableData && tableData.length > 0" ref="barChart" height="230px"
:bar-data="tableData" />
<div v-else class="no-data-bg"></div>
</div>
</el-tab-pane>
</el-tabs>
</el-col>
</el-row>
</div>
<!-- 当天数据占1/2剩余高度 -->
<div class="content-card energyOverlimitLog">
<span class="blue-block"></span>
<span class="tip">当天数据</span>
<el-row style="margin-top: 10px;">
<el-col class="custom-tabs">
<el-tabs v-model="activeLabelDay" :stretch="true" @tab-click="handleTabClick">
<el-tab-pane :label="'全部数据'" name="table">
<base-table-s ref="lineTodayTable" style="margin-bottom: 16px;" v-if="activeLabelDay == 'table'"
:page="1" :limit="100" :table-props="tableProps" :table-data="tableData2" :max-height="300" />
</el-tab-pane>
<el-tab-pane :label="'\u3000当天生产折线图\u3000'" name="graph">
<div style="height: 230px;" v-if="activeLabelDay == 'graph'" class="graph">
<barChart v-if="tableData2 && tableData2.length > 0" ref="barChart" height="230px"
:bar-data="tableData2" />
<div v-else class="no-data-bg"></div>
</div>
</el-tab-pane>
</el-tabs>
</el-col>
</el-row>
</div>
</div>
<!-- 3.2 时间维度为自定义时的内容两个平分高度的div -->
<div class="content-inner" v-else>
<div class="content-inner" v-if="listQuery.timeType === 2">
<div class="content-card energyOverlimitLog">
<span class="blue-block"></span>
<span class="tip" v-if="listQuery.startTime && listQuery.endTime">
@@ -86,7 +132,7 @@
<span class="tip" v-else>生产表格数据 </span>
<div class="graph">
<base-table-s ref="lineCustomTable" style="margin-bottom: 16px;" v-if="activeLabel == 'table'" :page="1"
:limit="100" :table-props="tableProps" :max-height="210" :table-data="tableDataCustom" />
:limit="100" :table-props="tableProps" :max-height="300" :table-data="tableDataCustom" />
</div>
</div>
@@ -113,7 +159,7 @@
<span class="tip">当班数据</span>
<base-table-s ref="productCurrentShiftTable" style="margin-bottom: 16px;" v-if="activeLabel == 'table'"
:page="1" :limit="100" :table-props="productTableProps" :table-data="productTableData"
:max-height="210" />
:max-height="300" />
</div>
<!-- 当天数据占1/2剩余高度 -->
@@ -122,7 +168,7 @@
<span class="tip">当天数据</span>
<base-table-s ref="productTodayTable" style="margin-bottom: 16px;" v-if="activeLabelDay == 'table'"
:page="1" :limit="100" :table-props="productTablePropsDay" :table-data="productTableDataDay"
:max-height="210" />
:max-height="300" />
</div>
</div>
</div>
@@ -142,7 +188,7 @@
</span>
<span class="tip" v-else>生产表格数据 </span>
<base-table-s ref="productCustomTable" style="margin-bottom: 16px;" v-if="activeLabel == 'table'" :page="1"
:limit="100" :table-props="productTableProps" :table-data="productTableData" :max-height="210" />
:limit="100" :table-props="productTableProps" :table-data="productTableData" :max-height="300" />
</div>
</div>
</div>
@@ -154,7 +200,7 @@
import { parseTime } from '@/filter/code-filter';
import {
getLineAuto, getPdList, getPdlAutoReportNewSearchNow, getProductAuto,
getProcessAutoReportGroup, getProcessAutoReportDay, getProcessAutoReportNew
getProcessAutoReportGroup, getProcessAutoReportDay, getProcessAutoReportNew, getPdlAutoReportNewSearchLastGroup
} from '@/api/core/monitoring/auto';
import { getFactoryPage } from '@/api/core/base/factory';
import * as XLSX from 'xlsx';
@@ -165,11 +211,11 @@ import ButtonNav from '@/components/ButtonNav';
import { listData } from '@/api/system/dict/data';
// 表格列配置
const tableProps = [
// {
// prop: 'factoryName',
// label: '工厂',
// fixed: true
// },
{
prop: 'factoryName',
label: '工厂',
fixed: true
},
{
prop: 'lineName',
label: '产线',
@@ -368,15 +414,27 @@ export default {
productTableDataDay: [],
tableDataCustom: [],
list: [], // 折线图数据
timeTypeOptions: {
productLine: [ // 按产线监控(包含上一班)
{ id: 1, name: '当天' },
{ id: 2, name: '自定义' },
{ id: 3, name: '上一班' }
],
product: [ // 按产品监控(不含上一班)
{ id: 1, name: '当天' },
{ id: 2, name: '自定义' }
]
},
formConfig: [
{
type: 'select',
label: '时间维度',
selectOptions: [
{ id: 1, name: '当天' },
{ id: 2, name: '自定义' }
{ id: 2, name: '自定义' },
{ id: 3, name: '上一班' }
],
width: 80,
width: 100,
onchange: true,
// defaultSelect: 1,
clearable: false,
@@ -437,6 +495,7 @@ export default {
{ id: 3, name: '月' },
{ id: 4, name: '年' }
],
onchange: true,
width: 80,
param: 'reportType'
},
@@ -467,6 +526,13 @@ export default {
productTableProps() {
// 当班数据的完整表头(基础列 + 当班动态表头)
const baseColumns = [
{
prop: 'sizes',
label: '规格',
width: 105,
showOverflowtooltip: true,
fixed: true
},
{
prop: 'process',
label: '产品工艺',
@@ -476,7 +542,8 @@ export default {
prop: 'processType',
label: '产品类型',
filter: (val) => (val != 1 ? '面板' : '背板'),
fixed: true
fixed: true,
sortable: true,
},
{
prop: 'factoryName',
@@ -513,13 +580,16 @@ export default {
this.$nextTick(() => {
if (this.$refs.buttonNav) {
this.$refs.buttonNav.currentMenu = '按产线监控';
this.formConfig[0].selectOptions = this.timeTypeOptions.productLine;
}
if (this.$refs.searchBarForm) {
// this.$refs.searchBarForm.formInline.timeVal = [
// yesterday.getTime(),
// end.getTime()
// ];
this.$refs.searchBarForm.formInline.timeType = 1
}
});
@@ -576,18 +646,26 @@ export default {
// 3. 执行公共重置
resetCommon();
// if (this.activeName === 'productLine') {
// } else {
// }
// 4. 根据类型设置差异化的表单配置(仅处理不同的部分)
if (isProductLine) {
// 按产线监控:显示产线,隐藏产品工艺和类型
// 按产线监控:显示产线,隐藏产品工艺和类型,时间维度包含上一班
this.formConfig[0].selectOptions = this.timeTypeOptions.productLine; // 恢复上一班选项
this.formConfig[3].type = 'select'; // 显示产线
this.formConfig[4].type = ''; // 隐藏产品工艺
this.formConfig[5].type = ''; // 隐藏产品类型
this.getDayDataList();
} else {
// 按产品监控:显示产品工艺和类型,隐藏产线
// 按产品监控:显示产品工艺和类型,隐藏产线,时间维度移除上一班
this.formConfig[0].selectOptions = this.timeTypeOptions.product; // 不含上一班
this.formConfig[3].type = ''; // 隐藏产线
this.formConfig[4].type = 'select'; // 显示产品工艺
this.formConfig[5].type = 'select'; // 显示产品类型
this.getProductList();
}
},
@@ -601,6 +679,12 @@ export default {
{ ref: 'lineCurrentShiftTable', name: '产线监控_当班数据' },
{ ref: 'lineTodayTable', name: '产线监控_当天数据' }
], '产线监控_当班及当天数据汇总'); // 传入自定义汇总文件名
} else if (this.listQuery.timeType === 1) {
// 产线-当天:导出当班 + 当天两个表格(自定义汇总文件名)
this.exportMultipleTables([
{ ref: 'lineCurrentShiftTable', name: '产线监控_上一班数据' },
{ ref: 'lineTodayTable', name: '产线监控_当天数据' }
], '产线监控_当班及当天数据汇总'); // 传入自定义汇总文件名
} else {
// 产线-自定义:导出一个表格(自定义文件名)
this.exportSingleTable('lineCustomTable', '产线监控_自定义时间数据');
@@ -801,31 +885,126 @@ export default {
this.formConfig[2].selectOptions = res.data.list || [];
});
},
getWeekTimeRange(date) {
const targetDate = new Date(date);
const day = targetDate.getDay() || 7; // 周日转为7
const year = targetDate.getFullYear();
const month = targetDate.getMonth();
const dateNum = targetDate.getDate();
// 本周一 00:00:00
const startDate = new Date(year, month, dateNum - day + 1);
startDate.setHours(0, 0, 0, 0);
// 本周日 23:59:59
const endDate = new Date(year, month, dateNum - day + 7);
endDate.setHours(23, 59, 59, 999);
return {
startTime: startDate.getTime(),
endTime: endDate.getTime()
};
},
// 辅助函数获取本年的开始和结束时间戳1月1日00:00:00 到 12月31日23:59:59
getYearTimeRange(date) {
const targetDate = new Date(date);
const year = targetDate.getFullYear();
// 本年1月1日 00:00:00
const startDate = new Date(year, 0, 1);
startDate.setHours(0, 0, 0, 0);
// 本年12月31日 23:59:59
const endDate = new Date(year, 11, 31);
endDate.setHours(23, 59, 59, 999);
return {
startTime: startDate.getTime(),
endTime: endDate.getTime()
};
},
// 搜索/导出按钮点击
buttonClick(val) {
if (this.activeName === 'productLine') {
this.listQuery.pageNo = 1;
this.listQuery.pageSize = 10;
this.listQuery.factoryId = val.factoryId || undefined;
this.listQuery.process = val.process ? val.process : [];
this.listQuery.lineId = val.lineId ? val.lineId : [];
this.listQuery.processType = val.processType ? val.processType : [];
this.listQuery.reportType = val.reportType || undefined;
this.listQuery.timeType = val.timeType || undefined;
this.listQuery.searchType = val.searchType || undefined;
// 处理不同时间选择类型
if (val.timeVal) {
this.listQuery.startTime = val.timeVal[0];
this.listQuery.endTime = val.timeVal[1];
} else if (val.timeValWeek) {
// 周选择器
const { startTime, endTime } = this.getWeekTimeRange(val.timeValWeek);
this.listQuery.startTime = startTime;
this.listQuery.endTime = endTime;
} else if (val.timeValMonth) {
// 月选择器
this.listQuery.startTime = val.timeValMonth[0];
this.listQuery.endTime = val.timeValMonth[1];
} else if (val.timeValYear) {
// 年选择器
const { startTime, endTime } = this.getYearTimeRange(val.timeValYear);
this.listQuery.startTime = startTime;
this.listQuery.endTime = endTime;
} else {
this.listQuery.startTime = undefined;
this.listQuery.endTime = undefined;
}
} else {
this.listQuery.pageNo = 1;
this.listQuery.pageSize = 10;
this.listQuery.factoryId = val.factoryId || []
this.listQuery.process = val.process ? val.process : [];
this.listQuery.lineId = undefined;
this.listQuery.processType = val.processType ? val.processType : [];
this.listQuery.reportType = val.reportType || undefined;
this.listQuery.timeType = val.timeType || undefined;
this.listQuery.searchType = val.searchType || undefined;
// 处理不同时间选择类型
if (val.timeVal) {
this.listQuery.startTime = val.timeVal[0];
this.listQuery.endTime = val.timeVal[1];
} else if (val.timeValWeek) {
// 周选择器
const { startTime, endTime } = this.getWeekTimeRange(val.timeValWeek);
this.listQuery.startTime = startTime;
this.listQuery.endTime = endTime;
} else if (val.timeValMonth) {
// 月选择器
this.listQuery.startTime = val.timeValMonth[0];
this.listQuery.endTime = val.timeValMonth[1];
} else if (val.timeValYear) {
// 年选择器
const { startTime, endTime } = this.getYearTimeRange(val.timeValYear);
this.listQuery.startTime = startTime;
this.listQuery.endTime = endTime;
} else {
this.listQuery.startTime = undefined;
this.listQuery.endTime = undefined;
}
}
switch (val.btnName) {
case 'search':
this.listQuery.pageNo = 1;
this.listQuery.pageSize = 10;
this.listQuery.factoryId = val.factoryId || undefined;
this.listQuery.process = val.process ? val.process : [];
this.listQuery.lineId = val.lineId ? val.lineId : [];
this.listQuery.processType = val.processType ? val.processType : [];
this.listQuery.reportType = val.reportType || undefined;
this.listQuery.timeType = val.timeType || undefined;
this.listQuery.searchType = val.searchType || undefined;
this.listQuery.startTime = val.timeVal ? val.timeVal[0] : undefined;
this.listQuery.endTime = val.timeVal ? val.timeVal[1] : undefined;
if (this.activeName === 'productLine') {
if (this.listQuery.timeType === 1) {
this.getDayDataList();
} else if (this.listQuery.timeType === 3) {
this.getLastDayDataList();
} else {
this.getDataList();
}
} else {
this.getProductList();
}
break;
case 'export':
@@ -834,6 +1013,9 @@ export default {
case 'reset':
this.$refs.searchBarForm.resetForm();
this.$refs.searchBarForm.formInline.timeType = 1;
this.formConfig[0].selectOptions = this.activeName === 'productLine'
? this.timeTypeOptions.productLine
: this.timeTypeOptions.product;
this.listQuery = {
pageSize: 100,
pageNo: 1,
@@ -842,12 +1024,9 @@ export default {
};
if (this.activeName === 'productLine') {
this.getDayDataList();
} else {
this.getProductList();
}
break;
default:
console.log(val);
@@ -1097,6 +1276,28 @@ export default {
return [...lineColumns, totalColumn];
},
// 为当天时调用的接口
getLastDayDataList() {
getPdlAutoReportNewSearchLastGroup().then((response) => {
this.tableData = response.data.classData.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;
return item;
});
// console.log(this.tableData, this.tableData2);
});
},
getDayDataList() {
getPdlAutoReportNewSearchNow().then((response) => {
this.tableData = response.data.classData.map((item, index) => {
@@ -1156,24 +1357,21 @@ export default {
console.log(value);
this.listQuery.timeType = value;
if (value === 1) {
// 切换为“当天”:隐藏查询类型、报表类型、时间范围
if (value === 2) {
// 切换为“自定义”:显示查询类型和时间范围
this.formConfig[1].type = 'select';
this.$refs.searchBarForm.formInline.timeType = value;
this.listQuery.timeType = value;
this.formConfig[7].type = 'datePicker';
} else {
this.formConfig[1].type = '';
this.formConfig[6].type = '';
this.formConfig[7].type = '';
this.$refs.searchBarForm.formInline.timeType = value;
this.listQuery.timeType = value;
this.$refs.searchBarForm.formInline.searchType = undefined;
this.$refs.searchBarForm.formInline.reportType = undefined;
this.$refs.searchBarForm.formInline.timeVal = undefined;
} else {
// 切换为“自定义”:显示查询类型和时间范围
this.formConfig[1].type = 'select';
this.$refs.searchBarForm.formInline.timeType = value;
this.listQuery.timeType = value;
this.formConfig[7].type = 'datePicker';
}
} else if (param === 'searchType') {
if (value === 1) {
@@ -1187,7 +1385,74 @@ export default {
this.formConfig[7].type = 'datePicker';
}
} else if (param === 'reportType') {
this.$refs.searchBarForm.formInline.timeVal = undefined;
if (this.$refs.searchBarForm && this.$refs.searchBarForm.formInline) {
const formInline = this.$refs.searchBarForm.formInline;
// 精准判断只有字段存在时才置为undefined不存在则不处理
if ('timeVal' in formInline) {
formInline.timeVal = undefined;
}
if ('timeValWeek' in formInline) {
formInline.timeValWeek = undefined;
}
if ('timeValMonth' in formInline) {
formInline.timeValMonth = undefined;
}
if ('timeValYear' in formInline) {
formInline.timeValYear = undefined;
}
}
this.listQuery.startTime = undefined;
this.listQuery.endTime = undefined;
if (value === 1) {
this.formConfig[7] = {
type: 'datePicker',
label: '时间范围',
dateType: 'datetimerange',
format: 'yyyy-MM-dd HH:mm:ss',
valueFormat: 'timestamp',
rangeSeparator: '-',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
param: 'timeVal',
width: 350
};
} else if (value === 2) {
this.formConfig[7] = {
type: 'datePicker',
label: '时间范围',
dateType: 'week',
placeholder: '选择日期',
format: 'yyyy-MM-dd',
valueFormat: 'yyyy-MM-dd',
param: 'timeValWeek',
width: 250,
};
} else if (value === 3) {
this.formConfig[7] = {
type: 'datePicker',
label: '时间范围',
dateType: 'monthrange',
format: 'yyyy-MM-dd',
valueFormat: 'timestamp',
rangeSeparator: '-',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
param: 'timeValMonth',
defaultTime: ['00:00:00', '23:59:59'],
width: 250,
};
} else if (value === 4) {
this.formConfig[7] = {
type: 'datePicker',
label: '时间范围',
dateType: 'year',
placeholder: '选择年份',
format: 'yyyy',
valueFormat: 'timestamp',
param: 'timeValYear',
width: 250,
};
}
} else if (param === 'factoryId') {
// 切换工厂时刷新产线列表
this.listQuery.lineId = [];
@@ -1234,7 +1499,7 @@ export default {
.content-wrapper {
// flex: 1;
width: 100%;
height: calc(100vh - 303px);
height: calc(100vh - 280px);
// overflow: hidden;
}

View File

@@ -77,7 +77,7 @@ const tableProps = [
},
{
prop: 'palletNum',
label: '下片托数'
label: '一托玻璃数量/片'
},
{
prop: 'startTime',
@@ -92,7 +92,7 @@ const tableProps = [
width: 160
},
{
prop: 'outputNum',
prop: 'length',
label: '玻璃长度/mm'
},
{
@@ -314,9 +314,10 @@ export default {
case 'search':
this.listQuery.pageNo = 1;
this.listQuery.pageSize = 10;
this.listQuery.productId = val.productId ? val.productId : undefined;
this.listQuery.startTime = val.timeVal ? val.timeVal[0]: undefined;
this.listQuery.endTime = val.timeVal ? val.timeVal[1]: undefined;
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();

View File

@@ -77,7 +77,7 @@ const tableProps = [
},
{
prop: 'palletNum',
label: '下片托数'
label: '一托玻璃数量/片'
},
{
prop: 'startTime',
@@ -92,7 +92,7 @@ const tableProps = [
width: 160
},
{
prop: 'outputNum',
prop: 'length',
label: '玻璃长度/mm'
},
{

View File

@@ -262,6 +262,14 @@ export default {
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),
},
],

View File

@@ -34,7 +34,7 @@
<el-tabs v-model="activeLabel" :stretch="true" @tab-click="handleTabClick">
<el-tab-pane :label="'全部数据'" name="table">
<base-table-s ref="lineCurrentShiftTable" style="margin-bottom: 16px;" v-if="activeLabel == 'table'"
:page="1" :limit="100" :table-props="tableProps" :table-data="tableData" :max-height="210" />
:page="1" :limit="100" :table-props="tableProps" :table-data="tableData" :max-height="300" />
</el-tab-pane>
<el-tab-pane :label="'\u3000当天生产折线图\u3000'" name="graph">
<div style="height: 230px;" v-if="activeLabel == 'graph'" class="graph">
@@ -57,7 +57,7 @@
<el-tabs v-model="activeLabelDay" :stretch="true" @tab-click="handleTabClick">
<el-tab-pane :label="'全部数据'" name="table">
<base-table-s ref="lineTodayTable" style="margin-bottom: 16px;" v-if="activeLabelDay == 'table'"
:page="1" :limit="100" :table-props="tableProps" :table-data="tableData2" :max-height="210" />
:page="1" :limit="100" :table-props="tableProps" :table-data="tableData2" :max-height="300" />
</el-tab-pane>
<el-tab-pane :label="'\u3000当天生产折线图\u3000'" name="graph">
<div style="height: 230px;" v-if="activeLabelDay == 'graph'" class="graph">
@@ -71,22 +71,68 @@
</el-row>
</div>
</div>
<div class="content-inner" v-if="listQuery.timeType === 3">
<!-- 当班数据占1/2剩余高度 -->
<div class="content-card energyOverlimitLog">
<span class="blue-block"></span>
<span class="tip">上一班数据</span>
<el-row style="margin-top: 10px;">
<el-col class="custom-tabs">
<el-tabs v-model="activeLabel" :stretch="true" @tab-click="handleTabClick">
<el-tab-pane :label="'全部数据'" name="table">
<base-table-s ref="lineCurrentShiftTable" style="margin-bottom: 16px;" v-if="activeLabel == 'table'"
:page="1" :limit="100" :table-props="tableProps" :table-data="tableData" :max-height="300" />
</el-tab-pane>
<el-tab-pane :label="'\u3000当天生产折线图\u3000'" name="graph">
<div style="height: 230px;" v-if="activeLabel == 'graph'" class="graph">
<barChart v-if="tableData && tableData.length > 0" ref="barChart" height="230px"
:bar-data="tableData" />
<div v-else class="no-data-bg"></div>
</div>
</el-tab-pane>
</el-tabs>
</el-col>
</el-row>
</div>
<!-- 当天数据占1/2剩余高度 -->
<div class="content-card energyOverlimitLog">
<span class="blue-block"></span>
<span class="tip">当天数据</span>
<el-row style="margin-top: 10px;">
<el-col class="custom-tabs">
<el-tabs v-model="activeLabelDay" :stretch="true" @tab-click="handleTabClick">
<el-tab-pane :label="'全部数据'" name="table">
<base-table-s ref="lineTodayTable" style="margin-bottom: 16px;" v-if="activeLabelDay == 'table'"
:page="1" :limit="100" :table-props="tableProps" :table-data="tableData2" :max-height="300" />
</el-tab-pane>
<el-tab-pane :label="'\u3000当天生产折线图\u3000'" name="graph">
<div style="height: 230px;" v-if="activeLabelDay == 'graph'" class="graph">
<barChart v-if="tableData2 && tableData2.length > 0" ref="barChart" height="230px"
:bar-data="tableData2" />
<div v-else class="no-data-bg"></div>
</div>
</el-tab-pane>
</el-tabs>
</el-col>
</el-row>
</div>
</div>
<!-- 3.2 时间维度为自定义时的内容两个平分高度的div -->
<div class="content-inner" v-else>
<div class="content-inner" v-if="listQuery.timeType === 2">
<div class="content-card energyOverlimitLog">
<span class="blue-block"></span>
<span class="tip" v-if="listQuery.startTime && listQuery.endTime">
<span>{{ reportTypeMap[listQuery.reportType] }}</span>
<span>
{{ parseTime(listQuery.startTime) }} {{
parseTime(listQuery.endTime) }}
parseTime(listQuery.endTime) }}
</span>
</span>
<span class="tip" v-else>生产表格数据 </span>
<div class="graph">
<base-table-s ref="lineCustomTable" style="margin-bottom: 16px;" v-if="activeLabel == 'table'" :page="1"
:limit="100" :table-props="tableProps" :max-height="210" :table-data="tableDataCustom" />
:limit="100" :table-props="tableProps" :max-height="300" :table-data="tableDataCustom" />
</div>
</div>
@@ -113,7 +159,7 @@
<span class="tip">当班数据</span>
<base-table-s ref="productCurrentShiftTable" style="margin-bottom: 16px;" v-if="activeLabel == 'table'"
:page="1" :limit="100" :table-props="productTableProps" :table-data="productTableData"
:max-height="210" />
:max-height="300" />
</div>
<!-- 当天数据占1/2剩余高度 -->
@@ -122,7 +168,7 @@
<span class="tip">当天数据</span>
<base-table-s ref="productTodayTable" style="margin-bottom: 16px;" v-if="activeLabelDay == 'table'"
:page="1" :limit="100" :table-props="productTablePropsDay" :table-data="productTableDataDay"
:max-height="210" />
:max-height="300" />
</div>
</div>
</div>
@@ -142,7 +188,7 @@
</span>
<span class="tip" v-else>生产表格数据 </span>
<base-table-s ref="productCustomTable" style="margin-bottom: 16px;" v-if="activeLabel == 'table'" :page="1"
:limit="100" :table-props="productTableProps" :table-data="productTableData" :max-height="210" />
:limit="100" :table-props="productTableProps" :table-data="productTableData" :max-height="300" />
</div>
</div>
</div>
@@ -154,7 +200,7 @@
import { parseTime } from '@/filter/code-filter';
import {
getLineAuto, getPdList, getPdlAutoReportNewSearchNow, getProductAuto,
getProcessAutoReportGroup, getProcessAutoReportDay, getProcessAutoReportNew
getProcessAutoReportGroup, getProcessAutoReportDay, getProcessAutoReportNew, getPdlAutoReportNewSearchLastGroup
} from '@/api/core/monitoring/auto';
import { getFactoryPage } from '@/api/core/base/factory';
import * as XLSX from 'xlsx';
@@ -165,11 +211,11 @@ import ButtonNav from '@/components/ButtonNav';
import { listData } from '@/api/system/dict/data';
// 表格列配置
const tableProps = [
// {
// prop: 'factoryName',
// label: '工厂',
// fixed: true
// },
{
prop: 'factoryName',
label: '工厂',
fixed: true
},
{
prop: 'lineName',
label: '产线',
@@ -368,15 +414,27 @@ export default {
productTableDataDay: [],
tableDataCustom: [],
list: [], // 折线图数据
timeTypeOptions: {
productLine: [ // 按产线监控(包含上一班)
{ id: 1, name: '当天' },
{ id: 2, name: '自定义' },
{ id: 3, name: '上一班' }
],
product: [ // 按产品监控(不含上一班)
{ id: 1, name: '当天' },
{ id: 2, name: '自定义' }
]
},
formConfig: [
{
type: 'select',
label: '时间维度',
selectOptions: [
{ id: 1, name: '当天' },
{ id: 2, name: '自定义' }
{ id: 2, name: '自定义' },
{ id: 3, name: '上一班' }
],
width: 80,
width: 100,
onchange: true,
// defaultSelect: 1,
clearable: false,
@@ -438,6 +496,7 @@ export default {
{ id: 4, name: '年' }
],
width: 80,
onchange: true,
param: 'reportType'
},
{
@@ -513,17 +572,20 @@ export default {
this.$nextTick(() => {
if (this.$refs.buttonNav) {
this.$refs.buttonNav.currentMenu = '按产品监控';
this.formConfig[0].selectOptions = this.timeTypeOptions.product;
}
if (this.$refs.searchBarForm) {
// this.$refs.searchBarForm.formInline.timeVal = [
// yesterday.getTime(),
// end.getTime()
// ];
this.$refs.searchBarForm.formInline.timeType = 1
}
});
this.getProductList();
this.getDayDataList();
this.getPdLineList();
const queryParams = {
pageNo: 1,
@@ -579,15 +641,21 @@ export default {
// 4. 根据类型设置差异化的表单配置(仅处理不同的部分)
if (isProductLine) {
// 按产线监控:显示产线,隐藏产品工艺和类型
// 按产线监控:显示产线,隐藏产品工艺和类型,时间维度包含上一班
this.formConfig[0].selectOptions = this.timeTypeOptions.productLine; // 恢复上一班选项
this.formConfig[3].type = 'select'; // 显示产线
this.formConfig[4].type = ''; // 隐藏产品工艺
this.formConfig[5].type = ''; // 隐藏产品类型
this.getDayDataList();
} else {
// 按产品监控:显示产品工艺和类型,隐藏产线
// 按产品监控:显示产品工艺和类型,隐藏产线,时间维度移除上一班
this.formConfig[0].selectOptions = this.timeTypeOptions.product; // 不含上一班
this.formConfig[3].type = ''; // 隐藏产线
this.formConfig[4].type = 'select'; // 显示产品工艺
this.formConfig[5].type = 'select'; // 显示产品类型
this.getProductList();
}
},
@@ -596,48 +664,52 @@ export default {
// 按产线监控
if (this.activeName === 'productLine') {
if (this.listQuery.timeType === 1) {
// 产线-当天:导出当班 + 当天两个表格
// 产线-当天:导出当班 + 当天两个表格(自定义汇总文件名)
this.exportMultipleTables([
{ ref: 'lineCurrentShiftTable', name: '产线监控_当班数据' },
{ ref: 'lineTodayTable', name: '产线监控_当天数据' }
]);
], '产线监控_当班及当天数据汇总'); // 传入自定义汇总文件名
} else if (this.listQuery.timeType === 1) {
// 产线-当天:导出当班 + 当天两个表格(自定义汇总文件名)
this.exportMultipleTables([
{ ref: 'lineCurrentShiftTable', name: '产线监控_上一班数据' },
{ ref: 'lineTodayTable', name: '产线监控_当天数据' }
], '产线监控_当班及当天数据汇总'); // 传入自定义汇总文件名
} else {
// 产线-自定义:导出一个表格
// 产线-自定义:导出一个表格(自定义文件名)
this.exportSingleTable('lineCustomTable', '产线监控_自定义时间数据');
}
}
// 按产品监控
else {
if (this.listQuery.timeType === 1) {
// 产品-当天:导出当班 + 当天两个表格
// 产品-当天:导出当班 + 当天两个表格(自定义汇总文件名)
this.exportMultipleTables([
{ ref: 'productCurrentShiftTable', name: '产品监控_当班数据' },
{ ref: 'productTodayTable', name: '产品监控_当天数据' }
]);
], '产品监控_当班及当天数据汇总'); // 传入自定义汇总文件名
} else {
// 产品-自定义:导出一个表格
// 产品-自定义:导出一个表格(自定义文件名)
this.exportSingleTable('productCustomTable', '产品监控_自定义时间数据');
}
}
},
// 导出单个表格
// 导出单个表格(保持原有逻辑,文件名由调用方传入,已区分不同场景)
exportSingleTable(refName, fileName) {
const table = this.getTableDom(refName);
if (!table) return;
// 克隆表格并移除固定列
const clonedTable = table.cloneNode(true);
this.removeFixedColumns(clonedTable);
this.adjustTableForExport(clonedTable);
// 转换为工作簿并导出
const workbook = XLSX.utils.table_to_book(clonedTable);
const fullName = `${fileName}_${this.formatDate()}.xlsx`;
XLSX.writeFile(workbook, fullName);
},
// 导出多个表格(同一个Excel的不同sheet
exportMultipleTables(tables) {
// 导出多个表格(新增自定义汇总文件名参数,适配不同选项卡
exportMultipleTables(tables, customSummaryName) {
const workbook = XLSX.utils.book_new();
let hasValidTable = false;
@@ -646,11 +718,9 @@ export default {
if (!table) return;
hasValidTable = true;
// 克隆表格并移除固定列
const clonedTable = table.cloneNode(true);
this.removeFixedColumns(clonedTable);
this.adjustTableForExport(clonedTable);
// 转换为sheet并添加到工作簿
const worksheet = XLSX.utils.table_to_sheet(clonedTable);
XLSX.utils.book_append_sheet(workbook, worksheet, name);
});
@@ -660,31 +730,128 @@ export default {
return;
}
// 导出包含多个sheet的Excel
const fullName = `生产监控数据汇总_${this.formatDate()}.xlsx`;
// 动态生成汇总文件名:自定义名称 + 日期
const fullName = `${customSummaryName}_${this.formatDate()}.xlsx`;
XLSX.writeFile(workbook, fullName);
},
// 辅助方法获取表格DOM元素
// 辅助方法获取表格DOM元素包含表头
getTableDom(refName) {
const tableComponent = this.$refs[refName];
if (!tableComponent) {
this.$message.warning(`未找到${refName}表格`);
return null;
}
// 获取表格组件内部的el-table DOM
const tableDom = tableComponent.$el.querySelector('.el-table__body-wrapper table');
if (!tableDom) {
// 获取完整表格容器(包含表头和表体)
const tableContainer = tableComponent.$el.querySelector('.el-table');
if (!tableContainer) {
this.$message.warning(`${refName}表格数据为空`);
return null;
}
return tableDom;
return tableContainer;
},
// 辅助方法:移除固定列(避免重复导出
removeFixedColumns(table) {
const fixedColumns = table.querySelectorAll('.el-table__fixed, .el-table__fixed-right');
fixedColumns.forEach(col => col.remove());
// 重点修复:调整表格结构(解决表头重复问题
adjustTableForExport(tableContainer) {
// 1. 移除空表格提示和分页等无关元素
const emptyCells = tableContainer.querySelectorAll('.el-table__empty-block, .el-pagination');
emptyCells.forEach(cell => cell.remove());
// 2. 定位核心元素
const mainTable = tableContainer.querySelector('.el-table__body-wrapper table'); // 主表体
const fixedLeft = tableContainer.querySelector('.el-table__fixed'); // 左固定列容器
const fixedRight = tableContainer.querySelector('.el-table__fixed-right'); // 右固定列容器
// 3. 提取所有表头行(支持多级表头,按层级收集)
const allHeaderRows = []; // 存储所有层级的表头行(每行是一个单元格数组)
// 辅助函数:提取某个容器内的所有表头行(处理多级表头)
const extractHeaderRows = (container) => {
const headerWrapper = container?.querySelector('.el-table__header-wrapper table');
if (!headerWrapper) return [];
const thead = headerWrapper.querySelector('thead');
if (!thead) return [];
// 多级表头可能有多个tr
const headerRows = Array.from(thead.querySelectorAll('tr'));
return headerRows.map(row => Array.from(row.querySelectorAll('th'))); // 每行的单元格数组
};
// 收集左固定列的表头行(可能有多层)
const fixedLeftHeaderRows = extractHeaderRows(fixedLeft);
// 收集主表头的表头行(可能有多层)
const mainHeaderRows = extractHeaderRows(tableContainer);
// 收集右固定列的表头行(可能有多层)
const fixedRightHeaderRows = extractHeaderRows(fixedRight);
// 4. 合并表头行(按层级对齐,左固定列 → 主表头 → 右固定列)
// 确定最大表头层级(多级表头的行数)
const maxLevel = Math.max(
fixedLeftHeaderRows.length,
mainHeaderRows.length,
fixedRightHeaderRows.length
);
// 按层级合并每行的单元格
for (let i = 0; i < maxLevel; i++) {
const rowCells = [];
// 左固定列当前层级的单元格(不足则补空)
rowCells.push(...(fixedLeftHeaderRows[i] || []));
// 主表头当前层级的单元格(不足则补空)
rowCells.push(...(mainHeaderRows[i] || []));
// 右固定列当前层级的单元格(不足则补空)
rowCells.push(...(fixedRightHeaderRows[i] || []));
allHeaderRows.push(rowCells);
}
// 5. 清理所有原有表头和固定列容器(避免干扰)
tableContainer.querySelectorAll('thead').forEach(thead => thead.remove());
tableContainer.querySelectorAll('.el-table__header-wrapper').forEach(wrapper => wrapper.remove());
[fixedLeft, fixedRight].forEach(container => container?.remove());
// 6. 构建唯一表头并添加到主表体(支持多级表头)
if (mainTable && allHeaderRows.length > 0) {
const newThead = document.createElement('thead');
// 按层级添加表头行
allHeaderRows.forEach(cells => {
const newTr = document.createElement('tr');
cells.forEach(cell => {
// 复制单元格避免DOM残留引用问题
const clonedCell = cell.cloneNode(true);
newTr.appendChild(clonedCell);
});
newThead.appendChild(newTr);
});
// 将新表头插入主表体最前面
mainTable.insertBefore(newThead, mainTable.firstChild);
}
// 7. 合并固定列数据到主表体仅处理tbody数据
// 合并左固定列数据
if (fixedLeft && mainTable) {
const fixedLeftRows = fixedLeft.querySelectorAll('.el-table__body-wrapper tbody tr');
const mainRows = mainTable.querySelectorAll('tbody tr');
fixedLeftRows.forEach((fixedRow, index) => {
if (mainRows[index]) {
const fixedCells = Array.from(fixedRow.querySelectorAll('td'));
fixedCells.forEach(cell => {
mainRows[index].insertBefore(cell.cloneNode(true), mainRows[index].firstChild);
});
}
});
}
// 合并右固定列数据
if (fixedRight && mainTable) {
const fixedRightRows = fixedRight.querySelectorAll('.el-table__body-wrapper tbody tr');
const mainRows = mainTable.querySelectorAll('tbody tr');
fixedRightRows.forEach((fixedRow, index) => {
if (mainRows[index]) {
const fixedCells = Array.from(fixedRow.querySelectorAll('td'));
fixedCells.forEach(cell => {
mainRows[index].appendChild(cell.cloneNode(true));
});
}
});
}
},
// 辅助方法:格式化日期(用于文件名)
@@ -698,7 +865,6 @@ export default {
String(date.getMinutes()).padStart(2, '0')
].join('');
},
// 获取产线和工厂列表
getPdLineList() {
getPdList().then(res => {
@@ -712,28 +878,85 @@ export default {
// 搜索/导出按钮点击
buttonClick(val) {
if (this.activeName === 'productLine') {
this.listQuery.pageNo = 1;
this.listQuery.pageSize = 10;
this.listQuery.factoryId = val.factoryId || undefined;
this.listQuery.process = val.process ? val.process : [];
this.listQuery.lineId = val.lineId ? val.lineId : [];
this.listQuery.processType = val.processType ? val.processType : [];
this.listQuery.reportType = val.reportType || undefined;
this.listQuery.timeType = val.timeType || undefined;
this.listQuery.searchType = val.searchType || undefined;
// 处理不同时间选择类型
if (val.timeVal) {
this.listQuery.startTime = val.timeVal[0];
this.listQuery.endTime = val.timeVal[1];
} else if (val.timeValWeek) {
// 周选择器
const { startTime, endTime } = this.getWeekTimeRange(val.timeValWeek);
this.listQuery.startTime = startTime;
this.listQuery.endTime = endTime;
} else if (val.timeValMonth) {
// 月选择器
this.listQuery.startTime = val.timeValMonth[0];
this.listQuery.endTime = val.timeValMonth[1];
} else if (val.timeValYear) {
// 年选择器
const { startTime, endTime } = this.getYearTimeRange(val.timeValYear);
this.listQuery.startTime = startTime;
this.listQuery.endTime = endTime;
} else {
this.listQuery.startTime = undefined;
this.listQuery.endTime = undefined;
}
} else {
this.listQuery.pageNo = 1;
this.listQuery.pageSize = 10;
this.listQuery.factoryId = val.factoryId || []
this.listQuery.process = val.process ? val.process : [];
this.listQuery.lineId = undefined;
this.listQuery.processType = val.processType ? val.processType : [];
this.listQuery.reportType = val.reportType || undefined;
this.listQuery.timeType = val.timeType || undefined;
this.listQuery.searchType = val.searchType || undefined;
// 处理不同时间选择类型
if (val.timeVal) {
this.listQuery.startTime = val.timeVal[0];
this.listQuery.endTime = val.timeVal[1];
} else if (val.timeValWeek) {
// 周选择器
const { startTime, endTime } = this.getWeekTimeRange(val.timeValWeek);
this.listQuery.startTime = startTime;
this.listQuery.endTime = endTime;
} else if (val.timeValMonth) {
// 月选择器
this.listQuery.startTime = val.timeValMonth[0];
this.listQuery.endTime = val.timeValMonth[1];
} else if (val.timeValYear) {
// 年选择器
const { startTime, endTime } = this.getYearTimeRange(val.timeValYear);
this.listQuery.startTime = startTime;
this.listQuery.endTime = endTime;
} else {
this.listQuery.startTime = undefined;
this.listQuery.endTime = undefined;
}
}
switch (val.btnName) {
case 'search':
this.listQuery.pageNo = 1;
this.listQuery.pageSize = 10;
this.listQuery.factoryId = val.factoryId || undefined;
this.listQuery.process = val.process ? val.process : [];
this.listQuery.lineId = val.lineId ? val.lineId : [];
this.listQuery.processType = val.processType ? val.processType : [];
this.listQuery.reportType = val.reportType || undefined;
this.listQuery.timeType = val.timeType || undefined;
this.listQuery.searchType = val.searchType || undefined;
this.listQuery.startTime = val.timeVal ? val.timeVal[0] : undefined;
this.listQuery.endTime = val.timeVal ? val.timeVal[1] : undefined;
if (this.activeName === 'productLine') {
if (this.listQuery.timeType === 1) {
this.getDayDataList();
} else if (this.listQuery.timeType === 3) {
this.getLastDayDataList();
} else {
this.getDataList();
}
} else {
this.getProductList();
this.getProductList();
}
break;
case 'export':
@@ -742,6 +965,9 @@ export default {
case 'reset':
this.$refs.searchBarForm.resetForm();
this.$refs.searchBarForm.formInline.timeType = 1;
this.formConfig[0].selectOptions = this.activeName === 'productLine'
? this.timeTypeOptions.productLine
: this.timeTypeOptions.product;
this.listQuery = {
pageSize: 100,
pageNo: 1,
@@ -750,12 +976,9 @@ export default {
};
if (this.activeName === 'productLine') {
this.getDayDataList();
} else {
this.getProductList();
}
break;
default:
console.log(val);
@@ -763,8 +986,8 @@ export default {
},
getDataList() {
if(!this.listQuery.startTime &&!this.listQuery.endTime ){
return this.$modal.msgError('请选择时间');
if (!this.listQuery.startTime && !this.listQuery.endTime) {
return this.$modal.msgError('请选择时间');
}
this.dataListLoading = true;
// const arr = ['日', '周', '月', '年'];
@@ -798,7 +1021,7 @@ export default {
console.log('resGroup');
if (this.listQuery.timeType === 2) {
const resGroup = await getProcessAutoReportNew(this.listQuery);
console.log('resGroup',resGroup);
console.log('resGroup', resGroup);
// 格式化数据lineDetList提取到外层键名加lineId前缀
const formatData = (rawList) => {
@@ -827,7 +1050,7 @@ export default {
// 处理当班和当天数据(分别存储)
this.productTableData = formatData(resGroup.data.list); // 当班数据
console.log(this.productTableData,'productTableData');
console.log(this.productTableData, 'productTableData');
// --------------------------
// 生成当班数据的动态表头
@@ -1005,6 +1228,28 @@ export default {
return [...lineColumns, totalColumn];
},
// 为当天时调用的接口
getLastDayDataList() {
getPdlAutoReportNewSearchLastGroup().then((response) => {
this.tableData = response.data.classData.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;
return item;
});
// console.log(this.tableData, this.tableData2);
});
},
getDayDataList() {
getPdlAutoReportNewSearchNow().then((response) => {
this.tableData = response.data.classData.map((item, index) => {
@@ -1060,28 +1305,26 @@ export default {
// 搜索栏下拉选择变化
handleSearchBarChanged({ param, value }) {
console.log(value, param);
if (param === 'timeType') {
console.log(value);
this.listQuery.timeType = value;
if (value === 1) {
// 切换为“当天”:隐藏查询类型、报表类型、时间范围
if (value === 2) {
// 切换为“自定义”:显示查询类型和时间范围
this.formConfig[1].type = 'select';
this.$refs.searchBarForm.formInline.timeType = value;
this.listQuery.timeType = value;
this.formConfig[7].type = 'datePicker';
} else {
this.formConfig[1].type = '';
this.formConfig[6].type = '';
this.formConfig[7].type = '';
this.$refs.searchBarForm.formInline.timeType = value;
this.listQuery.timeType = value;
this.$refs.searchBarForm.formInline.searchType = undefined;
this.$refs.searchBarForm.formInline.reportType = undefined;
this.$refs.searchBarForm.formInline.timeVal = undefined;
} else {
// 切换为“自定义”:显示查询类型和时间范围
this.formConfig[1].type = 'select';
this.$refs.searchBarForm.formInline.timeType = value;
this.listQuery.timeType = value;
this.formConfig[7].type = 'datePicker';
}
} else if (param === 'searchType') {
if (value === 1) {
@@ -1095,7 +1338,74 @@ export default {
this.formConfig[7].type = 'datePicker';
}
} else if (param === 'reportType') {
this.$refs.searchBarForm.formInline.timeVal = undefined;
if (this.$refs.searchBarForm && this.$refs.searchBarForm.formInline) {
const formInline = this.$refs.searchBarForm.formInline;
// 精准判断只有字段存在时才置为undefined不存在则不处理
if ('timeVal' in formInline) {
formInline.timeVal = undefined;
}
if ('timeValWeek' in formInline) {
formInline.timeValWeek = undefined;
}
if ('timeValMonth' in formInline) {
formInline.timeValMonth = undefined;
}
if ('timeValYear' in formInline) {
formInline.timeValYear = undefined;
}
}
this.listQuery.startTime = undefined;
this.listQuery.endTime = undefined;
if (value === 1) {
this.formConfig[7] = {
type: 'datePicker',
label: '时间范围',
dateType: 'datetimerange',
format: 'yyyy-MM-dd HH:mm:ss',
valueFormat: 'timestamp',
rangeSeparator: '-',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
param: 'timeVal',
width: 350
};
} else if (value === 2) {
this.formConfig[7] = {
type: 'datePicker',
label: '时间范围',
dateType: 'week',
placeholder: '选择日期',
format: 'yyyy-MM-dd',
valueFormat: 'yyyy-MM-dd',
param: 'timeValWeek',
width: 250,
};
} else if (value === 3) {
this.formConfig[7] = {
type: 'datePicker',
label: '时间范围',
dateType: 'monthrange',
format: 'yyyy-MM-dd',
valueFormat: 'timestamp',
rangeSeparator: '-',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
param: 'timeValMonth',
defaultTime: ['00:00:00', '23:59:59'],
width: 250,
};
} else if (value === 4) {
this.formConfig[7] = {
type: 'datePicker',
label: '时间范围',
dateType: 'year',
placeholder: '选择年份',
format: 'yyyy',
valueFormat: 'timestamp',
param: 'timeValYear',
width: 250,
};
}
} else if (param === 'factoryId') {
// 切换工厂时刷新产线列表
this.listQuery.lineId = [];
@@ -1142,7 +1452,7 @@ export default {
.content-wrapper {
// flex: 1;
width: 100%;
height: calc(100vh - 303px);
height: calc(100vh - 280px);
// overflow: hidden;
}

View File

@@ -1,14 +1,8 @@
<!--
* @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" />
<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"
@@ -17,34 +11,47 @@
<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> -->
<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 * as XLSX from 'xlsx'
import FileSaver from 'file-saver'
import jsPDF from 'jspdf'
import html2canvas from 'html2canvas'
import { getCostOriginRadioHisData, getPdList } from '@/api/core/monitoring/index';
import { exportCostOriginRadioHisData } from '../../../../api/core/monitoring';
const tableProps = [
// 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: '报表类型'
@@ -52,12 +59,11 @@ const tableProps = [
{
prop: 'time',
label: '日期',
// filter: parseTime,
width: 160
},
{
{
prop: 'bindObjectName',
label: '产线'
label: '产线'
},
{
prop: 'thick',
@@ -67,11 +73,11 @@ const tableProps = [
prop: 'speed',
label: '在线速度'
},
{
{
prop: 'width',
label: '掰边宽度'
},
{
},
{
prop: 'inArea',
label: '拉引量/㎡'
},
@@ -79,21 +85,83 @@ const tableProps = [
prop: 'outArea',
label: '下片面积/㎡'
},
{
{
prop: 'ratio',
label: '良品率'
},
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
},
data() {
return {
urlOptions: {
getDataListURL: getCostOriginRadioHisData
},
tableData: [],
listQuery: {
pageSize: 10,
@@ -101,181 +169,366 @@ export default {
total: 1,
bindObjectId: undefined,
statisticType: undefined,
startTime: undefined,
endTime: undefined
},
pdLineList:[],
pdLineList: [],
exportLoading: false,
dataListLoading: false,
selectedList: [],
dialogVisible: false,
selectedList: [],
dialogVisible: false,
addOrEditTitle: '',
addOrUpdateVisible: false,
tableProps,
tableProps,
tableBtn: [
{
type: 'edit',
btnName: '编辑',
},
].filter((v) => v),
tableData: [],
fileName: '',
].filter(v => v),
fileName: '',
formConfig: [
{
type: 'select',
label: '维度',
label: '报表类型',
onchange: true,
selectOptions: [
{
id: '0',
name:'班组'
},
{
id: '1',
name: '日'
},
{
id: '2',
name: '周'
},
{
id: '3',
name: '月'
},
{
id: '4',
name: '年'
}
{ id: '0', name: '班组' },
{ id: '1', name: '日' },
{ id: '2', name: '周' },
{ id: '3', name: '月' },
{ id: '4', name: '年' }
],
param: 'statisticType'
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-MM-dd',
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: [],
{
type: 'select',
label: '产线',
selectOptions: [],
param: 'bindObjectId'
},
{
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: 'button',
btnName: '查询',
name: 'search',
color: 'primary',
},
{
type: 'separate',
},
{
// type: this.$auth.hasPermi('base:factory:export') ? 'button' : '',
type: 'button',
btnName: '导出',
name: 'export',
color: 'warning',
}
],
};
},
{
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() {
this.$refs.searchBarForm.formInline.statisticType = '1';
// Vue2 中 $refs 需在 $nextTick 中访问(确保 DOM 渲染完成)
this.$nextTick(() => {
if (this.$refs.searchBarForm) {
this.$refs.searchBarForm.formInline.statisticType = '1';
}
});
this.listQuery.statisticType = '1';
this.getDataList()
this.getPdLineList()
},
this.getDataList();
this.getPdLineList();
},
methods: {
selectType(val) {
// 报表类型切换时的回调(如需扩展可在此添加逻辑)
console.log('报表类型切换:', val);
},
handleClick(val) {
console.log(val);
console.log('操作按钮点击:', val);
if (val.type === 'edit') {
this.addOrUpdateVisible= true
this.addOrUpdateVisible = true;
// Vue2 中 $nextTick 确保子组件已渲染
this.$nextTick(() => {
this.$refs.addOrUpdate.init(val.data);
});
}
},
getPdLineList() {
getPdList().then((res) => {
this.formConfig[1].selectOptions = res.data || []
this.pdLineList = res.data || []; // 保存产线数据
})
},
selectChange(val) {
console.log(val)
this.selectedList = val
},
buttonClick(val) {
console.log('val', 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.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 => {
const arr = ['班组','日', '周', '月', '年'];
this.tableData = response.data?.list?.map((item) => {
item.reportType = arr[this.listQuery.statisticType];
item.statisticType = this.listQuery.statisticType
// 匹配 bindObjectName
const targetLine = this.pdLineList.find(line => line.id === item.bindObjectId);
item.bindObjectName = targetLine ? targetLine.name : ''; // 赋值名称,无匹配则为空
return item;
});
this.listQuery.total = response.data.total;
this.dataListLoading = false;
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() {
// 处理查询参数
let params = { ...this.listQuery };
params.pageNo = undefined;
params.pageSize = undefined;
this.$modal.confirm('是否确认导出原片报表?').then(() => {
this.exportLoading = true;
return exportCostOriginRadioHisData(params);
}).then(response => {
this.$download.excel(response, '原片报表.xls');
this.exportLoading = false;
}).catch(() => { });
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 || undefined;
this.dataForm.equipmentName = data.equipmentName || undefined;
this.dataForm.lineId = data.lineId || undefined;
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,498 @@
<template>
<el-dialog :visible.sync="visible" width="40%" @close="destroyAllCharts" 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">
<div v-show="activeLabel === 'duration'" id="barChart" style="width: 100%; height: 400px;"></div>
<!-- 报警次数-饼图 -->
<div v-show="activeLabel === 'times'" id="pieChart" style="width: 100%; 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';
// import { ElEmpty } from 'element-plus';
// 图表颜色配置(统一风格)
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: true, // 是否有数据标识
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', // 默认激活时长tab
dataForm: {
equipmentId: undefined,
equipmentName: undefined,
lineId: undefined
},
// 存储图表实例,用于销毁
chartInstances: {
bar: null,
pie: null
}
};
},
mounted() {
// 初始化默认日期(当天)
this.initDefaultDate();
},
methods: {
// 初始化默认日期当天零点到23:59:59
initDefaultDate() {
const today = new Date();
const start = new Date(today.setHours(0, 0, 0, 0)).getTime();
const end = new Date(today.setHours(23, 59, 59, 999)).getTime();
this.formConfig[0].defaultSelect = [start, end];
this.listQuery.startTime = start;
this.listQuery.endTime = end;
},
// 切换Tab时加载对应图表
handleTabClick() {
this.getDataList();
},
// 搜索按钮点击事件
buttonClick(val) {
switch (val.btnName) {
case 'search':
this.listQuery.startTime = val.timeVal?.[0];
this.listQuery.endTime = val.timeVal?.[1];
this.getDataList();
break;
case 'link':
this.disabled = true;
this.connectSSEBatch(this.listQuery.equipmentIds);
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.hasData = originData.length > 0;
if (this.hasData) {
// 根据当前Tab渲染对应图表
this.activeLabel === 'duration'
? this.renderBarChart(originData)
: this.renderPieChart(originData);
} else {
// 无数据时销毁已有图表
this.destroyAllCharts();
}
} catch (error) {
console.error('获取报警数据失败:', error);
this.hasData = false;
this.destroyAllCharts();
}
},
// 渲染报警时长-柱状图
renderBarChart(originData) {
// 先销毁原有图表
this.destroyChart('chart');
// 按报警时长降序排序
const sortedData = [...originData].sort((a, b) => b.alarmDuration - a.alarmDuration);
// 处理图表数据
const xData = sortedData.map(item => this.truncateText(item.alarmContent, 8)); // 文本截断最多8字
const seriesData = sortedData.map(item => item.alarmDuration);
const chartDom = document.getElementById('barChart');
this.chartInstances.bar = echarts.init(chartDom);
const option = {
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>报警时长:${item.alarmDuration}</div>
<div>占比:${item.alarmDurationRatio.toFixed(2)}%</div>
</div>
`;
}
},
grid: {
left: '5%',
right: '5%',
bottom: '18%', // 底部留足空间显示x轴标签
top: '10%',
containLabel: true
},
xAxis: [
{
type: 'category',
data: xData,
axisTick: { alignWithLabel: true },
axisLabel: {
interval: 0,
// rotate: 35, // 标签旋转
fontSize: 12,
color: CHART_CONFIG.lightFontColor
},
axisLine: { lineStyle: { color: '#e8e8e8' } }
}
],
yAxis: [
{
type: 'value',
axisLabel: {
fontSize: 11,
color: CHART_CONFIG.lightFontColor,
// formatter: '{value}s' // 显示单位
},
axisLine: { lineStyle: { color: '#e8e8e8' } },
splitLine: { lineStyle: { color: '#f5f5f5' } },
// 扩展y轴最大值避免label超出
max: (value) => value.max * 1.2
}
],
series: [
{
name: '报警时长',
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);
// 监听窗口 resize
this.addResizeListener('bar');
},
// 渲染报警次数-饼图
renderPieChart(originData) {
// 先销毁原有图表
this.destroyChart('pie');
// 按报警次数降序排序
const sortedData = [...originData].sort((a, b) => b.alarmCount - a.alarmCount);
// 处理图表数据(合并占比过小的项为"其他"
const pieData = this.handlePieData(sortedData);
const chartDom = document.getElementById('pieChart');
this.chartInstances.pie = echarts.init(chartDom);
const option = {
tooltip: {
trigger: 'item',
padding: 10,
textStyle: { fontSize: 11 },
formatter: (params) => {
return `
<div style="text-align: left;">
<div>${params.name}</div>
<div>报警次数:${params.value} 次</div>
<div>占比:${params.percent.toFixed(2)}%</div>
</div>
`;
}
},
series: [
{
name: '报警次数',
type: 'pie',
radius: ['50%', '70%'], // 环形饼图
center: ['50%', '50%'], // 饼图居中显示
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); // 最多8字
return `${truncatedName}(${params.value}次, ${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,
// 高亮样式hover时
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);
// 监听窗口 resize
this.addResizeListener('pie');
},
// 处理饼图数据(合并占比<5%的项)
handlePieData(data) {
const threshold = 5; // 阈值5%
let otherCount = 0;
const mainData = data.filter(item => {
if (item.alarmCountRatio >= threshold) {
return true;
} else {
otherCount += item.alarmCount;
return false;
}
}).map(item => ({
name: item.alarmContent,
value: item.alarmCount,
ratio: item.alarmCountRatio
}));
// 如果有"其他"项,添加到数据中
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;
},
// 添加窗口resize监听
addResizeListener(type) {
const chart = this.chartInstances[type];
if (chart) {
const resizeHandler = () => chart.resize();
window.addEventListener('resize', resizeHandler);
// 存储resize处理器用于后续移除
chart.resizeHandler = resizeHandler;
}
},
// 销毁单个图表
destroyChart(type) {
const chart = this.chartInstances[type];
if (chart) {
// 移除resize监听
window.removeEventListener('resize', chart.resizeHandler);
chart.dispose();
this.chartInstances[type] = null;
}
},
// 销毁所有图表
destroyAllCharts() {
Object.keys(this.chartInstances).forEach(type => {
this.destroyChart(type);
});
},
// 初始化弹窗数据
init(data) {
this.dataForm = {
equipmentId: data.equipmentId || undefined,
equipmentName: data.equipmentName || '未知设备',
lineId: data.lineId || '未知产线'
};
this.listQuery.equipmentId = data.equipmentId || undefined;
this.visible = true;
this.$nextTick(() => {
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;
/* 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>

File diff suppressed because it is too large Load Diff

View File

@@ -51,6 +51,7 @@
src="/static/videos/login.webm"
muted
autoplay
id="video"
loop
style="
user-select: none;
@@ -338,7 +339,8 @@ export default {
SysUserSocialTypeEnum: SystemUserSocialTypeEnum,
};
},
created() {
created() {
// 租户开关
this.tenantEnable = getTenantEnable();
if (this.tenantEnable) {
@@ -356,8 +358,15 @@ export default {
this.redirect = this.$route.query.redirect
? decodeURIComponent(this.$route.query.redirect)
: undefined;
this.getCookie();
},
this.getCookie();
},
mounted () {
const video = document.getElementById('video');
// 设置为0.5倍速数值越小速度越慢0.5为半速0.75为0.75倍速等)
video.playbackRate = 0.2;
},
methods: {
getCode() {
// 情况一,未开启:则直接登录

View File

@@ -0,0 +1,309 @@
<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: [],
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,674 @@
<template>
<div class="status-timegraph-container" style="background: #f2f4f9; flex: 1; display: flex; flex-direction: column">
<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 class="blue-title">
图表时间维度
<div style="position: relative; display: flex; align-items: center; gap: 10px; margin-top: 10px;">
<el-button type="primary" @click="handleExport"
style="position: absolute;top: 0px;right: 10px;z-index: 9999;">
导出
</el-button>
<el-tabs v-model="activeName" type="card" @tab-click="handleClick" style="flex: 1;">
<!-- 班组标签页 -->
<el-tab-pane label="班组" name="group">
<!-- 修改使用四宫格布局容器 -->
<div class="chart-grid">
<div id="mapGroupMain" class="chart-container" style="height: 400px;"></div>
<div id="mapGroupMainScrap" class="chart-container" style="height: 400px;"></div>
<div id="listGroupMain" class="chart-container" style="height: 400px;"></div>
<div id="listGroupMainScrap" class="chart-container" style="height: 400px;"></div>
</div>
</el-tab-pane>
<!-- 日标签页 -->
<el-tab-pane label="日" name="day">
<!-- 修改使用四宫格布局容器 -->
<div class="chart-grid">
<div id="mapDayMain" class="chart-container" style="height: 400px;"></div>
<div id="mapDayMainScrap" class="chart-container" style="height: 400px;"></div>
<div id="listDayMain" class="chart-container" style="height: 400px;"></div>
<div id="listDayMainScrap" class="chart-container" style="height: 400px;"></div>
</div>
</el-tab-pane>
<!-- 周标签页 -->
<el-tab-pane label="周" name="week">
<!-- 修改使用四宫格布局容器 -->
<div class="chart-grid">
<div id="mapWeekMain" class="chart-container" style="height: 400px;"></div>
<div id="mapWeekMainScrap" class="chart-container" style="height: 400px;"></div>
<div id="listWeekMain" class="chart-container" style="height: 400px;"></div>
<div id="listWeekMainScrap" class="chart-container" style="height: 400px;"></div>
</div>
</el-tab-pane>
<!-- 月标签页 -->
<el-tab-pane label="月" name="month">
<!-- 修改使用四宫格布局容器 -->
<div class="chart-grid">
<div id="mapMonthMain" class="chart-container" style="height: 400px;"></div>
<div id="mapMonthMainScrap" class="chart-container" style="height: 400px;"></div>
<div id="listMonthMain" class="chart-container" style="height: 400px;"></div>
<div id="listMonthMainScrap" class="chart-container" style="height: 400px;"></div>
</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
</el-col>
</el-row>
</el-row>
</div>
</template>
<script>
import {
getQualityIsraPage,
} from '@/api/monitoring/qualityIsra';
import moment from 'moment';
import * as echarts from 'echarts';
import tableHeightMixin from '@/mixins/lb/tableHeightMixin';
import { parseTime } from '@/utils/ruoyi';
import { getPdList } from '@/api/core/monitoring/auto';
import html2canvas from 'html2canvas';
export default {
name: 'QualityIsra',
mixins: [tableHeightMixin],
data() {
return {
isFold: false,
list: [],
dynamicProps: [],
activeName: 'group', // 默认激活“日”标签页
// 新增班组图表接口URL与后端确认一致
groupMapUrl: '/extend/check-isra-statistics/groupMap',
groupListUrl: '/extend/check-isra-statistics/groupList',
// 原有接口URL保持不变
dayMapUrl: '/extend/check-isra-statistics/dayMap',
weekMapUrl: '/extend/check-isra-statistics/weekMap',
monthMapUrl: '/extend/check-isra-statistics/monthMap',
dayListUrl: '/extend/check-isra-statistics/dayList',
weekListUrl: '/extend/check-isra-statistics/weekList',
monthListUrl: '/extend/check-isra-statistics/monthList',
searchBarFormConfig: [
{
type: 'select',
label: '是否报废',
placeholder: '请选择是否报废',
param: 'checkDiscard',
selectOptions: [
{ name: '否', id: 0 },
{ name: '是', id: 1 }
],
},
{
type: 'select',
label: '缺陷类型',
placeholder: '请选择缺陷类型',
param: 'checkType',
selectOptions: [],
labelField: 'name',
valueField: 'name',
defaultSelect: [],
filterable: true
},
{
type: 'select',
label: '产线',
selectOptions: [],
labelField: 'name',
valueField: 'name',
param: 'lineName',
filterable: true,
defaultSelect: []
},
{
type: 'datePicker',
label: '时间段',
dateType: 'datetimerange',
format: 'yyyy-MM-dd HH:mm:ss',
valueFormat: 'yyyy-MM-dd HH:mm:ss',
rangeSeparator: '-',
startPlaceholder: '开始日期',
endPlaceholder: '结束日期',
defaultTime: ['00:00:00', '23:59:59'],
param: 'checkTime',
},
{
type: 'button',
btnName: '查询',
name: 'search',
color: 'primary',
},
],
// 查询参数
queryParams: {
checkDiscard: undefined,
// checkType: undefined,
// lineName: undefined,
startTime: undefined,
endTime: undefined,
},
};
},
created() {
// 初始化折叠状态(如需使用可取消注释)
// this.isFold = this.searchBarWidth('QualityIsraBox', 1198);
},
computed: {
tableProps() {
return [
{
prop: 'checkType',
label: '缺陷类型',
},
{
prop: 'sumNum',
label: '缺陷总数',
},
...this.dynamicProps,
];
},
},
mounted() {
// 从路由参数获取产线信息并回显
if (this.$route.query.lineName) {
this.queryParams.lineName = this.$route.query.lineName;
this.searchBarFormConfig[2].defaultSelect = this.$route.query.lineName;
}
// 从路由参数获取时间信息并回显
if (this.$route.query.originalGlassOutputTime) {
const time = new Date(Number(this.$route.query.originalGlassOutputTime));
this.queryParams.startTime = parseTime(time);
this.queryParams.endTime = parseTime(time);
this.searchBarFormConfig[3].defaultSelect = [
this.queryParams.startTime,
this.queryParams.endTime,
];
}
// 初始化数据
this.getData();
this.getDict();
},
methods: {
/** 标签页切换事件 */
handleClick() {
this.getData();
},
/** 核心:获取并渲染图表数据(支持班组/日/周/月) */
getData() {
// 1. 根据当前激活的标签页选择对应的接口URL和图表容器ID
let mapUrl, listUrl, mapDomId, listDomId, mapDomIdScrap, listDomIdScrap;
switch (this.activeName) {
case 'group':
mapUrl = this.groupMapUrl;
listUrl = this.groupListUrl;
mapDomId = 'mapGroupMain';
listDomId = 'listGroupMain';
mapDomIdScrap = 'mapGroupMainScrap';
listDomIdScrap = 'listGroupMainScrap';
break;
case 'day':
mapUrl = this.dayMapUrl;
listUrl = this.dayListUrl;
mapDomId = 'mapDayMain';
listDomId = 'listDayMain';
mapDomIdScrap = 'mapGroupDayScrap';
listDomIdScrap = 'listGroupDayScrap';
break;
case 'week':
mapUrl = this.weekMapUrl;
listUrl = this.weekListUrl;
mapDomId = 'mapWeekMain';
listDomId = 'listWeekMain';
mapDomIdScrap = 'mapGroupWeekScrap';
listDomIdScrap = 'listGroupWeekScrap';
break;
case 'month':
mapUrl = this.monthMapUrl;
listUrl = this.monthListUrl;
mapDomId = 'mapMonthMain';
listDomId = 'listMonthMain';
mapDomIdScrap = 'mapGroupMonthScrap';
listDomIdScrap = 'listGroupMonthScrap';
break;
}
// 2. 请求“各类型缺陷对比图”数据
this.$axios({
url: mapUrl,
method: 'get',
params: this.queryParams
}).then((res) => {
const mapArr = [];
const mapLegendData = [];
// 处理接口返回数据(确保与后端数据格式匹配)
for (const type in res.data) {
const dataArr = [];
const xAxisData = [];
res.data[type].forEach(ele => {
dataArr.push(ele.num); // 缺陷数量
// 班组标签页使用“班组名称”作为X轴其他标签页使用“时间”
xAxisData.push(ele.checkTime);
});
mapArr.push({
name: type, // 缺陷类型名称
type: 'line', // 折线图
data: dataArr,
xAxisData: xAxisData // 存储当前系列的X轴数据
});
mapLegendData.push(type);
}
// 渲染“各类型缺陷对比图”
const mapChartDom = document.getElementById(mapDomId);
if (mapChartDom) {
const myChart = echarts.init(mapChartDom);
myChart.clear(); // 清除原有图表
myChart.setOption({
title: { text: '所有缺陷对比图' },
tooltip: { trigger: 'axis' },
legend: { data: mapLegendData, top: '10%', y: 'top', x: 'left' },
grid: { left: '3%', right: '4%', bottom: '3%', top: '20%', containLabel: true },
xAxis: {
type: 'category',
data: mapArr.length > 0 ? mapArr[0].xAxisData : [] // 使用第一个系列的X轴数据
},
yAxis: { type: 'value', name: '缺陷数量' },
series: mapArr
});
// 监听窗口 resize自动调整图表大小
window.addEventListener('resize', () => myChart.resize());
}
});
this.$axios({
url: mapUrl,
method: 'get',
params: {
startTime: this.queryParams.startTime,
endTime: this.queryParams.endTime,
checkDiscard:true
}
}).then((res) => {
const mapArr = [];
const mapLegendData = [];
// 处理接口返回数据(确保与后端数据格式匹配)
for (const type in res.data) {
const dataArr = [];
const xAxisData = [];
res.data[type].forEach(ele => {
dataArr.push(ele.num); // 缺陷数量
// 班组标签页使用“班组名称”作为X轴其他标签页使用“时间”
xAxisData.push(ele.checkTime);
});
mapArr.push({
name: type, // 缺陷类型名称
type: 'line', // 折线图
data: dataArr,
xAxisData: xAxisData // 存储当前系列的X轴数据
});
mapLegendData.push(type);
}
// 渲染“各类型缺陷对比图”
const mapChartDom = document.getElementById(mapDomIdScrap);
if (mapChartDom) {
const myChart = echarts.init(mapChartDom);
myChart.clear(); // 清除原有图表
myChart.setOption({
title: { text: '所有缺陷对比图' },
tooltip: { trigger: 'axis' },
legend: { data: mapLegendData, top: '10%', y: 'top', x: 'left' },
grid: { left: '3%', right: '4%', bottom: '3%', top: '20%', containLabel: true },
xAxis: {
type: 'category',
data: mapArr.length > 0 ? mapArr[0].xAxisData : [] // 使用第一个系列的X轴数据
},
yAxis: { type: 'value', name: '缺陷数量' },
series: mapArr
});
// 监听窗口 resize自动调整图表大小
window.addEventListener('resize', () => myChart.resize());
}
});
// 3. 请求“缺陷率趋势图”数据
this.$axios({
url: listUrl,
method: 'get',
params: this.queryParams
}).then((res) => {
const listNumArr = []; // 缺陷数量
const listRatioArr = []; // 缺陷率
const listXAxisData = []; // X轴数据
// 处理接口返回数据(确保与后端数据格式匹配)
res.data.forEach(ele => {
listNumArr.push(ele.num);
listRatioArr.push(ele.ratio); // 缺陷率(百分比)
// 班组标签页使用“班组名称”作为X轴其他标签页使用“时间”
listXAxisData.push(this.activeName === 'group' ? ele.groupName : ele.checkTime);
});
// 渲染“缺陷率趋势图”
const listChartDom = document.getElementById(listDomId);
if (listChartDom) {
const myChart = echarts.init(listChartDom);
myChart.clear(); // 清除原有图表
myChart.setOption({
title: { text: '所有缺陷率趋势图' },
tooltip: { trigger: 'axis' },
legend: { data: ['缺陷数量', '缺陷率'], top: '10%' },
grid: { left: '3%', right: '4%', bottom: '3%', top: '20%', containLabel: true },
xAxis: {
type: 'category',
data: listXAxisData
},
yAxis: [
{
type: 'value',
name: '缺陷数量',
axisLabel: { formatter: '{value}' }
},
{
type: 'value',
name: '缺陷率',
axisLabel: { formatter: '{value} %' },
position: 'right' // 缺陷率Y轴放在右侧
}
],
series: [
{
name: '缺陷数量',
type: 'bar',
barWidth: '3%',
data: listNumArr,
itemStyle: { color: 'rgba(40, 138, 255, 1)' } // 柱状图颜色
},
{
name: '缺陷率',
type: 'line',
yAxisIndex: 1, // 关联右侧Y轴
data: listRatioArr,
itemStyle: { color: 'rgba(115, 222, 147, 1)' }, // 折线图颜色
symbol: 'circle', // 标记点样式
symbolSize: 6
}
]
});
// 监听窗口 resize自动调整图表大小
window.addEventListener('resize', () => myChart.resize());
}
});
this.$axios({
url: listUrl,
method: 'get',
params: {
startTime: this.queryParams.startTime,
endTime: this.queryParams.endTime,
checkDiscard: true
}
}).then((res) => {
const listNumArr = []; // 缺陷数量
const listRatioArr = []; // 缺陷率
const listXAxisData = []; // X轴数据
// 处理接口返回数据(确保与后端数据格式匹配)
res.data.forEach(ele => {
listNumArr.push(ele.num);
listRatioArr.push(ele.ratio); // 缺陷率(百分比)
// 班组标签页使用“班组名称”作为X轴其他标签页使用“时间”
listXAxisData.push(this.activeName === 'group' ? ele.groupName : ele.checkTime);
});
// 渲染“缺陷率趋势图”
const listChartDom = document.getElementById(listDomIdScrap);
if (listChartDom) {
const myChart = echarts.init(listChartDom);
myChart.clear(); // 清除原有图表
myChart.setOption({
title: { text: '所有缺陷率趋势图' },
tooltip: { trigger: 'axis' },
legend: { data: ['缺陷数量', '缺陷率'], top: '10%' },
grid: { left: '3%', right: '4%', bottom: '3%', top: '20%', containLabel: true },
xAxis: {
type: 'category',
data: listXAxisData
},
yAxis: [
{
type: 'value',
name: '缺陷数量',
axisLabel: { formatter: '{value}' }
},
{
type: 'value',
name: '缺陷率',
axisLabel: { formatter: '{value} %' },
position: 'right' // 缺陷率Y轴放在右侧
}
],
series: [
{
name: '缺陷数量',
type: 'bar',
barWidth: '3%',
data: listNumArr,
itemStyle: { color: 'rgba(40, 138, 255, 1)' } // 柱状图颜色
},
{
name: '缺陷率',
type: 'line',
yAxisIndex: 1, // 关联右侧Y轴
data: listRatioArr,
itemStyle: { color: 'rgba(115, 222, 147, 1)' }, // 折线图颜色
symbol: 'circle', // 标记点样式
symbolSize: 6
}
]
});
// 监听窗口 resize自动调整图表大小
window.addEventListener('resize', () => myChart.resize());
}
});
},
/** 新增:导出数据功能 */
handleExport() {
// 1. 找到当前激活标签页内的 .chart-wrapper 容器
const container = document.querySelector(`.el-tab-pane[aria-selected="true"] .chart-wrapper`);
if (!container) {
this.$message.warning('未找到图表容器');
console.error('导出失败:未找到 .chart-wrapper 容器');
return;
}
this.$message.info('正在生成图片,请稍候...');
// 2. 获取当前标签页的两个图表实例,并强制刷新它们
// 这是解决问题的关键步骤
const chartIds = this.getChartIdsByActiveName();
chartIds.forEach(id => {
const chartDom = document.getElementById(id);
if (chartDom) {
const chartInstance = echarts.getInstanceByDom(chartDom);
if (chartInstance) {
chartInstance.resize(); // 强制图表重新渲染
}
}
});
// 3. 使用 setTimeout 等待图表渲染完成后再进行截图
// 延迟时间可以根据你的图表复杂度调整,一般 100-300ms 足够
setTimeout(() => {
html2canvas(container, {
scale: 2,
useCORS: true,
logging: false,
backgroundColor: null,
// 增加一个配置,确保能捕获到所有元素
windowWidth: container.scrollWidth,
windowHeight: container.scrollHeight
}).then(canvas => {
const imgBase64 = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.href = imgBase64;
const titleMap = { group: '班组', day: '日', week: '周', month: '月' };
const title = titleMap[this.activeName] || '数据';
link.download = `${title}度缺陷分析报告.png`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
this.$message.success('图表导出成功!');
}).catch(error => {
this.$message.error('图表导出失败!');
console.error('html2canvas 截图失败:', error);
});
}, 200); // 等待 200 毫秒
},
// 新增一个辅助方法,根据当前激活的标签页获取对应的图表 ID
getChartIdsByActiveName() {
switch (this.activeName) {
case 'group':
return ['mapGroupMain', 'listGroupMain'];
case 'day':
return ['mapDayMain', 'listDayMain'];
case 'week':
return ['mapWeekMain', 'listWeekMain'];
case 'month':
return ['mapMonthMain', 'listMonthMain'];
default:
return [];
}
},
/** 获取搜索栏的字典数据(缺陷类型、产线列表) */
async getDict() {
// 获取缺陷类型列表
const defectRes = await this.$axios({
url: '/extend/check-isra-standards/page',
method: 'get',
params: { pageSize: 100, pageNo: 1 }
});
this.searchBarFormConfig[1].selectOptions = defectRes.data.list || [];
// 获取产线列表
const lineRes = await getPdList();
this.searchBarFormConfig[2].selectOptions = lineRes.data || [];
},
/** 查询列表数据(如需使用表格可取消注释) */
// getList() {
// this.getDataList();
// },
/** 表格数据查询(如需使用表格可取消注释) */
// async getDataList() {
// this.loading = true;
// try {
// const { data } = await getQualityIsraPage(this.queryParams);
// this.dynamicProps = this.filterNameData(data.nameData);
// this.list = this.filterData(data.data);
// } catch (error) {
// console.error('获取表格数据失败:', error);
// } finally {
// this.loading = false;
// }
// },
/** 过滤动态表格列名(如需使用表格可取消注释) */
// filterNameData(nameData) {
// const nameSet = new Set(nameData.map(nd => nd.name));
// return Array.from(nameSet).sort().map(name => ({ prop: name, label: name }));
// },
/** 过滤表格数据(如需使用表格可取消注释) */
// filterData(data) {
// return data.map(item => {
// const keyValuePairs = {};
// item.data.forEach(d => {
// keyValuePairs[d.dynamicName] = d.dynamicValue;
// });
// return { ...keyValuePairs, sumNum: item.sumNum, checkType: item.checkType };
// });
// },
/** 搜索按钮点击事件(如需使用搜索栏可取消注释) */
// handleSearchBarBtnClick(val) {
// if (val.btnName === 'search') {
// this.queryParams.checkDiscard = (val?.checkDiscard === 0 || val?.checkDiscard === 1) ? val?.checkDiscard : undefined;
// this.queryParams.lineName = val.lineName ? val.lineName : undefined;
// this.queryParams.checkType = val.checkType ? val.checkType : undefined;
// this.queryParams.startTime = val.checkTime ? val.checkTime[0] : undefined;
// this.queryParams.endTime = val.checkTime ? val.checkTime[1] : undefined;
// this.getList();
// this.getData();
// }
// }
},
};
</script>
<style lang="scss">
.blue-title {
position: relative;
padding: 4px 0;
padding-left: 12px;
font-size: 14px;
color: #606266;
font-weight: 700;
margin-bottom: 12px;
&::before {
content: '';
position: absolute;
left: 0;
top: 6px;
height: 16px;
width: 4px;
border-radius: 1px;
background: #0b58ff;
}
}
/* 新增:四宫格布局样式 */
.chart-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
/* 创建两列,宽度相等 */
grid-template-rows: repeat(2, auto);
/* 创建两行,高度自适应内容 */
gap: 20px;
/* 图表之间的间距 */
width: 100%;
}
.chart-container {
width: 100%;
border: 1px solid #f0f0f0;
border-radius: 4px;
box-sizing: border-box;
}
</style>

View File

@@ -0,0 +1,270 @@
<!--
filename: dialogForm.vue
author: liubin
date: 2023-09-11 15:55:13
description: DialogForm for qualityInspectionRecord only
-->
<template>
<el-form
ref="form"
:model="innerDataForm"
label-width="100px"
v-loading="formLoading">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item
label="检测内容"
prop="inspectionDetId"
:rules="[{ required: true, message: '检测内容不能为空', trigger: 'blur' }]">
<el-select
v-model="innerDataForm.inspectionDetId"
placeholder="请选择检测内容"
filterable
clearable
@change="handleInspectionDetChange">
<el-option
v-for="opt in inspectionDetList"
:key="opt.value"
:label="opt.label"
:value="opt.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="来源"
prop="source"
:rules="[{ required: true, message: '来源不能为空', trigger: 'blur' }]">
<el-select
v-model="innerDataForm.source"
placeholder="请选择来源"
filterable
clearable
@change="$emit('update', innerDataForm)">
<el-option
v-for="opt in [
{ label: '手动', value: 1 },
{ label: '自动', value: 2 },
]"
:key="opt.value"
:label="opt.label"
:value="opt.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item
label="产线"
prop="productionLineId"
:rules="[{ required: true, message: '产线不能为空', trigger: 'blur' }]">
<el-select
v-model="innerDataForm.productionLineId"
placeholder="请选择产线"
filterable
clearable
@change="handleProductlineChange">
<el-option
v-for="opt in productionLineList"
:key="opt.value"
:label="opt.label"
:value="opt.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="工段"
prop="sectionId"
:rules="[{ required: true, message: '工段不能为空', trigger: 'blur' }]">
<el-select
v-model="innerDataForm.sectionId"
placeholder="请选择工段"
clearable
filterable
@change="$emit('update', innerDataForm)">
<el-option
v-for="opt in sectionList"
:key="opt.value"
:label="opt.label"
:value="opt.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="检测人员" prop="checkPerson">
<el-input
v-model="innerDataForm.checkPerson"
clearable
@change="$emit('update', innerDataForm)"
placeholder="请输入检测人员" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="检测时间"
prop="checkTime"
:rules="[{ required: true, message: '检测时间不能为空', trigger: 'blur' }]">
<el-date-picker
v-model="innerDataForm.checkTime"
type="datetime"
placeholder="请选择检测时间"
value-format="timestamp"
@change="$emit('update', innerDataForm)"></el-date-picker>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col>
<el-form-item label="描述" prop="explainText">
<el-input
v-model="innerDataForm.explainText"
placeholder="请输入描述信息"
@change="$emit('update', innerDataForm)"
type="textarea"></el-input>
</el-form-item>
</el-col>
<el-col>
<el-form-item label="备注" prop="remark">
<el-input
v-model="innerDataForm.remark"
@change="$emit('update', innerDataForm)"
placeholder="请输入备注"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script>
export default {
name: 'DialogForm',
model: {
prop: 'dataForm',
event: 'update',
},
emits: ['update'],
components: {},
props: {
dataForm: {
type: Object,
default: () => ({}),
},
},
data() {
return {
formLoading: true,
inspectionDetList: [],
productionLineList: [],
sectionList: [],
innerDataForm: {},
cacheInspectionDetList: null,
};
},
mounted() {
Promise.all([this.getProductLineList(), this.getInspectionDetList()]).then(
() => {
this.formLoading = false;
}
);
},
watch: {
// 'innerDataForm.productionLineId': {
// handler: async function (plId) {
// if (plId) await this.getWorksectionList(plId);
// },
// immediate: true,
// },
dataForm: {
handler: function (dataForm) {
this.innerDataForm = Object.assign({}, dataForm);
if (dataForm.productionLineId)
this.getWorksectionList(dataForm.productionLineId);
},
immediate: true,
},
},
methods: {
/** 模拟透传 ref */
validate(cb) {
return this.$refs.form.validate(cb);
},
resetFields(args) {
return this.$refs.form.resetFields(args);
},
handleInspectionDetChange(value) {
const { id, content } = this.cacheInspectionDetList.find(
(item) => item.id == value
);
this.innerDataForm.inspectionDetId = id;
this.innerDataForm.inspectionDetContent = content;
this.$emit('update', this.innerDataForm);
},
async handleProductlineChange(id) {
// await this.getWorksectionList(id);
this.innerDataForm.sectionId = null;
this.$emit('update', this.innerDataForm);
},
// getCode
async getCode(url) {
const response = await this.$axios(url);
return response.data;
},
// 获取产线列表
async getProductLineList() {
const response = await this.$axios('/base/core-production-line/listAll');
this.productionLineList = response.data.map((item) => ({
label: item.name,
value: item.id,
}));
},
// 获取检测内容列表
async getInspectionDetList() {
const response = await this.$axios(
'/base/quality-inspection-det/listAll'
);
this.cacheInspectionDetList = response.data;
this.inspectionDetList = response.data.map((item) => ({
label: item.content,
value: item.id,
}));
},
// 获取工段列表
async getWorksectionList(plId) {
const response = await this.$axios(
'/base/workshop-section/listByParentId',
{
params: {
id: plId,
},
}
);
this.sectionList = response.data.map((item) => ({
label: item.name,
value: item.id,
}));
},
},
};
</script>
<style scoped lang="scss">
.el-date-editor,
.el-select {
width: 100%;
}
</style>

View File

@@ -0,0 +1,468 @@
<template>
<div class="app-container">
<!-- <div class="blue-title">生产节拍时序图</div> -->
<SearchBar :formConfigs="searchBarFormConfig" ref="search-bar" @headBtnClick="handleSearchBarBtnClick" />
<!-- 列表 -->
<base-table :max-height="tableH" :table-props="tableProps" :page="1" :limit="20" :table-data="list">
</base-table>
<!-- </el-row> -->
</div>
</template>
<script>
import {
getQualityIsraPage,
// getQualityIsraDayMap,
// getQualityIsraWeekMap,
// getQualityIsraMonthMap,
// getQualityIsraDayList,
// getQualityIsraWeekList,
// getQualityIsraMonthList,
} from '@/api/monitoring/qualityIsra';
// import Editor from '@/components/Editor';
import moment from 'moment';
// import DialogForm from './dialogForm.vue';
import * as echarts from 'echarts';
import tableHeightMixin from '@/mixins/lb/tableHeightMixin';
// import { getPdList } from '@/api/base/coreProductionLine';
import { parseTime } from '@/utils/ruoyi';
import { getPdList, } from '@/api/core/monitoring/auto';
// import basicPageMixin from '@/mixins/lb/basicPageMixin';
export default {
name: 'QualityIsra',
// components: {
// DialogForm,
// },
mixins: [tableHeightMixin],
data() {
return {
isFold: false,
list: [],
dynamicProps: [],
activeName: 'day',
dayMapUrl: '/extend/check-isra-statistics/dayMap',
weekMapUrl: '/extend/check-isra-statistics/weekMap',
monthMapUrl: '/extend/check-isra-statistics/monthMap',
dayListUrl: '/extend/check-isra-statistics/dayList',
weekListUrl: '/extend/check-isra-statistics/weekList',
monthListUrl: '/extend/check-isra-statistics/monthList',
searchBarFormConfig: [
{
type: 'select',
label: '是否报废',
placeholder: '请选择是否报废',
param: 'checkDiscard',
selectOptions: [
{ name: '否', id: 0 },
{ name: '是', id: 1 }
],
},
{
type: 'select',
label: '缺陷类型',
placeholder: '请选择缺陷类型',
param: 'checkType',
selectOptions: [],
labelField: 'name',
valueField: 'name',
defaultSelect: [],
filterable: true
},
{
type: 'select',
label: '产线',
selectOptions: [],
labelField: 'name',
valueField: 'name',
param: 'lineName',
filterable: true,
defaultSelect: []
},
{
type: 'datePicker',
label: '时间段',
dateType: 'datetimerange', // datetimerange
// format: 'yyyy-MM-dd HH:mm:ss',
format: 'yyyy-MM-dd HH:mm:ss',
valueFormat: 'yyyy-MM-dd HH:mm:ss',
rangeSeparator: '-',
startPlaceholder: '开始日期',
endPlaceholder: '结束日期',
defaultTime: ['00:00:00', '23:59:59'],
param: 'checkTime',
// width: 350,
},
{
type:'button',
btnName: '查询',
name: 'search',
color: 'primary',
},
],
// 查询参数
queryParams: {
checkDiscard: undefined,
checkType:undefined,
lineName: undefined,
startTime: undefined,
endTime: undefined,
// productionLineId: null,
},
};
},
created() {
// this.getProductLineList();
// this.isFold = this.searchBarWidth('QualityIsraBox', 1198);
},
computed: {
tableProps() {
return [
{
// width: 128,
prop: 'checkType',
label: '缺陷类型',
},
{
// width: 128,
prop: 'sumNum',
label: '缺陷总数',
},
...this.dynamicProps,
];
},
},
mounted() {
if (this.$route.query.lineName) {
// console.log('打印看看产线', this.$route.query.lineName)
this.queryParams.lineName = this.$route.query.lineName
this.searchBarFormConfig[2].defaultSelect = this.$route.query.lineName
}
if (this.$route.query.originalGlassOutputTime) {
console.log('你好', this.$route.query.originalGlassOutputTime)
this.queryParams.startTime = parseTime(new Date(Number(this.$route.query.originalGlassOutputTime)))
this.queryParams.endTime = parseTime(new Date(Number(this.$route.query.originalGlassOutputTime)))
this.searchBarFormConfig[3].defaultSelect = [
this.queryParams.startTime,
this.queryParams.endTime,
];
}
this.getList()
this.getData()
this.getDict()
},
methods: {
handleClick() {
this.getData()
},
getData() {
this.$axios({
url: this.activeName === 'day' ? this.dayMapUrl : this.activeName === 'week' ? this.weekMapUrl : this.monthMapUrl,
method: 'get',
params: this.queryParams
}).then((res) => {
let mapArr= []
let mapLegendData = []
let obj = {
name: '',
type: 'line',
// stack: 'Total',
data: [],
mapXAxisData: [],
}
// let mapXAxisData = []
for (let i in res.data) {
// console.log(i)
let dataArr = []
res.data[i].forEach(ele => {
dataArr.push(ele.num)
obj.mapXAxisData.push(ele.checkTime)
})
obj.name = i
obj.data = dataArr
mapLegendData.push(i)
mapArr.push(obj)
}
console.log(mapArr);
// console.log(res.data[res]);
var chartDom = this.activeName === 'day' ? document.getElementById('mapDayMain') : this.activeName === 'week' ? document.getElementById('mapWeekMain') : document.getElementById('mapMonthMain')
var myChart = echarts.init(chartDom);
var option;
option = {
title: {
text: '各类型缺陷对比图',
// top:'5px'
},
tooltip: {
trigger: 'axis'
},
legend: {
data: mapLegendData,
top: "10%",
y: 'top',
x:'left'
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top:'20%',
containLabel: true
},
// toolbox: {
// feature: {
// saveAsImage: {}
// }
// },
xAxis: {
type: 'category',
// boundaryGap: false,
data: mapArr[0].mapXAxisData
},
yAxis: {
type: 'value'
},
series: mapArr
}
myChart.clear()
option && myChart.setOption(option);
})
this.$axios({
url: this.activeName === 'day' ? this.dayListUrl : this.activeName === 'week' ? this.weekListUrl : this.monthListUrl,
method: 'get',
params: this.queryParams
}).then((res) => {
// console.log(res);
let listNumArr = []
let listRatioArr = []
// let listLegendData = []
let listXAxisData = []
// for (let i in res.data) {
// console.log(i)
// let dataArr = []
res.data.forEach(ele => {;
listNumArr.push(ele.num)
listRatioArr.push(ele.ratio)
listXAxisData.push(ele.checkTime)
})
console.log(listNumArr);
// obj.name = i
// obj.data = dataArr
// listLegendData.push(i)
// listArr.push(obj)
// }
// console.log(res.data[res]);
var chartDom = this.activeName === 'day' ? document.getElementById('listDayMain') : this.activeName === 'week' ? document.getElementById('listWeekMain') : document.getElementById('listMonthMain')
var myChart = echarts.init(chartDom);
var option;
option = {
title: {
text: '缺陷率趋势图'
},
tooltip: {
trigger: 'axis'
},
legend: {
data: ['缺陷数量', '缺陷率'],
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
// toolbox: {
// feature: {
// saveAsImage: {}
// }
// },
xAxis: {
type: 'category',
// boundaryGap: false,
data: listXAxisData
},
yAxis: [
{
type: 'value',
name: '缺陷数量',
// min: 0,
// max: 250,
// interval: 50,
// axisLabel: {
// formatter: '{value} ml'
// }
},
{
type: 'value',
name: '缺陷率',
// min: 0,
// max: 25,
// interval: 5,
axisLabel: {
formatter: '{value} %'
}
}
],
series: [
{
name: '缺陷数量',
type: 'bar',
barWidth: '3%',
data: listNumArr,
// label: {
// show: true, //开启显示
// position: 'top', //在上方显示
// // formatter: '{c}%',//显示百分号
// textStyle: { //数值样式
// color: 'black',//字体颜色
// fontSize: 12//字体大小
// }
// }
},
{
name: '缺陷率',
type: 'line',
yAxisIndex: 1,
tooltip: {
valueFormatter: function (value) {
return value + '%';
}
},
data: listRatioArr
}
]
}
myChart.clear()
option && myChart.setOption(option);
})
},
/** 获取搜索栏的产线列表 */
async getDict() {
// const res = await getProductList()
// const result = await getWorkOrderList()
const res = await this.$axios({
url: '/extend/check-isra-standards/page',
method: 'get',
params: {
pageSize: 100,
pageNo:1
}
});
// console.log(res)
this.searchBarFormConfig[1].selectOptions = res.data.list
await getPdList().then((res) => {
// console.log(res);
this.searchBarFormConfig[2].selectOptions = res.data;
})
// this.searchBarFormConfig[1].selectOptions = res.data.map((item) => {
// return {
// name: item.name,
// id:item.id
// }
// })
// this.searchBarFormConfig[0].selectOptions = result.data.map((item) => {
// return {
// name: item.name,
// id: item.id
// }
// })
},
// getProductLineList() {
// this.$axios('/base/production-line/listAll').then((response) => {
// this.searchBarFormConfig[0].selectOptions = response.data.map(
// (item) => {
// return {
// name: item.name,
// id: item.id,
// };
// }
// );
// });
// },
getList() {
this.getDataList()
},
/** 查询列表 */
async getDataList() {
console.log('查询条件', this.queryParams);
this.loading = true;
// 执行查询
const {
data: { data, otherList, otherMap, nameData },
} = await getQualityIsraPage(this.queryParams)
console.log(this.queryParams);
this.dynamicProps = this.filterNameData(nameData)
this.list = this.filterData(data);
},
filterNameData(nameData) {
const ndSet = new Set();
nameData.forEach((nd) => {
ndSet.add(nd.name);
});
return Array.from(ndSet.values())
.sort()
.map((name) => ({
prop: name,
label: name,
}));
},
filterData(data) {
return data.map((item) => {
const { data: innerData } = item;
const keyValuePairs = {};
innerData.map((d) => {
keyValuePairs[d.dynamicName] = d.dynamicValue;
});
return {
// inspectionContent: item.inspectionContent,
...keyValuePairs,
sumNum: item.sumNum,
// sumInput: item.sumInput,
// productionName: item.productionName,
checkType: item.checkType,
// scrapRatio: item.scrapRatio,
};
});
},
/** 取消按钮 */
handleSearchBarBtnClick(val) {
console.log('11111', val)
if (val.btnName === 'search') {
this.queryParams.checkDiscard = (val?.checkDiscard === 0 || val?.checkDiscard === 1) ? val?.checkDiscard : undefined
this.queryParams.lineName = val.lineName ? val.lineName : undefined
this.queryParams.checkType = val.checkType ? val.checkType : undefined
// this.queryParams.productionId = val.productionId ? val.productionId : undefined
this.queryParams.startTime = val.checkTime ? val.checkTime[0] : undefined
this.queryParams.endTime = val.checkTime ? val.checkTime[1] : undefined
this.getList()
this.getData()
}
console.log(val);
}
},
};
</script>
<style lang="scss">
.blue-title {
position: relative;
padding: 4px 0;
padding-left: 12px;
font-size: 14px;
color: #606266;
font-weight: 700;
margin-bottom: 12px;
&::before {
content: '';
position: absolute;
left: 0;
top: 6px;
height: 16px;
width: 4px;
border-radius: 1px;
background: #0b58ff;
}
}
</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>

View File

@@ -3157,6 +3157,11 @@
"resolved" "https://registry.npmmirror.com/check-types/-/check-types-8.0.3.tgz"
"version" "8.0.3"
"chinese-lunar@^0.1.4":
"integrity" "sha512-E4seb9UZkSJtkm65Ry2ZLWTw5Z87DOyGcCePdg3ft7da342hnEliMtcDAPSn+YSo/o0xl1Egmhh671kxEJgY+g=="
"resolved" "https://registry.npmmirror.com/chinese-lunar/-/chinese-lunar-0.1.4.tgz"
"version" "0.1.4"
"chokidar@^2.1.8":
"integrity" "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg=="
"resolved" "https://registry.npmmirror.com/chokidar/-/chokidar-2.1.8.tgz"