快乐的昕的电脑
2025-10-31 6b80b1382df821da347fac967777841032b9904f
components/mold.vue
@@ -4,18 +4,26 @@
        <view class="top-section-grid">
            <view class="form-cell">
                <label class="form-label">刀具编号:</label>
                <input class="input" v-model="selectedToolNo" placeholder="请通过刀具目录选择" disabled />
                <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" type="number" v-model="useLimitInput" placeholder="每次换刀后手填" :disabled="!selectedToolNo || loadingForm" />
                <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" v-model="toolName" placeholder="刀具带出" disabled />
                <input class="input small-font" v-model="toolName" placeholder="刀具带出" disabled />
                <label class="form-label" style="margin-left: 16px;">规格型号:</label>
                <input class="input" v-model="toolModel" placeholder="刀具带出" disabled />
                <input class="input small-font" v-model="toolModel" placeholder="刀具带出" disabled />
            </view>
        </view>
@@ -99,11 +107,10 @@
        </view>
        <!-- 说明 -->
        <view class="tool-desc">
            <p style="color:red;">当前工单中,换了几次刀,就会产生几条数据。上刀时间、下刀时间在表中能看到。</p>
            <p style="color:red;">上刀时间和对应时间用生产计数器匹配,查出当时的生产数(累计计数)。</p>
            <p style="color:red;">寿命比预警值在刀具上,默认统一。</p>
        </view>
        <!--<view class="tool-desc">
            <p style="color:red;">'使用上限'以下刀时的'使用上限'为计算标准</p>
            <p style="color:red;">寿命比预警值默认为90%</p>
        </view>-->
    </view>
</template>
@@ -125,12 +132,20 @@
                searchKey: '',
                filteredTools: [],
                useLimitInput: '',
                lifeWarnInput: '', // 新增:寿命比预警值原始输入
                toolRecords: [],
                loadingTools: false,
                loadingForm: false,
                submitting: false,
                _searchTimer: null,
                workOrderCurrentCjNum: null // 工单当前数采
                workOrderCurrentCjNum: null, // 工单当前数采
                // 自动保存相关
                autoSaveTimer: null,
                isDirty: false, // 表单是否有未保存变更
                autoSaveIntervalMs: 5 * 60 * 1000, // 默认 5 分钟
                autoSaveEnabled: true,
                autoSaveActionName: 'handleUpTool' // 自动触发的方法名,可改为自定义保存方法
            };
        },
        computed: {
@@ -138,7 +153,65 @@
                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;
@@ -171,10 +244,12 @@
                            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')
                            model: getField(t, 'cutterModel', 'CUTTER_MODEL', 'cutteR_MODEL', 'model'),
                            lifeWarn: getField(t, 'modlLifeWorning', 'lifeWarn', 'LIFE_WARN', 'lifE_WARN')
                        }));
                        this.filteredTools = mapped;
@@ -224,6 +299,14 @@
                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;
