Compare commits

..

13 Commits

Author SHA1 Message Date
zwq
fe2408c667 更新大屏 2026-07-01 09:38:25 +08:00
a2375325a0 Merge pull request 'projects/qhd-line-zhp' (#467) from projects/qhd-line-zhp into projects/qhd-line-test
Reviewed-on: #467
2025-12-23 08:52:44 +08:00
‘937886381’
04d5896f6e Merge branch 'projects/qhd-line-test' into projects/qhd-line-zhp 2025-12-23 08:51:19 +08:00
‘937886381’
46a23c249c 修改 2025-12-23 08:49:37 +08:00
242a52b98e Merge pull request 'projects/qhd-line-zhp' (#465) from projects/qhd-line-zhp into projects/qhd-line-test
Reviewed-on: #465
2025-12-17 16:30:08 +08:00
‘937886381’
945237557f 修改 2025-12-17 16:27:13 +08:00
‘937886381’
8eb0a9fee1 Merge branch 'projects/qhd-line-test' into projects/qhd-line-zhp 2025-12-15 10:48:29 +08:00
‘937886381’
a9645d03a9 修改bug 2025-12-15 10:46:44 +08:00
2b5e476fdd Merge pull request 'projects/qhd-line-zhp' (#464) from projects/qhd-line-zhp into projects/qhd-line-test
Reviewed-on: #464
2025-12-12 16:57:01 +08:00
‘937886381’
f95d0473bf Merge branch 'projects/qhd-line-test' into projects/qhd-line-zhp 2025-12-12 16:51:39 +08:00
‘937886381’
25dc76dfbe 修改bug 2025-12-12 16:50:46 +08:00
cdeba3f334 Merge pull request '新增设备总览透光率产线产品监控等' (#462) from projects/qhd-line-zhp into projects/qhd-line-test
Reviewed-on: #462
2025-12-12 14:50:32 +08:00
‘937886381’
ba75500776 新增设备总览透光率产线产品监控等 2025-12-12 14:49:24 +08:00
54 changed files with 14803 additions and 3391 deletions

View File

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

Binary file not shown.

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
* @Date: 2023-09-12 09:44:53 * @Date: 2023-09-12 09:44:53
* @LastEditTime: 2023-09-15 14:12:26 * @LastEditTime: 2023-09-15 14:12:26
* @LastEditors: DY * @LastEditors: DY
* @Description: * @Description:
*/ */
import request from '@/utils/request' import request from '@/utils/request'
@@ -15,3 +15,11 @@ export function getPdlDataOneDay(data) {
data: data data: data
}) })
} }
// 获得近24小时产线生产数据-新版
export function getSectionDataOneDay(data) {
return request({
url: '/monitoring/production-monitor/getSectionDataOneDay',
method: 'post',
data: data
})
}

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
<div class='centerBottomL'> <div class='centerBottomL'>
<div class='title'> <div class='title'>
<svg-icon icon-class="dataBoard3" class='icon'/> <svg-icon icon-class="dataBoard3" class='icon'/>
<span>月数据</span> <span>月数据</span>
</div> </div>
<div class='dataBox' style='top:50px'> <div class='dataBox' style='top:50px'>
<p> <p>
@@ -13,7 +13,7 @@
</p> </p>
<p class='num'>{{monthData?.inputNum ? formatThousands(monthData.inputNum) : '-'}}</p> <p class='num'>{{monthData?.inputNum ? formatThousands(monthData.inputNum) : '-'}}</p>
</div> </div>
<div class='dataBox'style='top:180px'> <div class='dataBox' style='top:180px'>
<p> <p>
<span class='text'>总生产片数</span> <span class='text'>总生产片数</span>
<span class='precent' :class='{precentR:monthData?.outputNumChange>=0,precentG:monthData?.outputNumChange<0}'>{{monthData?.outputNumChange || '-'}}%</span> <span class='precent' :class='{precentR:monthData?.outputNumChange>=0,precentG:monthData?.outputNumChange<0}'>{{monthData?.outputNumChange || '-'}}%</span>
@@ -37,7 +37,7 @@ export default {
watch: { watch: {
dataObj(val) { dataObj(val) {
val.monthAndLastMonth && val.monthAndLastMonth.forEach(item => { val.monthAndLastMonth && val.monthAndLastMonth.forEach(item => {
if (item.dataType === "月") { if (item.dataType === "月") {
this.monthData = item this.monthData = item
} }
}) })
@@ -113,4 +113,4 @@ export default {
} }
} }
} }
</style> </style>

View File

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

View File

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

View File

@@ -6,7 +6,7 @@
</div> </div>
<div class='title-split'> <div class='title-split'>
<img src="../../../../assets//images/dataBoard/leftbar.png" alt=""> <img src="../../../../assets//images/dataBoard/leftbar.png" alt="">
<span class='text'></span> <span class='text'></span>
<img src="../../../../assets//images/dataBoard/rightbar.png" alt=""> <img src="../../../../assets//images/dataBoard/rightbar.png" alt="">
</div> </div>
<div class='data-box'> <div class='data-box'>
@@ -20,11 +20,11 @@
</div> </div>
<div class='right-data' style="top:15px;"> <div class='right-data' style="top:15px;">
<p><span class='num'>{{todayData?.inputNum ? formatThousands(todayData.inputNum) : '-'}}</span><span class='text'>片数</span></p> <p><span class='num'>{{todayData?.inputNum ? formatThousands(todayData.inputNum) : '-'}}</span><span class='text'>片数</span></p>
<p><span class='num'>{{todayData?.inputArea ? formatThousands(todayData.inputArea) : '-'}}</span><span class='text'>面积/</span></p> <!-- <p><span class='num'>{{todayData?.inputArea ? formatThousands(todayData.inputArea) : '-'}}</span><span class='text'>面积/</span></p> -->
</div> </div>
<div class='right-data' style="top:132px;"> <div class='right-data' style="top:132px;">
<p><span class='num'>{{todayData?.outputNum ? formatThousands(todayData.outputNum) : '-'}}</span><span class='text'>片数</span></p> <p><span class='num'>{{todayData?.outputNum ? formatThousands(todayData.outputNum) : '-'}}</span><span class='text'>片数</span></p>
<p><span class='num'>{{todayData?.outputArea ? formatThousands(todayData.outputArea) : '-'}}</span><span class='text'>面积/</span></p> <!-- <p><span class='num'>{{todayData?.outputArea ? formatThousands(todayData.outputArea) : '-'}}</span><span class='text'>面积/</span></p> -->
</div> </div>
</div> </div>
<div class='title-split'> <div class='title-split'>
@@ -43,11 +43,11 @@
</div> </div>
<div class='right-data' style="top:15px;"> <div class='right-data' style="top:15px;">
<p><span class='num'>{{yesterdayData?.inputNum ? formatThousands(yesterdayData.inputNum) : '-'}}</span><span class='text'>片数</span></p> <p><span class='num'>{{yesterdayData?.inputNum ? formatThousands(yesterdayData.inputNum) : '-'}}</span><span class='text'>片数</span></p>
<p><span class='num'>{{yesterdayData?.inputArea ? formatThousands(yesterdayData.inputArea) : '-'}}</span><span class='text'>面积/</span></p> <!-- <p><span class='num'>{{yesterdayData?.inputArea ? formatThousands(yesterdayData.inputArea) : '-'}}</span><span class='text'>面积/</span></p> -->
</div> </div>
<div class='right-data' style="top:132px;"> <div class='right-data' style="top:132px;">
<p><span class='num'>{{yesterdayData?.outputNum ? formatThousands(yesterdayData.outputNum) : '-'}}</span><span class='text'>片数</span></p> <p><span class='num'>{{yesterdayData?.outputNum ? formatThousands(yesterdayData.outputNum) : '-'}}</span><span class='text'>片数</span></p>
<p><span class='num'>{{yesterdayData?.outputArea ? formatThousands(yesterdayData.outputArea) : '-'}}</span><span class='text'>面积/</span></p> <!-- <p><span class='num'>{{yesterdayData?.outputArea ? formatThousands(yesterdayData.outputArea) : '-'}}</span><span class='text'>面积/</span></p> -->
</div> </div>
</div> </div>
</div> </div>
@@ -155,8 +155,9 @@ export default {
color: #FFFFFF; color: #FFFFFF;
letter-spacing: 1px; letter-spacing: 1px;
text-shadow: 0px 4px 2px rgba(0,0,0,0.62); text-shadow: 0px 4px 2px rgba(0,0,0,0.62);
line-height: 90px;
} }
} }
} }
} }
</style> </style>

