add:主页看板,任务完成情况统计、agv实时监控

This commit is contained in:
zhengxuming
2025-08-25 17:46:57 +08:00
parent 6a70aba6c9
commit 1abb81f7bb
34 changed files with 2601 additions and 4 deletions

View File

@@ -0,0 +1,18 @@
import request from '@/utils/request'
export function getTodayTaskStats(param) {
return request({
url: '/api/dashboard/today',
method: 'get',
data: param
})
}
export function getSevenDaysTaskStats() {
return request({
url: '/api/dashboard/seven-days',
method: 'get'
})
}
export default { getTodayTaskStats, getSevenDaysTaskStats }

View File

@@ -0,0 +1,27 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/trajectoryConfiguration',
method: 'post',
data
})
}
export function del(ids) {
return request({
url: 'api/trajectoryConfiguration/',
method: 'delete',
data: ids
})
}
export function edit(data) {
return request({
url: 'api/trajectoryConfiguration',
method: 'put',
data
})
}
export default { add, edit, del }

View File

@@ -0,0 +1,27 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/trajectoryBackground',
method: 'post',
data
})
}
export function del(ids) {
return request({
url: 'api/trajectoryBackground/',
method: 'delete',
data: ids
})
}
export function edit(data) {
return request({
url: 'api/trajectoryBackground',
method: 'put',
data
})
}
export default { add, edit, del }

Binary file not shown.

After

Width:  |  Height:  |  Size: 972 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 KiB

View File

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

View File

