<template>
|
<view class="page">
|
|
<!-- 操作按钮 -->
|
<view class="button-row">
|
<button class="save-btn" @click="handleRefresh" :disabled="loadingForm || submitting">刷新</button>
|
<button class="expand-btn" @click="toggleExpand">
|
{{ isExpanded ? '收起' : '展开' }}
|
</button>
|
</view>
|
|
<!-- 刀具使用记录表格 -->
|
<view class="table-section" :class="{'expanded': isExpanded}">
|
<table class="styled-table">
|
<thead>
|
<tr>
|
<th style="width:7%">刀具编号</th>
|
<th style="width:12%">刀具名称</th>
|
<th style="width:10%">上刀时间</th>
|
<th class="num" style="width:7%">上刀计数</th>
|
<th style="width:10%">下刀时间</th>
|
<th class="num" style="width:7%">下刀计数</th>
|
<th class="num" style="width:7%">使用次数</th>
|
<th class="num" style="width:7%">使用上限</th>
|
<th class="num" style="width:7%">寿命比%</th>
|
<th class="num" style="width:7%">寿命比预警值</th>
|
<th style="width:7%">预警状态</th>
|
<th style="width:7%">刀具在机状态</th>
|
</tr>
|
</thead>
|
<tbody>
|
<tr v-for="(item, idx) in visibleRecords" :key="item.id" :class="{'row-odd': idx % 2 === 0}">
|
<td>{{ item.no }}</td>
|
<td class="left">{{ item.name }}</td>
|
<td>{{ item.upTime }}</td>
|
<td class="num">{{ item.upCount != null ? item.upCount : '' }}</td>
|
<td>{{ item.downTime }}</td>
|
<!-- 只有第一行显示 currentCjNum,其它行显示 downCount -->
|
<td class="num">
|
<template v-if="idx === 0">
|
<!-- 第一行:下机时不实时显示 currentCjNum,显示 downCount -->
|
<template v-if="!item.downTime">
|
{{ item.currentCjNum != null ? item.currentCjNum : '' }}
|
</template>
|
<template v-else>
|
{{ item.downCount != null ? item.downCount : '' }}
|
</template>
|
</template>
|
<template v-else>
|
{{ item.downCount != null ? item.downCount : '' }}
|
</template>
|
</td>
|
<!--使用次数-->
|
<td class="num">
|
<template v-if="idx === 0">
|
<!-- 第一行实时计算使用次数,若为下机则不实时显示 -->
|
<template v-if="!item.downTime">
|
{{item.currentCjNum != null && item.upCount != null ? (Number(item.currentCjNum) - Number(item.upCount)) : (item.useCount != null ? item.useCount : '') }}
|
</template>
|
<template v-else>
|
{{ item.useCount != null ? item.useCount : '' }}
|
</template>
|
</template>
|
<template v-else>
|
{{ item.useCount != null ? item.useCount : '' }}
|
</template>
|
</td>
|
<td class="num">{{ item.useLimit != null ? item.useLimit : '' }}</td>
|
<!--寿命比%-->
|
<td class="num">
|
<template v-if="idx === 0">
|
<!-- 第一行实时计算寿命比%,若为下机则不实时显示 -->
|
<template v-if="!item.downTime">
|
{{item.currentCjNum != null && item.upCount != null && item.useLimit != null && Number(item.useLimit) > 0 ? Math.round((Number(item.currentCjNum) - Number(item.upCount)) / Number(item.useLimit) * 100) + '%' : (item.lifePercent != null ? item.lifePercent : '') }}
|
</template>
|
<template v-else>
|
{{ item.lifePercent != null ? item.lifePercent : '' }}
|
</template>
|
</template>
|
<template v-else>
|
{{ item.lifePercent }}
|
</template>
|
</td>
|
<td class="num">{{ item.lifeWarn }}</td>
|
<td :class="item.warnStatus === '预警' ? 'warn-cell' : (item.warnStatus === '正常' ? 'ok-cell' : '')">
|
<span v-if="item.warnStatus === '预警'" class="warn-badge">警告</span>
|
<span v-else>{{ item.warnStatus }}</span>
|
</td>
|
<td>
|
{{ item.downTime ? '下机' : '在机' }}
|
</td>
|
</tr>
|
<tr v-if="!toolRecords.length">
|
<td colspan="12">暂无数据</td>
|
</tr>
|
<tr v-if="hasMoreRecords && !isExpanded">
|
<td colspan="12" class="more-records-tip">
|
<span>还有 {{ remainingRecords }} 条记录,点击"展开"按钮查看全部</span>
|
</td>
|
</tr>
|
</tbody>
|
</table>
|
</view>
|
</view>
|
</template>
|
|
<script>
|
export default {
|
data() {
|
return {
|
machineNo: '',//机台编码
|
workOrderNo: '',//工单号
|
selectedToolNo: '',
|
toolName: '',
|
useLimitInput: '',
|
lifeWarnInput: '', // 寿命比预警值原始输入
|
toolRecords: [],
|
loadingForm: false,
|
submitting: false,
|
workOrderCurrentCjNum: null, // 工单当前数采
|
isExpanded: false, // 是否展开表格
|
defaultVisibleRows: 3, // 默认显示的行数(一半高度)
|
};
|
},
|
computed: {
|
// 计算默认显示多少行(总行数的一半)
|
defaultRows() {
|
const total = this.toolRecords.length;
|
if (total <= 3) return total; // 如果总行数小于等于3,全部显示
|
return Math.max(3, Math.floor(total / 2)); // 最少显示3行,最多显示一半
|
},
|
// 当前可见的记录
|
visibleRecords() {
|
if (this.isExpanded) {
|
return this.toolRecords;
|
} else {
|
return this.toolRecords.slice(0, this.defaultRows);
|
}
|
},
|
// 是否还有更多记录
|
hasMoreRecords() {
|
return this.toolRecords.length > this.defaultRows;
|
},
|
// 剩余记录数
|
remainingRecords() {
|
return this.toolRecords.length - this.defaultRows;
|
}
|
},
|
methods: {
|
// 切换展开/收起
|
toggleExpand() {
|
this.isExpanded = !this.isExpanded;
|
},
|
|
// 刷新按钮处理方法
|
async handleRefresh() {
|
if (this.machineNo && this.workOrderNo) {
|
await this.fetchFormData();
|
await this.fetchDefaultToolFromWorkOrder();
|
// 刷新后赋值第一行 currentCjNum
|
if (this.toolRecords.length > 0) {
|
this.$set(this.toolRecords[0], 'currentCjNum', this.workOrderCurrentCjNum);
|
}
|
}
|
this.$showMessage('刷新完成');
|
},
|
|
// 自动带出工单刀具信息,并获取工单最新采集数
|
async fetchDefaultToolFromWorkOrder() {
|
if (!this.machineNo) return;
|
try {
|
const res = await this.$post({
|
url: '/Womdaa/GetWomdaasByShow',
|
data: JSON.stringify({ machineNo: this.machineNo }),
|
headers: { 'Content-Type': 'application/json' }
|
});
|
if (res.status === 0 && Array.isArray(res.data?.tbBillList) && res.data.tbBillList.length > 0) {
|
const order = res.data.tbBillList[0];
|
this.selectedToolNo = order.cutterId || order.cutteR_ID || '';
|
this.toolName = order.cutterName || order.cutteR_NAME || '';
|
// 获取工单最新采集数
|
this.workOrderCurrentCjNum = order.CurrentCjNum ?? order.currentCjNum ?? null;
|
// 自动填充寿命比预警值
|
if (order.modlLifeWorning !== undefined && order.modlLifeWorning !== null) {
|
const warn = Number(order.modlLifeWorning);
|
this.lifeWarnInput = warn <= 1 ? (warn * 100).toFixed(0) : warn.toFixed(0);
|
} else {
|
this.lifeWarnInput = '';
|
}
|
} else {
|
this.workOrderCurrentCjNum = null;
|
}
|
} catch (e) {
|
console.warn('自动带出工单刀具失败', e);
|
this.workOrderCurrentCjNum = null;
|
}
|
},
|
|
async fetchFormData() {
|
if (!this.workOrderNo || !this.machineNo) {
|
console.warn('工单号或机台号为空,跳过获取表单数据');
|
return;
|
}
|
this.loadingForm = true;
|
const payload = {
|
workOrderNo: this.workOrderNo.trim(),
|
machineNo: this.machineNo.trim()
|
};
|
try {
|
const res = await this.$post({
|
url: '/MesCutterLedger/GetFormData',
|
data: JSON.stringify(payload),
|
headers: { 'Content-Type': 'application/json' }
|
});
|
if (res.status !== 0) {
|
this.$showMessage(res.message || '获取表单数据失败');
|
this.toolRecords = [];
|
return;
|
}
|
const list = Array.isArray(res.data) ? res.data
|
: (res.data && res.data.tbBillList) ? res.data.tbBillList
|
: (res.data && res.data.data) ? res.data.data
|
: [];
|
|
const getField = (obj, ...keys) => {
|
for (const k of keys) if (obj?.[k] !== undefined && obj?.[k] !== null) return obj[k];
|
return null;
|
};
|
|
const mapped = (list || []).map(t => {
|
const upTimeRaw = getField(t, 'uP_TIME', 'UP_TIME', 'uPTime', 'UPTIME', 'UpTime');
|
const downTimeRaw = getField(t, 'dowN_TIME', 'DOWN_TIME', 'downTime', 'DOWNTIME');
|
const lifeWarnRaw = getField(t, 'lifE_WARN', 'LIFE_WARN', 'lifeWarn', 'LIFEWARN');
|
const currentCjNum = getField(t, 'currentCjNum', 'CURRENTCJNUM', 'CurrentCjNum');
|
|
const useCount = getField(t, 'usE_COUNT', 'USE_COUNT', 'useCount');
|
const useLimit = getField(t, 'usE_LIMIT', 'USE_LIMIT', 'useLimit');
|
|
let percent = '';
|
if (useCount != null && useLimit != null && !isNaN(useCount) && !isNaN(useLimit) && Number(useLimit) > 0) {
|
percent = ((Number(useCount) / Number(useLimit)) * 100).toFixed(0) + '%';
|
}
|
|
const parseNumber = v => {
|
if (v === null || v === undefined || v === '') return null;
|
const s = String(v).replace(/[,%%]/g, '').trim();
|
const n = parseFloat(s);
|
return Number.isFinite(n) ? n : null;
|
};
|
const formatPercent = n => {
|
if (n === null || n === undefined || isNaN(n)) return '';
|
if (n <= 1) return `${(n * 100).toFixed(0)}%`;
|
return `${Number(n).toFixed(0)}%`;
|
};
|
const lifeWarnNum = parseNumber(lifeWarnRaw);
|
|
let warnStatus = getField(t, 'status', 'STATUS') || '';
|
if (lifeWarnNum !== null && useCount != null && useLimit != null && !isNaN(useCount) && !isNaN(useLimit) && Number(useLimit) > 0) {
|
const percentNum = Number(useCount) / Number(useLimit);
|
warnStatus = (percentNum >= lifeWarnNum) ? '预警' : '正常';
|
} else {
|
warnStatus = warnStatus || '未知';
|
}
|
|
return {
|
id: getField(t, 'id', 'ID') || `${getField(t, 'cutteR_ID') || getField(t, 'CUTTER_ID') || ''}-${upTimeRaw || ''}`,
|
no: getField(t, 'cutteR_ID', 'CUTTER_ID', 'cutterId', 'no') || '',
|
name: getField(t, 'cutteR_NAME', 'CUTTER_NAME', 'cutterName', 'name') || '',
|
upTime: this.formatDateTime(upTimeRaw),
|
upCount: getField(t, 'uP_COUNT', 'UP_COUNT', 'upCount') ?? '',
|
downTime: this.formatDateTime(downTimeRaw),
|
downCount: getField(t, 'dowN_COUNT', 'DOWN_COUNT', 'downCount') ?? '',
|
useCount: useCount ?? '',
|
useLimit: useLimit ?? '',
|
lifePercent: percent,
|
lifeWarn: formatPercent(lifeWarnNum),
|
warnStatus,
|
currentCjNum
|
};
|
});
|
|
// 按上刀时间降序排序(越晚的越上面)
|
mapped.sort((a, b) => {
|
const parse = s => {
|
if (!s) return 0;
|
const year = new Date().getFullYear();
|
return new Date(`${year}-${s.replace(/-/g, '-')}:00`).getTime();
|
};
|
return parse(b.upTime) - parse(a.upTime);
|
});
|
|
this.toolRecords = mapped;
|
} catch (error) {
|
console.error('获取表单数据错误:', error);
|
this.$showMessage('获取数据失败,请检查网络连接');
|
this.toolRecords = [];
|
} finally {
|
this.loadingForm = false;
|
}
|
},
|
|
formatDateTime(dateTimeStr) {
|
if (!dateTimeStr) return '';
|
try {
|
const s = String(dateTimeStr).trim();
|
if (/^\d{10}$/.test(s)) {
|
const d = new Date(Number(s) * 1000);
|
return `${d.getMonth() + 1}-${d.getDate()} ${d.getHours()}:${String(d.getMinutes()).padStart(2, '0')}`;
|
}
|
if (/^\d{13}$/.test(s)) {
|
const d = new Date(Number(s));
|
return `${d.getMonth() + 1}-${d.getDate()} ${d.getHours()}:${String(d.getMinutes()).padStart(2, '0')}`;
|
}
|
const date = new Date(dateTimeStr);
|
if (!isNaN(date.getTime())) {
|
return `${date.getMonth() + 1}-${d.getDate()} ${date.getHours()}:${String(date.getMinutes()).padStart(2, '0')}`;
|
}
|
const match = String(dateTimeStr).match(/(\d{1,4}[-\/]\d{1,2}[-\/]\d{1,2}).*?(\d{1,2}:\d{2})/);
|
if (match) return `${match[1].replace(/-/g, '/').replace(/^\d{4}\//, (m) => m)} ${match[2]}`;
|
return String(dateTimeStr);
|
} catch {
|
return String(dateTimeStr);
|
}
|
}
|
},
|
mounted() {
|
this.machineNo = uni.getStorageSync('machineNo') || '';
|
this.workOrderNo = uni.getStorageSync('daa001') || '';
|
|
if (this.machineNo && this.workOrderNo) {
|
this.fetchFormData().then(async () => {
|
await this.fetchDefaultToolFromWorkOrder();
|
// 进入页面时赋值第一行 currentCjNum
|
if (this.toolRecords.length > 0) {
|
this.$set(this.toolRecords[0], 'currentCjNum', this.workOrderCurrentCjNum);
|
}
|
});
|
} else {
|
console.warn('机台号或工单号为空,无法获取表单数据');
|
}
|
}
|
};
|
</script>
|
|
<style scoped>
|
.page {
|
padding: 20px;
|
}
|
|
.button-row {
|
display: flex;
|
justify-content: center;
|
gap: 32px;
|
margin: 2vh 0;
|
}
|
|
.save-btn, .cancel-btn, .expand-btn {
|
width: 20%;
|
padding: 1.5vh;
|
background-color: #00A2E9;
|
color: white;
|
border: none;
|
text-align: center;
|
border-radius: 4px;
|
transition: all 0.3s;
|
font-size: 22px;
|
font-weight: 500;
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
}
|
|
.expand-btn {
|
background-color: #52c41a;
|
}
|
|
.expand-btn:hover {
|
background-color: #73d13d;
|
}
|
|
.expand-btn:active {
|
background-color: #389e0d;
|
}
|
|
.cancel-btn {
|
background-color: #f5f5f5;
|
color: #333;
|
}
|
|
.save-btn:hover {
|
background-color: #40a9ff;
|
}
|
|
.save-btn:active {
|
background-color: #096dd9;
|
}
|
|
.save-btn:disabled, .cancel-btn:disabled, .expand-btn:disabled {
|
opacity: 0.6;
|
cursor: not-allowed;
|
}
|
|
.table-section {
|
display: flex;
|
justify-content: center;
|
margin: 1vh 0;
|
overflow-x: auto;
|
width: 100%;
|
max-height: 300px; /* 默认高度,大约显示3-4行 */
|
overflow-y: hidden;
|
transition: max-height 0.3s ease;
|
border: 1px solid #f0f0f0;
|
border-radius: 8px;
|
}
|
|
.table-section.expanded {
|
max-height: 800px; /* 展开时的高度,可以显示更多行 */
|
overflow-y: auto;
|
}
|
|
table.styled-table {
|
max-width: 1800px;
|
width: 98vw;
|
margin: 0 auto;
|
border-collapse: separate;
|
border-spacing: 0;
|
border: 2px solid #bfbfbf;
|
background: #fff;
|
border-radius: 12px;
|
overflow: hidden;
|
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
|
}
|
|
table.styled-table thead th {
|
background: #fafafa;
|
border-bottom: 2px solid #bfbfbf;
|
padding: 16px 10px;
|
font-weight: bold;
|
text-align: center;
|
font-size: 22px;
|
position: sticky;
|
top: 0;
|
z-index: 10;
|
}
|
|
table.styled-table tbody td {
|
border-bottom: 1px solid #e8e8e8;
|
padding: 14px 10px;
|
vertical-align: middle;
|
text-align: center;
|
font-size: 22px;
|
}
|
|
.table-section table th:first-child, .table-section table td:first-child {
|
border-left: 2px solid #bfbfbf;
|
}
|
|
.table-section table th:last-child, .table-section table td:last-child {
|
border-right: 2px solid #bfbfbf;
|
}
|
|
.row-odd {
|
background: #fff;
|
}
|
|
.row-odd + tr {
|
background: #fafafa;
|
}
|
|
.num {
|
text-align: center;
|
padding-right: 0;
|
font-variant-numeric: tabular-nums;
|
}
|
|
.left {
|
text-align: left;
|
padding-left: 8px;
|
}
|
|
.warn-cell {
|
color: #d93025;
|
font-weight: bold;
|
}
|
|
.ok-cell {
|
color: #333;
|
}
|
|
.warn-badge {
|
display: inline-block;
|
background: #ff4d4f;
|
color: #fff;
|
padding: 2px 6px;
|
border-radius: 3px;
|
font-weight: bold;
|
}
|
|
.more-records-tip {
|
text-align: center;
|
color: #666;
|
font-size: 18px;
|
padding: 20px !important;
|
background-color: #f9f9f9;
|
}
|
|
.more-records-tip span {
|
display: inline-block;
|
padding: 5px 15px;
|
background-color: #f0f0f0;
|
border-radius: 4px;
|
border: 1px dashed #ccc;
|
}
|
|
/* 响应式调整 */
|
@media (max-width: 1200px) {
|
.save-btn, .cancel-btn, .expand-btn {
|
width: 30%;
|
}
|
}
|
</style>
|