View File

@@ -2,7 +2,7 @@
<div class='rightBottomBox'> <div class='rightBottomBox'>
<div class='title'> <div class='title'>
<svg-icon icon-class="dataBoard3" class='icon'/> <svg-icon icon-class="dataBoard3" class='icon'/>
<span><span class='dotted'></span>班组生产排名</span> <span><span class='dotted'></span>班组生产排名</span>
</div> </div>
<div class='rankingLeft'> <div class='rankingLeft'>
<div class='rankingLeftTitle'>产量</div> <div class='rankingLeftTitle'>产量</div>

View File

@@ -21,7 +21,7 @@
</div> </div>
<div class='box' style='width: 115px;'> <div class='box' style='width: 115px;'>
<p class='name'> <p class='name'>
<span style='margin-right: 3px;'>月</span> <span style='margin-right: 3px;'>月</span>
<img v-show='nokSumDet.month >= nokSumDet.lastMonth' src="../../../../assets/images/dataBoard/arrUp.png" alt="" width='5' height='15'> <img v-show='nokSumDet.month >= nokSumDet.lastMonth' src="../../../../assets/images/dataBoard/arrUp.png" alt="" width='5' height='15'>
<img v-show='nokSumDet.month < nokSumDet.lastMonth' src="../../../../assets/images/dataBoard/arrDown.png" alt="" width='5' height='15'> <img v-show='nokSumDet.month < nokSumDet.lastMonth' src="../../../../assets/images/dataBoard/arrDown.png" alt="" width='5' height='15'>
</p> </p>
@@ -29,7 +29,7 @@
</div> </div>
<div class='box' style='width: 110px;'> <div class='box' style='width: 110px;'>
<p class='name'> <p class='name'>
<span style='margin-right: 3px;'>年</span> <span style='margin-right: 3px;'>年</span>
<img v-show='nokSumDet.year >= nokSumDet.lastYear' src="../../../../assets/images/dataBoard/arrUp.png" alt="" width='5' height='15'> <img v-show='nokSumDet.year >= nokSumDet.lastYear' src="../../../../assets/images/dataBoard/arrUp.png" alt="" width='5' height='15'>
<img v-show='nokSumDet.year < nokSumDet.lastYear' src="../../../../assets/images/dataBoard/arrDown.png" alt="" width='5' height='15'> <img v-show='nokSumDet.year < nokSumDet.lastYear' src="../../../../assets/images/dataBoard/arrDown.png" alt="" width='5' height='15'>
</p> </p>

File diff suppressed because it is too large Load Diff

View File

@@ -88,7 +88,32 @@ export default {
}; };
}, },
created() {}, created() {},
methods: { methods: {
}, dataFormSubmit() {
this.$refs["dataForm"].validate((valid) => {
if (!valid) {
return false;
}
// 修改的提交
if (this.dataForm.id) {
this.urlOptions.updateURL(this.dataForm).then(response => {
this.$modal.msgSuccess("修改成功");
this.visible = false;
this.$emit("refreshDataList");
});
return;
}
// 添加的提交
this.urlOptions.createURL(this.dataForm).then(response => {
if (response.code === 1001033 || response.code === 1001034) {
return this.$modal.msgError(response.msg);
}
this.$modal.msgSuccess("新增成功");
this.visible = false;
this.$emit("refreshDataList");
});
});
},
},
}; };
</script> </script>

View File