@@ -0,0 +1,575 @@
<template>
<div class="task-stats-container">
<!-- 左侧任务完成情况统计 -->
<el-card class="main-card left-card">
<div slot="header">
<div class="card-header">
<h2>任务完成情况统计</h2>
<el-button
type="primary"
icon="el-icon-refresh"
:loading="refreshing"
@click="refreshData"
>
刷新数据
</el-button>
</div>
</div>
<!-- 统计卡片区域 -->
<div class="stats-overview">
<el-card v-for="(item, index) in todayOverview" :key="index" class="stat-card">
<div class="stat-item">
<div class="stat-label">{{ item.label }}</div>
<div class="stat-value">{{ item.value }}</div>
<div class="stat-desc">{{ item.desc }}</div>
</div>
</el-card>
</div>
<!-- 图表区域 - 垂直布局 -->
<div class="charts-container">
<!-- 当天任务完成情况饼图 -->
<el-card class="chart-card">
<div slot="header">当天任务完成情况</div>
<div class="chart-wrapper">
<v-chart :options="pieChartOptions" autoresize />
</div>
</el-card>
<!-- 7天任务完成情况柱状图 -->
<el-card class="chart-card">
<div slot="header">近7天任务完成情况趋势</div>
<div class="chart-wrapper">
<v-chart :options="barChartOptions" autoresize />
</div>
</el-card>
</div>
</el-card>
<!-- 右侧show-track -->
<el-card class="main-card right-card">
<shou-track src="" />
</el-card>
</div>
</template>
<script>
import { Card, Button, Tag } from 'element-ui'
import VChart from 'vue-echarts'
import 'echarts/lib/chart/pie'
import 'echarts/lib/chart/bar'
import 'echarts/lib/component/tooltip'
import 'echarts/lib/component/legend'
import 'echarts/lib/component/grid'
import 'echarts/lib/component/axis'
import taskStatsApi from '@/api/acs/dashboard/dashboard'
import ShouTrack from './showTrack/index.vue'
export default {
name: 'TaskStats',
components: {
'el-card': Card,
'el-button': Button,
'v-chart': VChart,
'shou-track': ShouTrack
},
data() {
return {
// 数据状态
pieData: [],
barData: [],
todayOverview: [],
refreshing: false,
// 饼图配置
pieChartOptions: {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'horizontal',
bottom: 2,
itemWidth: 6,
itemHeight: 3,
textStyle: {
fontSize: 8
}
},
series: [
{
name: '任务状态',
type: 'pie',
radius: ['30%', '60%'],
center: ['50%', '50%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 8,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '16',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: []
}
]
},
// 柱状图配置
barChartOptions: {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: function(params) {
let result = params[0].axisValue + '<br/>'
let total = 0
params.forEach(param => {
result += param.marker + param.seriesName + ': ' + param.value + '<br/>'
total += param.value
})
result += '<b>总计: ' + total + '</b>'
return result
}
},
legend: {
data: ['就绪', '执行中', '完成', '取消', '强制完成', '异常'],
top: 2,
left: 'center',
itemWidth: 6,
itemHeight: 3,
textStyle: {
fontSize: 8
},
pageButtonItemGap: 2,
pageButtonGap: 2,
pageButtonPosition: 'end',
pageIconSize: 8
},
grid: {
left: '5%',
right: '5%',
top: '12%',
bottom: '5%',
containLabel: true
},
xAxis: {
type: 'category',
data: [],
axisLabel: {
rotate: 0,
fontSize: 9,
interval: 0
},
axisTick: {
alignWithLabel: true
}
},
yAxis: {
type: 'value',
name: '任务数量',
nameTextStyle: {
fontSize: 9
},
minInterval: 1,
axisLabel: {
fontSize: 9
}
},
series: [
{
name: '就绪',
type: 'bar',
stack: 'total',
emphasis: { focus: 'series' },
data: [],
itemStyle: { color: this.getStatusColor('就绪') },
barWidth: '50%',
label: {
show: false
}
},
{
name: '执行中',
type: 'bar',
stack: 'total',
emphasis: { focus: 'series' },
data: [],
itemStyle: { color: this.getStatusColor('执行中') },
barWidth: '50%',
label: {
show: false
}
},
{
name: '完成',
type: 'bar',
stack: 'total',
emphasis: { focus: 'series' },
data: [],
itemStyle: { color: this.getStatusColor('完成') },
barWidth: '50%',
label: {
show: false
}
},
{
name: '取消',
type: 'bar',
stack: 'total',
emphasis: { focus: 'series' },
data: [],
itemStyle: { color: this.getStatusColor('取消') },
barWidth: '50%',
label: {
show: false
}
},
{
name: '强制完成',
type: 'bar',
stack: 'total',
emphasis: { focus: 'series' },
data: [],
itemStyle: { color: this.getStatusColor('强制完成') },
barWidth: '50%',
label: {
show: false
}
},
{
name: '异常',
type: 'bar',
stack: 'total',
emphasis: { focus: 'series' },
data: [],
itemStyle: { color: this.getStatusColor('异常') },
barWidth: '50%',
label: {
show: false
}
}
]
}
}
},
created() {
this.refreshData()
this.setTime()
},
mounted() {
// 确保组件挂载后图表能够正确渲染
this.$nextTick(() => {
console.log('组件已挂载,重新渲染图表')
this.updatePieChart()
this.updateBarChart()
})
},
methods: {
setTime() {
this.timer = setInterval(() => {
this.fetchData()
}, 30000)
},
// 刷新数据
refreshData() {
this.refreshing = true
this.fetchData().finally(() => {
this.refreshing = false
})
},
// 获取所有统计数据
fetchData() {
return Promise.all([
this.fetchTodayStats(),
this.fetchSevenDaysStats()
])
},
// 获取当天任务统计
fetchTodayStats() {
return taskStatsApi.getTodayTaskStats()
.then(response => {
if (response.code === 200) {
this.pieData = response.data
this.updatePieChart()
this.updateTodayOverview()
} else {
this.$message.error('获取当天任务统计失败: ' + response.message)
}
})
.catch(error => {
this.$message.error('获取当天任务统计失败: ' + error.message)
})
},
// 获取近7天任务统计
fetchSevenDaysStats() {
return taskStatsApi.getSevenDaysTaskStats()
.then(response => {
if (response.code === 200) {
this.barData = response.data
console.log('7天数据:', this.barData) // 调试信息
this.updateBarChart()
} else {
this.$message.error('获取近7天任务统计失败: ' + response.message)
// 如果没有数据,显示默认数据
this.barData = []
this.updateBarChart()
}
})
.catch(error => {
console.error('获取7天数据失败:', error)
this.$message.error('获取近7天任务统计失败: ' + error.message)
// 如果请求失败,显示默认数据
this.barData = []
this.updateBarChart()
})
},
// 更新饼图数据
updatePieChart() {
console.log('更新饼图数据:', this.pieData)
this.pieChartOptions.series[0].data = this.pieData.map(item => ({
name: this.formatStatusName(item.status),
value: item.count,
itemStyle: { color: this.getStatusColor(item.status) }
}))
console.log('饼图配置:', this.pieChartOptions)
},
// 更新柱状图数据
updateBarChart() {
console.log('更新柱状图数据:', this.barData)
if (!this.barData || this.barData.length === 0) {
// 如果没有数据显示默认的7天日期
const defaultDates = []
for (let i = 6; i >= 0; i--) {
const date = new Date()
date.setDate(date.getDate() - i)
defaultDates.push(this.formatDate(date))
}
this.barChartOptions.xAxis.data = defaultDates
// 设置默认的空数据
this.barChartOptions.series.forEach(series => {
series.data = [0, 0, 0, 0, 0, 0, 0]
})
console.log('柱状图配置(默认数据):', this.barChartOptions)
return
}
// 格式化日期显示
this.barChartOptions.xAxis.data = this.barData.map(item => {
if (typeof item.date === 'string') {
// 如果是字符串格式,尝试格式化
const date = new Date(item.date)
if (!isNaN(date.getTime())) {
return this.formatDate(date)
}
}
return item.date
})
// 提取各类任务数据,确保数据为数字类型
this.barChartOptions.series[0].data = this.barData.map(item => parseInt(item.readyCount) || 0)
this.barChartOptions.series[1].data = this.barData.map(item => parseInt(item.busyCount) || 0)
this.barChartOptions.series[2].data = this.barData.map(item => parseInt(item.finishedCount) || 0)
this.barChartOptions.series[3].data = this.barData.map(item => parseInt(item.cancelCount) || 0)
this.barChartOptions.series[4].data = this.barData.map(item => parseInt(item.forcedCompletionCount) || 0)
this.barChartOptions.series[5].data = this.barData.map(item => parseInt(item.errorCount) || 0)
console.log('柱状图配置(实际数据):', this.barChartOptions)
},
// 更新今日概览数据
updateTodayOverview() {
// 移除统计卡片,设置为空数组
this.todayOverview = []
// 如果没有数据,设置默认的饼图数据
if (!this.pieData || this.pieData.length === 0) {
this.pieChartOptions.series[0].data = [
{ name: '已完成', value: 0, itemStyle: { color: this.getStatusColor('完成') }},
{ name: '进行中', value: 0, itemStyle: { color: this.getStatusColor('执行中') }},
{ name: '待处理', value: 0, itemStyle: { color: this.getStatusColor('就绪') }}
]
}
},
// 格式化状态名称(英文转中文)
formatStatusName(status) {
const statusMap = {
'COMPLETED': '已完成',
'IN_PROGRESS': '进行中',
'PENDING': '待处理',
'FAILED': '失败'
}
return statusMap[status] || status
},
// 获取状态对应的颜色
getStatusColor(status) {
const colorMap = {
'就绪': '#0d2fda',
'执行中': '#faad14',
'完成': '#41ff00',
'取消': '#971112',
'强制完成': '#339e0f',
'异常': '#ff0000'
}
return colorMap[status] || '#8c8c8c'
},
// 格式化日期显示
formatDate(date) {
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
return `${month}-${day}`
}
}
}
</script>
<style scoped>
.task-stats-container {
padding: 20px;
background-color: #f5f7fa;
height: 100vh;
display: flex;
gap: 20px;
overflow: hidden;
}
.main-card {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
height: 35px;
flex-shrink: 0;
padding: 0 10px;
}
.card-header h2 {
font-size: 16px;
margin: 0;
}
.stats-overview {
display: none;
}
.stat-card {
transition: all 0.3s ease;
}
.stat-card:hover {
transform: translateY(-5px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
}
.stat-item {
padding: 15px;
text-align: center;
}
.stat-label {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.stat-value {
font-size: 24px;
font-weight: bold;
margin-bottom: 4px;
color: #1890ff;
}
.stat-desc {
font-size: 12px;
color: #999;
}
.charts-container {
display: flex;
flex-direction: column;
gap: 8px;
flex: 1;
overflow: hidden;
}
.chart-card {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
}
.chart-wrapper {
flex: 1;
min-height: 200px;
width: 100%;
height: 100%;
position: relative;
}
.chart-wrapper .echarts {
width: 100% !important;
height: 100% !important;
min-height: 200px !important;
}
/* 响应式设计 */
@media (max-width: 1024px) {
.task-stats-container {
flex-direction: column;
}
}
@media (max-width: 768px) {
.stats-overview {
grid-template-columns: 1fr 1fr;
}
.chart-wrapper {
min-height: 0;
}
}
@media (max-width: 480px) {
.stats-overview {
grid-template-columns: 1fr;
}
.card-header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
}
</style>

View File

@@ -0,0 +1,226 @@
<template>
<div class="container">
<div class="canvas-wrap" :style="getBgStyle(configInfo)">
<div v-for="e in carData" :key="e.device_code" class="car-wrap" :style="getCarWrapStyle(e)">
<div class="car_img" :style="getCarImgStyle(e)" />
<div class="car_name">{{ e.configuration_code }}</div>
</div>
</div>
<!-- 缩放指示器 -->
<div class="zoom-indicator">
<span>{{ Math.round(scale * 100) }}%</span>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import apiTrack from '@/views/dashboard/showTrack/track'
// const fullDomain = window.location.protocol + '//' + window.location.host
export default {
data() {
return {
configInfo: { refresh_time: 1000 },
carData: [],
deviceData: [],
carCoor: [],
scale: 1, // 缩放比例
minScale: 0.5, // 最小缩放比例
maxScale: 3 // 最大缩放比例
}
},
computed: {
...mapGetters([
'baseApi'
])
},
async created() {
const res1 = await apiTrack.trackEdit()
this.configInfo = [...res1.content][0]
const res2 = await apiTrack.carEdit()
this.carData = [...res2.content]
this.deviceData = this.carData.map(e => { return { device_code: e.device_code } })
this._queryDevice(this.deviceData)
this.timerFun(this._queryDevice, this.configInfo.refresh_time * 1000)()
},
mounted() {
// 添加鼠标滚轮事件监听
this.$el.addEventListener('wheel', this.handleWheel, { passive: false })
},
beforeDestroy() {
// 移除事件监听
this.$el.removeEventListener('wheel', this.handleWheel)
},
methods: {
timerFun(f, time) {
const _this = this
return function backFun() {
clearTimeout(_this.intervalId)
_this.intervalId = setTimeout(function() {
f(_this.deviceData)
backFun()
}, time)
}
},
_queryDevice(arr) {
apiTrack.queryDevice(arr).then(res => {
this.carCoor = [...res.data]
})
},
getBgStyle(obj) {
if (obj.image_code) {
const width = this.$el.offsetWidth * obj.zoom_ratio
const height = parseFloat(obj.max_y) * (this.$el.offsetWidth / this.$el.offsetWidth) * obj.zoom_ratio
// const fullImageUrl = require('@/assets/images/agvBackground.jpg')
const fullImageUrl = `${this.baseApi}/file/图片/${obj.image_code}`
return {
width: `${width}px`,
height: `${height}px`,
backgroundImage: `url(${fullImageUrl})`,
backgroundSize: '100% 100%',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
transform: `scale(${this.scale})`,
transformOrigin: 'center center'
}
}
},
getPositionById(code) {
const position = this.carCoor.find((p) => p.device_code === code)
return position || { x: 0, y: 0 }
},
getCarWrapStyle(el) {
const width = el.image_width
const height = el.image_height
const position = this.getPositionById(el.device_code)
// 计算缩放比例,保持小车位置相对正确
// 由于背景图片现在是100%宽度,需要调整小车位置的比例
const scaleRatio = 1 // 保持原始比例,小车位置基于原始坐标系统
const x = (position.x * parseFloat(this.configInfo.zoom_ratio) * scaleRatio * this.$el.offsetWidth / this.configInfo.max_x)
const y = position.y * parseFloat(this.configInfo.zoom_ratio) * scaleRatio
let sty = {}
switch (this.configInfo.coordinate_origin) {
case '1':
sty = {
width: `${width * this.scale}px`,
height: `${height * this.scale}px`,
left: `${x}px`,
top: `${y}px`
}
break
case '2':
sty = {
width: `${width * this.scale}px`,
height: `${height * this.scale}px`,
left: `${x}px`,
bottom: `${y}px`
}
break
case '3 ':
sty = {
width: `${width * this.scale}px`,
height: `${height * this.scale}px`,
right: `${x}px`,
top: `${y}px`
}
break
case '4':
sty = {
width: `${width * this.scale}px`,
height: `${height * this.scale}px`,
right: `${x}px`,
bottom: `${y}px`
}
break
default:
sty = {}
}
return sty
},
getCarImgStyle(el) {
// const fullImageUrl = require('@/assets/images/agvCar.png')
const fullImageUrl = `${this.baseApi}/file/图片/${el.image_code}`
const position = this.getPositionById(el.device_code)
const angle = parseFloat(position.angle)
return {
backgroundImage: `url(${fullImageUrl})`,
transform: `rotate(-${angle}deg)`
}
},
// 处理鼠标滚轮事件
handleWheel(event) {
event.preventDefault()
const delta = event.deltaY > 0 ? -0.1 : 0.1
const newScale = Math.max(this.minScale, Math.min(this.maxScale, this.scale + delta))
if (newScale !== this.scale) {
this.scale = newScale
// 强制更新视图
this.$forceUpdate()
}
}
}
}
</script>
<style scoped>
.container {
overflow: auto;
scrollbar-width: none;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
width: 100%;
}
.container::-webkit-scrollbar {
display: none;
}
.canvas-wrap {
position: relative;
background: #f3f3f3 top center / 100% auto no-repeat;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
transition: transform 0.1s ease-out;
}
.car-wrap {
position: absolute;
transform: translate(-50%, -50%);
transform-origin: center;
}
.car_img {
width: 100%;
height: 100%;
background: top center / 100% auto no-repeat;
}
.car_name {
position: absolute;
width: 100%;
bottom: -20px;
font-size: 12px;
color: #fff;
line-height: 16px;
display: flex;
justify-content: center;
white-space: nowrap;
}
.zoom-indicator {
position: absolute;
top: 104px;
right: 21px;
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 5px 10px;
border-radius: 4px;
font-size: 12px;
z-index: 1000;
}
</style>

View File

@@ -0,0 +1,87 @@
import request from '@/utils/request'
export function trackEdit(data) {
return request({
url: 'api/trajectoryBackground/queryAllEnable',
method: 'post',
data
})
}
export function carEdit(data) {
return request({
url: 'api/trajectoryConfiguration/queryAllEnable',
method: 'post',
data
})
}
export function queryDevice(data) {
return request({
url: 'api/trajectory/queryDevice',
method: 'post',
data
})
}
// export function trackEdit() {
// const res = {
// code: 200,
// msg: 'ok',
// content: [{
// background_code: '图片',
// image_code: 'aaa.jpg',
// zoom_ratio: '0.5',
// coordinate_origin: '4',
// max_x: '1000',
// max_y: '1000',
// refresh_time: 10000
// }]
// }
// return new Promise((resolve, reject) => {
// resolve(res)
// })
// }
// export function carEdit() {
// const res = {
// code: 200,
// msg: 'ok',
// content: [{
// configuration_code: '1号agv',
// image_code: 'aaa.jpg',
// image_width: '20',
// image_height: '20',
// device_code: '1',
// x: 100,
// y: 100,
// angle: 90
// }, {
// configuration_code: '2号agv',
// image_code: 'aaa.jpg',
// image_width: '20',
// image_height: '20',
// device_code: '2',
// x: 200,
// y: 200,
// angle: 0
// }]
// }
// return new Promise((resolve, reject) => {
// resolve(res)
// })
// }
// export function queryDevice() {
// const res = {
// data: [{ device_code: '1', x: Math.floor(Math.random() * 10) * 100, y: Math.floor(Math.random() * 10) * 100, angle: 90 }, { device_code: '2', x: Math.floor(Math.random() * 10) * 100, y: Math.floor(Math.random() * 10) * 100, angle: 0 }],
// data1: [{ device_code: '1', x: 100, y: 100, angle: 90 }, { device_code: '2', x: 200, y: 200, angle: 0 }],
// status: '200',
// message: '操作成功'
// }
// return new Promise((resolve, reject) => {
// resolve(res)
// })
// }
export default { trackEdit, carEdit, queryDevice }

View File

@@ -0,0 +1,270 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<!--如果想在工具栏加入更多按钮可以使用插槽方式 slot = 'left' or 'right'-->
<!-- <div v-if="crud.props.searchToggle">-->
<!-- &lt;!&ndash; 搜索 &ndash;&gt;-->
<!-- <el-select-->
<!-- v-model="query.device_type"-->
<!-- class="filter-item"-->
<!-- clearable-->
<!-- placeholder="设备类型"-->
<!-- size="small"-->
<!-- style="width: 450px"-->
<!-- >-->
<!-- <el-option v-for="item in device_types" :key="item.id" :label="item.label" :value="item.value" />-->
<!-- </el-select>-->
<!-- <rrOperation />-->
<!-- </div>-->
<crudOperation :permission="permission" />
<!--表单组件-->
<el-dialog
:before-close="crud.cancelCU"
:close-on-click-modal="false"
:title="crud.status.title"
:visible.sync="crud.status.cu > 0"
width="500px"
>
<el-form ref="form" :model="form" :rules="rules" label-width="80px" size="small">
<el-form-item label="图片上传" prop="image_code">
<el-upload
:action="imagesUploadApi"
:before-upload="beforeUpload_u"
:file-list="fileList"
:headers="headers"
:limit="1"
list-type="picture"
:on-success="handleSuccess"
class="upload-demo"
multiple
>
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
</el-form-item>
<el-form-item label="图标高度" prop="image_height">
<el-input v-model="form.image_height" style="width: 370px;" />
</el-form-item>
<el-form-item label="图标宽度" prop="image_width">
<el-input v-model="form.image_width" style="width: 370px;" />
</el-form-item>
<el-form-item label="配置名称" prop="configuration_code">
<el-input v-model="form.configuration_code" style="width: 370px;" />
</el-form-item>
<el-form-item label="绑定设备" prop="device_code">
<el-select v-model="form.device_code" placeholder="请选择">
<el-option
v-for="item in options"
:key="item.device_code"
:label="item.device_name"
:value="item.device_code"
/>
</el-select>
</el-form-item>
<el-form-item label="是否启用">
<el-switch
v-model="form.is_active"
active-value="1"
inactive-value="0"
active-color="#13ce66"
inactive-color="#ff4949"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table
ref="table"
v-loading="crud.loading"
:data="crud.data"
size="small"
style="width: 100%;"
@selection-change="crud.selectionChangeHandler"
>
<el-table-column type="selection" width="55" />
<el-table-column label="图标" prop="image_name">
<template slot-scope="{row}">
<el-image
:preview-src-list="[baseApi + '/file/图片/' + row.image_code]"
:src=" baseApi + '/file/图片/' + row.image_code"
class="el-avatar"
fit="contain"
lazy
>
<div slot="error">
<i class="el-icon-document" />
</div>
</el-image>
</template>
</el-table-column>
<el-table-column label="配置名称" prop="configuration_code" />
<el-table-column label="图标编号" prop="image_code" />
<el-table-column label="图标高度" prop="image_height" />
<el-table-column label="图标宽度" prop="image_width" />
<el-table-column label="绑定设备编号" prop="device_code" />
<el-table-column label="是否启用" prop="is_active">
<template slot-scope="{row}">
<span v-if="row.is_active === '1'"><el-tag type="success">启用</el-tag></span>
<span v-else><el-tag type="warning">未启用</el-tag></span>
</template>
</el-table-column>
<el-table-column prop="create_name" label="创建人" />
<el-table-column prop="create_time" label="创建时间" min-width="135" />
<el-table-column
v-permission="['admin','stageImage:edit','stageImage:del']"
align="center"
label="操作"
width="150px"
>
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
/>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</div>
</div>
</template>
<script>
import CRUD, { crud, form, header, presenter } from '@crud/crud'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import pagination from '@crud/Pagination'
import { mapGetters } from 'vuex'
import { getToken } from '@/utils/auth'
import { selectDeviceList } from '@/api/acs/device/device'
import crudTrajectoryConfiguration from '@/api/trajectory/configuration/trajectory_configuration'
const defaultForm = {
configuration_uuid: null,
configuration_code: null,
image_code: null,
image_name: null,
image_height: null,
image_width: null,
device_code: null,
is_active: '1',
is_delete: null,
create_by: null,
create_time: null,
update_by: null,
update_time: null
}
export default {
name: 'TrajectoryConfiguration',
components: { pagination, crudOperation, udOperation },
mixins: [presenter(), header(), form(defaultForm), crud()],
cruds() {
return CRUD({
title: '设备配置',
url: 'api/trajectoryConfiguration',
idField: 'configuration_uuid',
sort: 'configuration_uuid,desc',
crudMethod: { ...crudTrajectoryConfiguration },
optShow: {
add: true,
edit: true,
del: true
}
})
},
data() {
return {
value: [],
permission: {
add: ['admin', 'TrajectoryConfiguration:add'],
edit: ['admin', 'TrajectoryConfiguration:edit'],
del: ['admin', 'TrajectoryConfiguration:del']
},
headers: { 'Authorization': getToken() },
rules: {
configuration_code: [
{ required: true, message: '配置名称不能为空', trigger: 'blur' }
],
image_code: [
{ required: true, message: '图标编号不能为空', trigger: 'blur' }
],
image_name: [
{ required: true, message: '图标名称不能为空', trigger: 'blur' }
],
image_height: [
{ required: true, message: '图标高度不能为空', trigger: 'blur' }
],
image_width: [
{ required: true, message: '图标宽度不能为空', trigger: 'blur' }
],
device_code: [
{ required: true, message: '绑定设备不能为空', trigger: 'blur' }
]
},
fileList: [],
options: []
}
},
computed: {
...mapGetters([
'imagesUploadApi',
'baseApi'
])
},
mounted() {
// 获取所有设备
selectDeviceList().then(data => {
console.log(data)
this.options = data
})
},
methods: {
// 钩子在获取表格数据之前执行false 则代表不获取数据
[CRUD.HOOK.beforeRefresh]() {
return true
},
[CRUD.HOOK.afterSubmit]() {
this.fileList = []
}, [CRUD.HOOK.beforeToAdd]() {
this.fileList = []
},
[CRUD.HOOK.afterToEdit]() {
let new_lst = []
const image_code = this.form.image_code
const image_name = this.baseApi + '/file/图片/' + image_code
new_lst = [{ name: image_code, url: image_name }]
this.fileList = new_lst
},
beforeUpload_u(file) {
const testmsg = file.name.substring(file.name.lastIndexOf('.') + 1)
const extension = (testmsg === 'png' || testmsg === 'jpg' || testmsg === 'svg')
let bool = false
if (extension) {
bool = true
} else {
bool = false
}
if (!extension) {
this.$confirm(`上传文件只能是png/jpg/svg格式!`)
}
return bool
},
handleSuccess(response) {
console.log(response)
this.form.image_code = response.real_name
this.form.image_name = response.path
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,270 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<!--如果想在工具栏加入更多按钮可以使用插槽方式 slot = 'left' or 'right'-->
<crudOperation :permission="permission" />
<!--表单组件-->
<el-dialog
:close-on-click-modal="false"
:before-close="crud.cancelCU"
:visible.sync="crud.status.cu > 0"
:title="crud.status.title"
width="500px"
>
<el-form ref="form" :model="form" :rules="rules" label-width="80px" size="small">
<el-form-item label="图片上传" prop="image_code">
<el-upload
:action="imagesUploadApi"
:before-upload="beforeUpload_u"
:file-list="fileList"
:headers="headers"
:limit="1"
list-type="picture"
:on-success="handleSuccess"
class="upload-demo"
multiple
>
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
</el-form-item>
<el-form-item label="背景图名称" prop="background_code">
<el-input v-model="form.background_code" style="width: 370px;" />
</el-form-item>
<el-form-item label="缩放比例" prop="zoom_ratio">
<el-input v-model="form.zoom_ratio" style="width: 370px;" />
</el-form-item>
<el-form-item label="坐标原点" prop="coordinate_origin">
<el-select v-model="form.coordinate_origin" placeholder="请选择">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="X坐标最大值" prop="max_x">
<el-input v-model="form.max_x" style="width: 370px;" />
</el-form-item>
<el-form-item label="Y坐标最大值" prop="max_y">
<el-input v-model="form.max_y" style="width: 370px;" />
</el-form-item>
<el-form-item label="刷新时间()" prop="refresh_time">
<el-input v-model="form.refresh_time" style="width: 370px;" />
</el-form-item>
<el-form-item label="图片描述" prop="remark">
<el-input v-model="form.remark" style="width: 370px;" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">{{ $t('auto.common.Cancel') }}</el-button>
<el-button :loading="crud.cu === 2" type="primary" @click="crud.submitCU">{{ $t('auto.common.Confirm') }}</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table
ref="table"
v-loading="crud.loading"
:data="crud.data"
size="small"
style="width: 100%;"
@selection-change="crud.selectionChangeHandler"
>
<el-table-column type="selection" width="55" />
<el-table-column label="背景图" prop="image_name">
<template slot-scope="{row}">
<el-image
:preview-src-list="[baseApi + '/file/图片/' + row.image_code]"
:src=" baseApi + '/file/图片/' + row.image_code"
class="el-avatar"
fit="contain"
lazy
>
<div slot="error">
<i class="el-icon-document" />
</div>
</el-image>
</template>
</el-table-column>
<el-table-column prop="background_code" label="背景图名称" />
<el-table-column prop="image_code" label="背景图片编码" />
<el-table-column prop="image_name" label="背景图片地址" />
<el-table-column prop="zoom_ratio" label="缩放比例" />
<el-table-column prop="coordinate_origin" label="坐标原点">
<template slot-scope="{row}">
<span v-if="options.length!==0">{{ getCoordinateOrigin(row.coordinate_origin) }}</span>
</template>
</el-table-column>
<el-table-column prop="max_x" label="X坐标最大值" />
<el-table-column prop="max_y" label="Y坐标最大值" />
<el-table-column prop="refresh_time" label="刷新时间()" />
<el-table-column label="是否启用" prop="is_active">
<template slot-scope="{row}">
<span v-if="row.is_active === '1'"><el-tag type="success">启用</el-tag></span>
<span v-else><el-tag type="warning">未启用</el-tag></span>
</template>
</el-table-column>
<el-table-column prop="create_name" label="创建人" />
<el-table-column prop="create_time" label="创建时间" min-width="135" />
<el-table-column label="备注" prop="remark" />
<el-table-column v-permission="['admin','stage:edit','stage:del']" :label="$t('task.select.Operation')" width="150px" align="center">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
/>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</div>
</div>
</template>
<script>
import crudTrajectory from '@/api/trajectory/trajectory_background'
import { get } from '@/views/system/dict/dictDetail'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import pagination from '@crud/Pagination'
// import i18n from '@/i18n'
import { mapGetters } from 'vuex'
import { getToken } from '@/utils/auth'
const defaultForm = {
background_uuid: null,
background_code: null,
image_code: null,
image_name: null,
zoom_ratio: null,
coordinate_origin: null,
max_x: null,
max_y: null,
refresh_time: null,
is_active: null,
is_delete: null,
create_by: null,
create_time: null,
update_by: null,
update_time: null,
remark: null
}
export default {
name: 'Trajectory',
components: { pagination, crudOperation, udOperation },
mixins: [presenter(), header(), form(defaultForm), crud()],
cruds() {
return CRUD({
title: '轨迹背景图',
url: 'api/trajectoryBackground',
idField: 'background_uuid',
sort: 'background_uuid,desc',
crudMethod: { ...crudTrajectory },
optShow: {
add: true,
edit: true,
del: true
}
})
},
data() {
return {
permission: {
add: ['admin', 'Trajectory:add'],
edit: ['admin', 'Trajectory:edit'],
del: ['admin', 'Trajectory:del']
},
rules: {
background_code: [
{ required: true, message: '背景图名称不能为空', trigger: 'blur' }
],
image_code: [
{ required: true, message: '背景图不能为空', trigger: 'blur' }
],
zoom_ratio: [
{ required: true, message: '缩放比例不能为空,范围0-1', trigger: 'blur' }
],
coordinate_origin: [
{ required: true, message: '坐标原点不能为空', trigger: 'blur' }
],
max_x: [
{ required: true, message: 'X坐标最大值不能为空', trigger: 'blur' }
],
max_y: [
{ required: true, message: 'Y坐标最大值不能为空', trigger: 'blur' }
],
refresh_time: [
{ required: true, message: '刷新时间不能为空', trigger: 'blur' }
]
},
fileList: [],
headers: { 'Authorization': getToken() },
options: []
}
},
mounted() {
// 获取坐标原点字典
get('coordinate_origin').then(data => {
console.log(data)
this.options = data.content
})
},
computed: {
...mapGetters([
'imagesUploadApi',
'baseApi'
])
},
methods: {
// 钩子在获取表格数据之前执行false 则代表不获取数据
[CRUD.HOOK.beforeRefresh]() {
return true
},
[CRUD.HOOK.afterSubmit]() {
this.fileList = []
}, [CRUD.HOOK.beforeToAdd]() {
this.fileList = []
},
[CRUD.HOOK.afterToEdit]() {
let new_lst = []
const image_code = this.form.image_code
const image_name = this.baseApi + '/file/图片/' + image_code
new_lst = [{ name: image_code, url: image_name }]
this.fileList = new_lst
},
beforeUpload_u(file) {
const testmsg = file.name.substring(file.name.lastIndexOf('.') + 1)
const extension = (testmsg === 'png' || testmsg === 'jpg' || testmsg === 'svg')
let bool = false
if (extension) {
bool = true
} else {
bool = false
}
if (!extension) {
this.$confirm(`上传文件只能是png/jpg/svg格式!`)
}
return bool
},
handleSuccess(response) {
console.log(response)
this.form.image_code = response.real_name
this.form.image_name = response.path
},
getCoordinateOrigin(value) {
const item = this.options.find(item => item.value === value)
return item ? item.label : ''
}
}
}
</script>
<style scoped>
</style>