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

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 />