add:增加看板及报表

This commit is contained in:
zhangzq
2026-03-09 17:12:50 +08:00
parent c6c783712a
commit 5c4ac88b22
43 changed files with 3471 additions and 4 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

View File

@@ -170,7 +170,7 @@
@selection-change="crud.selectionChangeHandler"
>
<el-table-column type="selection" width="55" />
<el-table-column :label="$t('menu.table.menu_title')" :prop="$langPre.computedProp('title')" :min-width="flexWidth($langPre.computedProp('title'),crud.data,$t('menu.table.menu_title'))" />
<el-table-column :label="$t('menu.table.menu_title')" prop="title" :min-width="150" />
<el-table-column :label="$t('menu.table.system')" prop="system_type" :min-width="flexWidth('system_type',crud.data,$t('menu.table.system'))">
<template slot-scope="scope">
{{ dict.label.system_type[scope.row.system_type] }} : {{ scope.row.system_type }}

View File

@@ -0,0 +1,20 @@
import request from '@/utils/request'
export function query(params) {
return request({
url: 'api/report/consumeReport',
method: 'get',
params
})
}
export function downloadExcel(params) {
return request({
url: 'api/report/consumeReport/download',
method: 'get',
params,
responseType: 'blob'
})
}
export default { query, downloadExcel }

View File