@@ -6,77 +6,47 @@
* @Description: * @Description:
--> -->
<template> <template>
<el-form <el-form :model="dataForm" :rules="dataRule" ref="dataForm" v-if="visible" @keyup.enter.native="dataFormSubmit()"
:model="dataForm" label-width="100px" label-position="top">
:rules="dataRule" <el-row :gutter="20">
ref="dataForm" <el-col :span="12">
v-if="visible" <el-form-item label="原料名称" prop="materialId">
@keyup.enter.native="dataFormSubmit()" <el-select v-model="dataForm.materialId" filterable @change="setCode" :style="{ width: '100%' }"
label-width="100px" placeholder="请选择原料名称">
label-position="top"> <el-option v-for="item in MaterialList" :key="item.id" :label="item.name" :value="item.id"></el-option>
<el-row :gutter="20"> </el-select>
<el-col :span="12"> </el-form-item>
<el-form-item label="原料名称" prop="materialId"> </el-col>
<el-select <el-col :span="12">
v-model="dataForm.materialId" <el-form-item label="原料编码" prop="code">
filterable <el-input v-model="dataForm.code" clearable readonly />
@change="setCode" </el-form-item>
:style="{ width: '100%' }" </el-col>
placeholder="请选择原料名称"> <el-col :span="12">
<el-option <el-form-item label="生效开始时间" prop="startTime">
v-for="item in MaterialList" <el-date-picker v-model="dataForm.startTime" type="date" value-format="timestamp" :style="{ width: '100%' }"
:key="item.id" placeholder="选择开始时间"></el-date-picker>
:label="item.name" </el-form-item>
:value="item.id"></el-option> </el-col>
</el-select> <el-col :span="12">
</el-form-item> <el-form-item label="生效结束时间" prop="endTime">
</el-col> <el-date-picker v-model="dataForm.endTime" type="date" value-format="timestamp" :style="{ width: '100%' }"
<el-col :span="12"> placeholder="选择结束时间"></el-date-picker>
<el-form-item label="原料编码" prop="code"> </el-form-item>
<el-input v-model="dataForm.code" clearable readonly /> </el-col>
</el-form-item> <el-col :span="12">
</el-col> <el-form-item label="单价" prop="price">
<el-col :span="12"> <el-input-number :min="0" style="width: 75%" v-model="dataForm.price" clearable placeholder="请输入单价" />
<el-form-item label="生效开始时间" prop="startTime"> {{ unit }}
<el-date-picker </el-form-item>
v-model="dataForm.startTime" </el-col>
type="date" <el-col :span="12">
value-format="timestamp" <el-form-item label="备注" prop="remark">
:style="{ width: '100%' }" <el-input v-model="dataForm.remark" clearable placeholder="请输入备注" />
placeholder="选择开始时间"></el-date-picker> </el-form-item>
</el-form-item> </el-col>
</el-col> </el-row>
<el-col :span="12"> </el-form>
<el-form-item label="生效结束时间" prop="endTime">
<el-date-picker
v-model="dataForm.endTime"
type="date"
value-format="timestamp"
:style="{ width: '100%' }"
placeholder="选择结束时间"></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="单价" prop="price">
<el-input-number
:min="0"
style="width: 75%"
v-model="dataForm.price"
clearable
placeholder="请输入允许留存时间" />
{{ unit }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="备注" prop="remark">
<el-input
v-model="dataForm.remark"
clearable
placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</template> </template>
<script> <script>
@@ -146,11 +116,34 @@ export default {
this.dataForm.code = item.code; this.dataForm.code = item.code;
this.unit = this.unit =
'元/' + '元/' +
this.urlOptions.dictArr.dict0.find((d) => d.value === item.unit) this.urlOptions.dictArr.dict0.find((d) => d.value == item.unit)
.label; .label;
} }
}); });
}, },
dataFormSubmit() {
this.$refs["dataForm"].validate((valid) => {
if (!valid) {
return false;
}
// 修改的提交
if (this.dataForm.id) {
this.urlOptions.updateURL(this.dataForm).then(response => {
this.$modal.msgSuccess("修改成功");
this.visible = false;
this.$emit("refreshDataList");
});
return;
}
// 添加的提交
this.urlOptions.createURL(this.dataForm).then(response => {
console.log('response', response);
this.$modal.msgSuccess("新增成功");
this.visible = false;
this.$emit("refreshDataList");
});
});
},
}, },
}; };
</script> </script>

View File

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

View File