@@ -252,20 +335,22 @@
                if (!this.workOrderNo) { this.$showMessage('工单号不能为空'); return; }
                if (!this.machineNo) { this.$showMessage('机台号不能为空'); return; }
                if (!this.selectedToolNo) { this.$showMessage('刀具编号不能为空'); return; }
                if (!this.useLimitInput) { this.$showMessage('使用上限不能为空'); return; }
                //上刀不强制录入使用上限
                //if (!this.useLimitInput) { this.$showMessage('使用上限不能为空'); return; }
                const useLimit = Number(this.useLimitInput);
                if (isNaN(useLimit) || useLimit <= 0) { this.$showMessage('请输入有效的使用上限'); return; }
                //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 // 上刀计数(工单当前数采)
                    sdjs,// 上刀计数(工单当前数采)
                    modlLifeWorning: lifeWarnRatio // 新增
                };
                try {
                    this.submitting = true;
@@ -274,30 +359,26 @@
                        data: JSON.stringify(payload),
                        headers: { 'Content-Type': 'application/json' }
                    });
                    if (res.data && res.data.outMsg) {
                        const isRepeat = res.data.outMsg === '重复上下刀';
                    if (res.data && res.data.outSum === "0") {
                        uni.showToast({
                            title: res.data.outMsg,
                            icon: isRepeat ? 'error' : 'none'
                        });
                        if (isRepeat) return; // 报错时不再继续
                    } else if (res.status === 0) {
                        uni.showToast({
                            title: '下刀提交成功',
                            icon: 'success'
                        });
                    } else {
                        uni.showToast({
                            title: res.message || '下刀提交失败',
                            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;
                }
@@ -313,14 +394,15 @@
                // 下刀计数同样取工单最新采集数
                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 // 下刀计数
                    xdjs,// 下刀计数
                    modlLifeWorning: lifeWarnRatio // 新增
                };
                try {
                    this.submitting = true;
@@ -329,30 +411,26 @@
                        data: JSON.stringify(payload),
                        headers: { 'Content-Type': 'application/json' }
                    });
                    if (res.data && res.data.outMsg) {
                        const isRepeat = res.data.outMsg === '重复上下刀';
                    if (res.data && res.data.outSum === "0") {
                        uni.showToast({
                            title: res.data.outMsg,
                            icon: isRepeat ? 'error' : 'none'
                        });
                        if (isRepeat) return; // 报错时不再继续
                    } else if (res.status === 0) {
                        uni.showToast({
                            title: '下刀提交成功',
                            icon: 'success'
                        });
                    } else {
                        uni.showToast({
                            title: res.message || '下刀提交失败',
                            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;
                }
@@ -362,6 +440,8 @@
                this.toolName = '';
                this.toolModel = '';
                this.useLimitInput = '';
                this.lifeWarnInput = ''; // 新增:清空
                this.isDirty = false;
            },
            async fetchFormData() {
                if (!this.workOrderNo || !this.machineNo) {
@@ -394,19 +474,6 @@
                        return null;
                    };
                    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 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');
@@ -417,11 +484,7 @@
                        const useLimit = getField(t, 'usE_LIMIT', 'USE_LIMIT', 'useLimit');
                        let percent = '';
                        if (
                            useCount != null && useLimit != null &&
                            !isNaN(useCount) && !isNaN(useLimit) &&
                            Number(useLimit) > 0
                        ) {
                        if (useCount != null && useLimit != null && !isNaN(useCount) && !isNaN(useLimit) && Number(useLimit) > 0) {
                            percent = ((Number(useCount) / Number(useLimit)) * 100).toFixed(0) + '%';
                        }
@@ -463,6 +526,18 @@
                        };
                    });
                    // 新增:按上刀时间降序排序(越晚的越上面)
                    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
@@ -492,6 +567,13 @@
                        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;
                    }
@@ -528,9 +610,8 @@
            this.fetchTools('');
            this.machineNo = uni.getStorageSync('machineNo') || '';
            this.workOrderNo = uni.getStorageSync('daa001') || '';
            console.log('机台号:', this.machineNo);
            console.log('工单号:', this.workOrderNo);
            // 去除默认预警值 (90% -> 0.9)
            //this.lifeWarnInput = '90';
            if (this.machineNo && this.workOrderNo) {
                this.fetchFormData();
@@ -538,21 +619,29 @@
            } 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; /* 新增,居中 */
        width: 95vw;
        max-width: 1600px;
        margin-left: auto;
        margin-right: auto;
    }
    .form-cell {
@@ -574,6 +663,11 @@
        border-radius: 6px;
        background: #f8f8f8;
    }
        /* 新增:小字体样式 */
        .input.small-font {
            font-size: 0.8vw; /* 调小字体 */
        }
    .form-select {
        width: 12vw;
@@ -675,7 +769,7 @@
    }
    .tool-btn {
        flex: 0 0 24%; /* 每行4个按钮 */
        flex: 0 0 24%;
        box-sizing: border-box;
        margin: 5px 1% 5px 0;
        padding: 12px 18px;
@@ -731,7 +825,6 @@
        box-shadow: none;
    }
    /* 表格整体居中,宽度限制,内容居中 */
    .table-section {
        display: flex;
        justify-content: center;
@@ -740,8 +833,8 @@
    }
    table.styled-table {
        max-width: 1600px; /* 原为1400px,调宽 */
        width: 95vw; /* 原为90vw,调宽 */
        max-width: 1600px;
        width: 95vw;
        margin: 0 auto;
        border-collapse: separate;
        border-spacing: 0;
@@ -769,13 +862,11 @@
            text-align: center;
        }
    .table-section table th:first-child,
    .table-section table td:first-child {
    .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 {
    .table-section table th:last-child, .table-section table td:last-child {
        border-right: 2px solid #bfbfbf;
    }