<template>
|
<view class="page">
|
<!-- 刀具选择区 -->
|
<view class="top-section-grid">
|
<view class="form-cell">
|
<label class="form-label">刀具编号:</label>
|
<input class="input small-font" v-model="selectedToolNo" placeholder="请通过刀具目录选择" disabled />
|
<button class="btn-blue" @click="openToolDialog" :disabled="loadingTools">刀具目录</button>
|
</view>
|
<view class="form-cell">
|
<label class="form-label">设置使用上限:</label>
|
<input class="input small-font" type="number" v-model="useLimitInput" placeholder="每次换刀后手填" :disabled="!selectedToolNo || loadingForm" />
|
</view>
|
<!-- 新增:寿命比预警值输入框 -->
|
<view class="form-cell">
|
<label class="form-label">寿命比预警值:</label>
|
<input class="input small-font"
|
v-model="lifeWarnInput"
|
placeholder="如0.9或90或90%"
|
:disabled="!selectedToolNo || loadingForm" />
|
</view>
|
<view class="form-cell">
|
<label class="form-label">刀具名称:</label>
|
<input class="input small-font" v-model="toolName" placeholder="刀具带出" disabled />
|
<label class="form-label" style="margin-left: 16px;">规格型号:</label>
|
<input class="input small-font" v-model="toolModel" placeholder="刀具带出" disabled />
|
</view>
|
</view>
|
|
<!-- 操作按钮 -->
|
<view class="button-row">
|
<button class="save-btn" @click="handleUpTool" :disabled="submitting || loadingForm">上刀提交</button>
|
<button class="save-btn" @click="handleDownTool" :disabled="submitting || loadingForm">下刀提交</button>
|
<button class="cancel-btn" @click="cancel" :disabled="submitting || loadingForm">取消</button>
|
</view>
|
|
<!-- 刀具目录弹窗 -->
|
<view v-if="showToolDialog" class="dialog-overlay">
|
<view class="dialog">
|
<view class="form-group">
|
<input v-model="searchKey" placeholder="输入刀具编码、名称模糊搜索" class="input" @input="onSearchKeyInput" />
|
<button class="btn-blue" @click="searchTool" :disabled="loadingTools">搜索</button>
|
</view>
|
<view class="tool-list">
|
<button v-for="tool in filteredTools"
|
:key="tool.no"
|
class="tool-btn"
|
:class="{ active: activeToolNo === tool.no }"
|
@click="selectTool(tool)">
|
{{ tool.no }} | {{ tool.name }}
|
</button>
|
</view>
|
<view class="dialog-actions">
|
<div style="display: flex; align-items: center;">
|
<button class="btn-blue" @click="prevPage" :disabled="pageIndex === 1 || loadingTools">上一页</button>
|
<span style="margin: 0 12px;">第{{ pageIndex }}页 / 共{{ totalPages }}页</span>
|
<button class="btn-blue" @click="nextPage" :disabled="pageIndex === totalPages || loadingTools">下一页</button>
|
</div>
|
<div style="display: flex; gap: 18px; align-items: center;">
|
<button class="btn-blue" @click="confirmTool">确定</button>
|
<button class="btn-disabled" @click="closeToolDialog">取消</button>
|
</div>
|
</view>
|
</view>
|
</view>
|
|
<!-- 刀具使用记录表格 -->
|
<view class="table-section">
|
<table class="styled-table">
|
<thead>
|
<tr>
|
<th style="width:8%">刀具编号</th>
|
<th style="width:14%">刀具名称</th>
|
<th style="width:12%">上刀时间</th>
|
<th class="num" style="width:8%">上刀计数</th>
|
<th style="width:12%">下刀时间</th>
|
<th class="num" style="width:8%">下刀计数</th>
|
<th class="num" style="width:8%">使用次数</th>
|
<th class="num" style="width:8%">使用上限</th>
|
<th class="num" style="width:8%">寿命比%</th>
|
<th class="num" style="width:8%">寿命比预警值</th>
|
<th style="width:8%">预警状态</th>
|
</tr>
|
</thead>
|
<tbody>
|
<tr v-for="(item, idx) in toolRecords" :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>
|
<td class="num">{{ item.downCount != null ? item.downCount : '' }}</td>
|
<td class="num">{{ item.useCount != null ? item.useCount : '' }}</td>
|
<td class="num">{{ item.useLimit != null ? item.useLimit : '' }}</td>
|
<td class="num">{{ item.lifePercent }}</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>
|
</tr>
|
<tr v-if="!toolRecords.length">
|
<td colspan="11">暂无数据</td>
|
</tr>
|
</tbody>
|
</table>
|
</view>
|
|
<!-- 说明 -->
|
<!--<view class="tool-desc">
|
<p style="color:red;">'使用上限'以下刀时的'使用上限'为计算标准</p>
|
<p style="color:red;">寿命比预警值默认为90%</p>
|
</view>-->
|
</view>
|
</template>
|
|
<script>
|
export default {
|
data() {
|
return {
|
machineNo: '',//机台编码
|
workOrderNo: '',//工单号
|
activeToolNo: '', // 当前选中的刀具编号
|
pageIndex: 1,
|
pageSize: 20, //单页显示的刀具数量
|
total: 0,
|
toolList: [],
|
selectedToolNo: '',
|
toolName: '',
|
toolModel: '',
|
showToolDialog: false,
|
searchKey: '',
|
filteredTools: [],
|
useLimitInput: '',
|
lifeWarnInput: '', // 新增:寿命比预警值原始输入
|
toolRecords: [],
|
loadingTools: false,
|
loadingForm: false,
|
submitting: false,
|
_searchTimer: null,
|
workOrderCurrentCjNum: null, // 工单当前数采
|
|
// 自动保存相关
|
autoSaveTimer: null,
|
isDirty: false, // 表单是否有未保存变更
|
autoSaveIntervalMs: 5 * 60 * 1000, // 默认 5 分钟
|
autoSaveEnabled: true,
|
autoSaveActionName: 'handleUpTool' // 自动触发的方法名,可改为自定义保存方法
|
};
|
},
|
computed: {
|
totalPages() {
|
return Math.max(1, Math.ceil(this.total / this.pageSize) || 1);
|
}
|
},
|
watch: {
|
// 标记脏数据:按需监听字段变化
|
selectedToolNo() { this.isDirty = true; },
|
useLimitInput() { this.isDirty = true; },
|
lifeWarnInput() { this.isDirty = true; },
|
toolName() { this.isDirty = true; },
|
toolModel() { this.isDirty = true; }
|
},
|
methods: {
|
// 自动保存:启动
|
startAutoSave() {
|
if (!this.autoSaveEnabled) return;
|
this.stopAutoSave();
|
this.autoSaveTimer = setInterval(() => {
|
this.autoSaveTick();
|
}, this.autoSaveIntervalMs);
|
},
|
// 自动保存:停止
|
stopAutoSave() {
|
if (this.autoSaveTimer) {
|
clearInterval(this.autoSaveTimer);
|
this.autoSaveTimer = null;
|
}
|
},
|
// 自动保存:每次定时执行时的逻辑
|
async autoSaveTick() {
|
if (!this.autoSaveEnabled) return;
|
if (!this.isDirty) return;
|
if (this.submitting || this.loadingForm) return;
|
|
const fn = this.autoSaveActionName && typeof this[this.autoSaveActionName] === 'function'
|
? this[this.autoSaveActionName]
|
: null;
|
if (!fn) {
|
console.warn('自动保存:未找到方法', this.autoSaveActionName);
|
return;
|
}
|
|
try {
|
this.submitting = true;
|
await fn.call(this); // 调用保存方法(例如 handleUpTool)
|
// 如果保存成功,清脏标记(保存方法内部若失败没有抛出可保持此方式)
|
this.isDirty = false;
|
} catch (e) {
|
console.error('自动保存失败:', e);
|
} finally {
|
this.submitting = false;
|
}
|
},
|
|
// 新增:寿命比预警值归一化 (返回 0~1 或 null)
|
normalizeLifeWarn(v) {
|
if (v == null) return null;
|
const raw = String(v).trim().replace(/[%%]/g, '');
|
if (raw === '') return null;
|
const num = Number(raw);
|
if (!isFinite(num) || num <= 0) return null;
|
return num > 1 ? (num / 100) : num;
|
},
|
openToolDialog() {
|
this.showToolDialog = true;
|
this.pageIndex = 1;
|
this.fetchTools(this.searchKey);
|
},
|
closeToolDialog() {
|
this.showToolDialog = false;
|
},
|
async fetchTools(searchKey) {
|
this.loadingTools = true;
|
try {
|
const res = await this.$post({
|
url: '/MesCutterLedger/QueryTools',
|
data: JSON.stringify({
|
searchKey,
|
pageIndex: this.pageIndex,
|
pageSize: this.pageSize
|
}),
|
headers: { 'Content-Type': 'application/json' }
|
});
|
|
if (res.status === 0) {
|
const payload = 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;
|
};
|
|
// 这里需要把 lifeWarn 字段也带出来
|
const mapped = (payload || []).map(t => ({
|
no: getField(t, 'cutterId', 'CUTTER_ID', 'cutteR_ID', 'daA001', 'no'),
|
name: getField(t, 'cutterName', 'CUTTER_NAME', 'cutteR_NAME', 'name'),
|
model: getField(t, 'cutterModel', 'CUTTER_MODEL', 'cutteR_MODEL', 'model'),
|
lifeWarn: getField(t, 'modlLifeWorning', 'lifeWarn', 'LIFE_WARN', 'lifE_WARN')
|
}));
|
|
this.filteredTools = mapped;
|
this.toolList = mapped.slice();
|
|
const totalFromRes = Number(
|
res.data?.total ?? res.data?.totalCount ?? res.total ?? res.totalCount ?? mapped.length
|
);
|
this.total = Number.isFinite(totalFromRes) ? totalFromRes : mapped.length;
|
if (this.pageIndex > this.totalPages) {
|
this.pageIndex = this.totalPages;
|
}
|
} else {
|
this.$showMessage(res.message || '查询失败');
|
}
|
} catch (err) {
|
console.error('fetchTools 错误:', err);
|
this.$showMessage('查询刀具失败,请检查网络或接口');
|
} finally {
|
this.loadingTools = false;
|
}
|
},
|
async prevPage() {
|
if (this.pageIndex > 1) {
|
this.pageIndex--;
|
await this.fetchTools(this.searchKey);
|
}
|
},
|
async nextPage() {
|
if (this.pageIndex < this.totalPages) {
|
this.pageIndex++;
|
await this.fetchTools(this.searchKey);
|
}
|
},
|
onSearchKeyInput() {
|
clearTimeout(this._searchTimer);
|
this._searchTimer = setTimeout(() => {
|
this.searchTool();
|
}, 300);
|
},
|
async searchTool() {
|
this.pageIndex = 1;
|
await this.fetchTools(this.searchKey);
|
},
|
selectTool(tool) {
|
this.selectedToolNo = tool.no;
|
this.toolName = tool.name;
|
this.toolModel = tool.model;
|
this.activeToolNo = tool.no;
|
// 新增:带出寿命比预警值
|
if (tool.lifeWarn !== undefined && tool.lifeWarn !== null) {
|
// 格式化为百分比字符串
|
const warn = Number(tool.lifeWarn);
|
this.lifeWarnInput = warn <= 1 ? `${(warn * 100).toFixed(0)}%` : `${warn.toFixed(0)}%`;
|
} else {
|
this.lifeWarnInput = '';
|
}
|
},
|
confirmTool() {
|
this.showToolDialog = false;
|
},
|
async setUseLimit() {
|
if (!this.selectedToolNo) {
|
this.$showMessage('请选择刀具后再设置上限');
|
return;
|
}
|
const v = Number(this.useLimitInput);
|
if (isNaN(v) || v <= 0) {
|
this.$showMessage('请输入有效的使用上限');
|
return;
|
}
|
this.toolRecords = this.toolRecords.map(r => {
|
if (r.no === this.selectedToolNo) {
|
return { ...r, useLimit: v };
|
}
|
return r;
|
});
|
this.toolList = this.toolList.map(t => t.no === this.selectedToolNo ? { ...t, useLimit: v } : t);
|
this.$showMessage('使用上限已设置(仅前端显示)');
|
},
|
// 上刀时传递 sdjs = 工单 currentCjNum
|
async handleUpTool() {
|
if (!this.workOrderNo) { this.$showMessage('工单号不能为空'); return; }
|
if (!this.machineNo) { this.$showMessage('机台号不能为空'); return; }
|
if (!this.selectedToolNo) { this.$showMessage('刀具编号不能为空'); return; }
|
//上刀不强制录入使用上限
|
//if (!this.useLimitInput) { this.$showMessage('使用上限不能为空'); return; }
|
const useLimit = Number(this.useLimitInput);
|
//if (isNaN(useLimit) || useLimit <= 0) { this.$showMessage('请输入有效的使用上限'); return; }
|
|
// sdjs 使用工单 currentCjNum
|
const sdjs = this.workOrderCurrentCjNum != null ? Number(this.workOrderCurrentCjNum) : null;
|
const lifeWarnRatio = this.normalizeLifeWarn(this.lifeWarnInput);
|
const payload = {
|
workOrderNo: this.workOrderNo,
|
machineNo: this.machineNo,
|
toolNo: this.selectedToolNo,
|
type: '上机',
|
useLimit,
|
sdjs,// 上刀计数(工单当前数采)
|
modlLifeWorning: lifeWarnRatio // 新增
|
};
|
try {
|
this.submitting = true;
|
const res = await this.$post({
|
url: '/MesCutterLedger/SubmitToolAction',
|
data: JSON.stringify(payload),
|
headers: { 'Content-Type': 'application/json' }
|
});
|
if (res.data && res.data.outSum === "0") {
|
uni.showToast({
|
title: res.data.outMsg || '',
|
icon: 'error'
|
});
|
return;
|
} else if (res.data && res.data.outMsg) {
|
uni.showToast({
|
title: res.data.outMsg,
|
icon: 'none'
|
});
|
}
|
// 后续逻辑继续执行
|
if (res.status === 0) {
|
await this.fetchFormData();
|
}
|
} catch (err) {
|
console.error(err);
|
this.$showMessage('上刀提交失败,请检查网络');
|
throw err; // 抛出以便自动保存逻辑捕获并保留 isDirty
|
} finally {
|
this.submitting = false;
|
}
|
},
|
// 下刀时传递 xdjs = 工单 currentCjNum
|
async handleDownTool() {
|
if (!this.workOrderNo) { this.$showMessage('工单号不能为空'); return; }
|
if (!this.machineNo) { this.$showMessage('机台号不能为空'); return; }
|
if (!this.selectedToolNo) { this.$showMessage('刀具编号不能为空'); return; }
|
if (!this.useLimitInput) { this.$showMessage('使用上限不能为空'); return; }
|
const useLimit = Number(this.useLimitInput);
|
if (isNaN(useLimit) || useLimit <= 0) { this.$showMessage('请输入有效的使用上限'); return; }
|
|
// 下刀计数同样取工单最新采集数
|
const xdjs = this.workOrderCurrentCjNum != null ? Number(this.workOrderCurrentCjNum) : null;
|
const lifeWarnRatio = this.normalizeLifeWarn(this.lifeWarnInput);
|
const payload = {
|
workOrderNo: this.workOrderNo,
|
machineNo: this.machineNo,
|
toolNo: this.selectedToolNo,
|
type: '下机',
|
useLimit,
|
xdjs,// 下刀计数
|
modlLifeWorning: lifeWarnRatio // 新增
|
};
|
try {
|
this.submitting = true;
|
const res = await this.$post({
|
url: '/MesCutterLedger/SubmitToolAction',
|
data: JSON.stringify(payload),
|
headers: { 'Content-Type': 'application/json' }
|
});
|
if (res.data && res.data.outSum === "0") {
|
uni.showToast({
|
title: res.data.outMsg || '',
|
icon: 'error'
|
});
|
return;
|
} else if (res.data && res.data.outMsg) {
|
uni.showToast({
|
title: res.data.outMsg,
|
icon: 'none'
|
});
|
}
|
// 后续逻辑继续执行
|
if (res.status === 0) {
|
await this.fetchFormData();
|
}
|
} catch (err) {
|
console.error(err);
|
this.$showMessage('下刀提交失败,请检查网络');
|
throw err;
|
} finally {
|
this.submitting = false;
|
}
|
},
|
cancel() {
|
this.selectedToolNo = '';
|
this.toolName = '';
|
this.toolModel = '';
|
this.useLimitInput = '';
|
this.lifeWarnInput = ''; // 新增:清空
|
this.isDirty = false;
|
},
|
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) => {
|
// 时间格式如 "10-24 16:03",转为 Date 对象比较
|
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;
|
const totalFromRes = Number(
|
res.data?.total ?? res.data?.totalCount ?? res.total ?? res.totalCount ?? mapped.length
|
);
|
this.total = Number.isFinite(totalFromRes) ? totalFromRes : mapped.length;
|
} catch (error) {
|
console.error('获取表单数据错误:', error);
|
this.$showMessage('获取数据失败,请检查网络连接');
|
this.toolRecords = [];
|
} finally {
|
this.loadingForm = false;
|
}
|
},
|
// 自动带出工单刀具信息,并获取工单最新采集数
|
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.toolModel = order.cutterModel || order.cutteR_MODEL || '';
|
// 关键:获取工单最新采集数
|
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;
|
}
|
},
|
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}-${date.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.fetchTools('');
|
this.machineNo = uni.getStorageSync('machineNo') || '';
|
this.workOrderNo = uni.getStorageSync('daa001') || '';
|
// 去除默认预警值 (90% -> 0.9)
|
//this.lifeWarnInput = '90';
|
|
if (this.machineNo && this.workOrderNo) {
|
this.fetchFormData();
|
this.fetchDefaultToolFromWorkOrder();
|
} else {
|
console.warn('机台号或工单号为空,无法获取表单数据');
|
}
|
|
// 启动自动保存定时器
|
this.startAutoSave();
|
},
|
beforeDestroy() {
|
// 清理定时器,防止内存泄漏
|
this.stopAutoSave();
|
}
|
};
|
</script>
|
|
<style scoped>
|
/* 原样保持,未改动样式,只插入了一个输入框 */
|
.top-section-grid {
|
display: flex;
|
justify-content: center;
|
align-items: flex-end;
|
gap: 32px;
|
margin-bottom: 2vh;
|
width: 95vw;
|
max-width: 1600px;
|
margin-left: auto;
|
margin-right: auto;
|
}
|
|
.form-cell {
|
display: flex;
|
align-items: center;
|
}
|
|
.form-label {
|
width: 90px;
|
font-weight: bold;
|
}
|
|
.input {
|
padding: 1vh;
|
font-size: 1.1vw;
|
border: 1px solid #ccc;
|
width: 10vw;
|
margin-right: 8px;
|
border-radius: 6px;
|
background: #f8f8f8;
|
}
|
|
/* 新增:小字体样式 */
|
.input.small-font {
|
font-size: 0.9vw; /* 调小字体 */
|
}
|
|
.form-select {
|
width: 12vw;
|
padding: 1vh;
|
font-size: 1.1vw;
|
margin-right: 8px;
|
border-radius: 6px;
|
background: #f8f8f8;
|
}
|
|
.btn-blue {
|
background-color: #00A2E9;
|
color: white;
|
border: none;
|
padding: 14px 36px;
|
margin-left: 8px;
|
border-radius: 10px;
|
cursor: pointer;
|
font-size: 1.1vw;
|
font-weight: bold;
|
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
|
transition: background 0.2s, box-shadow 0.2s;
|
}
|
|
.btn-blue:active,
|
.btn-blue:focus {
|
background-color: #0086c2;
|
box-shadow: 0 4px 12px rgba(0,162,233,0.12);
|
}
|
|
.btn-blue:disabled {
|
background: #b3e0f7;
|
color: #eee;
|
cursor: not-allowed;
|
box-shadow: none;
|
}
|
|
.button-row {
|
display: flex;
|
justify-content: center;
|
gap: 32px;
|
margin: 2vh 0;
|
}
|
|
.save-btn, .cancel-btn {
|
width: 28%;
|
padding: 1.5vh;
|
background-color: #00A2E9;
|
color: white;
|
font-size: 1.2vw;
|
border: none;
|
text-align: center;
|
border-radius: 6px;
|
transition: background 0.2s;
|
}
|
|
.cancel-btn {
|
background-color: #eee;
|
color: #333;
|
}
|
|
.dialog-overlay {
|
position: fixed;
|
top: 0;
|
left: 0;
|
right: 0;
|
bottom: 0;
|
background: rgba(0,0,0,0.18);
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
z-index: 1000;
|
}
|
|
.dialog {
|
background: #fff;
|
padding: 3vh 3vw;
|
border-radius: 18px;
|
width: 100vw;
|
max-width: 1200px;
|
min-height: 60vh;
|
box-sizing: border-box;
|
box-shadow: 0 8px 32px rgba(0,0,0,0.12);
|
}
|
|
.form-group .input {
|
width: 24vw;
|
border-radius: 6px;
|
background: #f8f8f8;
|
}
|
|
.tool-list {
|
display: flex;
|
flex-wrap: wrap;
|
margin: 1vh 0;
|
max-height: 40vh;
|
overflow-y: auto;
|
gap: 10px 0;
|
}
|
|
.tool-btn {
|
flex: 0 0 24%;
|
box-sizing: border-box;
|
margin: 5px 1% 5px 0;
|
padding: 12px 18px;
|
background: #f5f5f5;
|
border: 1px solid #d0d0d0;
|
border-radius: 8px;
|
cursor: pointer;
|
color: #333;
|
text-align: left;
|
font-size: 1vw;
|
transition: background 0.15s, color 0.15s;
|
word-break: break-all;
|
}
|
|
.tool-btn.active {
|
background: #00A2E9;
|
color: #fff;
|
font-weight: bold;
|
border: 1.5px solid #0086c2;
|
}
|
|
.dialog-actions {
|
display: flex;
|
justify-content: space-between;
|
align-items: flex-end;
|
margin-top: 2vh;
|
padding-top: 2vh;
|
border-top: 1px solid #eee;
|
}
|
|
.dialog-actions > div:first-child {
|
display: flex;
|
align-items: center;
|
gap: 24px;
|
}
|
|
.dialog-actions > div:last-child {
|
display: flex;
|
flex-direction: row;
|
gap: 18px;
|
align-items: center;
|
}
|
|
.btn-disabled {
|
background: #f2f2f2;
|
color: #bbb;
|
border: none;
|
padding: 14px 36px;
|
border-radius: 10px;
|
font-size: 1.1vw;
|
font-weight: bold;
|
cursor: not-allowed;
|
box-shadow: none;
|
}
|
|
.table-section {
|
display: flex;
|
justify-content: center;
|
margin: 1vh 0;
|
overflow-x: auto;
|
}
|
|
table.styled-table {
|
max-width: 1600px;
|
width: 95vw;
|
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;
|
font-size: 1.2vw;
|
text-align: center;
|
}
|
|
table.styled-table tbody td {
|
border-bottom: 1px solid #e8e8e8;
|
padding: 14px 10px;
|
font-size: 1.1vw;
|
vertical-align: middle;
|
text-align: center;
|
}
|
|
.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;
|
font-size: 0.9vw;
|
}
|
|
.header-badge {
|
display: inline-block;
|
background: #fff7e6;
|
color: #8a6d00;
|
border: 1px solid #ffe58f;
|
padding: 1px 6px;
|
border-radius: 3px;
|
margin-left: 6px;
|
font-size: 0.8vw;
|
vertical-align: middle;
|
}
|
|
.warn {
|
color: red;
|
font-weight: bold;
|
}
|
|
.bottom-section {
|
display: flex;
|
justify-content: space-around;
|
margin-top: 2vh;
|
}
|
|
.tool-desc {
|
margin-top: 2vh;
|
}
|
</style>
|