@@ -6,65 +6,39 @@
* @Description: * @Description:
--> -->
<template> <template>
<div class="app-container"> <div class="app-container">
<search-bar <search-bar :formConfigs="formConfig" ref="searchBarForm" @select-changed="handleSearchBarChanged"
:formConfigs="formConfig" @headBtnClick="buttonClick" />
ref="searchBarForm" <div v-if="showData.length">
@select-changed="handleSearchBarChanged" <base-table class="right-aside" v-loading="dataListLoading" :table-props="tableProps" :page="1" :limit="999"
@headBtnClick="buttonClick" /> :table-data="showData">
<div v-if="showData.length"> <method-btn v-if="showData.length" slot="handleBtn" :width="80" label="操作" :method-list="tableBtn"
<base-table @clickBtn="handleClick" />
class="right-aside" </base-table>
v-loading="dataListLoading" <barChart v-for="item in chartData" :key="item.name + 'echart'" style="margin-top: 50px" height="600px"
:table-props="tableProps" :id="item.name + 'echart'" :title="item.name + ' 节拍趋势图'" :bar-data="item" />
:page="1" </div>
:limit="999" <div v-else class="no-data-bg"></div>
:table-data="showData"> <base-dialog :dialogTitle="addOrEditTitle" :dialogVisible="addOrUpdateVisible" @cancel="handleCancel"
<method-btn @confirm="handleConfirm" :before-close="handleCancel" close-on-click-modal top="0" width="50%">
v-if="showData.length" <eq-detail ref="eqDetail" />
slot="handleBtn" <slot name="footer">
:width="80" <el-row slot="footer" type="flex" justify="end">
label="操作" <el-col :span="24">
:method-list="tableBtn" <el-button size="small" class="btnTextStyle" @click="handleCancel">
@clickBtn="handleClick" /> 取消
</base-table> </el-button>
<barChart </el-col>
v-for="item in chartData" </el-row>
:key="item.name + 'echart'" </slot>
style="margin-top: 50px" </base-dialog>
height="600px" </div>
:id="item.name + 'echart'"
:title="item.name + ' 节拍趋势图'"
:bar-data="item" />
</div>
<div v-else class="no-data-bg"></div>
<base-dialog
:dialogTitle="addOrEditTitle"
:dialogVisible="addOrUpdateVisible"
@cancel="handleCancel"
@confirm="handleConfirm"
:before-close="handleCancel"
close-on-click-modal
top="0"
width="50%">
<eq-detail ref="eqDetail" />
<slot name="footer">
<el-row slot="footer" type="flex" justify="end">
<el-col :span="24">
<el-button size="small" class="btnTextStyle" @click="handleCancel">
取消
</el-button>
</el-col>
</el-row>
</slot>
</base-dialog>
</div>
</template> </template>
<script> <script>
import eqDetail from './eq-detail'; import eqDetail from './eq-detail';
import { parseTime } from '../../mixins/code-filter'; import { parseTime } from '../../mixins/code-filter';
import { getPdList } from '@/api/core/monitoring/auto'; import { getPLlistByFactory } from '@/api/core/monitoring/auto';
import { getNewCTNow, getNewCTCharts } from '@/api/core/analysis/index'; import { getNewCTNow, getNewCTCharts } from '@/api/core/analysis/index';
import { getFactoryPage } from '@/api/core/base/factory'; import { getFactoryPage } from '@/api/core/base/factory';
// import codeFilter from '../../mixins/code-filter' // import codeFilter from '../../mixins/code-filter'
@@ -73,230 +47,233 @@ import FileSaver from 'file-saver';
import barChart from './BarChart.vue'; import barChart from './BarChart.vue';
const tableProps = [ const tableProps = [
{ {
prop: 'factoryName', prop: 'factoryName',
label: '工厂', label: '工厂',
}, },
{ {
prop: 'lineName', prop: 'lineName',
label: '产线', label: '产线',
}, },
{ {
prop: 'size', prop: 'size',
label: '规格', label: '规格',
showOverflowtooltip: true, showOverflowtooltip: true,
}, },
{ {
prop: 'process', prop: 'process',
label: '产品工艺', label: '产品工艺',
}, },
{ {
prop: 'edgeCt', prop: 'edgeCt',
label: '磨边当前节拍pcs/min', label: '磨边当前节拍pcs/min',
}, },
{ {
prop: 'temperCt', prop: 'temperCt',
label: '钢化当前节拍pcs/min', label: '钢化当前节拍pcs/min',
}, },
{ {
prop: 'downCt', prop: 'downCt',
label: '下片当前节拍pcs/min', label: '下片当前节拍pcs/min',
}, },
]; ];
export default { export default {
components: { components: {
barChart, barChart,
eqDetail, eqDetail,
}, },
data() { data() {
return { return {
urlOptions: { urlOptions: {
getDataListURL: getNewCTNow, getDataListURL: getNewCTNow,
}, },
listQuery: { listQuery: {
lineId: [], lineId: [],
}, },
fileName: '', fileName: '',
dataListLoading: false, dataListLoading: false,
tableProps, tableProps,
tableBtn: [ tableBtn: [
{ {
type: 'eq', type: 'eq',
btnName: '详情', btnName: '详情',
}, },
].filter((v) => v), ].filter((v) => v),
showData: [], showData: [],
tableData: [], tableData: [],
chartData: [], chartData: [],
formConfig: [ formConfig: [
{ {
type: 'select', type: 'select',
label: '工厂', label: '工厂',
selectOptions: [], selectOptions: [],
param: 'factoryId', param: 'factoryId',
onchange: true, collapseTags: true,
}, multiple: true,
{ onchange: true,
type: 'select', },
label: '产线', {
selectOptions: [], type: 'select',
param: 'lineId', label: '产线',
multiple: true, selectOptions: [],
}, param: 'lineId',
{ collapseTags: true,
type: 'datePicker', multiple: true,
label: '时间范围', },
dateType: 'datetimerange', {
format: 'yyyy-MM-dd HH:mm:ss', type: 'datePicker',
valueFormat: 'timestamp', label: '时间范围',
rangeSeparator: '-', dateType: 'datetimerange',
startPlaceholder: '开始时间', format: 'yyyy-MM-dd HH:mm:ss',
endPlaceholder: '结束时间', valueFormat: 'timestamp',
defaultTime: ['00:00:00', '23:59:59'], rangeSeparator: '-',
param: 'timeVal', startPlaceholder: '开始时间',
width: 350, endPlaceholder: '结束时间',
clearable: false, defaultTime: ['00:00:00', '23:59:59'],
}, param: 'timeVal',
{ width: 350,
type: 'button', clearable: false,
btnName: '查询', },
name: 'search', {
color: 'primary', type: 'button',
}, btnName: '查询',
// { name: 'search',
// type: 'separate', color: 'primary',
// }, },
// { // {
// // type: this.$auth.hasPermi('base:factory:export') ? 'button' : '', // type: 'separate',
// type: 'button', // },
// btnName: '导出', // {
// name: 'export', // // type: this.$auth.hasPermi('base:factory:export') ? 'button' : '',
// color: 'warning', // type: 'button',
// }, // btnName: '导出',
], // name: 'export',
addOrEditTitle: '', // color: 'warning',
addOrUpdateVisible: false, // },
}; ],
}, addOrEditTitle: '',
created() { addOrUpdateVisible: false,
// 获取当前时间 };
const now = new Date(); },
// 获取前一天的同一时间 created() {
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000); // 获取当前时间
// 设置为00:00:00 const now = new Date();
yesterday.setHours(0, 0, 0, 0); // 获取前一天的同一时间
// 设置为23:59:59 const yesterday = new Date(now.getTime());
const end = new Date(yesterday.getTime()); // 设置为00:00:00
end.setHours(23, 59, 59, 59); yesterday.setHours(0, 0, 0, 0);
this.listQuery.startTime = yesterday.getTime(); // 设置为23:59:59
this.listQuery.endTime = end.getTime(); const end = new Date(yesterday.getTime());
this.$nextTick(() => { end.setHours(23, 59, 59, 59);
this.$refs.searchBarForm.formInline.timeVal = [ this.listQuery.startTime = yesterday.getTime();
yesterday.getTime(), this.listQuery.endTime = end.getTime();
end.getTime(), this.$nextTick(() => {
]; this.$refs.searchBarForm.formInline.timeVal = [
}); yesterday.getTime(),
this.getDataList(); end.getTime(),
this.getPdLineList(); ];
}, });
methods: { this.getDataList();
handleExport() { this.getPdLineList();
let tables = document.querySelector('.el-table').cloneNode(true); },
const fix = tables.querySelector('.el-table__fixed'); methods: {
const fixRight = tables.querySelector('.el-table__fixed-right'); handleExport() {
if (fix) { let tables = document.querySelector('.el-table').cloneNode(true);
tables.removeChild(tables.querySelector('.el-table__fixed')); const fix = tables.querySelector('.el-table__fixed');
} const fixRight = tables.querySelector('.el-table__fixed-right');
if (fixRight) { if (fix) {
tables.removeChild(tables.querySelector('.el-table__fixed-right')); tables.removeChild(tables.querySelector('.el-table__fixed'));
} }
let exportTable = XLSX.utils.table_to_book(tables); if (fixRight) {
tables.removeChild(tables.querySelector('.el-table__fixed-right'));
}
let exportTable = XLSX.utils.table_to_book(tables);
var exportTableOut = XLSX.write(exportTable, { var exportTableOut = XLSX.write(exportTable, {
bookType: 'xlsx', bookType: 'xlsx',
bookSST: true, bookSST: true,
type: 'array', type: 'array',
}); });
// sheetjs.xlsx为导出表格的标题名称 // sheetjs.xlsx为导出表格的标题名称
try { try {
FileSaver.saveAs( FileSaver.saveAs(
new Blob([exportTableOut], { new Blob([exportTableOut], {
type: 'application/octet-stream', type: 'application/octet-stream',
}), }),
this.fileName + '产线自动报表.xlsx' this.fileName + '产线自动报表.xlsx'
); );
} catch (e) { } catch (e) {
if (typeof console !== 'undefined') console.log(e, exportTableOut); if (typeof console !== 'undefined') console.log(e, exportTableOut);
} }
return exportTableOut; return exportTableOut;
}, },
getPdLineList() { getPdLineList() {
getPdList().then((res) => { // getPLlistByFactory().then((res) => {
this.formConfig[1].selectOptions = res.data || []; // this.formConfig[1].selectOptions = res.data || [];
}); // });
const params = { const params = {
pageSize: 100, pageSize: 100,
pageNo: 1, pageNo: 1,
}; };
getFactoryPage(params).then((res) => { getFactoryPage(params).then((res) => {
this.formConfig[0].selectOptions = res.data.list || []; this.formConfig[0].selectOptions = res.data.list || [];
}); });
}, },
buttonClick(val) { buttonClick(val) {
switch (val.btnName) { switch (val.btnName) {
case 'search': case 'search':
this.listQuery.factoryId = val.factoryId || undefined; this.listQuery.factoryId = val.factoryId || undefined;
this.listQuery.lineId = val.lineId ? val.lineId : []; this.listQuery.lineId = val.lineId ? val.lineId : [];
this.listQuery.startTime = val.timeVal ? val.timeVal[0] : undefined; this.listQuery.startTime = val.timeVal ? val.timeVal[0] : undefined;
this.listQuery.endTime = val.timeVal ? val.timeVal[1] : undefined; this.listQuery.endTime = val.timeVal ? val.timeVal[1] : undefined;
this.getDataList(); this.getDataList();
break; break;
case 'export': case 'export':
this.handleExport(); this.handleExport();
break; break;
default: default:
console.log(val); console.log(val);
} }
}, },
// 获取数据列表 // 获取数据列表
getDataList() { getDataList() {
this.dataListLoading = true; this.dataListLoading = true;
this.urlOptions.getDataListURL(this.listQuery).then((response) => { this.urlOptions.getDataListURL(this.listQuery).then((response) => {
this.tableData = response.data; this.tableData = response.data;
this.dataListLoading = false; this.dataListLoading = false;
this.showData = this.tableData; this.showData = this.tableData;
}); });
getNewCTCharts(this.listQuery).then((response) => { getNewCTCharts(this.listQuery).then((response) => {
this.chartData = response.data; this.chartData = response.data;
}); });
}, },
handleSearchBarChanged({ param, value }) { handleSearchBarChanged({ param, value }) {
this.listQuery.lineId = []; this.listQuery.lineId = [];
this.$refs.searchBarForm.formInline.lineId = undefined; this.$refs.searchBarForm.formInline.lineId = undefined;
getPdList(value).then((res) => { getPLlistByFactory({ factoryIds: this.$refs.searchBarForm.formInline.factoryId }).then((res) => {
this.formConfig[1].selectOptions = res.data || []; this.formConfig[1].selectOptions = res.data || [];
}); });
}, },
handleClick(val) { handleClick(val) {
this.addOrUpdateVisible = true; this.addOrUpdateVisible = true;
this.addOrEditTitle = this.addOrEditTitle =
val.data?.factoryName + '-' + val.data?.lineName + ' 详情'; val.data?.factoryName + '-' + val.data?.lineName + ' 详情';
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.eqDetail.init( this.$refs.eqDetail.init(
val.data.lineId, val.data.lineId,
this.listQuery.startTime, this.listQuery.startTime,
this.listQuery.endTime this.listQuery.endTime
); );
}); });
}, },
handleCancel() { handleCancel() {
this.addOrUpdateVisible = false; this.addOrUpdateVisible = false;
this.addOrEditTitle = ''; this.addOrEditTitle = '';
}, },
handleConfirm() { handleConfirm() {
this.handleCancel(); this.handleCancel();
}, },
}, },
}; };
</script> </script>