@@ -0,0 +1,263 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<el-form
size="mini"
:inline="true"
class="demo-form-inline"
label-position="right"
label-width="80px"
label-suffix=":"
>
<el-form-item label="班组">
<el-input
v-model="query.groupCode"
clearable
placeholder="请输入班组编码"
style="width: 200px"
/>
</el-form-item>
<el-form-item label="物料">
<el-input
v-model="query.materialCode"
clearable
placeholder="编码、名称"
style="width: 200px"
/>
</el-form-item>
<el-form-item label="批次号">
<el-input
v-model="query.batchNo"
clearable
placeholder="请输入批次号"
style="width: 200px"
/>
</el-form-item>
<el-form-item label="开始时间">
<el-date-picker
v-model="query.startDate"
type="date"
placeholder="选择开始时间"
value-format="yyyy-MM-dd"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker
v-model="query.endDate"
type="date"
placeholder="选择结束时间"
value-format="yyyy-MM-dd"
clearable
style="width: 200px"
/>
</el-form-item>
<rrOperation />
</el-form>
</div>
<!--如果想在工具栏加入更多按钮可以使用插槽方式 slot = 'left' or 'right'-->
<crudOperation :permission="permission">
<el-button
slot="left"
class="filter-item"
size="mini"
type="success"
icon="el-icon-download"
:loading="downloadLoading"
@click="handleDownload"
>
导出EXCEL
</el-button>
</crudOperation>
<!--表格渲染-->
<el-table ref="table" border v-loading="crud.loading" :data="crud.data" size="mini" style="width: 100%;" @selection-change="crud.selectionChangeHandler">
<el-table-column type="selection" width="55" />
<el-table-column prop="rowNum" label="序号" width="80" align="center" />
<el-table-column prop="groupCode" label="班组编码" width="120" />
<el-table-column prop="groupName" label="班组名称" width="120" />
<el-table-column prop="materialCode" label="物料编码" width="150" />
<el-table-column prop="materialName" label="物料名称" />
<el-table-column prop="materialSpec" label="规格型号" />
<el-table-column prop="batchNo" label="批次" width="150" />
<el-table-column prop="receiveQty" label="领料重量" width="100" align="right" />
<el-table-column prop="returnQty" label="退料重量" width="100" align="right" />
<el-table-column prop="consumeQty" label="消耗重量" width="100" align="right" />
<el-table-column prop="qtyUnitName" label="单位" width="80" align="center" />
</el-table>
<!--分页组件-->
<pagination />
</div>
</div>
</template>
<script>
import crudConsumeReport from '@/views/wms/biBoard/consumeReport/consumeReport'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import pagination from '@crud/Pagination'
const defaultForm = { }
export default {
name: 'ConsumeReport',
components: { pagination, crudOperation, rrOperation },
mixins: [presenter(), header(), form(defaultForm), crud()],
cruds() {
return CRUD({
title: '班组焊材消耗报表',
url: 'api/report/consumeReport',
idField: 'rowNum',
sort: '',
query: { groupCode: '', materialCode: '', batchNo: '', startDate: '', endDate: '' },
crudMethod: { ...crudConsumeReport },
optShow: {
add: false,
edit: false,
del: false,
download: false,
reset: true
}
})
},
data() {
return {
permission: {},
downloadLoading: false
}
},
methods: {
handleDownload() {
this.downloadLoading = true
const params = {
groupCode: this.query.groupCode || '',
materialCode: this.query.materialCode || '',
batchNo: this.query.batchNo || '',
startDate: this.query.startDate || '',
endDate: this.query.endDate || ''
}
crudConsumeReport.downloadExcel(params).then(res => {
const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = '班组焊材消耗报表_' + new Date().getTime() + '.xlsx'
link.click()
window.URL.revokeObjectURL(url)
this.$message.success('导出成功')
this.downloadLoading = false
}).catch(() => {
this.$message.error('导出失败')
this.downloadLoading = false
})
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep {
.vue-treeselect__menu {
overflow-x: auto !important;
width: 300px;
max-height: 300px !important;
}
.vue-treeselect__label {
overflow: unset;
text-overflow: unset;
}
.vue-treeselect__control {
height: 20px !important;
}
.vue-treeselect__multi-value-item-container,
.vue-treeselect--has-value .vue-treeselect__multi-value {
height: 30px;
line-height: 24px;
padding: 0;
}
.vue-treeselect__limit-tip,
.vue-treeselect--searchable.vue-treeselect--multi.vue-treeselect--has-value
.vue-treeselect__input-container {
padding-top: 0;
}
.vue-treeselect__placeholder,
.vue-treeselect__single-value {
height: 28px;
line-height: 32px;
font-size: small;
color: "#CCCFD6";
}
.vue-treeselect--has-value .vue-treeselect__input {
height: 18px !important;
line-height: 18px !important;
}
.vue-treeselect div,
.vue-treeselect span {
box-sizing: content-box;
}
.vue-treeselect__multi-value-label {
display: block;
width: 140px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.vue-treeselect__value-container {
display: block;
height: 32px;
}
}
</style>

View File

@@ -0,0 +1,263 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<el-form
size="mini"
:inline="true"
class="demo-form-inline"
label-position="right"
label-width="80px"
label-suffix=":"
>
<el-form-item label="班组">
<el-input
v-model="query.groupCode"
clearable
placeholder="请输入班组编码"
style="width: 200px"
/>
</el-form-item>
<el-form-item label="物料">
<el-input
v-model="query.materialCode"
clearable
placeholder="编码、名称"
style="width: 200px"
/>
</el-form-item>
<el-form-item label="批次号">
<el-input
v-model="query.batchNo"
clearable
placeholder="请输入批次号"
style="width: 200px"
/>
</el-form-item>
<el-form-item label="开始时间">
<el-date-picker
v-model="query.startDate"
type="date"
placeholder="选择开始时间"
value-format="yyyy-MM-dd"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker
v-model="query.endDate"
type="date"
placeholder="选择结束时间"
value-format="yyyy-MM-dd"
clearable
style="width: 200px"
/>
</el-form-item>
<rrOperation />
</el-form>
</div>
<!--如果想在工具栏加入更多按钮可以使用插槽方式 slot = 'left' or 'right'-->
<crudOperation :permission="permission">
<el-button
slot="left"
class="filter-item"
size="mini"
type="success"
icon="el-icon-download"
:loading="downloadLoading"
@click="handleDownload"
>
导出EXCEL
</el-button>
</crudOperation>
<!--表格渲染-->
<el-table ref="table" border v-loading="crud.loading" :data="crud.data" size="mini" style="width: 100%;" @selection-change="crud.selectionChangeHandler">
<el-table-column type="selection" width="55" />
<el-table-column prop="rowNum" label="序号" width="80" align="center" />
<el-table-column prop="confirmTime" label="领退时间" width="180" />
<el-table-column prop="groupCode" label="班组编码" width="120" />
<el-table-column prop="groupName" label="班组名称" width="120" />
<el-table-column prop="iosType" label="领退类型" width="100" align="center" />
<el-table-column prop="materialCode" label="物料编码" width="150" />
<el-table-column prop="materialName" label="物料名称" />
<el-table-column prop="materialSpec" label="规格型号" />
<el-table-column prop="batchNo" label="批次" width="150" />
<el-table-column prop="weight" label="重量" width="100" align="right" />
<el-table-column prop="qtyUnitName" label="单位" width="80" align="center" />
</el-table>
<!--分页组件-->
<pagination />
</div>
</div>
</template>
<script>
import crudIosReport from '@/views/wms/biBoard/iosReport/iosReport'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import pagination from '@crud/Pagination'
const defaultForm = { }
export default {
name: 'IosReport',
components: { pagination, crudOperation, rrOperation },
mixins: [presenter(), header(), form(defaultForm), crud()],
cruds() {
return CRUD({
title: '焊材领退明细报表',
url: 'api/iosReport',
idField: 'rowNum',
sort: '',
query: { groupCode: '', materialCode: '', batchNo: '', startDate: '', endDate: '' },
crudMethod: { ...crudIosReport },
optShow: {
add: false,
edit: false,
del: false,
download: false,
reset: true
}
})
},
data() {
return {
permission: {},
downloadLoading: false
}
},
methods: {
handleDownload() {
this.downloadLoading = true
const params = {
groupCode: this.query.groupCode || '',
materialCode: this.query.materialCode || '',
batchNo: this.query.batchNo || '',
startDate: this.query.startDate || '',
endDate: this.query.endDate || ''
}
crudIosReport.downloadExcel(params).then(res => {
const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = '焊材领退明细报表_' + new Date().getTime() + '.xlsx'
link.click()
window.URL.revokeObjectURL(url)
this.$message.success('导出成功')
this.downloadLoading = false
}).catch(() => {
this.$message.error('导出失败')
this.downloadLoading = false
})
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep {
.vue-treeselect__menu {
overflow-x: auto !important;
width: 300px;
max-height: 300px !important;
}
.vue-treeselect__label {
overflow: unset;
text-overflow: unset;
}
.vue-treeselect__control {
height: 20px !important;
}
.vue-treeselect__multi-value-item-container,
.vue-treeselect--has-value .vue-treeselect__multi-value {
height: 30px;
line-height: 24px;
padding: 0;
}
.vue-treeselect__limit-tip,
.vue-treeselect--searchable.vue-treeselect--multi.vue-treeselect--has-value
.vue-treeselect__input-container {
padding-top: 0;
}
.vue-treeselect__placeholder,
.vue-treeselect__single-value {
height: 28px;
line-height: 32px;
font-size: small;
color: "#CCCFD6";
}
.vue-treeselect--has-value .vue-treeselect__input {
height: 18px !important;
line-height: 18px !important;
}
.vue-treeselect div,
.vue-treeselect span {
box-sizing: content-box;
}
.vue-treeselect__multi-value-label {
display: block;
width: 140px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.vue-treeselect__value-container {
display: block;
height: 32px;
}
}
</style>

View File

@@ -0,0 +1,20 @@
import request from '@/utils/request'
export function query(params) {
return request({
url: 'api/iosReport',
method: 'get',
params
})
}
export function downloadExcel(params) {
return request({
url: 'api/iosReport/download',
method: 'get',
params,
responseType: 'blob'
})
}
export default { query, downloadExcel }

View File

@@ -0,0 +1,886 @@
<template>
<div class="screen-container">
<!-- 头部标题 -->
<div class="screen-header">
<div class="header-logo">
<img src="@/assets/images/logo.png" alt="logo" class="logo-img">
</div>
<div class="header-title">
哈电汽轮机镇江有限责任公司数字化焊材库智慧大屏
</div>
<div class="header-time">
<div class="time">{{ currentTime }}</div>
<div class="date">{{ currentDate }}</div>
</div>
</div>
<!-- 主体内容 -->
<div class="screen-content">
<!-- 左侧区域 - 库存信息 -->
<div class="left-panel">
<div class="panel-title">库存分类总览</div>
<div class="inventory-category">
<div class="category-item">
<div class="item-label">合格</div>
<div class="item-value">{{ inventoryData.category.hasGoodsCount || 0 }}</div>
</div>
<div class="category-item">
<div class="item-label">不合格</div>
<div class="item-value">0</div>
</div>
<div class="category-item">
<div class="item-label">待检</div>
<div class="item-value">0</div>
</div>
</div>
<div class="panel-title">焊材库存占比</div>
<div class="chart-container">
<div id="structChart" style="width: 100%; height: 280px;"></div>
</div>
<div class="panel-title">焊材领料Top5</div>
<div class="chart-container">
<div id="materialTop5Chart" style="width: 100%; height: 300px;"></div>
</div>
</div>
<!-- 中间区域 -->
<div class="center-panel">
<div class="center-left">
<!-- 3D仓库图 -->
<div class="warehouse-3d">
<img :src="warehouseImg" alt="仓库3D图" class="warehouse-img">
</div>
<!-- 底部监控区域 -->
<div class="bottom-monitor">
<!-- AGV监控 -->
<div class="agv-monitor">
<div class="monitor-title">AGV监控</div>
<div class="agv-list">
<div v-for="(agv, index) in displayAgvList" :key="index" class="agv-item">
<div class="agv-icon">
<img :src="getAgvIcon(agv.icon)" alt="AGV" class="agv-img">
</div>
<div class="agv-id">{{ agv.carId }}</div>
<div class="agv-status" :class="getAgvStatusClass(agv.status)">
{{ getAgvStatusText(agv.status) }}
</div>
<div class="agv-task">当前任务{{ agv.taskCode || '无' }}</div>
<div class="agv-battery">电量{{ agv.battery || 0 }}%</div>
</div>
</div>
</div>
<!-- 温湿度监控 -->
<div class="temp-monitor">
<div class="monitor-title">温湿度监控</div>
<div id="tempChart" style="width: 100%; height: 200px;"></div>
</div>
</div>
</div>
<div class="center-right">
<!-- 预留右侧区域 -->
</div>
</div>
<!-- 右侧区域 - 报表数据 -->
<div class="right-panel">
<div class="panel-title">
班组焊材领退明细
<el-radio-group v-model="reportType" size="mini" @change="handleReportTypeChange">
<el-radio-button label="today">当日</el-radio-button>
<el-radio-button label="week">本周</el-radio-button>
</el-radio-group>
</div>
<div class="detail-list scroll-list">
<div v-for="(item, index) in displayIosReportData" :key="index" class="detail-item">
<div class="detail-time">{{ item.confirmTime }}</div>
<div class="detail-type" :class="item.iosType === '领料' ? 'type-out' : 'type-in'">
{{ item.iosType }}
</div>
<div class="detail-material">{{ item.materialCode }}</div>
<div class="detail-weight">{{ item.weight }}</div>
<div class="detail-person">{{ item.groupName }}</div>
</div>
</div>
<div class="panel-title">班组焊材消耗</div>
<div class="chart-container">
<div id="consumeChart" style="width: 100%; height: 250px;"></div>
</div>
<div class="panel-title">当日领退明细</div>
<div class="detail-list scroll-list">
<div v-for="(item, index) in displayTodayDetailList" :key="index" class="detail-item">
<div class="detail-time">{{ item.confirmTime }}</div>
<div class="detail-type" :class="item.iosType === '领料' ? 'type-out' : 'type-in'">
{{ item.iosType }}
</div>
<div class="detail-material">{{ item.materialCode }}</div>
<div class="detail-weight">{{ item.weight }}</div>
<div class="detail-person">{{ item.groupName }}</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import * as echarts from 'echarts'
import request from '@/utils/request'
export default {
name: 'BiScreen',
data() {
return {
currentTime: '',
currentDate: '',
reportType: 'today',
warehouseImg: require('@/assets/images/boardImg.png'),
inventoryData: {
category: {},
top5: [],
structStat: {}
},
agvList: [],
tempData: [],
consumeReportData: [],
iosReportData: [],
todayDetailList: [],
materialTop5Data: [],
timer: null,
dataTimer: null,
scrollTimer1: null,
scrollTimer2: null,
scrollTimer3: null,
scrollIndex1: 0,
scrollIndex2: 0,
scrollIndex3: 0
}
},
mounted() {
this.updateTime()
this.timer = setInterval(this.updateTime, 1000)
this.loadAllData()
this.dataTimer = setInterval(this.loadAllData, 30000) // 30秒刷新一次
this.startScrolling()
},
beforeDestroy() {
if (this.timer) {
clearInterval(this.timer)
}
if (this.dataTimer) {
clearInterval(this.dataTimer)
}
if (this.scrollTimer1) {
clearInterval(this.scrollTimer1)
}
if (this.scrollTimer2) {
clearInterval(this.scrollTimer2)
}
if (this.scrollTimer3) {
clearInterval(this.scrollTimer3)
}
},
computed: {
displayIosReportData() {
if (this.iosReportData.length <= 5) {
return this.iosReportData
}
const result = []
for (let i = 0; i < 5; i++) {
const index = (this.scrollIndex1 + i) % this.iosReportData.length
result.push(this.iosReportData[index])
}
return result
},
displayTodayDetailList() {
if (this.todayDetailList.length <= 5) {
return this.todayDetailList
}
const result = []
for (let i = 0; i < 5; i++) {
const index = (this.scrollIndex2 + i) % this.todayDetailList.length
result.push(this.todayDetailList[index])
}
return result
},
displayAgvList() {
if (this.agvList.length <= 4) {
return this.agvList
}
const result = []
for (let i = 0; i < 4; i++) {
const index = (this.scrollIndex3 + i) % this.agvList.length
result.push(this.agvList[index])
}
return result
}
},
methods: {
updateTime() {
const now = new Date()
this.currentTime = now.toTimeString().slice(0, 8)
this.currentDate = now.toLocaleDateString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
weekday: 'long'
})
},
async loadAllData() {
await Promise.all([
this.loadInventoryData(),
this.loadAgvStatus(),
this.loadTempTrend(),
this.loadReportData(),
this.loadMaterialTop5()
])
},
async loadInventoryData() {
try {
const res = await request({
url: '/api/screen/inventory',
method: 'get'
})
this.inventoryData = res
this.$nextTick(() => {
this.initStructChart()
})
} catch (error) {
console.error('加载库存数据失败', error)
}
},
async loadAgvStatus() {
try {
const res = await request({
url: '/api/screen/agvStatus',
method: 'get'
})
this.agvList = res.agvList || []
} catch (error) {
console.error('加载AGV状态失败', error)
}
},
async loadTempTrend() {
try {
const res = await request({
url: '/api/screen/tempTrend',
method: 'get'
})
this.tempData = res.tempData || []
this.$nextTick(() => {
this.initTempChart()
})
} catch (error) {
console.error('加载温度数据失败', error)
}
},
async loadReportData() {
try {
const url = this.reportType === 'today' ? '/api/screen/todayReport' : '/api/screen/weekReport'
const res = await request({
url: url,
method: 'get'
})
this.consumeReportData = res.consumeReport || []
this.iosReportData = res.iosReport || []
this.todayDetailList = res.iosReport || []
this.scrollIndex1 = 0
this.scrollIndex2 = 0
this.$nextTick(() => {
this.initConsumeChart()
})
} catch (error) {
console.error('加载报表数据失败', error)
}
},
async loadMaterialTop5() {
try {
const res = await request({
url: '/api/screen/materialTop5',
method: 'get'
})
this.materialTop5Data = res.top5 || []
this.$nextTick(() => {
this.initMaterialTop5Chart()
})
} catch (error) {
console.error('加载焊材Top5数据失败', error)
}
},
handleReportTypeChange() {
this.loadReportData()
},
startScrolling() {
// 班组焊材领退明细滚动
this.scrollTimer1 = setInterval(() => {
if (this.iosReportData.length > 5) {
this.scrollIndex1 = (this.scrollIndex1 + 1) % this.iosReportData.length
}
}, 3000)
// 当日领退明细滚动
this.scrollTimer2 = setInterval(() => {
if (this.todayDetailList.length > 5) {
this.scrollIndex2 = (this.scrollIndex2 + 1) % this.todayDetailList.length
}
}, 3000)
// AGV监控滚动
this.scrollTimer3 = setInterval(() => {
if (this.agvList.length > 4) {
this.scrollIndex3 = (this.scrollIndex3 + 1) % this.agvList.length
}
}, 3000)
},
getAgvIcon(icon) {
if (!icon) {
return require('@/assets/images/agv.png')
}
try {
return require(`@/assets/images/${icon}`)
} catch (e) {
return require('@/assets/images/agv.png')
}
},
getAgvStatusClass(status) {
if (!status) return 'status-idle'
if (status.status === '正常') return 'status-normal'
if (status.status === '故障') return 'status-error'
return 'status-idle'
},
getAgvStatusText(status) {
return status ? status.status || '空闲' : '空闲'
},
initStructChart() {
const chart = echarts.init(document.getElementById('structChart'))
const data = this.inventoryData.structStat || {}
const option = {
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
right: 10,
top: 'center',
textStyle: {
color: '#fff'
}
},
series: [
{
type: 'pie',
radius: ['40%', '70%'],
center: ['40%', '50%'],
data: [
{ value: data.hasGoods || 0, name: '有货', itemStyle: { color: '#5470c6' } },
{ value: data.emptyBox || 0, name: '空料箱', itemStyle: { color: '#91cc75' } },
{ value: data.noGoods || 0, name: '无货', itemStyle: { color: '#fac858' } }
],
label: {
color: '#fff'
}
}
]
};
chart.setOption(option)
},
initTempChart() {
const chart = echarts.init(document.getElementById('tempChart'))
const times = this.tempData.map(item => item.time)
const temps = this.tempData.map(item => item.temperature)
const option = {
tooltip: {
trigger: 'axis'
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '10%',
containLabel: true
},
xAxis: {
type: 'category',
data: times,
axisLine: { lineStyle: { color: '#fff' } },
axisLabel: { color: '#fff' }
},
yAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#fff' } },
axisLabel: { color: '#fff' },
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } }
},
series: [
{
data: temps,
type: 'line',
smooth: true,
itemStyle: { color: '#ee6666' },
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(238, 102, 102, 0.5)' },
{ offset: 1, color: 'rgba(238, 102, 102, 0.1)' }
])
}
}
]
}
chart.setOption(option)
},
initConsumeChart() {
const chart = echarts.init(document.getElementById('consumeChart'))
const groups = this.consumeReportData.map(item => item.groupName)
const consumeQty = this.consumeReportData.map(item => item.consumeQty)
const option = {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' }
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '10%',
containLabel: true
},
xAxis: {
type: 'category',
data: groups,
axisLine: { lineStyle: { color: '#fff' } },
axisLabel: { color: '#fff', rotate: 30 }
},
yAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#fff' } },
axisLabel: { color: '#fff' },
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } }
},
series: [
{
data: consumeQty,
type: 'bar',
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#83bff6' },
{ offset: 1, color: '#188df0' }
])
},
barWidth: '60%'
}
]
}
chart.setOption(option)
},
initMaterialTop5Chart() {
const chart = echarts.init(document.getElementById('materialTop5Chart'))
const materials = this.materialTop5Data.map(item => item.materialName)
const qtys = this.materialTop5Data.map(item => item.totalQty)
const option = {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' }
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '10%',
containLabel: true
},
xAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#fff' } },
axisLabel: { color: '#fff' },
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } }
},
yAxis: {
type: 'category',
data: materials,
axisLine: { lineStyle: { color: '#fff' } },
axisLabel: { color: '#fff' }
},
series: [
{
data: qtys,
type: 'bar',
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#fac858' },
{ offset: 1, color: '#ee6666' }
])
},
barWidth: '60%'
}
]
}
chart.setOption(option)
}
}
}
</script>
<style lang="scss" scoped>
.screen-container {
width: 100vw;
height: 100vh;
background: linear-gradient(to bottom, #0a1e3e, #0f2847);
color: #fff;
overflow: hidden;
font-family: 'Microsoft YaHei', Arial, sans-serif;
}
.screen-header {
height: 80px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 30px;
background: rgba(0, 0, 0, 0.3);
border-bottom: 2px solid #1e90ff;
.header-logo {
width: 200px;
.logo-img {
height: 50px;
}
}
.header-title {
flex: 1;
text-align: center;
font-size: 32px;
font-weight: bold;
color: #00d4ff;
text-shadow: 0 0 10px rgba(0, 212, 255, 0.8);
}
.header-time {
width: 200px;
text-align: right;
.time {
font-size: 28px;
font-weight: bold;
color: #00d4ff;
}
.date {
font-size: 14px;
color: #8ab4f8;
margin-top: 5px;
}
}
}
.screen-content {
display: flex;
height: calc(100vh - 80px);
padding: 20px;
gap: 20px;
}
.left-panel,
.right-panel {
flex: 0 0 25%;
background: rgba(0, 30, 60, 0.6);
border: 1px solid rgba(30, 144, 255, 0.3);
border-radius: 8px;
padding: 20px;
overflow-y: auto;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: rgba(30, 144, 255, 0.5);
border-radius: 3px;
}
}
.center-panel {
flex: 1;
display: flex;
gap: 20px;
}
.center-left {
flex: 0 0 50%;
display: flex;
flex-direction: column;
gap: 20px;
}
.center-right {
flex: 1;
}
.panel-title {
font-size: 18px;
font-weight: bold;
color: #00d4ff;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid rgba(30, 144, 255, 0.3);
display: flex;
justify-content: space-between;
align-items: center;
}
.inventory-category {
display: flex;
justify-content: space-around;
margin-bottom: 30px;
.category-item {
text-align: center;
.item-label {
font-size: 14px;
color: #8ab4f8;
margin-bottom: 10px;
}
.item-value {
font-size: 32px;
font-weight: bold;
color: #00d4ff;
}
}
}
.chart-container {
margin-bottom: 30px;
}
.warehouse-3d {
flex: 2;
background: rgba(0, 30, 60, 0.6);
border: 1px solid rgba(30, 144, 255, 0.3);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
.warehouse-img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
}
.bottom-monitor {
flex: 1;
display: flex;
gap: 20px;
}
.agv-monitor,
.temp-monitor {
flex: 1;
background: rgba(0, 30, 60, 0.6);
border: 1px solid rgba(30, 144, 255, 0.3);
border-radius: 8px;
padding: 20px;
overflow: hidden;
}
.monitor-title {
font-size: 18px;
font-weight: bold;
color: #00d4ff;
margin-bottom: 15px;
}
.agv-list {
overflow: hidden;
}
.agv-item {
display: flex;
align-items: center;
gap: 10px;
background: rgba(0, 50, 100, 0.4);
border: 1px solid rgba(30, 144, 255, 0.2);
border-radius: 4px;
padding: 6px 10px;
margin-bottom: 6px;
transition: all 0.5s ease;
.agv-icon {
flex: 0 0 40px;
.agv-img {
width: 40px;
height: 25px;
object-fit: contain;
}
}
.agv-id {
flex: 0 0 60px;
font-size: 13px;
font-weight: bold;
color: #fff;
}
.agv-status {
flex: 0 0 60px;
font-size: 12px;
text-align: center;
padding: 2px 6px;
border-radius: 3px;
background: rgba(255, 255, 255, 0.1);
&.status-normal {
color: #52c41a;
background: rgba(82, 196, 26, 0.2);
}
&.status-error {
color: #ff4d4f;
background: rgba(255, 77, 79, 0.2);
}
&.status-idle {
color: #faad14;
background: rgba(250, 173, 20, 0.2);
}
}
.agv-task {
flex: 1;
font-size: 11px;
color: #8ab4f8;
}
.agv-battery {
flex: 0 0 80px;
font-size: 11px;
color: #00d4ff;
text-align: right;
font-weight: bold;
}
}
.report-table {
margin-bottom: 30px;
::v-deep .el-table {
background: transparent;
color: #fff;
th, td {
background: transparent;
border-color: rgba(30, 144, 255, 0.2);
color: #fff;
}
th {
background: rgba(0, 50, 100, 0.4);
}
tr:hover > td {
background: rgba(0, 100, 200, 0.2);
}
}
}
.detail-list {
&.scroll-list {
height: 300px;
overflow: hidden;
}
.detail-item {
display: flex;
align-items: center;
padding: 10px;
margin-bottom: 10px;
background: rgba(0, 50, 100, 0.4);
border: 1px solid rgba(30, 144, 255, 0.2);
border-radius: 4px;
font-size: 12px;
transition: all 0.5s ease;
.detail-time {
flex: 0 0 90px;
color: #8ab4f8;
}
.detail-type {
flex: 0 0 50px;
text-align: center;
padding: 2px 8px;
border-radius: 3px;
&.type-out {
background: rgba(255, 77, 79, 0.2);
color: #ff4d4f;
}
&.type-in {
background: rgba(82, 196, 26, 0.2);
color: #52c41a;
}
}
.detail-material {
flex: 1;
margin: 0 10px;
color: #fff;
}
.detail-weight {
flex: 0 0 60px;
text-align: right;
color: #00d4ff;
font-weight: bold;
}
.detail-person {
flex: 0 0 80px;
text-align: right;
color: #8ab4f8;
}
}
}
</style>

