写入功能完成
This commit is contained in:
@@ -6,8 +6,8 @@
|
||||
<div class="title-text">
|
||||
<span>Performance Report</span>
|
||||
<!-- 实绩报表 -->
|
||||
<small>Please select time range to view data</small>
|
||||
<!-- 请选择时间范围后查看数据 -->
|
||||
<small>Displaying data for the last month by default</small>
|
||||
<!-- 默认展示近一个月数据 -->
|
||||
</div>
|
||||
</div>
|
||||
<el-tag size="small" effect="plain">Performance</el-tag>
|
||||
@@ -70,14 +70,14 @@
|
||||
<!-- 时间范围 -->
|
||||
<span class="info-range">{{ displayTimeRange }}</span>
|
||||
</div>
|
||||
<el-button type="text" icon="el-icon-refresh-left" @click="handleReturn">Reselect Time</el-button>
|
||||
<el-button type="text" icon="el-icon-refresh-left" @click="handleReturn">Change Time Range</el-button>
|
||||
<!-- 重新选择时间 -->
|
||||
</div>
|
||||
|
||||
<!-- 实绩报表 -->
|
||||
<ReportBody
|
||||
v-loading="loading"
|
||||
title="Performance Report"
|
||||
<!-- 实绩报表 -->
|
||||
|
||||
:summary="reportSummary"
|
||||
:dataset="reportDetail"
|
||||
:columns="columns"
|
||||
@@ -97,7 +97,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
hasSelectedTime: false,
|
||||
hasSelectedTime: true, // Directly show report content
|
||||
timeRange: {
|
||||
startTime: '',
|
||||
endTime: ''
|
||||
@@ -123,7 +123,31 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.setDefaultTimeRangeAndFetch()
|
||||
},
|
||||
methods: {
|
||||
setDefaultTimeRangeAndFetch() {
|
||||
const end = new Date()
|
||||
const start = new Date()
|
||||
start.setMonth(start.getMonth() - 1)
|
||||
|
||||
this.timeRange.endTime = this.formatDate(end)
|
||||
this.timeRange.startTime = this.formatDate(start)
|
||||
|
||||
this.fetchReportData()
|
||||
},
|
||||
|
||||
formatDate(date) {
|
||||
const year = date.getFullYear()
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||||
const day = date.getDate().toString().padStart(2, '0')
|
||||
const hours = date.getHours().toString().padStart(2, '0')
|
||||
const minutes = date.getMinutes().toString().padStart(2, '0')
|
||||
const seconds = date.getSeconds().toString().padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
},
|
||||
|
||||
handleTimeReset() {
|
||||
this.timeRange = {
|
||||
startTime: '',
|
||||
@@ -146,9 +170,7 @@ export default {
|
||||
},
|
||||
handleReturn() {
|
||||
this.hasSelectedTime = false
|
||||
this.handleTimeReset()
|
||||
this.reportSummary = []
|
||||
this.reportDetail = []
|
||||
// Do not reset time, so user can see the previous selection
|
||||
},
|
||||
async fetchReportData() {
|
||||
this.loading = true
|
||||
@@ -157,7 +179,7 @@ export default {
|
||||
startTime: this.timeRange.startTime,
|
||||
endTime: this.timeRange.endTime,
|
||||
pageNum: 1,
|
||||
pageSize: 1000
|
||||
pageSize: 1000 // Assuming we fetch all data for the report
|
||||
}
|
||||
const res = await getReportSummary(queryParams)
|
||||
this.reportSummary = [
|
||||
@@ -309,4 +331,4 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<div class="title-text">
|
||||
<span>Roll Change Report</span>
|
||||
<!-- 换辊报表 -->
|
||||
<small>Please select time range to view data</small>
|
||||
<!-- 请选择时间范围后查看数据 -->
|
||||
<small>Displaying data for the last month by default</small>
|
||||
<!-- 默认展示近一个月数据 -->
|
||||
</div>
|
||||
</div>
|
||||
<el-tag size="small" effect="plain">Roll Change</el-tag>
|
||||
@@ -23,8 +23,6 @@
|
||||
<div class="selector-content">
|
||||
<el-form :inline="true" size="small">
|
||||
<el-form-item label="Start Time">
|
||||
<!-- 开始时间 -->
|
||||
<!-- Select start time / 选择开始时间 -->
|
||||
<el-date-picker
|
||||
v-model="timeRange.startTime"
|
||||
type="datetime"
|
||||
@@ -35,8 +33,6 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="End Time">
|
||||
<!-- 结束时间 -->
|
||||
<!-- Select end time / 选择结束时间 -->
|
||||
<el-date-picker
|
||||
v-model="timeRange.endTime"
|
||||
type="datetime"
|
||||
@@ -50,9 +46,7 @@
|
||||
<el-button type="primary" :disabled="!canQuery" icon="el-icon-search" @click="handleTimeConfirm">
|
||||
Query
|
||||
</el-button>
|
||||
<!-- 查询 -->
|
||||
<el-button icon="el-icon-refresh" @click="handleTimeReset">Reset</el-button>
|
||||
<!-- 重置 -->
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
@@ -62,22 +56,17 @@
|
||||
<div class="content-toolbar">
|
||||
<div class="toolbar-info">
|
||||
<span class="info-label">Report Type:</span>
|
||||
<!-- 报表类型 -->
|
||||
<span class="info-value">Roll Change Report</span>
|
||||
<!-- 换辊报表 -->
|
||||
<span class="info-divider">|</span>
|
||||
<span class="info-label">Time Range:</span>
|
||||
<!-- 时间范围 -->
|
||||
<span class="info-range">{{ displayTimeRange }}</span>
|
||||
</div>
|
||||
<el-button type="text" icon="el-icon-refresh-left" @click="handleReturn">Reselect Time</el-button>
|
||||
<!-- 重新选择时间 -->
|
||||
<el-button type="text" icon="el-icon-refresh-left" @click="handleReturn">Change Time Range</el-button>
|
||||
</div>
|
||||
|
||||
<ReportBody
|
||||
v-loading="loading"
|
||||
title="Roll Change Report"
|
||||
<!-- 换辊报表 -->
|
||||
:summary="reportSummary"
|
||||
:dataset="reportDetail"
|
||||
:columns="columns"
|
||||
@@ -97,7 +86,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
hasSelectedTime: false,
|
||||
hasSelectedTime: true,
|
||||
timeRange: {
|
||||
startTime: '',
|
||||
endTime: ''
|
||||
@@ -114,16 +103,38 @@ export default {
|
||||
displayTimeRange() {
|
||||
if (!this.canQuery) return '-'
|
||||
return `${this.timeRange.startTime} to ${this.timeRange.endTime}`
|
||||
// 至
|
||||
},
|
||||
overviewInfo() {
|
||||
return {
|
||||
reportLabel: 'Roll Change Report', // 换辊报表
|
||||
reportLabel: 'Roll Change Report',
|
||||
rangeText: this.displayTimeRange
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.setDefaultTimeRangeAndFetch()
|
||||
},
|
||||
methods: {
|
||||
setDefaultTimeRangeAndFetch() {
|
||||
const end = new Date()
|
||||
const start = new Date()
|
||||
start.setMonth(start.getMonth() - 1)
|
||||
|
||||
this.timeRange.endTime = this.formatDateTime(end)
|
||||
this.timeRange.startTime = this.formatDateTime(start)
|
||||
|
||||
this.fetchReportData()
|
||||
},
|
||||
formatDateTime(date) {
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
},
|
||||
|
||||
handleTimeReset() {
|
||||
this.timeRange = {
|
||||
startTime: '',
|
||||
@@ -132,11 +143,11 @@ export default {
|
||||
},
|
||||
async handleTimeConfirm() {
|
||||
if (!this.canQuery) {
|
||||
this.$message.warning('Please select complete time range') // 请选择完整的时间范围
|
||||
this.$message.warning('Please select complete time range')
|
||||
return
|
||||
}
|
||||
if (new Date(this.timeRange.startTime) > new Date(this.timeRange.endTime)) {
|
||||
this.$message.warning('Start time cannot be later than end time') // 开始时间不能晚于结束时间
|
||||
this.$message.warning('Start time cannot be later than end time')
|
||||
return
|
||||
}
|
||||
this.hasSelectedTime = true
|
||||
@@ -144,9 +155,6 @@ export default {
|
||||
},
|
||||
handleReturn() {
|
||||
this.hasSelectedTime = false
|
||||
this.handleTimeReset()
|
||||
this.reportSummary = []
|
||||
this.reportDetail = []
|
||||
},
|
||||
async fetchReportData() {
|
||||
this.loading = true
|
||||
@@ -162,26 +170,26 @@ export default {
|
||||
...item
|
||||
}))
|
||||
this.columns = [
|
||||
{ label: 'Change ID', prop: 'changeid' }, // 换辊号
|
||||
{ label: 'Roll ID', prop: 'rollid' }, // 轧辊号
|
||||
{ label: 'Type', prop: 'type' }, // 类型
|
||||
{ label: 'Position', prop: 'position' }, // 位置
|
||||
{ label: 'Diameter', prop: 'diameter' }, // 直径
|
||||
{ label: 'Roughness', prop: 'rough' }, // 粗糙度
|
||||
{ label: 'Crown', prop: 'crown' }, // 凸度
|
||||
{ label: 'Rolled Length', prop: 'rolledLength' }, // 轧制长度
|
||||
{ label: 'Rolled Weight', prop: 'rolledWeight' }, // 轧制重量
|
||||
{ label: 'Rolled Count', prop: 'rolledCount' }, // 轧制次数
|
||||
{ label: 'Install Time', prop: 'instalTime' }, // 上线时间
|
||||
{ label: 'Uninstall Time', prop: 'deinstalTime' } // 下线时间
|
||||
{ label: 'Change ID', prop: 'changeid' },
|
||||
{ label: 'Roll ID', prop: 'rollid' },
|
||||
{ label: 'Type', prop: 'type' },
|
||||
{ label: 'Position', prop: 'position' },
|
||||
{ label: 'Diameter', prop: 'diameter' },
|
||||
{ label: 'Roughness', prop: 'rough' },
|
||||
{ label: 'Crown', prop: 'crown' },
|
||||
{ label: 'Rolled Length', prop: 'rolledLength' },
|
||||
{ label: 'Rolled Weight', prop: 'rolledWeight' },
|
||||
{ label: 'Rolled Count', prop: 'rolledCount' },
|
||||
{ label: 'Install Time', prop: 'instalTime' },
|
||||
{ label: 'Uninstall Time', prop: 'deinstalTime' }
|
||||
]
|
||||
this.reportSummary = [
|
||||
{ label: 'Data Count', value: this.reportDetail.length }, // 数据条数
|
||||
{ label: 'Time Range', value: this.displayTimeRange } // 时间范围
|
||||
{ label: 'Data Count', value: this.reportDetail.length },
|
||||
{ label: 'Time Range', value: this.displayTimeRange }
|
||||
]
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
this.$message.error('Failed to fetch report data') // 获取报表数据失败
|
||||
this.$message.error('Failed to fetch report data')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
@@ -300,4 +308,4 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<div class="title-text">
|
||||
<span>Stoppage Report</span>
|
||||
<!-- 停机报表 -->
|
||||
<small>Please select time range to view data</small>
|
||||
<!-- 请选择时间范围后查看数据 -->
|
||||
<small>Displaying data for the last month by default</small>
|
||||
<!-- 默认展示近一个月数据 -->
|
||||
</div>
|
||||
</div>
|
||||
<el-tag size="small" effect="plain">Stoppage</el-tag>
|
||||
@@ -18,13 +18,10 @@
|
||||
<div class="selector-header">
|
||||
<i class="el-icon-calendar"></i>
|
||||
<span>Please select query date range</span>
|
||||
<!-- 请选择查询日期范围 -->
|
||||
</div>
|
||||
<div class="selector-content">
|
||||
<el-form :inline="true" size="small">
|
||||
<el-form-item label="Start Date">
|
||||
<!-- 开始日期 -->
|
||||
<!-- Select start date / 选择开始日期 -->
|
||||
<el-date-picker
|
||||
v-model="timeRange.startTime"
|
||||
type="date"
|
||||
@@ -35,8 +32,6 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="End Date">
|
||||
<!-- 结束日期 -->
|
||||
<!-- Select end date / 选择结束日期 -->
|
||||
<el-date-picker
|
||||
v-model="timeRange.endTime"
|
||||
type="date"
|
||||
@@ -50,9 +45,7 @@
|
||||
<el-button type="primary" :disabled="!canQuery" icon="el-icon-search" @click="handleTimeConfirm">
|
||||
Query
|
||||
</el-button>
|
||||
<!-- 查询 -->
|
||||
<el-button icon="el-icon-refresh" @click="handleTimeReset">Reset</el-button>
|
||||
<!-- 重置 -->
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
@@ -62,22 +55,17 @@
|
||||
<div class="content-toolbar">
|
||||
<div class="toolbar-info">
|
||||
<span class="info-label">Report Type:</span>
|
||||
<!-- 报表类型 -->
|
||||
<span class="info-value">Stoppage Report</span>
|
||||
<!-- 停机报表 -->
|
||||
<span class="info-divider">|</span>
|
||||
<span class="info-label">Time Range:</span>
|
||||
<!-- 时间范围 -->
|
||||
<span class="info-range">{{ displayTimeRange }}</span>
|
||||
</div>
|
||||
<el-button type="text" icon="el-icon-refresh-left" @click="handleReturn">Reselect Time</el-button>
|
||||
<!-- 重新选择时间 -->
|
||||
<el-button type="text" icon="el-icon-refresh-left" @click="handleReturn">Change Date Range</el-button>
|
||||
</div>
|
||||
|
||||
<ReportBody
|
||||
v-loading="loading"
|
||||
title="Stoppage Report"
|
||||
<!-- 停机报表 -->
|
||||
:summary="reportSummary"
|
||||
:dataset="reportDetail"
|
||||
:columns="columns"
|
||||
@@ -97,7 +85,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
hasSelectedTime: false,
|
||||
hasSelectedTime: true,
|
||||
timeRange: {
|
||||
startTime: '',
|
||||
endTime: ''
|
||||
@@ -105,14 +93,14 @@ export default {
|
||||
reportSummary: [],
|
||||
reportDetail: [],
|
||||
columns: [
|
||||
{ label: 'Coil ID', prop: 'coilid' }, // 钢卷号
|
||||
{ label: 'Shift', prop: 'shift' }, // 班次号
|
||||
{ label: 'Group', prop: 'area' }, // 组
|
||||
{ label: 'Start Time', prop: 'startDate' }, // 开始时间
|
||||
{ label: 'End Time', prop: 'endDate' }, // 结束时间
|
||||
{ label: 'Duration [min]', prop: 'duration' }, // 持续时间[分钟]
|
||||
{ label: 'Stoppage Type', prop: 'unit' }, // 停机类型
|
||||
{ label: 'Remark', prop: 'remark' } // 备注
|
||||
{ label: 'Coil ID', prop: 'coilid' },
|
||||
{ label: 'Shift', prop: 'shift' },
|
||||
{ label: 'Group', prop: 'area' },
|
||||
{ label: 'Start Time', prop: 'startDate' },
|
||||
{ label: 'End Time', prop: 'endDate' },
|
||||
{ label: 'Duration [min]', prop: 'duration' },
|
||||
{ label: 'Stoppage Type', prop: 'unit' },
|
||||
{ label: 'Remark', prop: 'remark' }
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -123,16 +111,35 @@ export default {
|
||||
displayTimeRange() {
|
||||
if (!this.canQuery) return '-'
|
||||
return `${this.timeRange.startTime} to ${this.timeRange.endTime}`
|
||||
// 至
|
||||
},
|
||||
overviewInfo() {
|
||||
return {
|
||||
reportLabel: 'Stoppage Report', // 停机报表
|
||||
reportLabel: 'Stoppage Report',
|
||||
rangeText: this.displayTimeRange
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.setDefaultDateRangeAndFetch()
|
||||
},
|
||||
methods: {
|
||||
setDefaultDateRangeAndFetch() {
|
||||
const end = new Date()
|
||||
const start = new Date()
|
||||
start.setMonth(start.getMonth() - 1)
|
||||
|
||||
this.timeRange.endTime = this.formatDate(end)
|
||||
this.timeRange.startTime = this.formatDate(start)
|
||||
|
||||
this.fetchReportData()
|
||||
},
|
||||
formatDate(date) {
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
},
|
||||
|
||||
handleTimeReset() {
|
||||
this.timeRange = {
|
||||
startTime: '',
|
||||
@@ -141,11 +148,11 @@ export default {
|
||||
},
|
||||
async handleTimeConfirm() {
|
||||
if (!this.canQuery) {
|
||||
this.$message.warning('Please select complete date range') // 请选择完整的日期范围
|
||||
this.$message.warning('Please select complete date range')
|
||||
return
|
||||
}
|
||||
if (new Date(this.timeRange.startTime) > new Date(this.timeRange.endTime)) {
|
||||
this.$message.warning('Start date cannot be later than end date') // 开始日期不能晚于结束日期
|
||||
this.$message.warning('Start date cannot be later than end date')
|
||||
return
|
||||
}
|
||||
this.hasSelectedTime = true
|
||||
@@ -153,9 +160,6 @@ export default {
|
||||
},
|
||||
handleReturn() {
|
||||
this.hasSelectedTime = false
|
||||
this.handleTimeReset()
|
||||
this.reportSummary = []
|
||||
this.reportDetail = []
|
||||
},
|
||||
async fetchReportData() {
|
||||
this.loading = true
|
||||
@@ -171,14 +175,14 @@ export default {
|
||||
|
||||
const res2 = await getStoppageSummary(queryParams)
|
||||
this.reportSummary = [
|
||||
{ label: 'Statistics Range', value: this.displayTimeRange }, // 统计区间
|
||||
{ label: 'Stoppage Count', value: res2.data?.[0] || 0 }, // 停机次数
|
||||
{ label: 'Stoppage Duration [hours]', value: res2.data?.[1] || 0 }, // 停机时长[小时]
|
||||
{ label: 'Operation Rate', value: (res2.data?.[2] || 0) + ' %' } // 作业率
|
||||
{ label: 'Statistics Range', value: this.displayTimeRange },
|
||||
{ label: 'Stoppage Count', value: res2.data?.[0] || 0 },
|
||||
{ label: 'Stoppage Duration [hours]', value: res2.data?.[1] || 0 },
|
||||
{ label: 'Operation Rate', value: (res2.data?.[2] || 0) + ' %' }
|
||||
]
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
this.$message.error('Failed to fetch report data') // 获取报表数据失败
|
||||
this.$message.error('Failed to fetch report data')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
@@ -257,7 +261,7 @@ export default {
|
||||
|
||||
.selector-content {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
max-width: 640px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
}
|
||||
@@ -297,4 +301,4 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
<div v-loading="loading" class="card-grid-container">
|
||||
<el-row :gutter="20">
|
||||
<el-col
|
||||
v-for="plan in plans"
|
||||
:key="plan.id"
|
||||
v-for="setup in setups"
|
||||
:key="setup.ID"
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="8"
|
||||
@@ -30,21 +30,38 @@
|
||||
>
|
||||
<el-card class="parameter-card" shadow="hover">
|
||||
<div slot="header" class="card-header">
|
||||
<!-- Steel Grade only / 只展示钢种,不展示计划 -->
|
||||
<span class="card-title">Steel Grade: {{ plan.steelGrade || '-' }}</span>
|
||||
<div class="card-header-content">
|
||||
<!-- 头部信息:参考 setup/panels 卡片表头“多字段拼接”的风格 -->
|
||||
<div class="card-title-row">
|
||||
<span class="card-title">
|
||||
Plan ID: {{ setup.planid || '-' }}
|
||||
| Coil ID: {{ setup.coilid || '-' }}
|
||||
| Steel Grade: {{ setup.steelGrade || setup.grade || '-' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="card-subtitle">
|
||||
<span>Entry Thickness: {{ setup.entryThick || '-' }}</span>
|
||||
<span>Entry Width: {{ setup.entryWidth || '-' }}</span>
|
||||
<span>Entry Weight: {{ setup.entryWeight || '-' }}</span>
|
||||
<span>Entry Length: {{ setup.entryLength || '-' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="card-subtitle">
|
||||
<span>TL Elongation: {{ setup.tlElong || '-' }}</span>
|
||||
<span>TM Roll Force: {{ setup.tmRollforce || '-' }}</span>
|
||||
<span>TM Bending Force: {{ setup.tmBendforce || '-' }}</span>
|
||||
<span v-if="setup.updateTime">Updated: {{ formatTime(setup.updateTime) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
<span v-if="lastSuccess && lastSuccess.lastSendTime" class="last-send-time">
|
||||
<i class="el-icon-time"></i>
|
||||
Last Sent: {{ formatTime(lastSuccess.lastSendTime) }}
|
||||
</span>
|
||||
|
||||
<el-button
|
||||
type="primary"
|
||||
size="mini"
|
||||
icon="el-icon-s-promotion"
|
||||
@click="handleSend(plan)"
|
||||
:loading="plan.sending"
|
||||
@click="handleSend(setup)"
|
||||
:loading="setup.sending"
|
||||
>
|
||||
Send
|
||||
</el-button>
|
||||
@@ -52,8 +69,8 @@
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<!-- 可编辑表单:值 + OPC点位(点位可先为空,后续协商配置) -->
|
||||
<el-form :model="plan.params" label-position="top" size="mini">
|
||||
<!-- 可编辑表单 -->
|
||||
<el-form :model="setup.params" label-position="top" size="mini">
|
||||
<el-row :gutter="10">
|
||||
<el-col
|
||||
v-for="item in driveFields"
|
||||
@@ -62,17 +79,9 @@
|
||||
>
|
||||
<el-form-item :label="item.label">
|
||||
<el-input
|
||||
v-model="plan.params[item.key]"
|
||||
v-model="setup.params[item.key]"
|
||||
:placeholder="getPlaceholder(item.key)"
|
||||
/>
|
||||
|
||||
<!-- OPC address input / OPC点位输入框(可先不填) -->
|
||||
<el-input
|
||||
v-model="driveAddress[item.key]"
|
||||
size="mini"
|
||||
class="addr-input"
|
||||
placeholder="OPC address (optional)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -82,8 +91,8 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div v-if="plans.length === 0 && !loading" class="empty-data">
|
||||
<el-empty description="No Data"></el-empty>
|
||||
<div v-if="setups.length === 0 && !loading" class="empty-data">
|
||||
<el-empty description="No Setup History Data"></el-empty>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -91,59 +100,38 @@
|
||||
|
||||
<script>
|
||||
// Import APIs / 引入接口
|
||||
import { listPlan } from '@/api/l2/plan'
|
||||
import { listSetup } from '@/api/business/setup'
|
||||
import { createSendJob, executeSendJob } from '@/api/l2/sendJob'
|
||||
import { getLastSuccess } from '@/api/l2/sendTemplate'
|
||||
|
||||
// Drive fields definition (English UI, Chinese comments) / 传动字段定义(英文界面,中文注释)
|
||||
// 说明:key 必须与 setupForm 字段一致(来自 plan/components/setupForm.vue)
|
||||
// Drive + Plan fields definition (show effect first; OPC address can be edited later)
|
||||
// 传动 + 计划字段定义(先把效果做出来;OPC点位后续可协商配置)
|
||||
const DRIVE_FIELDS = [
|
||||
// ---- Drive tension / 传动张力 ----
|
||||
{ key: 'porTension', label: 'Pay-off Reel Tension', source: 'setup' },
|
||||
{ key: 'celTension', label: 'Entry Loop Tension', source: 'setup' },
|
||||
{ key: 'cleanTension', label: 'Cleaning Section Tension', source: 'setup' },
|
||||
{ key: 'furTension', label: 'Furnace Zone Tension', source: 'setup' },
|
||||
{ key: 'towerTension', label: 'Cooling Tower Tension', source: 'setup' },
|
||||
{ key: 'tmNoneTension', label: 'TM No Tension', source: 'setup' },
|
||||
{ key: 'tmEntryTension', label: 'TM Entry Tension', source: 'setup' },
|
||||
{ key: 'tmExitTension', label: 'TM Exit Tension', source: 'setup' },
|
||||
{ key: 'tlNoneTension', label: 'TL No Tension', source: 'setup' },
|
||||
{ key: 'tlExitTension', label: 'TL Exit Tension', source: 'setup' },
|
||||
{ key: 'coatTension', label: 'Post-treatment Tension', source: 'setup' },
|
||||
{ key: 'cxlTension', label: 'Exit Loop Tension', source: 'setup' },
|
||||
{ key: 'trTension', label: 'Take-up Reel Tension', source: 'setup' },
|
||||
{ key: 'porTension', label: 'Pay-off Reel Tension' },
|
||||
{ key: 'celTension', label: 'Entry Loop Tension' },
|
||||
{ key: 'cleanTension', label: 'Cleaning Section Tension' },
|
||||
{ key: 'furTension', label: 'Furnace Zone Tension' },
|
||||
{ key: 'towerTension', label: 'Cooling Tower Tension' },
|
||||
{ key: 'tmNoneTension', label: 'TM No Tension' },
|
||||
{ key: 'tmEntryTension', label: 'TM Entry Tension' },
|
||||
{ key: 'tmExitTension', label: 'TM Exit Tension' },
|
||||
{ key: 'tlNoneTension', label: 'TL No Tension' },
|
||||
{ key: 'tlExitTension', label: 'TL Exit Tension' },
|
||||
{ key: 'coatTension', label: 'Post-treatment Tension' },
|
||||
{ key: 'cxlTension', label: 'Exit Loop Tension' },
|
||||
{ key: 'trTension', label: 'Take-up Reel Tension' },
|
||||
|
||||
// ---- TL / TM setup ----
|
||||
{ key: 'tlElong', label: 'TL Elongation', source: 'setup' },
|
||||
{ key: 'tlLvlMesh1', label: 'TL Leveling Roll Mesh 1', source: 'setup' },
|
||||
{ key: 'tlLvlMesh2', label: 'TL Leveling Roll Mesh 2', source: 'setup' },
|
||||
{ key: 'tlAcbMesh', label: 'TL Anti-crossbow Mesh', source: 'setup' },
|
||||
{ key: 'tlElong', label: 'TL Elongation' },
|
||||
{ key: 'tlLvlMesh1', label: 'TL Leveling Roll Mesh 1' },
|
||||
{ key: 'tlLvlMesh2', label: 'TL Leveling Roll Mesh 2' },
|
||||
{ key: 'tlAcbMesh', label: 'TL Anti-crossbow Mesh' },
|
||||
|
||||
{ key: 'tmBendforce', label: 'TM Bending Force', source: 'setup' },
|
||||
{ key: 'tmAcrMesh', label: 'TM Anti-crimping Roll Mesh', source: 'setup' },
|
||||
{ key: 'tmBrMesh', label: 'TM Anti-tremor Roll Mesh', source: 'setup' },
|
||||
{ key: 'tmRollforce', label: 'TM Roll Force', source: 'setup' },
|
||||
|
||||
// ---- Plan (from listPlan response) / 计划参数(来自 listPlan 返回)----
|
||||
{ key: 'entryWidth', label: 'Entry Width', source: 'plan' },
|
||||
{ key: 'entryThick', label: 'Entry Thick', source: 'plan' },
|
||||
{ key: 'entryWeight', label: 'Entry Weight', source: 'plan' },
|
||||
{ key: 'entryLength', label: 'Entry Length', source: 'plan' },
|
||||
|
||||
{ key: 'steelGrade', label: 'Steel Grade', source: 'plan' },
|
||||
|
||||
{ key: 'spmElongation', label: 'SPM Elongation', source: 'plan' },
|
||||
{ key: 'spmRollforce', label: 'SPM Roll Force', source: 'plan' },
|
||||
{ key: 'spmBendingForce', label: 'SPM Bending Force', source: 'plan' },
|
||||
|
||||
{ key: 'yieldPoint', label: 'Yield Point', source: 'plan' }
|
||||
{ key: 'tmBendforce', label: 'TM Bending Force' },
|
||||
{ key: 'tmAcrMesh', label: 'TM Anti-crimping Roll Mesh' },
|
||||
{ key: 'tmBrMesh', label: 'TM Anti-tremor Roll Mesh' },
|
||||
{ key: 'tmRollforce', label: 'TM Roll Force' }
|
||||
]
|
||||
|
||||
// OPC address mapping / OPC点位映射
|
||||
// 说明:此处后续可协商配置;当前允许在页面上编辑(默认可为空)
|
||||
const DRIVE_ADDRESS = {
|
||||
porTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionPorBR1',
|
||||
celTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR3',
|
||||
@@ -176,9 +164,8 @@ export default {
|
||||
return {
|
||||
loading: false,
|
||||
lastSuccess: null,
|
||||
plans: [],
|
||||
setups: [], // Changed from 'plans' to 'setups'
|
||||
driveFields: DRIVE_FIELDS,
|
||||
// 可编辑的 OPC 点位(默认从常量拷贝;你也可以后续改成从后端/本地存储加载)
|
||||
driveAddress: { ...DRIVE_ADDRESS }
|
||||
}
|
||||
},
|
||||
@@ -189,59 +176,37 @@ export default {
|
||||
async reload() {
|
||||
this.loading = true
|
||||
try {
|
||||
// last success for DRIVE / 获取传动上次成功
|
||||
// 1. Get last success for DRIVE
|
||||
const lastRes = await getLastSuccess('DRIVE')
|
||||
this.lastSuccess = lastRes && lastRes.code === 200 ? lastRes.data : null
|
||||
|
||||
// plans / 获取计划
|
||||
const planRes = await listPlan({ status: 'NEW,READY,ONLINE,PRODUCING' })
|
||||
// 兼容后端返回结构:既可能是 {rows: []} 也可能是 {data: []}
|
||||
const planList = (planRes && (planRes.rows || planRes.data)) || []
|
||||
|
||||
const tasks = planList.map(async (p) => {
|
||||
let setup = {}
|
||||
try {
|
||||
const setupRes = await listSetup({ coilid: p.coilid, planid: p.planid })
|
||||
setup = (setupRes.rows && setupRes.rows.length > 0) ? setupRes.rows[0] : {}
|
||||
} catch (e) {
|
||||
setup = {}
|
||||
}
|
||||
// 2. Get setup history list (instead of plans)
|
||||
const setupRes = await listSetup({ pageNum: 1, pageSize: 20 }) // Fetch latest 20 for example
|
||||
const setupList = (setupRes && setupRes.rows) || []
|
||||
|
||||
// 3. Map setup list to display data
|
||||
this.setups = setupList.map(s => {
|
||||
const params = {}
|
||||
this.driveFields.forEach(f => {
|
||||
const fromPlan = p ? p[f.key] : undefined
|
||||
const fromSetup = setup ? setup[f.key] : undefined
|
||||
const fromSetup = s ? s[f.key] : undefined
|
||||
const fromLast = this.lastSuccess?.values?.[f.key]
|
||||
|
||||
// 优先级:setup(如果字段来自setup) / plan(如果字段来自plan) -> lastSuccess -> ''
|
||||
if (f.source === 'plan') {
|
||||
if (fromPlan !== undefined && fromPlan !== null && String(fromPlan) !== '') {
|
||||
params[f.key] = String(fromPlan)
|
||||
} else if (fromLast !== undefined && fromLast !== null) {
|
||||
params[f.key] = String(fromLast)
|
||||
} else {
|
||||
params[f.key] = ''
|
||||
}
|
||||
// Priority: current setup value > last success value > ''
|
||||
if (fromSetup !== undefined && fromSetup !== null && String(fromSetup) !== '') {
|
||||
params[f.key] = String(fromSetup)
|
||||
} else if (fromLast !== undefined && fromLast !== null) {
|
||||
params[f.key] = String(fromLast)
|
||||
} else {
|
||||
if (fromSetup !== undefined && fromSetup !== null && String(fromSetup) !== '') {
|
||||
params[f.key] = String(fromSetup)
|
||||
} else if (fromLast !== undefined && fromLast !== null) {
|
||||
params[f.key] = String(fromLast)
|
||||
} else {
|
||||
params[f.key] = ''
|
||||
}
|
||||
params[f.key] = ''
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
...p,
|
||||
setup,
|
||||
...s,
|
||||
params,
|
||||
sending: false
|
||||
}
|
||||
})
|
||||
|
||||
this.plans = await Promise.all(tasks)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
this.$message.error('Load failed')
|
||||
@@ -255,11 +220,11 @@ export default {
|
||||
this.$message.info('No last success data')
|
||||
return
|
||||
}
|
||||
this.plans.forEach(plan => {
|
||||
this.setups.forEach(setup => {
|
||||
this.driveFields.forEach(f => {
|
||||
const v = this.lastSuccess.values[f.key]
|
||||
if (v !== undefined) {
|
||||
this.$set(plan.params, f.key, String(v))
|
||||
this.$set(setup.params, f.key, String(v))
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -277,41 +242,41 @@ export default {
|
||||
return new Date(t).toLocaleString()
|
||||
},
|
||||
|
||||
handleSend(plan) {
|
||||
handleSend(setup) {
|
||||
this.$confirm(
|
||||
`Confirm to send parameters for Steel Grade [${plan.steelGrade || '-'}]?`,
|
||||
`Confirm to send parameters for Coil [${setup.coilid || '-'}]?`,
|
||||
'Warning',
|
||||
{
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => this.doSend(plan)).catch(() => {})
|
||||
).then(() => this.doSend(setup)).catch(() => {})
|
||||
},
|
||||
|
||||
async doSend(plan) {
|
||||
plan.sending = true
|
||||
async doSend(setup) {
|
||||
setup.sending = true
|
||||
try {
|
||||
const items = this.driveFields.map(f => ({
|
||||
paramCode: f.key,
|
||||
// OPC点位允许为空:为空则本次不发送该字段(先做效果,后续再配置)
|
||||
address: this.driveAddress[f.key],
|
||||
valueRaw: String(plan.params[f.key] || ''),
|
||||
address: this.driveAddress[f.key], // OPC address can be empty
|
||||
valueRaw: String(setup.params[f.key] || ''),
|
||||
setTime: new Date()
|
||||
})).filter(it => !!it.address)
|
||||
})).filter(it => !!it.address) // Filter out items without an address
|
||||
|
||||
if (!items.length) {
|
||||
this.$message.warning('OPC点位为空:当前没有可发送的字段(请先在输入框里填写点位)')
|
||||
this.$message.warning('OPC addresses are not configured. Nothing to send.')
|
||||
return
|
||||
}
|
||||
|
||||
const dto = {
|
||||
deviceName: 'CGL_LINE_1',
|
||||
bizKey: setup.coilid, // Use coilid as business key
|
||||
groups: [
|
||||
{
|
||||
groupNo: 1,
|
||||
groupType: 'DRIVE',
|
||||
groupName: `Drive/Plan Params for ${plan.steelGrade || ''}`,
|
||||
groupName: `Drive Params for ${setup.coilid || ''}`,
|
||||
items
|
||||
}
|
||||
]
|
||||
@@ -329,7 +294,7 @@ export default {
|
||||
console.error(e)
|
||||
this.$message.error(e.message || 'Send failed')
|
||||
} finally {
|
||||
plan.sending = false
|
||||
setup.sending = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -337,13 +302,15 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-title { margin-bottom: 20px; }
|
||||
.toolbar { margin-bottom: 20px; display:flex; flex-wrap:wrap; gap:8px; align-items:center; }
|
||||
.card-grid-container { min-height: 300px; }
|
||||
.card-col { margin-bottom: 20px; }
|
||||
.parameter-card .card-header { display:flex; justify-content:space-between; align-items:center; }
|
||||
.card-title { font-weight: 600; }
|
||||
.header-right { display:flex; align-items:center; }
|
||||
.card-header-content { flex-grow: 1; }
|
||||
.card-title-row { margin-bottom: 4px; }
|
||||
.card-title { font-weight: 600; font-size: 16px; }
|
||||
.card-subtitle { font-size: 12px; color: #909399; display: flex; gap: 12px; }
|
||||
.header-right { flex-shrink: 0; margin-left: 16px; }
|
||||
.last-send-time { font-size: 12px; color:#909399; margin-right:16px; }
|
||||
.empty-data { margin-top: 20px; }
|
||||
</style>
|
||||
|
||||
@@ -16,42 +16,51 @@
|
||||
|
||||
<!-- WebSocket Connection Status Indicator / WebSocket 连接状态指示 -->
|
||||
<div v-if="!isLoading" class="ws-status-bar">
|
||||
<!-- Set Values Floating Window Trigger / 设定值悬浮窗触发按钮 -->
|
||||
<el-tooltip content="Set Values" placement="top">
|
||||
<el-button type="text" class="ws-open-btn" @click="openSetValuesPanel">
|
||||
<i class="el-icon-setting"></i>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="Measurement Data" placement="top">
|
||||
<!-- 测量数据 -->
|
||||
<el-badge :is-dot="true" :type="socketStatus.measure ? 'success' : 'danger'">
|
||||
<i class="el-icon-data-analysis"></i>
|
||||
</el-badge>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="Position Tracking" placement="top">
|
||||
<!-- 位置追踪 -->
|
||||
<el-badge :is-dot="true" :type="socketStatus.position ? 'success' : 'danger'">
|
||||
<i class="el-icon-location"></i>
|
||||
</el-badge>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="Operation Signal" placement="top">
|
||||
<!-- 操作信号 -->
|
||||
<el-badge :is-dot="true" :type="socketStatus.signal ? 'success' : 'danger'">
|
||||
<i class="el-icon-bell"></i>
|
||||
</el-badge>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="Material Mapping" placement="top">
|
||||
<!-- 物料映射 -->
|
||||
<el-badge :is-dot="true" :type="socketStatus.matmap ? 'success' : 'danger'">
|
||||
<i class="el-icon-map-location"></i>
|
||||
</el-badge>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="Calculation Result" placement="top">
|
||||
<!-- 计算结果 -->
|
||||
<el-badge :is-dot="true" :type="socketStatus.calcSetup ? 'success' : 'danger'">
|
||||
<i class="el-icon-s-marketing"></i>
|
||||
</el-badge>
|
||||
</el-tooltip>
|
||||
<!-- Left: status icons -->
|
||||
<div class="ws-status-left">
|
||||
<!-- Set Values Floating Window Trigger / 设定值悬浮窗触发按钮 -->
|
||||
<el-tooltip content="Set Values" placement="top">
|
||||
<el-button type="text" class="ws-open-btn" @click="openSetValuesPanel">
|
||||
<i class="el-icon-setting"></i>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="Measurement Data" placement="top">
|
||||
<!-- 测量数据 -->
|
||||
<el-badge :is-dot="true" :type="socketStatus.measure ? 'success' : 'danger'">
|
||||
<i class="el-icon-data-analysis"></i>
|
||||
</el-badge>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="Position Tracking" placement="top">
|
||||
<!-- 位置追踪 -->
|
||||
<el-badge :is-dot="true" :type="socketStatus.position ? 'success' : 'danger'">
|
||||
<i class="el-icon-location"></i>
|
||||
</el-badge>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="Operation Signal" placement="top">
|
||||
<!-- 操作信号 -->
|
||||
<el-badge :is-dot="true" :type="socketStatus.signal ? 'success' : 'danger'">
|
||||
<i class="el-icon-bell"></i>
|
||||
</el-badge>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="Material Mapping" placement="top">
|
||||
<!-- 物料映射 -->
|
||||
<el-badge :is-dot="true" :type="socketStatus.matmap ? 'success' : 'danger'">
|
||||
<i class="el-icon-map-location"></i>
|
||||
</el-badge>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="Calculation Result" placement="top">
|
||||
<!-- 计算结果 -->
|
||||
<el-badge :is-dot="true" :type="socketStatus.calcSetup ? 'success' : 'danger'">
|
||||
<i class="el-icon-s-marketing"></i>
|
||||
</el-badge>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
|
||||
<!-- Right: quick links -->
|
||||
<div class="ws-status-right">
|
||||
<el-button type="primary" size="small" @click="$router.push('/furnace')">Furnace Setup</el-button>
|
||||
<el-button type="primary" size="small" @click="$router.push('/drive')">Drive Setup</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!isLoading" class="device-layout">
|
||||
@@ -2372,6 +2381,7 @@ import { getDriveSetupValue, getFurnaceSetupValue } from '@/api/l2/setupValue'
|
||||
/* WebSocket 状态栏 */
|
||||
.ws-status-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 15px;
|
||||
padding: 10px;
|
||||
background: #f5f7fa;
|
||||
@@ -2379,6 +2389,24 @@ import { getDriveSetupValue, getFurnaceSetupValue } from '@/api/l2/setupValue'
|
||||
margin-bottom: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ws-status-left {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ws-status-right {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ws-status-right .el-button {
|
||||
min-width: 140px;
|
||||
height: 34px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ws-status-bar i {
|
||||
font-size: 18px;
|
||||
|
||||
Reference in New Issue
Block a user