From eb5601ade3682f0a6aeb956725cd494a32ab3edb Mon Sep 17 00:00:00 2001 From: wangyu <823267011@qq.com> Date: Thu, 21 May 2026 13:41:21 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=85=B8=E8=BD=A7=E5=AE=9E?= =?UTF-8?q?=E7=BB=A9=E6=8F=90=E4=BA=A4=E9=97=AE=E9=A2=98=EF=BC=8C=E8=A7=84?= =?UTF-8?q?=E7=A8=8B=E9=87=8D=E6=96=B0=E5=AE=8C=E6=88=90=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acid/components/ActualPerformance.vue | 602 +++++++++++------- .../acid/components/QualityReportDialog.vue | 98 ++- klp-ui/src/views/wms/processSpec/index.vue | 36 +- klp-ui/src/views/wms/processSpec/planSpec.vue | 418 ++++-------- .../DrMillProcessRecipeController.java | 26 +- .../klp/service/impl/DrRecipeSyncService.java | 54 +- 6 files changed, 677 insertions(+), 557 deletions(-) diff --git a/klp-ui/src/views/micro/pages/acid/components/ActualPerformance.vue b/klp-ui/src/views/micro/pages/acid/components/ActualPerformance.vue index 728ffdce..3416d481 100644 --- a/klp-ui/src/views/micro/pages/acid/components/ActualPerformance.vue +++ b/klp-ui/src/views/micro/pages/acid/components/ActualPerformance.vue @@ -197,11 +197,94 @@ 查找 重置 + + +
+
规程关联
+ + 请先选择钢卷 +
+ + + +
+ + 共 {{ specVersionsForDialog.length }} 条 +
+ + + + + + + + + + + +
+ 取消 + 确认关联 +
+
+ @@ -216,10 +299,11 @@ import { getTimingRealtimeData, getPresetSetupByCoilId } from '@/api/l2/timing' -import { listProcessSpecVersion } from '@/api/wms/processSpecVersion' -import { listProcessPlan, addProcessPlan } from '@/api/wms/processPlan' -import { listProcessPlanParam, addProcessPlanParam, updateProcessPlanParam } from '@/api/wms/processPlanParam' -import { upsertProcessCoilRecord } from '@/api/wms/processCoilRecord' +import { listProcessSpecVersion, getProcessSpecVersion } from '@/api/wms/processSpecVersion' +import { listProcessCoilRecord, upsertProcessCoilRecord } from '@/api/wms/processCoilRecord' +import { listProcessSpec } from '@/api/wms/processSpec' +import { listProcessPlan } from '@/api/wms/processPlan' +import { listProcessPlanParam } from '@/api/wms/processPlanParam' import { batchAddProcessAnomaly } from '@/api/wms/processAnomaly' // 趋势参数树结构,对应 PLTCM_PRO_SEG 列名 @@ -266,10 +350,11 @@ const TREND_GROUPS = [ ] // V_VBDA_GAUGE 厚度曲线:4 个图,列名来自 DDL +// 厚度图纵轴显示实测/设定值百分比,速度图保持原单位 const GAUGE_COLS = [ - { col: 'THICK0', title: '入口测厚仪 [mm]' }, - { col: 'THICK1', title: '1架出口厚度 [mm]' }, - { col: 'THICK4', title: '末架出口厚度 [mm]' }, + { col: 'THICK0', title: '入口测厚仪 [%]' }, + { col: 'THICK1', title: '1架出口厚度 [%]' }, + { col: 'THICK4', title: '末架出口厚度 [%]' }, { col: 'EXIT_SPEED', title: '轧制速度 [m/min]' } ] @@ -317,8 +402,9 @@ function calcYRange(vals) { /** * 生成折线图 option。 * extras: [{ name, data, color, dash }] — 上下限或参考线 + * yAxisSuffix: 纵轴标签后缀,如 '%' */ -function makeLine(title, xData, yData, extras = []) { +function makeLine(title, xData, yData, extras = [], yAxisSuffix = '') { const allVals = [yData, ...extras.map(e => e.data)].flat() const range = calcYRange(allVals) const hasExtras = extras.length > 0 @@ -338,7 +424,12 @@ function makeLine(title, xData, yData, extras = []) { ? { data: [title, ...extras.map(e => e.name)], top: 4, right: 4, textStyle: { fontSize: 9 }, itemWidth: 14, itemHeight: 8 } : { show: false }, - tooltip: { trigger: 'axis' }, + tooltip: { + trigger: 'axis', + formatter: yAxisSuffix + ? params => params.map(p => `${p.marker}${p.seriesName}: ${p.value != null ? p.value + yAxisSuffix : '—'}`).join('
') + : undefined + }, grid: { top: hasExtras ? 44 : 36, bottom: 28, left: 8, right: 16, containLabel: true }, xAxis: { type: 'category', data: xData, @@ -346,7 +437,11 @@ function makeLine(title, xData, yData, extras = []) { }, yAxis: { type: 'value', min: range.min, max: range.max, - nameTextStyle: { fontSize: 10 }, axisLabel: { fontSize: 10 } + nameTextStyle: { fontSize: 10 }, + axisLabel: { + fontSize: 10, + formatter: yAxisSuffix ? val => val + yAxisSuffix : undefined + } }, dataZoom: [ { type: 'inside', xAxisIndex: 0, zoomOnMouseWheel: true, moveOnMouseMove: true }, @@ -396,7 +491,33 @@ export default { pagination: { page: 1, pageSize: 50, total: 0 }, topTableHeight: 'calc(40vh - 80px)', chartInstances: [], - resizeHandler: null + resizeHandler: null, + // ── 规程关联 ── + coilBinding: null, // 当前选中钢卷已有的关联记录 { versionId, versionCode, ... } + coilBindingLoading: false, + specBindDialog: false, + specVersionLoading: false, + specVersionRawList: [], // 所有 wms_process_spec_version + specList: [], // 所有 wms_process_spec(用于显示规程名) + specBindSelectedId: null, // 弹窗内选中的 versionId + specBindShowAll: false, // true=显示全部版本,false=只显示生效版本 + specBindLoading: false + } + }, + computed: { + /** 弹窗中展示的版本列表(含规程名,支持只看生效版本) */ + specVersionsForDialog() { + const specMap = {} + this.specList.forEach(s => { specMap[s.specId] = s }) + let list = this.specVersionRawList.map(v => ({ + ...v, + specCode: specMap[v.specId] ? specMap[v.specId].specCode : '—', + specName: specMap[v.specId] ? specMap[v.specId].specName : '—', + })) + if (!this.specBindShowAll) { + list = list.filter(v => v.isActive === 1) + } + return list } }, created() { @@ -431,18 +552,15 @@ export default { // 快速点击防重:每次点击递增版本号,旧的 sync 任务检测到版本号变更后自动放弃 const clickRev = ++this._clickRev - // 记录点击前的钢卷号,用于判断是否同一行重复点击 - const prevExcoilId = this.selectedRow - ? (this.selectedRow.EXCOILID || this.selectedRow.excoilid || null) - : null - this.selectedRow = row this.segData = null this.gaugeRows = null this.shapeRows = null this.presetData = null this.selectedTrendParam = null + this.coilBinding = null this.disposeAllCharts() + this.loadCoilBinding() const encoilId = row.ENCOILID || row.encoilid const excoilId = row.EXCOILID || row.excoilid @@ -456,13 +574,6 @@ export default { // 如果期间又点击了其他行则放弃后续操作 if (this._clickRev !== clickRev) return - // 同一钢卷重复点击:跳过规程同步,避免重复写入 - const isSameCoil = excoilId && excoilId === prevExcoilId - if (!isSameCoil) { - // 后台静默同步到规程(不阻塞 UI) - this.autoSyncToActiveSpec(excoilId || encoilId, clickRev) - } - await this.$nextTick() // 加载完成后自动选中第一个趋势参数 if (this.activeTab === 'trend' && this.segData) { @@ -624,6 +735,7 @@ export default { }, // ── 厚度曲线 (V_VBDA_GAUGE) ────────────────── + // 有参考值的厚度图纵轴显示百分比:实测值 / 设定值 × 100 renderGaugeCharts() { const rows = this.gaugeRows if (!rows || !rows.length) return @@ -633,33 +745,48 @@ export default { const chartRefs = ['chartGauge1', 'chartGauge2', 'chartGauge3', 'chartGauge4'] const charts = chartRefs.map((ref, i) => { const { col, title } = GAUGE_COLS[i] - const yData = rows.map(r => { - const v = getRowVal(r, col) - return v == null ? null : parseFloat(v.toFixed(4)) - }) - const extras = [] const refCol = refColMap[col] + if (refCol) { - const refData = rows.map(r => { + // ── 厚度图:转为百分比(实测 / 设定 × 100) ── + const yData = rows.map(r => { + const v = getRowVal(r, col) const rv = getRowVal(r, refCol) - return rv == null ? null : parseFloat(rv.toFixed(4)) + if (v == null || rv == null || rv === 0) return null + return parseFloat(((v / rv) * 100).toFixed(3)) }) - // 上限 = REF + TOPLIMIT;下限 = REF + BOTLIMIT(TOPLIMIT/BOTLIMIT 单位与测厚仪一致) - const upData = rows.map((r, j) => { + const extras = [] + // 目标值恒为 100% + const refLine = rows.map(r => { + const rv = getRowVal(r, refCol) + return (rv != null && rv !== 0) ? 100 : null + }) + // 上限百分比 = (REF + TOPLIMIT) / REF × 100 + const upData = rows.map(r => { const rv = getRowVal(r, refCol) const tl = getRowVal(r, 'TOPLIMIT') - return rv == null ? null : parseFloat((rv + (tl ?? 3)).toFixed(4)) + if (rv == null || rv === 0) return null + return parseFloat(((rv + (tl ?? 3)) / rv * 100).toFixed(3)) }) - const loData = rows.map((r, j) => { + // 下限百分比 = (REF + BOTLIMIT) / REF × 100 + const loData = rows.map(r => { const rv = getRowVal(r, refCol) const bl = getRowVal(r, 'BOTLIMIT') - return rv == null ? null : parseFloat((rv + (bl ?? -3)).toFixed(4)) + if (rv == null || rv === 0) return null + return parseFloat(((rv + (bl ?? -3)) / rv * 100).toFixed(3)) }) - if (refData.some(v => v != null)) extras.push({ name: '目标值', data: refData, color: '#909399', dash: false }) + if (refLine.some(v => v != null)) extras.push({ name: '目标值(100%)', data: refLine, color: '#909399', dash: false }) if (upData.some(v => v != null)) extras.push({ name: '上限', data: upData, color: '#F56C6C', dash: true }) if (loData.some(v => v != null)) extras.push({ name: '下限', data: loData, color: '#67C23A', dash: true }) + return this.makeChart(ref, makeLine(title, xData, yData, extras, '%')) + } else { + // ── 速度等无参考值的图:保持原始单位 ── + const yData = rows.map(r => { + const v = getRowVal(r, col) + return v == null ? null : parseFloat(v.toFixed(4)) + }) + return this.makeChart(ref, makeLine(title, xData, yData)) } - return this.makeChart(ref, makeLine(title, xData, yData, extras)) }) this.chartInstances = charts.filter(Boolean) this.setupResize() @@ -852,199 +979,13 @@ export default { this.gaugeRows = null this.shapeRows = null this.selectedTrendParam = null + this.coilBinding = null this.disposeAllCharts() this.pagination.page = 1 this.loadExcoilCount() this.loadExcoilList() }, - // ── 自动同步到生效规程 ──────────────────────── - async autoSyncToActiveSpec(coilId, rev) { - if (!this.presetData && !this.segData) return - const guard = () => rev !== undefined && this._clickRev !== rev - try { - // ① 查找生效版本 - const verRes = await listProcessSpecVersion({ isActive: 1, pageNum: 1, pageSize: 10 }) - if (guard()) return - const activeVer = (verRes.rows || []).find(v => v.isActive === 1) - if (!activeVer) return - const versionId = activeVer.versionId - - // ② 构建本次写入条目 - const items = this.buildSpecSyncItems() - if (!items.length) return - - // ③ 加载已有 plan 点位 - const plansRes = await listProcessPlan({ versionId, pageNum: 1, pageSize: 500 }) - if (guard()) return - const planMap = {} - ;(plansRes.rows || []).forEach(p => { planMap[p.pointCode] = p }) - - // ④ 逐条 upsert 点位 & 参数,收集异常 - const anomalies = [] - const detectedAt = new Date().toISOString() - // 提取本次钢卷的 enCoilId(入口卷号) - const enCoilId = this.selectedRow ? (this.selectedRow.ENCOILID || this.selectedRow.encoilid || null) : null - - for (const item of items) { - if (guard()) return - - // 确保 plan 点位存在(幂等:插入失败时降级重查,应对并发竞态) - let planId - const ep = planMap[item.pointCode] - if (ep) { - planId = ep.planId - } else { - try { - const r = await addProcessPlan({ - versionId, - segmentType: 'PROCESS', - segmentName: item.groupLabel, - pointName: item.pointName, - pointCode: item.pointCode, - sortOrder: 0 - }) - if (guard()) return - planId = r.data - // 写入本地 map,同一次 sync 内不再重复插入 - planMap[item.pointCode] = { planId } - } catch (_dupErr) { - // 唯一键冲突:该点位已由并发请求写入,重新查询获取真实 planId - const refetch = await listProcessPlan({ versionId, pageNum: 1, pageSize: 500 }) - if (guard()) return - const found = (refetch.rows || []).find(p => p.pointCode === item.pointCode) - if (!found) continue // 极端情况:查不到则跳过本条目 - planId = found.planId - planMap[item.pointCode] = found - } - } - - // 查已存储参数 - const prRes = await listProcessPlanParam({ planId, pageNum: 1, pageSize: 100 }) - if (guard()) return - const stored = (prRes.rows || []).find(p => p.paramCode === item.paramCode) - - // 异常检测:与已有上下限比对 - if (stored) { - const sUp = stored.upperLimit != null ? Number(stored.upperLimit) : null - const sLo = stored.lowerLimit != null ? Number(stored.lowerLimit) : null - const aUp = item.upperLimit - const aLo = item.lowerLimit - const overTypes = [] - if (sUp != null && aUp != null && aUp > sUp) overTypes.push('OVER_MAX') - if (sLo != null && aLo != null && aLo < sLo) overTypes.push('UNDER_MIN') - if (overTypes.length) { - anomalies.push({ - versionId, - planId, - paramId: stored.paramId || null, - coilId, - enCoilId, - paramCode: item.paramCode, - paramName: item.paramName, - unit: item.unit, - anomalyType: overTypes.length === 2 ? 'BOTH' : overTypes[0], - storedTarget: stored.targetValue != null ? Number(stored.targetValue) : null, - storedUpper: sUp, - storedLower: sLo, - actualTarget: item.targetValue, - actualMax: aUp, - actualMin: aLo, - deviationMax: sUp != null && aUp != null ? parseFloat((aUp - sUp).toFixed(4)) : null, - deviationMin: sLo != null && aLo != null ? parseFloat((aLo - sLo).toFixed(4)) : null, - detectedAt - }) - } - } - - // 写入/更新参数 - // target_value 始终覆盖(反映最新L1设定) - // upper/lower 仅首次写入(null 时写入作为基线) - // actualSrcId / presetSrcId 仅首次写入 - if (stored) { - await updateProcessPlanParam({ - ...stored, - targetValue: item.targetValue ?? stored.targetValue, - upperLimit: stored.upperLimit ?? item.upperLimit, - lowerLimit: stored.lowerLimit ?? item.lowerLimit, - unit: item.unit || stored.unit, - actualSrcId: stored.actualSrcId || enCoilId, - presetSrcId: stored.presetSrcId || coilId - }) - } else { - await addProcessPlanParam({ - planId, - paramCode: item.paramCode, - paramName: item.paramName, - targetValue: item.targetValue, - upperLimit: item.upperLimit, - lowerLimit: item.lowerLimit, - unit: item.unit, - actualSrcId: enCoilId, - presetSrcId: coilId - }) - } - } - - if (guard()) return - - // ⑤ 写入钢卷服役记录(幂等 upsert) - await upsertProcessCoilRecord({ - versionId, - coilId, - enCoilId, - hasAnomaly: anomalies.length > 0 ? 1 : 0, - anomalyCnt: anomalies.length, - processTime: detectedAt - }) - - // ⑥ 持久化异常到数据库 - if (anomalies.length) { - await batchAddProcessAnomaly(anomalies) - console.log(`[规程同步] 检测到 ${anomalies.length} 个参数异常,已写入数据库`) - } - } catch (e) { - console.warn('[规程同步] 后台同步失败:', e) - } - }, - - /** 从当前 segData + presetData 构建写入条目 */ - buildSpecSyncItems() { - const items = [] - for (const group of TREND_GROUPS) { - for (const item of group.children) { - const col = item.col - const maxArr = this.segData ? this.seg(col + 'MAX').filter(v => v != null) : [] - const minArr = this.segData ? this.seg(col + 'MIN').filter(v => v != null) : [] - const presetCol = TREND_PRESET_MAP[col] - - let targetValue = null - if (presetCol && this.presetData) { - const sv = this.presetData[presetCol] !== undefined - ? this.presetData[presetCol] - : this.presetData[presetCol.toLowerCase()] - if (sv != null && Number(sv) !== 0) targetValue = parseFloat(Number(sv).toFixed(4)) - } - const upperLimit = maxArr.length ? parseFloat(Math.max(...maxArr).toFixed(4)) : null - const lowerLimit = minArr.length ? parseFloat(Math.min(...minArr).toFixed(4)) : null - - if (targetValue == null && upperLimit == null && lowerLimit == null) continue - items.push({ - groupLabel: group.label, - pointCode: col, - pointName: item.label, - paramCode: col, - paramName: item.label, - targetValue, - upperLimit, - lowerLimit, - unit: TREND_UNIT_MAP[col] || '' - }) - } - } - return items - }, - openQualityReport(row) { // If the clicked row is already the selected row, use in-memory data const isSame = this.selectedRow && @@ -1056,6 +997,178 @@ export default { this.$refs.qualityReport.open(row, segData, gaugeRows, shapeRows, presetData) }, + // ── 规程关联 ───────────────────────────────────────── + /** 加载当前选中钢卷的已有规程关联记录 */ + async loadCoilBinding() { + if (!this.selectedRow) return + const coilId = this.selectedRow.EXCOILID || this.selectedRow.excoilid + if (!coilId) return + this.coilBindingLoading = true + try { + const res = await listProcessCoilRecord({ coilId, pageNum: 1, pageSize: 1 }) + const rec = (res.rows || [])[0] + if (rec) { + // 补充版本号显示 + try { + const verRes = await getProcessSpecVersion(rec.versionId) + this.coilBinding = { ...rec, versionCode: verRes.data?.versionCode || String(rec.versionId) } + } catch { + this.coilBinding = { ...rec, versionCode: String(rec.versionId) } + } + } else { + this.coilBinding = null + } + } catch { + this.coilBinding = null + } finally { + this.coilBindingLoading = false + } + }, + + /** 打开选择规程版本的弹窗 */ + async openSpecBindDialog() { + this.specBindSelectedId = this.coilBinding ? this.coilBinding.versionId : null + this.specBindShowAll = !this.coilBinding // 若已关联默认显示全部;否则只看生效版本 + this.specBindDialog = true + this.specVersionLoading = true + try { + const [verRes, specRes] = await Promise.all([ + listProcessSpecVersion({ pageNum: 1, pageSize: 500 }), + listProcessSpec({ pageNum: 1, pageSize: 200 }) + ]) + this.specVersionRawList = verRes.rows || [] + this.specList = specRes.rows || [] + } finally { + this.specVersionLoading = false + } + }, + + /** 确认关联,并自动检测 L1 实绩与规程参数限值的偏差,将异常落库 */ + async confirmSpecBind() { + if (!this.specBindSelectedId) return + this.specBindLoading = true + try { + const coilId = this.selectedRow.EXCOILID || this.selectedRow.excoilid + const enCoilId = this.selectedRow.ENCOILID || this.selectedRow.encoilid || null + const versionId = this.specBindSelectedId + + // 1. 先写入基础关联记录(异常数稍后更新) + await upsertProcessCoilRecord({ + versionId, + coilId, + enCoilId, + hasAnomaly: 0, + anomalyCnt: 0, + processTime: new Date().toISOString() + }) + + // 2. 异常检测(仅当存在 SEG 数据时执行) + let anomalyCnt = 0 + if (this.segData) { + try { + // 2.1 获取该版本下所有方案点位 + const planRes = await listProcessPlan({ versionId }) + const plans = planRes.rows || [] + + // 2.2 汇总所有点位的参数 + const allParams = [] + for (const plan of plans) { + const paramRes = await listProcessPlanParam({ planId: plan.planId }) + ;(paramRes.rows || []).forEach(p => allParams.push({ ...p, planId: plan.planId })) + } + + // 2.3 逐参数比对,构建异常列表 + const anomalies = [] + for (const param of allParams) { + const col = (param.paramCode || '').toUpperCase() + if (!col) continue + + // 从 SEG 系列数据取段内最大/最小值数组,再求全卷极值 + const maxArr = this.seg(col + 'MAX').filter(v => v != null) + const minArr = this.seg(col + 'MIN').filter(v => v != null) + if (!maxArr.length && !minArr.length) continue + + const actualMax = maxArr.length ? Math.max(...maxArr) : null + const actualMin = minArr.length ? Math.min(...minArr) : null + + // 从预设数据取实际设定值(如有对应映射) + let actualTarget = null + const presetCol = TREND_PRESET_MAP[col] + if (presetCol && this.presetData) { + const raw = this.presetData[presetCol] !== undefined + ? this.presetData[presetCol] + : this.presetData[presetCol.toLowerCase()] + if (raw != null) actualTarget = parseFloat(Number(raw).toFixed(4)) + } + + const upper = param.upperLimit != null ? Number(param.upperLimit) : null + const lower = param.lowerLimit != null ? Number(param.lowerLimit) : null + if (upper == null && lower == null) continue // 未配置限值,跳过 + + const overMax = upper != null && actualMax != null && actualMax > upper + const underMin = lower != null && actualMin != null && actualMin < lower + if (!overMax && !underMin) continue + + const anomalyType = (overMax && underMin) ? 'BOTH' : (overMax ? 'OVER_MAX' : 'UNDER_MIN') + + anomalies.push({ + versionId, + planId: param.planId, + paramId: param.paramId, + coilId, + enCoilId, + paramCode: param.paramCode, + paramName: param.paramName, + unit: param.unit || TREND_UNIT_MAP[col] || null, + anomalyType, + storedTarget: param.targetValue != null ? Number(param.targetValue) : null, + storedUpper: upper, + storedLower: lower, + actualTarget, + actualMax, + actualMin, + deviationMax: overMax ? parseFloat((actualMax - upper).toFixed(4)) : null, + deviationMin: underMin ? parseFloat((actualMin - lower).toFixed(4)) : null, + detectedAt: new Date().toISOString() + }) + } + + anomalyCnt = anomalies.length + + // 2.4 批量写入异常记录 + if (anomalies.length > 0) { + await batchAddProcessAnomaly(anomalies) + } + + // 2.5 若有异常则更新关联记录的计数字段 + if (anomalyCnt > 0) { + await upsertProcessCoilRecord({ + versionId, + coilId, + enCoilId, + hasAnomaly: 1, + anomalyCnt, + processTime: new Date().toISOString() + }) + } + } catch (e) { + console.warn('[规程关联] 异常检测失败,跳过', e) + } + } + + const msg = anomalyCnt > 0 + ? `关联成功,检测到 ${anomalyCnt} 个参数异常` + : '关联成功,无异常' + this.$message[anomalyCnt > 0 ? 'warning' : 'success'](msg) + this.specBindDialog = false + await this.loadCoilBinding() + } catch { + this.$message.error('关联失败,请重试') + } finally { + this.specBindLoading = false + } + }, + calcLengthPerTon(row) { const len = parseFloat(row.EXIT_LENGTH || row.exit_length) const wt = parseFloat(row.MEAS_EXIT_WEIGHT || row.meas_exit_weight) @@ -1276,4 +1389,45 @@ export default { font-size: 13px; } +/* ── 规程关联区块 ── */ +.spec-bind-block { + border-top: 1px solid #ebeef5; + padding-top: 12px; + display: flex; + flex-direction: column; + gap: 6px; +} + +.bind-status { + display: flex; + align-items: center; + justify-content: space-between; + gap: 6px; +} + +.bind-label { + font-size: 12px; + color: #909399; + white-space: nowrap; +} + +.bind-val { + font-size: 12px; + color: #303133; + font-weight: 500; + &.muted { color: #c0c4cc; font-weight: normal; } +} + +/* ── 弹窗内工具栏 ── */ +.spec-bind-toolbar { + display: flex; + align-items: center; + gap: 12px; +} + +.spec-bind-hint { + font-size: 12px; + color: #909399; +} + diff --git a/klp-ui/src/views/micro/pages/acid/components/QualityReportDialog.vue b/klp-ui/src/views/micro/pages/acid/components/QualityReportDialog.vue index 1c4b6767..08de5cb9 100644 --- a/klp-ui/src/views/micro/pages/acid/components/QualityReportDialog.vue +++ b/klp-ui/src/views/micro/pages/acid/components/QualityReportDialog.vue @@ -10,7 +10,10 @@ >
{{ coilLabel }} - 导出 PDF + + 加载图表数据… + + 导出 PDF
@@ -136,6 +139,11 @@