opt:富佳项目看板优化

This commit is contained in:
2025-11-19 19:10:50 +08:00
parent 44d5eb6fbd
commit c32665d1be
41 changed files with 2379 additions and 17 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
webpackJsonp([4],{"2L/E":function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i={data:function(){return{loginname:"",password:"",baseUrl:this.$store.getters.baseUrl,setJxtTime:this.$store.getters.setJxtTime/1e3,setTime:this.$store.getters.setTime/1e3,options:[{value:"01",label:"仓储监控1"},{value:"02",label:"仓储监控2"}],value:this.$store.getters.pageNo,secCode:this.$store.getters.secCode||""}},created:function(){"02"===this.$store.getters.iskb?(console.log(this.$store.getters.iskb,1),"01"!==this.value&&this.secCode?this.$router.push("/screen02"):this.$router.push("/screen01")):console.log(this.$store.getters.iskb,2)},methods:{_config:function(){if(this.setTime<1||this.setJxtTime<1)this.$message({message:"刷新时间设置过短",type:"warning"});else if(this.setTime>10800||this.setJxtTime>10800)this.$message({message:"刷新时间设置过长",type:"warning"});else{var e={baseUrl:this.baseUrl,setTime:1e3*this.setTime,setJxtTime:1e3*this.setJxtTime,pageNo:this.value,iskb:"1",secCode:this.secCode};console.log(),this.$store.dispatch("setConfig",e),this.$router.push("/screen"+this.value)}}}},a={render:function(){var e=this,t=e.$createElement,s=e._self._c||t;return s("div",{staticClass:"body-container"},[s("h1",[e._v("仓储监控")]),e._v(" "),s("div",{staticClass:"login_wrap"},[s("div",{staticClass:"login_cnt"},[s("div",{staticClass:"title-name"},[e._v("系统配置")]),e._v(" "),s("div",{staticClass:"login_card"},[s("div",{staticClass:"card_wrap"},[s("div",{staticClass:"inputOuter"},[s("label",[e._v("域名地址")]),e._v(" "),s("input",{directives:[{name:"model",rawName:"v-model",value:e.baseUrl,expression:"baseUrl"}],staticClass:"inputStyle",attrs:{type:"text"},domProps:{value:e.baseUrl},on:{input:function(t){t.target.composing||(e.baseUrl=t.target.value)}}})]),e._v(" "),s("div",{staticClass:"inputOuter"},[s("label",[e._v("看板")]),e._v(" "),s("el-select",{staticClass:"selectWraper",attrs:{placeholder:"请选择"},model:{value:e.value,callback:function(t){e.value=t},expression:"value"}},e._l(e.options,function(e){return s("el-option",{key:e.value,attrs:{label:e.label,value:e.value}})}))],1),e._v(" "),s("div",{directives:[{name:"show",rawName:"v-show",value:1===Number(e.value),expression:"Number(value) === 1"}],staticClass:"inputOuter"},[s("label",[e._v("刷新时间(秒)")]),e._v(" "),s("input",{directives:[{name:"model",rawName:"v-model",value:e.setJxtTime,expression:"setJxtTime"}],staticClass:"inputStyle",attrs:{type:"number"},domProps:{value:e.setJxtTime},on:{input:function(t){t.target.composing||(e.setJxtTime=t.target.value)}}})]),e._v(" "),s("div",{directives:[{name:"show",rawName:"v-show",value:2===Number(e.value),expression:"Number(value) === 2"}],staticClass:"inputOuter"},[s("label",[e._v("刷新时间(秒)")]),e._v(" "),s("input",{directives:[{name:"model",rawName:"v-model",value:e.setTime,expression:"setTime"}],staticClass:"inputStyle",attrs:{type:"number"},domProps:{value:e.setTime},on:{input:function(t){t.target.composing||(e.setTime=t.target.value)}}})]),e._v(" "),s("div",{staticClass:"inputOuter"},[s("label",[e._v("仓库编码")]),e._v(" "),s("input",{directives:[{name:"model",rawName:"v-model",value:e.secCode,expression:"secCode"}],staticClass:"inputStyle",attrs:{type:"text"},domProps:{value:e.secCode},on:{input:function(t){t.target.composing||(e.secCode=t.target.value)}}})])]),e._v(" "),s("button",{staticClass:"btn",on:{click:e._config}},[e._v("配置")])])])])])},staticRenderFns:[]};var l=s("VU/8")(i,a,!1,function(e){s("6f9t")},"data-v-16b21ccb",null);t.default=l.exports},"6f9t":function(e,t){}});
//# sourceMappingURL=4.e43104d0a0bc6d6e3e94.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
!function(e){var n=window.webpackJsonp;window.webpackJsonp=function(r,c,a){for(var i,u,f,s=0,l=[];s<r.length;s++)u=r[s],t[u]&&l.push(t[u][0]),t[u]=0;for(i in c)Object.prototype.hasOwnProperty.call(c,i)&&(e[i]=c[i]);for(n&&n(r,c,a);l.length;)l.shift()();if(a)for(s=0;s<a.length;s++)f=o(o.s=a[s]);return f};var r={},t={6:0};function o(n){if(r[n])return r[n].exports;var t=r[n]={i:n,l:!1,exports:{}};return e[n].call(t.exports,t,t.exports,o),t.l=!0,t.exports}o.e=function(e){var n=t[e];if(0===n)return new Promise(function(e){e()});if(n)return n[2];var r=new Promise(function(r,o){n=t[e]=[r,o]});n[2]=r;var c=document.getElementsByTagName("head")[0],a=document.createElement("script");a.type="text/javascript",a.charset="utf-8",a.async=!0,a.timeout=12e4,o.nc&&a.setAttribute("nonce",o.nc),a.src=o.p+"static/js/"+e+"."+{0:"f8b57ab4d9de4af7357a",1:"aa1371a9cb75c5e6363a",2:"51f40f46d4c4ef29cd6c",3:"814b882c67752c331ed3",4:"e43104d0a0bc6d6e3e94"}[e]+".js";var i=setTimeout(u,12e4);function u(){a.onerror=a.onload=null,clearTimeout(i);var n=t[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),t[e]=void 0)}return a.onerror=a.onload=u,c.appendChild(a),r},o.m=e,o.c=r,o.d=function(e,n,r){o.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},o.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(n,"a",n),n},o.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},o.p="./",o.oe=function(e){throw console.error(e),e}}([]);
//# sourceMappingURL=manifest.7b7124d31a251fc046ff.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,180 @@
{
"code":"1",
"desc":"查询成功",
"result":{
"yDateMax":6000,
"xDate":[
{
"id":"12-25"
},
{
"id":"12-26"
},
{
"id":"12-27"
},
{
"id":"12-28"
},
{
"id":"12-29"
},
{
"id":"12-30"
},
{
"id":"12-31"
},
{
"id":"1-1"
},
{
"id":"1-2"
},
{
"id":"1-3"
},
{
"id":"1-4"
},
{
"id":"1-5"
},
{
"id":"1-6"
},
{
"id":"1-7"
}
],
"yDate0":[
{
"num":"1000"
},
{
"num":"1500"
},
{
"num":"0"
},
{
"num":"2300"
},
{
"num":"4300"
},
{
"num":"3900"
},
{
"num":"2800"
},
{
"num":"2600"
},
{
"num":"2500"
},
{
"num":"4500"
},
{
"num":"1200"
},
{
"num":"1800"
},
{
"num":"1900"
},
{
"num":"1700"
}
],
"yDate1":[
{
"num":"900"
},
{
"num":"1400"
},
{
"num":"2600"
},
{
"num":"2200"
},
{
"num":"4200"
},
{
"num":"3800"
},
{
"num":"2500"
},
{
"num":"2300"
},
{
"num":"2400"
},
{
"num":"4000"
},
{
"num":"1000"
},
{
"num":"1200"
},
{
"num":"1100"
},
{
"num":"1100"
}
],
"yDate2":[
{
"num":"0"
},
{
"num":"0"
},
{
"num":"1200"
},
{
"num":"0"
},
{
"num":"0"
},
{
"num":"0"
},
{
"num":"0"
},
{
"num":"0"
},
{
"num":"0"
},
{
"num":"0"
},
{
"num":"0"
},
{
"num":"0"
},
{
"num":"0"
}
]
}
}