View File

@@ -0,0 +1,193 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<el-form
size="mini"
:inline="true"
class="demo-form-inline"
label-position="right"
label-width="80px"
label-suffix=":"
>
<el-form-item label="开始日期">
<el-date-picker
v-model="query.startDate"
type="date"
placeholder="选择开始日期"
value-format="yyyy-MM-dd"
clearable
/>
</el-form-item>
<el-form-item label="结束日期">
<el-date-picker
v-model="query.endDate"
type="date"
placeholder="选择结束日期"
value-format="yyyy-MM-dd"
clearable
/>
</el-form-item>
<rrOperation />
</el-form>
</div>
<!--如果想在工具栏加入更多按钮可以使用插槽方式 slot = 'left' or 'right'-->
<crudOperation :permission="permission" />
<!--表格渲染-->
<el-table ref="table" border v-loading="crud.loading" :data="crud.data" size="mini" style="width: 100%;" @selection-change="crud.selectionChangeHandler">
<el-table-column type="selection" width="55" />
<el-table-column prop="recordDatee" label="日期" />
<el-table-column prop="recordTimee" label="记录时间" />
<el-table-column prop="temp" label="温度℃" />
<el-table-column prop="recorder" label="记录人" />
</el-table>
<!--分页组件-->
<pagination />
</div>
</div>
</template>
<script>
import crudTempRecord from '@/views/wms/biBoard/temp/tempRecord'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import pagination from '@crud/Pagination'
const defaultForm = { }
export default {
name: 'TempRecord',
components: { pagination, crudOperation, rrOperation },
mixins: [presenter(), header(), form(defaultForm), crud()],
cruds() {
return CRUD({
title: '温度记录表',
url: 'api/tempRecord',
idField: 'id',
sort: '',
query: { startDate: '', endDate: '' },
crudMethod: { ...crudTempRecord },
optShow: {
add: false,
edit: false,
del: false,
download: false,
reset: true
}
})
},
data() {
return {
permission: {
}
}
},
methods: {
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep {
.vue-treeselect__menu {
overflow-x: auto !important;
width: 300px;
max-height: 300px !important;
}
.vue-treeselect__label {
overflow: unset;
text-overflow: unset;
}
.vue-treeselect__control {
height: 20px !important;
}
.vue-treeselect__multi-value-item-container,
.vue-treeselect--has-value .vue-treeselect__multi-value {
height: 30px;
line-height: 24px;
padding: 0;
}
.vue-treeselect__limit-tip,
.vue-treeselect--searchable.vue-treeselect--multi.vue-treeselect--has-value
.vue-treeselect__input-container {
padding-top: 0;
}
.vue-treeselect__placeholder,
.vue-treeselect__single-value {
height: 28px;
line-height: 32px;
font-size: small;
color: "#CCCFD6";
}
.vue-treeselect--has-value .vue-treeselect__input {
height: 18px !important;
line-height: 18px !important;
}
.vue-treeselect div,
.vue-treeselect span {
box-sizing: content-box;
}
.vue-treeselect__multi-value-label {
display: block;
width: 140px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.vue-treeselect__value-container {
display: block;
height: 32px;
}
}
</style>

View File

@@ -0,0 +1,11 @@
import request from '@/utils/request'
export function query(params) {
return request({
url: 'api/tempRecord',
method: 'get',
params
})
}
export default { query }