View File

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

View File

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

View File

@@ -12,19 +12,19 @@
<el-skeleton v-if="initing" :rows="6" animated /> <el-skeleton v-if="initing" :rows="6" animated />
<!-- :span-method="mergeColumnHandler" --> <!-- :span-method="mergeColumnHandler" -->
<div v-else :class="{ 'no-data-bg': !tableData || tableData.length == 0 }"> <div v-else :class="{ 'no-data-bg': !tableData || tableData.length == 0 }">
<base-table <base-table
v-if="tableData && tableData.length > 0" v-if="tableData && tableData.length > 0"
:table-props="tableProps" :table-props="tableProps"
:table-data="tableData" :table-data="tableData"
:max-height="tableH" :max-height="tableH"
@emitFun="handleEmitFun" :span-method="mergeColumnHandler"
/> @emitFun="handleEmitFun" />
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { getPdlDataOneDay } from '@/api/core/monitoring/data24' import { getSectionDataOneDay } from '@/api/core/monitoring/data24';
import tableHeightMixin from '@/mixins/lb/tableHeightMixin'; import tableHeightMixin from '@/mixins/lb/tableHeightMixin';
export default { export default {
@@ -34,8 +34,8 @@ export default {
mixins: [tableHeightMixin], mixins: [tableHeightMixin],
data() { data() {
return { return {
urlOptions: { urlOptions: {
getDataListURL: getPdlDataOneDay getDataListURL: getSectionDataOneDay,
}, },
initing: false, initing: false,
queryParams: { queryParams: {
@@ -45,6 +45,7 @@ export default {
list: [], list: [],
arr: [], arr: [],
spanArr: [], spanArr: [],
spanArrProLine: [], // 产线合并行数组
timeList: [], timeList: [],
tableData: [], tableData: [],
tableProps: [], tableProps: [],
@@ -58,78 +59,130 @@ export default {
methods: { methods: {
/** 构建tableProps - 依据第一个元素所提供的信息 */ /** 构建tableProps - 依据第一个元素所提供的信息 */
buildProps(plData) { buildProps(plData) {
plData.forEach(item => { this.timeList = []; // 重置避免重复
this.timeList.push(item.name) this.arr = []; // 重置避免重复
}) plData.forEach((item) => {
const timeArray = Array.from(new Set(this.timeList)) this.timeList.push(item.name);
console.log('nihc', timeArray) });
const timeArray = Array.from(new Set(this.timeList));
for (const times of timeArray) { for (const times of timeArray) {
if (times !== '投入数' && times !== '产出数' && times !== '报废数量' && times !== '产出面积') { if (!['投入数', '产出数', '报废数量', '报废比例'].includes(times)) {
const subprop = { const subprop = {
label: times.slice(0, 10) + ' ' + times.slice(11), label: `${times.slice(0, 10)} ${times.slice(11)}`,
align: 'center', align: 'center',
children: [ children: [
{ prop: times + '_in', label: '投入数量', {
filter: (val) => (val != null ? val.toFixed(2) : '-'), }, prop: `${times}_in`,
{ prop: times + '_out', label: '产出数量', label: '投入数',
filter: (val) => (val != null ? val.toFixed(2) : '-'), }, filter: (val) => (val != null ? val : '-'),
{ prop: times + '_junk', label: '报废数量', },
filter: (val) => (val != null ? val.toFixed(2) : '-'), }, {
{ prop: times + '_area', label: '产出面积', prop: `${times}_out`,
filter: (val) => (val != null ? val.toFixed(2) : '-'), } label: '产出数',
] filter: (val) => (val != null ? val : '-'),
} },
this.arr.push(subprop) {
prop: `${times}_junk`,
label: '报废数量',
filter: (val) => (val != null ? val : '-'),
},
{
prop: `${times}_area`,
label: '报废比例',
filter: (val) => (val != null ? val.toFixed(2) : '-'),
},
],
};
this.arr.push(subprop);
} }
} }
this.tableProps = this.arr this.tableProps = [
{
prop: 'proLineName',
label: '产线',
fixed: 'left',
width: 120,
showOverflowTooltip: true,
},
{
prop: 'spec',
label: '产品规格',
fixed: 'left',
width: 110,
showOverflowTooltip: true,
filter: (val) => val != null ? val.join(' / ') : '-',
},
{
prop: 'sectionName',
label: '工段名称',
fixed: 'left',
width: 120,
showOverflowTooltip: true,
},
{
prop: 'allNum',
label: '生产总数',
fixed: 'left',
showOverflowTooltip: true,
},
...this.arr
];
},
/** 通用合并行计算方法 */
calcRowSpan(arr) {
const spanArr = [];
let count = 0;
arr.forEach((item, index) => {
if (index === 0) {
spanArr.push(1);
} else {
if (item === arr[index - 1]) {
spanArr[count] += 1;
spanArr.push(0);
} else {
count = index;
spanArr.push(1);
}
}
});
return spanArr;
}, },
setRowSpan(arr) {
let count = 0
arr.forEach((item, index) => {
if(index === 0) {
this.spanArr.push(1)
} else {
if (item === arr[index - 1]) {
this.spanArr[count] += 1
this.spanArr.push(0)
} else {
count = index
this.spanArr.push(1)
}
}
})
console.log('打印数组长度', this.spanArr)
},
/** 把 list 里的数据转换成 tableProps 对应的格式 */ /** 把 list 里的数据转换成 tableProps 对应的格式 */
convertList(list) { convertList(list) {
let sectionArr= [] this.tableData = []; // 重置避免重复
console.log('打印看下数据list', list) const proLineNames = []; // 存储产线名称用于计算合并
list.forEach((ele, index) => { const specNames = []; // 存储规格名称用于计算合并
let tempData = []
ele.data.forEach(item => { list.forEach((ele) => {
item.children.forEach(params => { const tempData = {};
if (params.dynamicName === '投入数量') { // 基础字段赋值
tempData[item.dynamicName + '_in'] = params.dynamicValue tempData.proLineName = ele.proLineName;
} else if (params.dynamicName === '产出数量') { tempData.spec = ele.spec;
tempData[item.dynamicName + '_out'] = params.dynamicValue tempData.sectionName = ele.sectionName;
} else if (params.dynamicName === '报废数量') { tempData.allNum = ele.allNum;
tempData[item.dynamicName + '_junk'] = params.dynamicValue
} else { // 动态字段赋值
tempData[item.dynamicName + '_area'] = params.dynamicValue ele.data.forEach((item) => {
} item.children.forEach((params) => {
}) const keyMap = {
}) '投入数': `${item.dynamicName}_in`,
tempData['proLineName'] = ele.proLineName '产出数': `${item.dynamicName}_out`,
tempData['spec'] = ele.spec '报废数量': `${item.dynamicName}_junk`,
this.tableData.push(tempData) '报废比例': `${item.dynamicName}_area`,
console.log('看看数据', this.tableData, tempData) };
const { proLineName } = tempData const key = keyMap[params.dynamicName];
sectionArr.push(proLineName) if (key) tempData[key] = params.dynamicValue;
}) });
this.setRowSpan(sectionArr) });
console.log('工段名称列表', sectionArr)
this.tableData.push(tempData);
proLineNames.push(ele.proLineName);
specNames.push(ele.spec);
});
// 计算合并行数组
this.spanArrProLine = this.calcRowSpan(proLineNames);
}, },
buildData(data) { buildData(data) {
@@ -138,38 +191,55 @@ export default {
/** 合并table列的规则 */ /** 合并table列的规则 */
mergeColumnHandler({ row, column, rowIndex, columnIndex }) { mergeColumnHandler({ row, column, rowIndex, columnIndex }) {
if (columnIndex == 0) { // 列索引0: 产线列, 列索引1: 产品规格列
if (this.spanArr[rowIndex]) { if (columnIndex === 0) {
return [ return {
this.spanArr[rowIndex], // row span rowspan: this.spanArrProLine[rowIndex] || 0,
1, // col span colspan: this.spanArrProLine[rowIndex] ? 1 : 0
]; };
} else {
return [0, 0];
}
} }
if (columnIndex === 1) {
return {
rowspan: this.spanArrProLine[rowIndex] || 0,
colspan: this.spanArrProLine[rowIndex] ? 1 : 0
};
}
// 其他列不合并
return { rowspan: 1, colspan: 1 };
}, },
async getList() { async getList() {
this.urlOptions.getDataListURL().then(res => { this.urlOptions.getDataListURL().then((res) => {
console.log('看看数据', res) console.log('看看数据', res);
this.arr = [ this.arr = [
{ {
prop: 'proLineName', prop: 'proLineName',
label: '产线', label: '产线',
fixed: 'left', fixed: 'left',
showOverflowTooltip: true showOverflowTooltip: true,
}, },
{ {
prop: 'spec', prop: 'spec',
label: '产品规格', label: '产品规格',
fixed: 'left', fixed: 'left',
showOverflowTooltip: true showOverflowTooltip: true,
} },
] {
prop: 'sectionName',
label: '工段名称',
fixed: 'left',
showOverflowTooltip: true,
},
{
prop: 'allNum',
label: '生产总数',
fixed: 'left',
showOverflowTooltip: true,
},
];
this.buildProps(res.data.nameData); this.buildProps(res.data.nameData);
this.buildData(res.data.data); this.buildData(res.data.data);
}) });
// // const data = this.res.data; // // const data = this.res.data;
// // console.log('recent-24', data); // // console.log('recent-24', data);

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,275 @@
<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 {
tableProps: [
{ 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: '规格' },
],
searchBarFormConfig: [
{
type: 'select',
label: '工厂',
selectOptions: [],
param: 'factoryId',
},
{
type: 'select',
label: '产线',
selectOptions: [],
multiple: true,
param: 'lineId',
},
{
type: 'input',
label: '缺陷数>',
// selectOptions: [],
param: 'checkNum',
},
{
type: 'datePicker',
label: '时间范围',
dateType: 'datetimerange',
format: 'yyyy-MM-dd HH:mm:ss',
valueFormat: 'timestamp',
rangeSeparator: '-',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
param: 'timeVal',
width: 350,
defaultSelect: [],
},
{
type: 'button',
btnName: '查询',
name: 'search',
color: 'primary',
},
{
type: 'separate',
},
{
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
// },
// },
mounted() {
const { startTimestamp, endTimestamp } = this.getThreeDaysAgoThisTimeToNowTimeStamps();
// 找到时间范围的配置项并赋值对应你代码中的timeVal参数
this.searchBarFormConfig[3].defaultSelect = [startTimestamp, endTimestamp]; // 赋值给日期选择器
this.queryParams.startTime = startTimestamp;
this.queryParams.endTime = endTimestamp;
this.getList();
this.getDict()
},
methods: {
getThreeDaysAgoThisTimeToNowTimeStamps() {
const now = new Date();
// 1. 计算三天前的当前时刻使用setDate直接修改日期保留时分秒等信息
const threeDaysAgoThisTime = new Date(now); // 复制当前日期对象,避免修改原对象
threeDaysAgoThisTime.setDate(threeDaysAgoThisTime.getDate() - 3); // 日期减3天时分秒保持和当前一致
// 2. 获取时间戳(毫秒级和秒级)
// 开始时间戳:三天前的当前时刻
const startTimestamp = threeDaysAgoThisTime.getTime(); // 毫秒级
const startTimestampSec = Math.floor(startTimestamp / 1000); // 秒级
// 结束时间戳:当前时刻
const endTimestamp = now.getTime(); // 毫秒级
const endTimestampSec = Math.floor(endTimestamp / 1000); // 秒级
// 封装日期格式化函数转换为yyyy-MM-dd HH:mm:ss格式
const formatDateTime = (date) => {
const y = date.getFullYear();
// 月份是从0开始的所以要+1补零确保是两位
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
const h = String(date.getHours()).padStart(2, '0');
const min = String(date.getMinutes()).padStart(2, '0');
const s = String(date.getSeconds()).padStart(2, '0');
return `${y}-${m}-${d} ${h}:${min}:${s}`;
};
// 格式化后的字符串:三天前的当前时刻 和 当前时刻
const startDateTimeStr = formatDateTime(threeDaysAgoThisTime);
const endDateTimeStr = formatDateTime(now);
return {
startTimestamp, // 三天前当前时刻的毫秒级时间戳
endTimestamp, // 当前时刻的毫秒级时间戳
startTimestampSec, // 三天前当前时刻的秒级时间戳
endTimestampSec, // 当前时刻的秒级时间戳
startDateTimeStr, // yyyy-MM-dd HH:mm:ss格式的开始时间字符串
endDateTimeStr // yyyy-MM-dd HH:mm:ss格式的结束时间字符串
};
},
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
this.getList();
break;
case 'add':
this.handleAdd();
break;
case 'export':
this.handleExport();
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,606 @@
<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,
defaultSelect: [],
},
{
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
// },
// },
mounted() {
const { startTimestamp, endTimestamp } = this.getThreeDaysAgoThisTimeToNowTimeStamps();
// 找到时间范围的配置项并赋值对应你代码中的timeVal参数
this.searchBarFormConfig[2].defaultSelect = [startTimestamp, endTimestamp]; // 赋值给日期选择器
this.queryParams.startTime = startTimestamp;
this.queryParams.endTime = endTimestamp;
this.getList();
this.getDict()
},
methods: {
getThreeDaysAgoThisTimeToNowTimeStamps() {
const now = new Date();
// 1. 计算三天前的当前时刻使用setDate直接修改日期保留时分秒等信息
const threeDaysAgoThisTime = new Date(now); // 复制当前日期对象,避免修改原对象
threeDaysAgoThisTime.setDate(threeDaysAgoThisTime.getDate() - 3); // 日期减3天时分秒保持和当前一致
// 2. 获取时间戳(毫秒级和秒级)
// 开始时间戳:三天前的当前时刻
const startTimestamp = threeDaysAgoThisTime.getTime(); // 毫秒级
const startTimestampSec = Math.floor(startTimestamp / 1000); // 秒级
// 结束时间戳:当前时刻
const endTimestamp = now.getTime(); // 毫秒级
const endTimestampSec = Math.floor(endTimestamp / 1000); // 秒级
// 封装日期格式化函数转换为yyyy-MM-dd HH:mm:ss格式
const formatDateTime = (date) => {
const y = date.getFullYear();
// 月份是从0开始的所以要+1补零确保是两位
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
const h = String(date.getHours()).padStart(2, '0');
const min = String(date.getMinutes()).padStart(2, '0');
const s = String(date.getSeconds()).padStart(2, '0');
return `${y}-${m}-${d} ${h}:${min}:${s}`;
};
// 格式化后的字符串:三天前的当前时刻 和 当前时刻
const startDateTimeStr = formatDateTime(threeDaysAgoThisTime);
const endDateTimeStr = formatDateTime(now);
return {
startTimestamp, // 三天前当前时刻的毫秒级时间戳
endTimestamp, // 当前时刻的毫秒级时间戳
startTimestampSec, // 三天前当前时刻的秒级时间戳
endTimestampSec, // 当前时刻的秒级时间戳
startDateTimeStr, // yyyy-MM-dd HH:mm:ss格式的开始时间字符串
endDateTimeStr // yyyy-MM-dd HH:mm:ss格式的结束时间字符串
};
},
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,462 @@
<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,
defaultSelect: [],
},
{
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
// },
// },
mounted() {
const { startTimestamp, endTimestamp } = this.getThreeDaysAgoThisTimeToNowTimeStamps();
// 找到时间范围的配置项并赋值对应你代码中的timeVal参数
this.searchBarFormConfig[2].defaultSelect = [startTimestamp, endTimestamp]; // 赋值给日期选择器
this.queryParams.startTime = startTimestamp;
this.queryParams.endTime = endTimestamp;
this.getList();
this.getDict()
},
methods: {
getThreeDaysAgoThisTimeToNowTimeStamps() {
const now = new Date();
// 1. 计算三天前的当前时刻使用setDate直接修改日期保留时分秒等信息
const threeDaysAgoThisTime = new Date(now); // 复制当前日期对象,避免修改原对象
threeDaysAgoThisTime.setDate(threeDaysAgoThisTime.getDate() - 3); // 日期减3天时分秒保持和当前一致
// 2. 获取时间戳(毫秒级和秒级)
// 开始时间戳:三天前的当前时刻
const startTimestamp = threeDaysAgoThisTime.getTime(); // 毫秒级
const startTimestampSec = Math.floor(startTimestamp / 1000); // 秒级
// 结束时间戳:当前时刻
const endTimestamp = now.getTime(); // 毫秒级
const endTimestampSec = Math.floor(endTimestamp / 1000); // 秒级
// 封装日期格式化函数转换为yyyy-MM-dd HH:mm:ss格式
const formatDateTime = (date) => {
const y = date.getFullYear();
// 月份是从0开始的所以要+1补零确保是两位
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
const h = String(date.getHours()).padStart(2, '0');
const min = String(date.getMinutes()).padStart(2, '0');
const s = String(date.getSeconds()).padStart(2, '0');
return `${y}-${m}-${d} ${h}:${min}:${s}`;
};
// 格式化后的字符串:三天前的当前时刻 和 当前时刻
const startDateTimeStr = formatDateTime(threeDaysAgoThisTime);
const endDateTimeStr = formatDateTime(now);
return {
startTimestamp, // 三天前当前时刻的毫秒级时间戳
endTimestamp, // 当前时刻的毫秒级时间戳
startTimestampSec, // 三天前当前时刻的秒级时间戳
endTimestampSec, // 当前时刻的秒级时间戳
startDateTimeStr, // yyyy-MM-dd HH:mm:ss格式的开始时间字符串
endDateTimeStr // yyyy-MM-dd HH:mm:ss格式的结束时间字符串
};
},
getDict() {
getPdList().then(res => {
this.searchBarFormConfig[1].selectOptions = res.data || [];
});
getFactoryPage({ pageSize: 100, pageNo: 1 }).then(res => {
this.searchBarFormConfig[0].selectOptions = res.data.list || [];
});
},
/** base table related */
handleTableBtnClick({ data, type }) {
switch (type) {
case 'edit':
this.handleUpdate(data);
break;
case 'delete':
this.handleDelete(data);
break;
}
},
/** search bar related */
handleSearchBarBtnClick(btn) {
switch (btn.btnName) {
case 'search':
this.queryParams.lineId = btn.lineId ? btn.lineId : undefined
this.queryParams.factoryId = btn.factoryId ? btn.factoryId : undefined
this.queryParams.startTime = btn.timeVal ? btn.timeVal[0] : undefined
this.queryParams.endTime = btn.timeVal ? btn.timeVal[1] : undefined
this.getList();
break;
case 'add':
this.handleAdd();
break;
case 'export':
this.handleExport();
break;
case 'reset':
this.$refs['search-bar'].resetForm();
this.resetQuery();
break;
}
},
/** 查询列表 */
getList() {
this.loading = true;
// 执行查询
getSectionDefect(this.queryParams).then((response) => {
this.list = response.data;
// 定义颜色数组,与你提供的顺序一致
const colors = [
'rgba(99, 189, 255, 1)',
'rgba(113, 100, 255, 1)',
'rgba(255, 104, 96, 1)',
'rgba(255, 151, 71, 1)',
'rgba(176, 235, 66, 1)',
'rgba(214, 128, 255, 1)',
'rgba(0, 67, 210, 1)'
];
const series = response.data.map((item, index) => {
// 为每个系列分配颜色index 从 0 开始,依次对应 colors 数组
const color = colors[index % colors.length]; // 使用取模确保不越界
return {
name: item.lineName,
type: 'bar',
stack: 'Ad',
barWidth: '20',
emphasis: {
focus: 'series'
},
itemStyle: {
color: color // 指定当前系列的颜色
},
data: [
item.originalNgCount,
item.edgeNgCount,
item.silkNgCount,
item.drillNgCount,
item.coatingNgCount,
item.packNgCount
]
};
});
this.loading = false;
this.getChart(series);
});
},
getChart(series) {
var chartDom = document.getElementById('chart');
var myChart = echarts.init(chartDom);
var option;
option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
grid: {
left: 30,
top: 20,
right: 30,
bottom:20
},
legend: {},
xAxis: [
{
type: 'category',
data: ['原片不良', '磨边不良', '丝印不良', '打孔不良', '镀膜不良', ' 钢包不良']
}
],
yAxis: [
{
type: 'value'
}
],
series: series
};
option && myChart.setOption(option);
},
/** 导出按钮操作 */
handleExport() {
// 处理查询参数
let tables = document.querySelector('.el-table').cloneNode(true)
const fix = tables.querySelector('.el-table__fixed')
const fixRight = tables.querySelector('.el-table__fixed-right')
if (fix) {
tables.removeChild(tables.querySelector('.el-table__fixed'))
}
if (fixRight) {
tables.removeChild(tables.querySelector('.el-table__fixed-right'))
}
let exportTable = XLSX.utils.table_to_book(tables)
var exportTableOut = XLSX.write(exportTable, {
bookType: 'xlsx', bookSST: true, type: 'array'
})
// sheetjs.xlsx为导出表格的标题名称
try {
FileSaver.saveAs(new Blob([exportTableOut], {
type: 'application/octet-stream'
}), '工段不良.xlsx')
} catch (e) {
if (typeof console !== 'undefined') console.log(e, exportTableOut)
}
return exportTableOut
},
},
};
</script>
<style scoped>
/* 添加 scoped 以避免样式污染 */
.app-container {
padding: 20px;
}
.content-wrapper {
display: flex;
flex-direction: column;
/* 垂直排列 */
gap: 20px;
}
.table-container,
.chart-container {
flex: 1;
/* 让两个容器平分父容器的空间 */
min-height: 300px;
/* 设置最小高度,防止内容过少时变形 */
border: 1px solid #ebeef5;
/* 添加一个边框,方便看清分隔 */
border-radius: 4px;
padding: 10px;
background-color: #fff;
}
/* 确保图表容器的父元素也有高度ECharts 才能正确渲染 */
.chart-container {
display: flex;
flex-direction: column;
}
</style>

View File

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