View File

@@ -40,7 +40,7 @@ export const constantRouterMap = [
children: [
{
path: 'dashboard',
component: (resolve) => require(['@/views/system/redis/index'], resolve),
component: (resolve) => require(['@/views/dashboard/Dashboard2'], resolve),
name: 'Dashboard',
meta: { title: i18n.t('common.home'), icon: 'index', affix: true, noCache: true }
// meta: { title: '首页', icon: 'index', affix: true, noCache: true }

View File

@@ -0,0 +1,712 @@
<template>
<div class="agv-dashboard">
<!-- 顶部导航栏 -->
<el-header class="header">
<div class="header-left">
<i class="el-icon-truck"></i>
<span class="header-title">库位状态看板</span>
</div>
<div class="header-right">
<el-button
type="primary"
icon="el-icon-refresh"
@click="refreshData"
:loading="isRefreshing"
>
手动刷新
</el-button>
<el-button
type="text"
icon="el-icon-pause"
@click="toggleRefresh"
class="refresh-toggle"
>
{{ isAutoRefresh ? '暂停刷新' : '开启刷新' }}
</el-button>
<span class="last-update">
上次更新: {{ lastUpdateTime }}
</span>
</div>
</el-header>
<!-- 主要内容区域 -->
<el-main class="main-content">
<!-- 数据概览卡片 -->
<div class="overview-cards">
<el-card class="stat-card" shadow="hover">
<div class="stat-title">库位总数</div>
<div class="stat-value">{{ overview.totalLocations }}</div>
</el-card>
<el-card class="stat-card" shadow="hover">
<div class="stat-title">货架数量</div>
<div class="stat-value">{{ overview.totalShelves }}</div>
</el-card>
<el-card class="stat-card" shadow="hover">
<div class="stat-title">当前库存</div>
<div class="stat-value">{{ overview.currentInventory }}</div>
</el-card>
<el-card class="stat-card" shadow="hover">
<div class="stat-title">剩余库位</div>
<div class="stat-value">{{ overview.availableLocations }}</div>
</el-card>
</div>
<div class="overview-cards">
<el-card class="stat-card" shadow="hover">
<div class="stat-title">入库中库位</div>
<div class="stat-value">{{ overview.inboundQuantity }}</div>
</el-card>
<el-card class="stat-card" shadow="hover">
<div class="stat-title">出库中库位</div>
<div class="stat-value">{{ overview.outboundQuantity }}</div>
</el-card>
<el-card class="stat-card" shadow="hover">
<div class="stat-title">移入中库位</div>
<div class="stat-value">{{ overview.moveToQuantity }}</div>
</el-card>
<el-card class="stat-card" shadow="hover">
<div class="stat-title">移出中库位</div>
<div class="stat-value">{{ overview.moveFromQuantity }}</div>
</el-card>
</div>
<!-- 楼层分布图表 -->
<div class="floor-charts">
<el-card class="chart-card" shadow="hover">
<div class="chart-header">
<span class="chart-title">各楼层库位分布</span>
<el-select
v-model="selectedFloor"
class="floor-select"
@change="handleFloorChange"
>
<el-option label="全部楼层" value="all"></el-option>
<el-option label="一层" value="1"></el-option>
<el-option label="二层" value="2"></el-option>
<el-option label="三层" value="3"></el-option>
</el-select>
</div>
<div class="chart-container">
<div id="floorPieChart" class="chart"></div>
<div id="floorBarChart" class="chart"></div>
</div>
</el-card>
</div>
</el-main>
<!-- 底部信息 -->
<el-footer class="footer">
<div class="footer-content">
<span class="refresh-interval">
自动刷新间隔: {{ refreshInterval }}
</span>
</div>
</el-footer>
</div>
</template>
<script>
import echarts from 'echarts'
import dashboard, { getDashboardData } from '@/views/dashboard/mixins/dashboard'
export default {
name: 'AGVDashboard',
data() {
return {
storCode : 'FJ',
// 概览数据
overview: {
totalLocations: 0,
totalShelves: 0,
currentInventory: 0,
availableLocations: 0,
inboundQuantity: 0,
outboundQuantity: 0,
moveToQuantity: 0,
moveFromQuantity: 0
},
// 库位数据
locations: [],
filteredLocations: [],
searchCode: '',
// 分页信息
currentPage: 1,
pageSize: 20,
// 图表相关
selectedFloor: 'all',
floorPieChart: null,
floorBarChart: null,
// 刷新相关
isAutoRefresh: true,
isRefreshing: false,
refreshInterval: 30,
lastUpdateTime: '',
refreshTimer: null,
// 详情弹窗
isDetailDialogOpen: false,
selectedLocation: {}
}
},
created() {
// 初始化数据
this.fetchData()
// 启动定时刷新
this.startRefreshTimer()
},
destroyed() {
// 清除定时器
if (this.refreshTimer) {
clearInterval(this.refreshTimer)
}
},
watch: {
// 监听搜索条件变化
searchCode(val) {
this.filterLocations()
},
// 监听选中楼层变化
selectedFloor(val) {
this.updateCharts()
}
},
methods: {
// 获取数据
fetchData() {
this.isRefreshing = true
// 模拟API请求
setTimeout(() => {
// 查询数据
this.generateMockData()
// 停止刷新状态
this.isRefreshing = false
}, 1000)
},
// 生成模拟数据
generateMockData() {
const param = {}
param.storCode = this.storCode
// 查询概览数据
dashboard.getDashboardData(param).then(res => {
this.overview = res.overview
this.locations = res.locations
// 更新图表
this.locations.forEach(item => {
item.floor = parseInt(item.floor)
})
this.updateCharts()
// 更新最后更新时间
this.lastUpdateTime = new Date().toLocaleString()
// 过滤库位数据
this.filterLocations()
})
},
// 过滤库位数据
filterLocations() {
if (this.searchCode) {
this.filteredLocations = this.locations.filter(location =>
location.structCode.includes(this.searchCode) ||
location.shelfCode.includes(this.searchCode)
)
} else {
this.filteredLocations = [...this.locations]
}
},
// 更新图表
updateCharts() {
// 初始化饼图
if (!this.floorPieChart) {
this.floorPieChart = echarts.init(document.getElementById('floorPieChart'))
}
// 初始化柱状图
if (!this.floorBarChart) {
this.floorBarChart = echarts.init(document.getElementById('floorBarChart'))
}
// 准备图表数据
let pieData, barData
if (this.selectedFloor === 'all') {
// 全部楼层数据
pieData = [
{ name: '一层', value: this.calculateFloorCount(1) },
{ name: '二层', value: this.calculateFloorCount(2) },
{ name: '三层', value: this.calculateFloorCount(3) }
]
barData = this.prepareBarData([1, 2, 3])
} else {
// 特定楼层数据
const floor = parseInt(this.selectedFloor)
pieData = this.prepareFloorStatusData(floor)
barData = this.prepareBarData([floor])
}
// 更新饼图
this.floorPieChart.setOption({
title: {
text: this.selectedFloor === 'all' ? '各楼层库位总数' : `${this.selectedFloor}层库位状态分布`,
left: 'center'
},
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left'
},
// 添加更鲜艳的配色方案
color: ['#1890ff', '#52c41a', '#ff7875', '#faad14', '#722ed1', '#13c2c2'],
series: [
{
name: '库位数量',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: 20,
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: pieData
}
]
})
// 更新柱状图
this.floorBarChart.setOption({
title: {
text: '各楼层库位状态统计',
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {
data: ['空闲', '占用', '入库中', '出库中', '异常'],
bottom: 0
},
grid: {
left: '3%',
right: '4%',
bottom: '15%',
containLabel: true
},
xAxis: {
type: 'category',
data: barData.categories
},
yAxis: {
type: 'value'
},
// 添加更鲜艳的配色方案
color: ['#1890ff', '#52c41a', '#ff7875', '#faad14', '#722ed1', '#13c2c2', '#fa8c16'],
series: barData.series
})
},
// 计算特定楼层的库位总数
calculateFloorCount(floor) {
return this.locations.filter(location => location.floor === floor).length
},
// 准备特定楼层的状态数据
prepareFloorStatusData(floor) {
const statuses = ['空闲', '占用', '入库中', '出库中', '移入中', '移出中','其他占用']
const floorLocations = this.locations.filter(location => location.floor === floor)
return statuses.map(status => {
return {
name: status,
value: floorLocations.filter(location => location.status === status).length
}
})
},
// 准备柱状图数据
prepareBarData(floors) {
const statuses = ['空闲', '占用', '入库中', '出库中', '移入中', '移出中', '其他占用']
const categories = floors.map(floor => `${floor}`)
const series = statuses.map(status => {
return {
name: status,
type: 'bar',
stack: 'total',
emphasis: {
focus: 'series'
},
data: floors.map(floor => {
return this.locations.filter(
location => location.floor === floor && location.status === status
).length
})
}
})
return {
categories: categories,
series: series
}
},
// 手动刷新数据
refreshData() {
this.fetchData()
},
// 切换自动刷新状态
toggleRefresh() {
this.isAutoRefresh = !this.isAutoRefresh
if (this.isAutoRefresh) {
this.startRefreshTimer()
} else {
this.clearRefreshTimer()
}
},
// 启动刷新定时器
startRefreshTimer() {
this.clearRefreshTimer()
this.refreshTimer = setInterval(() => {
this.fetchData()
}, this.refreshInterval * 1000)
},
// 清除刷新定时器
clearRefreshTimer() {
if (this.refreshTimer) {
clearInterval(this.refreshTimer)
this.refreshTimer = null
}
},
// 获取楼层标签类型
getFloorTagType(floor) {
const types = ['', 'primary', 'success', 'warning']
return types[floor] || 'info'
},
// 获取状态标签类型
getStatusTagType(status) {
switch (status) {
case '空闲':
return 'success'
case '占用':
return 'info'
case '入库中':
return 'primary'
case '出库中':
return 'warning'
case '异常':
return 'danger'
default:
return 'info'
}
},
// 处理楼层选择变化
handleFloorChange() {
this.updateCharts()
},
// 处理分页大小变化
handleSizeChange(val) {
this.pageSize = val
this.currentPage = 1
},
// 处理当前页变化
handleCurrentChange(val) {
this.currentPage = val
},
// 处理行点击事件
handleRowClick(row) {
this.selectedLocation = row
this.isDetailDialogOpen = true
}
}
}
</script>
<style scoped>
/* 全局样式 */
.agv-dashboard {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f7fa;
}
/* 头部样式 */
.header {
background-color: #1890ff;
color: #fff;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
height: 64px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.header-left {
display: flex;
align-items: center;
}
.header-left i {
font-size: 24px;
margin-right: 10px;
}
.header-title {
font-size: 18px;
font-weight: bold;
}
.header-right {
display: flex;
align-items: center;
}
.last-update {
margin-left: 20px;
font-size: 14px;
opacity: 0.9;
}
.refresh-toggle {
color: #fff;
margin-left: 10px;
}
/* 主要内容区域 */
.main-content {
flex: 1;
padding: 20px;
overflow-y: auto;
}
/* 概览卡片样式 */
.overview-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.stat-card {
background-color: #fff;
border-radius: 8px;
padding: 20px;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.stat-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}
.stat-title {
font-size: 14px;
color: #666;
margin-bottom: 10px;
}
.stat-value {
font-size: 28px;
font-weight: bold;
color: #1890ff;
margin-bottom: 5px;
}
.stat-change {
display: flex;
align-items: center;
font-size: 12px;
}
.increase {
color: #52c41a;
margin-right: 5px;
}
.decrease {
color: #ff4d4f;
margin-right: 5px;
}
.compared {
color: #999;
}
/* 图表区域样式 */
.floor-charts {
margin-bottom: 20px;
}
.chart-card {
background-color: #fff;
border-radius: 8px;
padding: 20px;
}
.chart-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.chart-title {
font-size: 16px;
font-weight: bold;
color: #333;
}
.floor-select {
width: 120px;
}
.chart-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 20px;
}
.chart {
height: 350px;
width: 100%;
}
/* 表格区域样式 */
.location-table {
margin-bottom: 20px;
}
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.table-title {
font-size: 16px;
font-weight: bold;
color: #333;
}
.search-input {
width: 250px;
}
.pagination {
margin-top: 15px;
display: flex;
justify-content: flex-end;
}
/* 详情弹窗样式 */
.detail-info {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
}
.detail-row {
display: flex;
margin-bottom: 10px;
}
.detail-label {
width: 100px;
text-align: right;
padding-right: 10px;
font-weight: bold;
color: #666;
}
.detail-value {
flex: 1;
color: #333;
}
/* 底部样式 */
.footer {
background-color: #fff;
text-align: center;
padding: 10px 0;
color: #666;
font-size: 14px;
border-top: 1px solid #e8e8e8;
}
.footer-content {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 0 20px;
}
.refresh-interval {
color: #1890ff;
}
/* 响应式样式 */
@media (max-width: 768px) {
.overview-cards {
grid-template-columns: 1fr;
}
.chart-container {
grid-template-columns: 1fr;
}
.detail-info {
grid-template-columns: 1fr;
}
}
</style>

View File

@@ -0,0 +1,789 @@
<template>
<div class="transport-dashboard">
<!-- 顶部导航栏 -->
<el-header class="header">
<div class="header-left">
<i class="el-icon-s-tools"></i>
<span class="header-title">任务完成情况看板</span>
</div>
<div class="header-right">
<el-button
type="primary"
icon="el-icon-refresh"
@click="refreshData"
:loading="isRefreshing"
>
手动刷新
</el-button>
<el-button
type="text"
icon="el-icon-pause"
@click="toggleRefresh"
class="refresh-toggle"
>
{{ isAutoRefresh ? '暂停刷新' : '开启刷新' }}
</el-button>
<span class="last-update">
上次更新: {{ lastUpdateTime }}
</span>
</div>
</el-header>
<!-- 主要内容区域 -->
<el-main class="main-content">
<!-- 数据概览卡片 - 第一行 -->
<div class="overview-cards">
<el-card class="stat-card" shadow="hover">
<div class="stat-title">总任务数</div>
<div class="stat-value">{{ stats.totalTasks }}</div>
</el-card>
<el-card class="stat-card" shadow="hover">
<div class="stat-title">今日任务数</div>
<div class="stat-value">{{ stats.todayTasks }}</div>
<div class="stat-change">
<span :class="weeklyComparison.growthRate >= 0 ? 'positive' : 'negative'">
<i :class="weeklyComparison.growthRate >= 0 ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"></i>
{{ stats.dayOnDay }}%
</span>
<span class="compared">较昨日</span>
</div>
</el-card>
<el-card class="stat-card" shadow="hover">
<div class="stat-title">本月任务数</div>
<div class="stat-value">{{ stats.monthTasks }}</div>
</el-card>
<el-card class="stat-card" shadow="hover">
<div class="stat-title">库位数量</div>
<div class="stat-value">{{ stats.locationCount }}</div>
</el-card>
</div>
<!-- 数据概览卡片 - 第二行 -->
<div class="overview-cards">
<!-- <el-card class="stat-card" shadow="hover">
<div class="stat-title">月均任务数</div>
<div class="stat-value">{{ stats.monthlyAvg }}</div>
</el-card>-->
<el-card class="stat-card" shadow="hover">
<div class="stat-title">在库数量</div>
<div class="stat-value">{{ stats.inventoryCount }}</div>
</el-card>
<el-card class="stat-card" shadow="hover">
<div class="stat-title">入库数量</div>
<div class="stat-value">{{ stats.inboundCount }}</div>
</el-card>
<el-card class="stat-card" shadow="hover">
<div class="stat-title">出库数量</div>
<div class="stat-value">{{ stats.outboundCount }}</div>
</el-card>
<el-card class="stat-card" shadow="hover">
<div class="stat-title">运转数量</div>
<div class="stat-value">{{ stats.operationCount }}</div>
</el-card>
</div>
<!-- 任务统计图表 - 合并为一个组件 -->
<div class="task-charts">
<el-card class="chart-card" shadow="hover">
<div class="chart-header">
<span class="chart-title">{{ viewType === 'daily' ? '每日任务完成情况' : '周任务数据对比' }}</span>
<el-select
v-model="viewType"
class="view-type-select"
@change="handleViewTypeChange"
>
<el-option label="每日任务完成情况" value="daily"></el-option>
<el-option label="周任务数据对比" value="weekly"></el-option>
</el-select>
<div class="growth-rate" v-if="viewType === 'weekly' && weeklyComparison.growthRate !== 0">
<span :class="weeklyComparison.growthRate >= 0 ? 'positive' : 'negative'">
<i :class="weeklyComparison.growthRate >= 0 ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"></i>
{{ weeklyComparison.growthRate }}%
</span>
<span class="compared">较上周</span>
</div>
</div>
<div class="chart-container">
<div id="taskChart" class="chart"></div>
</div>
</el-card>
</div>
</el-main>
<!-- 底部信息 -->
<el-footer class="footer">
<div class="footer-content">
<span class="refresh-interval">
自动刷新间隔: {{ refreshInterval }}
</span>
</div>
</el-footer>
</div>
</template>
<script>
import echarts from 'echarts'
import dashboard2 from './mixins/dashboard2.js'
import axios from 'axios'
export default {
mixins: [dashboard2],
data() {
return {
isRefreshing: false,
isAutoRefresh: true,
lastUpdateTime: '2025-11-19 15:30:22',
refreshInterval: 30,
searchKeyword: '',
currentPage: 1,
pageSize: 10,
// 视图类型:'daily' - 每日任务完成情况,'weekly' - 周任务数据对比
viewType: 'daily',
// 近三周数据
threeWeeksData: {
weeks: [], // 周标签
inboundData: [], // 入库任务数据
outboundData: [], // 出库任务数据
otherData: [] // 其他搬运数据
},
stats: {
totalTasks: 0,
todayTasks: 0,
dayOnDay: 0,
monthTasks: 0,
monthlyAvg: 0,
locationCount: 0,
inventoryCount: 0,
outboundCount: 0,
operationCount: 0
},
// 每日详细数据
historyList: [],
// 周对比数据
weeklyComparison: {
thisWeek: {
totalTasks: 0,
completedTasks: 0,
completionRate: 0
},
lastWeek: {
totalTasks: 0,
completedTasks: 0,
completionRate: 0
},
growthRate: 0
},
// 定时刷新的定时器
refreshTimer: null
}
},
computed: {
filteredHistory() {
return this.historyList.filter(item =>
item.date.includes(this.searchKeyword) ||
item.inbound.toString().includes(this.searchKeyword) ||
item.outbound.toString().includes(this.searchKeyword) ||
item.other.toString().includes(this.searchKeyword)
)
}
},
mounted() {
// 初始化时获取数据
this.refreshData()
// 初始化图表
this.initChart()
// 设置自动刷新
this.setupAutoRefresh()
// 在窗口大小改变时,重置图表大小
window.addEventListener('resize', () => {
this.initChart()
})
},
beforeDestroy() {
// 移除事件监听
window.removeEventListener('resize', () => {
this.initChart()
})
// 清除定时器
if (this.refreshTimer) {
clearInterval(this.refreshTimer)
}
},
methods: {
// 设置自动刷新
setupAutoRefresh() {
if (this.refreshTimer) {
clearInterval(this.refreshTimer)
}
if (this.isAutoRefresh) {
this.refreshTimer = setInterval(() => {
this.refreshData()
}, this.refreshInterval * 1000)
}
},
// 获取报表数据
async refreshData() {
this.isRefreshing = true
try {
// 使用mixin中的接口方法获取数据
const params = {
currentDate: new Date().toLocaleDateString('zh-CN')
}
// 直接解构从mixin返回的数据
const { stats, historyList, weeklyComparison } = await dashboard2.refreshDashboardData(params)
// 更新统计数据
this.stats = stats
// 更新历史数据
if (historyList) {
this.historyList = historyList
} else {
// 保留原有的模拟数据逻辑作为降级方案
this.historyList = Array.from({ length: 7 }, (_, i) => {
const date = new Date()
date.setDate(date.getDate() - 6 + i)
const dateStr = date.toISOString().split('T')[0]
return {
date: dateStr,
inbound: Math.floor(Math.random() * 20) + 40,
outbound: Math.floor(Math.random() * 20) + 30,
other: Math.floor(Math.random() * 15) + 25,
total: 100
}
})
}
// 更新周对比数据
if (weeklyComparison) {
this.weeklyComparison = weeklyComparison
this.updateTaskChart()
} else {
// 如果没有从mixin获取到周对比数据则调用原方法
await this.getWeeklyData()
}
// 更新任务完成情况图表
this.updateChart()
// 更新最后更新时间
this.lastUpdateTime = new Date().toLocaleString('zh-CN')
} catch (error) {
console.error('获取报表数据失败:', error)
this.$message.error('获取数据失败,请稍后重试')
} finally {
this.isRefreshing = false
}
},
// 获取周对比数据
async getWeeklyData() {
try {
// 使用mixin中的接口方法
const weekData = await this.getWeekData()
// 直接使用weekData假设mixin返回的数据格式符合需求
const data = weekData || {}
// 更新周对比数据
this.weeklyComparison = {
thisWeek: {
totalTasks: data.thisWeek && data.thisWeek.totalTasks !== undefined ? data.thisWeek.totalTasks : 0,
completedTasks: data.thisWeek && data.thisWeek.completedTasks !== undefined ? data.thisWeek.completedTasks : 0,
completionRate: data.thisWeek && data.thisWeek.completionRate !== undefined ? data.thisWeek.completionRate : 0
},
lastWeek: {
totalTasks: data.lastWeek && data.lastWeek.totalTasks !== undefined ? data.lastWeek.totalTasks : 0,
completedTasks: data.lastWeek && data.lastWeek.completedTasks !== undefined ? data.lastWeek.completedTasks : 0,
completionRate: data.lastWeek && data.lastWeek.completionRate !== undefined ? data.lastWeek.completionRate : 0
},
growthRate: data.growthRate || 0
}
// 更新周对比图表
this.updateTaskChart()
} catch (error) {
console.error('获取周对比数据失败:', error)
// 如果接口调用失败,使用模拟数据
this.weeklyComparison = {
thisWeek: {
totalTasks: 650,
completedTasks: 585,
completionRate: 90
},
lastWeek: {
totalTasks: 580,
completedTasks: 493,
completionRate: 85
},
growthRate: 12.1
}
this.updateTaskChart()
}
}
,
toggleRefresh() {
this.isAutoRefresh = !this.isAutoRefresh
this.setupAutoRefresh()
},
initChart() {
// 初始化任务统计图表(合并后的单一图表)
const taskChartDom = document.getElementById('taskChart')
if (taskChartDom.myChart) {
taskChartDom.myChart.dispose()
}
const taskChart = echarts.init(taskChartDom)
taskChartDom.myChart = taskChart
// 根据当前视图类型更新图表
this.updateTaskChart(taskChart)
},
// 处理视图类型切换
handleViewTypeChange() {
this.updateTaskChart()
},
// 更新任务统计图表(合并后的方法)
async updateTaskChart(myChart) {
// 如果没有传入图表实例则从DOM获取
if (!myChart) {
const chartDom = document.getElementById('taskChart')
if (!chartDom) return
myChart = chartDom.myChart || echarts.init(chartDom)
}
// 根据当前视图类型渲染不同的图表
if (this.viewType === 'daily') {
this.renderDailyChart(myChart)
} else {
await this.renderWeeklyChart(myChart)
}
},
// 渲染每日任务完成情况图表
renderDailyChart(myChart) {
// 直接使用每日数据
const xAxisData = this.historyList.map(item => item.date.slice(5)) // 提取 "11-13" 格式的日期
const inboundData = this.historyList.map(item => item.inbound)
const outboundData = this.historyList.map(item => item.outbound)
const otherData = this.historyList.map(item => item.other)
const option = {
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
legend: { data: ['入库任务', '出库任务', '其他搬运'], top: 10 },
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: { type: 'category', data: xAxisData },
yAxis: { type: 'value' },
series: [
{ name: '入库任务', type: 'bar', data: inboundData, itemStyle: { color: '#1890ff' } },
{ name: '出库任务', type: 'bar', data: outboundData, itemStyle: { color: '#52c41a' } },
{ name: '其他搬运', type: 'bar', data: otherData, itemStyle: { color: '#fa8c16' } }
]
}
myChart.setOption(option)
},
// 渲染周任务数据对比图表
async renderWeeklyChart(myChart) {
// 准备近3周数据异步获取
await this.prepareThreeWeeksData()
const option = {
tooltip: { trigger: 'axis' },
legend: { data: ['入库任务', '出库任务', '其他搬运'], top: 10 },
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: {
type: 'category',
data: this.threeWeeksData.weeks
},
yAxis: {
type: 'value'
},
series: [
{ name: '入库任务', type: 'bar', data: this.threeWeeksData.inboundData, itemStyle: { color: '#1890ff' } },
{ name: '出库任务', type: 'bar', data: this.threeWeeksData.outboundData, itemStyle: { color: '#52c41a' } },
{ name: '其他搬运', type: 'bar', data: this.threeWeeksData.otherData, itemStyle: { color: '#fa8c16' } }
]
}
myChart.setOption(option)
},
// 准备近3周的数据
async prepareThreeWeeksData() {
try {
// 从后台API获取真实的周任务数据
const response = await axios.get('/api/bigScreen/getWeeklyData')
const weeklyData = response.data
// 从响应数据中提取近3周的数据
// 如果后台返回的数据格式不符合预期,则使用默认值
if (weeklyData && weeklyData.success && weeklyData.data) {
const { weeks, inboundData, outboundData, otherData } = weeklyData.data
this.threeWeeksData = {
weeks: weeks || this.generateWeekDateLabels(),
inboundData: inboundData || [0, 0, 0],
outboundData: outboundData || [0, 0, 0],
otherData: otherData || [0, 0, 0]
}
} else {
// 后备方案:生成日期标签并使用空数据
this.threeWeeksData = {
weeks: this.generateWeekDateLabels(),
inboundData: [0, 0, 0],
outboundData: [0, 0, 0],
otherData: [0, 0, 0]
}
console.warn('未获取到有效的周任务数据')
}
} catch (error) {
console.error('获取周任务数据失败:', error)
// 出错时使用后备方案
this.threeWeeksData = {
weeks: this.generateWeekDateLabels(),
inboundData: [0, 0, 0],
outboundData: [0, 0, 0],
otherData: [0, 0, 0]
}
}
},
// 生成周日期标签MM-DD-MM-DD格式
generateWeekDateLabels() {
const weeks = []
const now = new Date()
for (let i = 2; i >= 0; i--) {
// 计算本周的开始日期(周一)
const weekDate = new Date(now)
weekDate.setDate(now.getDate() - i * 7)
// 获取本周周一
const day = weekDate.getDay() || 7 // 将周日的0转换为7
const monday = new Date(weekDate)
monday.setDate(weekDate.getDate() - day + 1)
// 获取本周周日
const sunday = new Date(monday)
sunday.setDate(monday.getDate() + 6)
// 格式化为 MM-DD 格式
const formatDate = (date) => {
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${month}-${day}`
}
weeks.push(`${formatDate(monday)}-${formatDate(sunday)}`)
}
return weeks
}
,
// 更新周对比图表
updateWeeklyChart(myChart) {
// 如果没有传入图表实例则从DOM获取
if (!myChart) {
const chartDom = document.getElementById('weeklyComparisonChart')
if (!chartDom) return
myChart = chartDom.myChart || echarts.init(chartDom)
}
const option = {
tooltip: { trigger: 'axis' },
legend: { data: ['本周', '上周'], top: 10 },
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: {
type: 'category',
data: ['总任务数', '已完成任务数', '完成率(%)']
},
yAxis: {
type: 'value'
},
series: [
{
name: '本周',
type: 'bar',
data: [
this.weeklyComparison.thisWeek.totalTasks,
this.weeklyComparison.thisWeek.completedTasks,
this.weeklyComparison.thisWeek.completionRate
],
itemStyle: { color: '#1890ff' }
},
{
name: '上周',
type: 'bar',
data: [
this.weeklyComparison.lastWeek.totalTasks,
this.weeklyComparison.lastWeek.completedTasks,
this.weeklyComparison.lastWeek.completionRate
],
itemStyle: { color: '#52c41a' }
}
]
}
myChart.setOption(option)
},
updateChart(myChart) {
// 如果没有传入图表实例则从DOM获取
if (!myChart) {
const chartDom = document.getElementById('taskChart')
if (!chartDom) return
myChart = chartDom.myChart || echarts.init(chartDom)
}
// 直接使用每日数据
const xAxisData = this.historyList.map(item => item.date.slice(5)) // 提取 "11-13" 格式的日期
const inboundData = this.historyList.map(item => item.inbound)
const outboundData = this.historyList.map(item => item.outbound)
const otherData = this.historyList.map(item => item.other)
const option = {
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
legend: { data: ['入库任务', '出库任务', '其他搬运'], top: 10 },
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: { type: 'category', data: xAxisData },
yAxis: { type: 'value' },
series: [
{ name: '入库任务', type: 'bar', data: inboundData, itemStyle: { color: '#1890ff' } },
{ name: '出库任务', type: 'bar', data: outboundData, itemStyle: { color: '#52c41a' } },
{ name: '其他搬运', type: 'bar', data: otherData, itemStyle: { color: '#fa8c16' } }
]
}
myChart.setOption(option)
}
,
handleSizeChange(val) {
this.pageSize = val
}
,
handleCurrentChange(val) {
this.currentPage = val
}
}
}
</script>
<style scoped>
/* 全局样式 */
.transport-dashboard {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f7fa;
}
/* 头部样式 */
.header {
background-color: #1890ff;
color: #fff;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
height: 64px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.header-left {
display: flex;
align-items: center;
}
.header-left i {
font-size: 24px;
margin-right: 10px;
}
.header-title {
font-size: 18px;
font-weight: bold;
}
.header-right {
display: flex;
align-items: center;
}
.last-update {
margin-left: 20px;
font-size: 14px;
opacity: 0.9;
}
.refresh-toggle {
color: #fff;
margin-left: 10px;
}
/* 主要内容区域 */
.main-content {
flex: 1;
padding: 20px;
overflow-y: auto;
}
/* 概览卡片样式 */
.overview-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.stat-card {
background-color: #fff;
border-radius: 8px;
padding: 20px;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.stat-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}
.stat-title {
font-size: 14px;
color: #666;
margin-bottom: 10px;
}
.stat-value {
font-size: 28px;
font-weight: bold;
color: #1890ff;
margin-bottom: 5px;
}
.stat-change {
display: flex;
align-items: center;
font-size: 12px;
}
.increase,
.positive {
color: #52c41a;
margin-right: 5px;
}
.decrease,
.negative {
color: #ff4d4f;
margin-right: 5px;
}
.compared {
color: #999;
}
.growth-rate {
display: flex;
align-items: center;
font-size: 14px;
margin-left: 20px;
}
/* 图表区域样式 */
.task-charts {
margin-bottom: 20px;
}
.chart-card {
background-color: #fff;
border-radius: 8px;
padding: 20px;
}
.chart-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.chart-title {
font-size: 16px;
font-weight: bold;
color: #333;
}
.chart-container {
height: 350px;
}
.chart {
height: 100%;
width: 100%;
}
/* 表格区域样式 */
.history-table {
margin-bottom: 20px;
}
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.table-title {
font-size: 16px;
font-weight: bold;
color: #333;
}
.search-input {
width: 250px;
}
.pagination {
margin-top: 15px;
display: flex;
justify-content: flex-end;
}
/* 底部样式 */
.footer {
background-color: #fff;
text-align: center;
padding: 10px 0;
color: #666;
font-size: 14px;
border-top: 1px solid #e8e8e8;
}
.footer-content {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 0 20px;
}
.refresh-interval {
color: #1890ff;
}
/* 响应式样式 */
@media (max-width: 768px) {
.overview-cards {
grid-template-columns: 1fr;
}
}
</style>

View File

@@ -0,0 +1,10 @@
import request from '@/utils/request'
export function getDashboardData(data) {
return request({
url: 'api/bigScreen/getDashboardData',
method: 'post',
data
})
}
export default { getDashboardData }

View File

@@ -0,0 +1,69 @@
import axios from 'axios'
export default {
/**
* 获取仪表板报表数据
* @param {Object} params - 查询参数
* @returns {Promise} 返回数据Promise
*/
async getReportData(params = {}) {
try {
const response = await axios.get('/api/bigScreen/getReportData', {
params: {
dateRange: params.dateRange || '',
searchKey: params.searchKey || '',
...params
}
})
return response.data
} catch (error) {
console.error('获取报表数据失败:', error)
throw error
}
},
/**
* 获取周对比数据
* @param {Object} params - 查询参数
* @returns {Promise} 返回数据Promise
*/
async getWeekData(params = {}) {
try {
const response = await axios.get('/api/bigScreen/getWeeklyData', {
params: {
currentWeek: params.currentWeek || '',
lastWeek: params.lastWeek || '',
...params
}
})
return response.data
} catch (error) {
console.error('获取周对比数据失败:', error)
throw error
}
},
/**
* 刷新所有仪表板数据
* @param {Object} params - 查询参数
* @returns {Promise<Object>} 返回包含报表数据和周对比数据的对象
*/
async refreshDashboardData(params = {}) {
try {
const [reportData, weekData] = await Promise.all([
this.getReportData(params),
this.getWeekData(params)
])
// 返回Dashboard2.vue中期望的格式
return {
stats: reportData && reportData.stats ? reportData.stats : undefined,
historyList: reportData && reportData.historyList ? reportData.historyList : undefined,
weeklyComparison: weekData
}
} catch (error) {
console.error('刷新仪表板数据失败:', error)
throw error
}
}
}

View File

@@ -179,7 +179,6 @@
<el-table-column prop="stor_name" :label="$t('wms.st.inbill.warehouse')" width="100px;" />
<el-table-column prop="struct_code" :label="$t('wms.st.inbill.warehouse_location')" width="100px;" />
<el-table-column prop="vehicle_code" :label="$t('wms.st.inbill.vehicle_code')" width="100px;" />
<el-table-column prop="barcode" :label="$t('wms.st.inbill.material_qrcode')" width="100px;" />
<el-table-column show-overflow-tooltip prop="bill_type" min-width="120" :formatter="bill_typeFormat" :label="$t('wms.st.inbill.bill_type')" />
<el-table-column show-overflow-tooltip min-width="120" prop="biz_date" :label="$t('wms.st.inbill.business_date')" />
<el-table-column show-overflow-tooltip prop="create_mode" :formatter="create_modeFormat" :label="$t('wms.st.inbill.create_mode')" />

View File

@@ -98,9 +98,9 @@
<el-table-column prop="pcsn" :label="$t('wms.statement.structivt.batch_number')" :min-width="flexWidth('pcsn',crud.data,'批次号')" />
<el-table-column prop="qty" :label="$t('wms.statement.structivt.stock_quantity')" :formatter="crud.formatNum3" :min-width="100" />
<el-table-column prop="frozen_qty" :label="$t('wms.statement.structivt.frozen_quantity')" :formatter="crud.formatNum3" :min-width="100" />
<el-table-column prop="update_time" :label="$t('wms.statement.structivt.storage_time')" :min-width="flexWidth('create_time',crud.data,'入库时间')" />
<el-table-column prop="qty_unit_name" :label="$t('wms.statement.structivt.measurement_unit')" :min-width="flexWidth('qty_unit_name',crud.data,'计量单位')" />
<el-table-column prop="remark" :label="$t('wms.statement.structivt.remark')" :min-width="flexWidth('remark',crud.data,'备注')" />
<el-table-column prop="update_time" :label="$t('wms.statement.structivt.storage_time')" :min-width="flexWidth('create_time',crud.data,'入库时间')" />
</el-table>
<!--分页组件-->
<pagination />