Browse Source

赫艾导出

master
shenzhouyu 13 hours ago
parent
commit
412ddd6ac2
  1. 1
      build/webpack.base.conf.js
  2. 1
      index.html
  3. 628
      src/views/modules/report/SPCProcess_Stability.vue
  4. 2
      static/config/init.js
  5. 45
      static/plugins/exceljs/exceljs.min.js

1
build/webpack.base.conf.js

@ -99,6 +99,7 @@ module.exports = {
externals: {
mockjs: 'Mock',
echarts: 'echarts',
exceljs: 'ExcelJS',
ueditor: 'UE'
}
}

1
index.html

@ -14,6 +14,7 @@
<script src="./static/config/index.js"></script>
<script src="./static/plugins/mock-1.0.0-beta3/mock-min.js"></script>
<script src="./static/plugins/echarts-3.8.5/echarts.common.min.js"></script>
<script src="./static/plugins/exceljs/exceljs.min.js"></script>
<script src="./static/plugins/ueditor-1.4.3.3/ueditor.config.js"></script>
<script src="./static/plugins/ueditor-1.4.3.3/ueditor.all.min.js"></script>
<script src="./static/plugins/ueditor-1.4.3.3/lang/zh-cn/zh-cn.js"></script>

628
src/views/modules/report/SPCProcess_Stability.vue

@ -62,7 +62,7 @@
<input v-model.number="query.kVal" type="number" min="5" max="200">
</div>
<button class="btn btn-primary" :disabled="loading" @click="runCalc">{{ loading ? '计算中...' : '计算' }}</button>
<!-- <button class="btn btn-ghost" @click="exportCSV">导出</button> -->
<button class="btn btn-ghost" @click="exportExcel">导出Excel</button>
</div>
</div>
</section>
@ -451,6 +451,26 @@ import {
getSpcMeasurements
} from '@/api/qc/qc_report.js'
const EXCEL_COLORS = {
accent: 'FF1B4D89',
accentSoft: 'FFE8F0F8',
steel: 'FF3B82C4',
amber: 'FFD97706',
amberBg: 'FFFFF7ED',
ok: 'FF047857',
okBg: 'FFECFDF5',
ng: 'FFB91C1C',
ngBg: 'FFFEF2F2',
warn: 'FFB45309',
warnBg: 'FFFFFBEB',
ink: 'FF1E293B',
muted: 'FF64748B',
line: 'FFE2E8F0',
paper: 'FFF4F6F8',
white: 'FFFFFFFF',
header: 'FF1B4D89'
}
const CT_TABLE = {
2: { d2: 1.128, A2: 1.880, D3: 0, D4: 3.267, c4: 0.7979 },
3: { d2: 1.693, A2: 1.023, D3: 0, D4: 2.574, c4: 0.8862 },
@ -1254,42 +1274,598 @@ export default {
})
this.chartInst.hist = chart
},
exportCSV () {
getExcelJS () {
return window.ExcelJS || null
},
exportExcel () {
if (!this.hasResult) {
this.$message.warning('请先计算后再导出')
return
}
let csv = '\uFEFF'
if (!this.getExcelJS()) {
this.$message.error('Excel组件未加载,请刷新页面后重试')
return
}
this.doExportExcel().catch(err => {
console.error(err)
this.$message.error('导出失败,请重试')
})
},
async doExportExcel () {
const ExcelJS = this.getExcelJS()
const wb = new ExcelJS.Workbook()
wb.creator = 'SPC Workstation'
wb.created = new Date()
const ws = wb.addWorksheet('SPC过程稳定性', {
views: [{ state: 'frozen', ySplit: 1, showGridLines: false }]
})
const maxCols = this.stage === 'ppk'
? Math.max(12, 1 + (this.result.groups || []).length)
: Math.max(12, 5 + this.nVal)
for (let i = 1; i <= maxCols; i++) {
ws.getColumn(i).width = i === 1 ? 16 : 12
}
let row = 1
row = this.excelWriteTitle(ws, row, maxCols)
row = this.excelWriteQuery(ws, row, maxCols)
row += 1
row = this.excelWriteKpi(ws, row)
row += 1
row = this.excelWriteStats(ws, row)
row += 1
row = this.excelWriteCriteria(ws, row)
row += 1
row = await this.excelWriteCharts(wb, ws, row)
row += 1
this.excelWriteDetail(ws, row)
const buffer = await wb.xlsx.writeBuffer()
const stageName = this.stage === 'ppk' ? 'PPK样品' : 'CPK量产'
const part = (this.query.partNo || 'SPC').replace(/[\\/:*?"<>|]/g, '_')
const item = (this.query.itemNo || '').replace(/[\\/:*?"<>|]/g, '_')
const stamp = this.excelStamp()
const fileName = 'SPC过程稳定性_' + stageName + '_' + part + (item ? '_' + item : '') + '_' + stamp + '.xlsx'
const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = fileName
a.click()
URL.revokeObjectURL(url)
this.$message.success('导出成功')
},
excelStamp () {
const d = new Date()
const p = n => String(n).padStart(2, '0')
return d.getFullYear() + p(d.getMonth() + 1) + p(d.getDate()) + '_' + p(d.getHours()) + p(d.getMinutes())
},
excelBorder () {
const c = { argb: EXCEL_COLORS.line }
return {
top: { style: 'thin', color: c },
left: { style: 'thin', color: c },
bottom: { style: 'thin', color: c },
right: { style: 'thin', color: c }
}
},
excelFill (argb) {
return { type: 'pattern', pattern: 'solid', fgColor: { argb: argb } }
},
excelFont (opts) {
return Object.assign({ name: '微软雅黑', size: 10, color: { argb: EXCEL_COLORS.ink } }, opts || {})
},
excelStyleCell (cell, opts) {
opts = opts || {}
if (opts.fill) cell.fill = this.excelFill(opts.fill)
if (opts.font) cell.font = this.excelFont(opts.font)
if (opts.align) cell.alignment = opts.align
if (opts.border !== false) cell.border = this.excelBorder()
},
excelMergeSet (ws, r1, c1, r2, c2, value, style) {
if (r1 !== r2 || c1 !== c2) ws.mergeCells(r1, c1, r2, c2)
const cell = ws.getCell(r1, c1)
cell.value = value
this.excelStyleCell(cell, style || {})
return cell
},
excelWriteTitle (ws, row, maxCols) {
const stageLabel = this.stage === 'ppk' ? '样品阶段 · 短期 PPK' : '量产阶段 · 长期 CPK'
const span = Math.min(maxCols, 12)
this.excelMergeSet(ws, row, 1, row, span,
'SPC 过程稳定性分析 | ' + stageLabel,
{
fill: EXCEL_COLORS.header,
font: { size: 16, bold: true, color: { argb: EXCEL_COLORS.white } },
align: { vertical: 'middle', horizontal: 'left', indent: 1 }
})
ws.getRow(row).height = 32
row += 1
this.excelMergeSet(ws, row, 1, row, span,
'DOC QP-8.2-03-R05 · Internal Use Only · 导出时间 ' + new Date().toLocaleString(),
{
fill: EXCEL_COLORS.paper,
font: { size: 9, color: { argb: EXCEL_COLORS.muted } },
align: { vertical: 'middle', horizontal: 'left', indent: 1 }
})
ws.getRow(row).height = 20
return row + 1
},
excelWriteQuery (ws, row, maxCols) {
const span = Math.min(maxCols, 12)
this.excelMergeSet(ws, row, 1, row, span, '查询条件', {
fill: EXCEL_COLORS.accentSoft,
font: { bold: true, size: 11, color: { argb: EXCEL_COLORS.accent } },
align: { vertical: 'middle', horizontal: 'left', indent: 1 }
})
ws.getRow(row).height = 22
row += 1
const unit = this.itemSpec.unit || ''
const fields = [
['工单号', this.query.woNumber || '--'],
['设备编号', this.query.deviceNo || '--'],
['操作员', this.query.operator || '--'],
['物料编码', this.query.partNo || '--'],
['物料名称', this.query.partDesc || '--'],
['检验项目编码', this.query.itemNo || '--'],
['检验项目名称', this.query.itemDesc || '--'],
['开始日期', this.query.startDate || '--'],
['截止日期', this.query.endDate || '--'],
['子组容量 n', String(this.nVal)],
['子组数 k', String(this.kVal)],
['规格限', 'LSL ' + (this.itemSpec.LSL != null ? this.itemSpec.LSL : '--') + ' / USL ' + (this.itemSpec.USL != null ? this.itemSpec.USL : '--') + (unit ? ' ' + unit : '')]
]
for (let i = 0; i < fields.length; i += 4) {
const chunk = fields.slice(i, i + 4)
chunk.forEach((pair, idx) => {
const c = idx * 3 + 1
this.excelMergeSet(ws, row, c, row, c, pair[0], {
fill: EXCEL_COLORS.paper,
font: { size: 9, color: { argb: EXCEL_COLORS.muted } },
align: { vertical: 'middle', horizontal: 'right' }
})
this.excelMergeSet(ws, row, c + 1, row, c + 2, pair[1], {
fill: EXCEL_COLORS.white,
font: { size: 10, bold: true },
align: { vertical: 'middle', horizontal: 'left', indent: 1 }
})
})
ws.getRow(row).height = 20
row += 1
}
return row
},
excelWriteKpi (ws, row) {
this.excelMergeSet(ws, row, 1, row, 12, '关键指标 KPI', {
fill: EXCEL_COLORS.accentSoft,
font: { bold: true, size: 11, color: { argb: EXCEL_COLORS.accent } },
align: { vertical: 'middle', horizontal: 'left', indent: 1 }
})
ws.getRow(row).height = 22
row += 1
const r = this.result
const unit = this.itemSpec.unit || ''
let cards
if (this.stage === 'ppk') {
const ok = this.ppkOk
const grade = this.gradeCpk(r.Ppk)
cards = [
{ label: 'PPK (Sample)', value: this.fm(r.Ppk, 2), sub: '标准≥1.67 · ' + (ok ? 'PASS' : 'FAIL'), tone: ok ? 'ok' : 'ng' },
{ label: 'Pp', value: this.fm(r.Pp, 2), sub: '过程潜力', tone: 'accent' },
{ label: 'PPU / PPL', value: this.fm(r.PPU, 2) + ' / ' + this.fm(r.PPL, 2), sub: '上侧 / 下侧', tone: 'normal' },
{ label: 'σ WITHIN', value: this.fm(r.sigma, 4) + (unit ? ' ' + unit : ''), sub: '组内·R̄/d₂', tone: 'amber' },
{ label: '样本量', value: String(r.N), sub: r.gCnt + '×' + r.groups[0].length + ' 子组', tone: 'normal' },
{ label: '判定', value: ok ? 'OK' : 'NG', sub: grade.label + ' · ' + (ok ? '满足样品准入' : '需改善制程'), tone: ok ? 'ok' : 'ng' }
]
} else {
const ok = this.cpkOk
const grade = this.gradeCpk(r.Cpk)
cards = [
{ label: 'CPK (Mass Prod)', value: this.fm(r.Cpk, 2), sub: '标准≥1.33 · ' + (ok ? 'PASS' : 'FAIL'), tone: ok ? 'ok' : 'ng' },
{ label: 'Cp', value: this.fm(r.Cp, 2), sub: '过程能力', tone: 'accent' },
{ label: 'CPU / CPL', value: this.fm(r.CPU, 2) + ' / ' + this.fm(r.CPL, 2), sub: '上侧 / 下侧', tone: 'normal' },
{ label: 'σ OVERALL', value: this.fm(r.sO, 4) + (unit ? ' ' + unit : ''), sub: '整体·样本标准差', tone: 'amber' },
{ label: 'PPM', value: this.formatNum(r.ppmT), sub: 'USL:' + this.formatNum(r.ppmU) + ' LSL:' + this.formatNum(r.ppmL), tone: 'normal' },
{ label: '判定', value: ok ? 'OK' : 'NG', sub: grade.label + ' · ' + (ok ? '量产稳定受控' : '需改善制程'), tone: ok ? 'ok' : 'ng' }
]
}
const toneMap = {
ok: { bg: EXCEL_COLORS.okBg, fg: EXCEL_COLORS.ok, border: EXCEL_COLORS.ok },
ng: { bg: EXCEL_COLORS.ngBg, fg: EXCEL_COLORS.ng, border: EXCEL_COLORS.ng },
accent: { bg: EXCEL_COLORS.accentSoft, fg: EXCEL_COLORS.accent, border: EXCEL_COLORS.steel },
amber: { bg: EXCEL_COLORS.amberBg, fg: EXCEL_COLORS.amber, border: EXCEL_COLORS.amber },
normal: { bg: EXCEL_COLORS.white, fg: EXCEL_COLORS.ink, border: EXCEL_COLORS.line }
}
cards.forEach((card, i) => {
const c1 = i * 2 + 1
const c2 = c1 + 1
const tone = toneMap[card.tone] || toneMap.normal
this.excelMergeSet(ws, row, c1, row, c2, card.label, {
fill: tone.bg,
font: { size: 8, bold: true, color: { argb: EXCEL_COLORS.muted } },
align: { vertical: 'middle', horizontal: 'center' }
})
this.excelMergeSet(ws, row + 1, c1, row + 1, c2, card.value, {
fill: tone.bg,
font: { size: 16, bold: true, color: { argb: tone.fg } },
align: { vertical: 'middle', horizontal: 'center' }
})
this.excelMergeSet(ws, row + 2, c1, row + 2, c2, card.sub, {
fill: tone.bg,
font: { size: 8, color: { argb: tone.fg } },
align: { vertical: 'middle', horizontal: 'center', wrapText: true }
})
;[row, row + 1, row + 2].forEach(rr => {
for (let cc = c1; cc <= c2; cc++) {
ws.getCell(rr, cc).border = {
top: { style: 'medium', color: { argb: tone.border } },
left: { style: 'medium', color: { argb: tone.border } },
bottom: { style: 'medium', color: { argb: tone.border } },
right: { style: 'medium', color: { argb: tone.border } }
}
}
})
})
ws.getRow(row).height = 18
ws.getRow(row + 1).height = 28
ws.getRow(row + 2).height = 22
return row + 3
},
excelWriteStats (ws, row) {
this.excelMergeSet(ws, row, 1, row, 12, '过程能力指数 / 控制界限 / 分布参数', {
fill: EXCEL_COLORS.accentSoft,
font: { bold: true, size: 11, color: { argb: EXCEL_COLORS.accent } },
align: { vertical: 'middle', horizontal: 'left', indent: 1 }
})
ws.getRow(row).height = 22
row += 1
const r = this.result
const unit = this.itemSpec.unit || ''
let leftBlock
let rightBlock
if (this.stage === 'ppk') {
leftBlock = {
title: '样品阶段 · PPK',
rows: [
['总均值 X̄̄', (this.fm(r.Xb) + ' ' + unit).trim()],
['平均极差 R̄', this.fm(r.Rb)],
['组内σ', this.fm(r.sigma, 4)],
['样本 N', r.N + ' (' + r.gCnt + '×' + r.groups[0].length + ')'],
['Pp', this.fm(r.Pp)],
['PPU (USL)', this.fm(r.PPU)],
['PPL (LSL)', this.fm(r.PPL)],
['Ppk', this.fm(r.Ppk)]
],
highlightKey: 'Ppk',
ok: this.ppkOk
}
const ct = getCt(r.groups[0].length)
rightBlock = {
title: 'X-bar / R 图界限',
rows: [
['X-bar UCL', this.fm(r.XU, 4)],
['X-bar CL (X̄̄)', this.fm(r.Xb, 4)],
['X-bar LCL', this.fm(r.XL, 4)],
['系数 A₂', ct.A2.toFixed(3)],
['R UCL', this.fm(r.RU, 4)],
['R CL (R̄)', this.fm(r.Rb, 4)],
['R LCL', this.fm(r.RL, 4)],
['X超限 / R超限', r.xOut + ' / ' + r.rOut]
]
}
} else {
leftBlock = {
title: '量产阶段 · CPK',
rows: [
['总均值 X̄', (this.fm(r.Xa) + ' ' + unit).trim()],
['整体σ (s)', this.fm(r.sO, 4)],
['样本总数 N', String(r.N)],
['Cp', this.fm(r.Cp)],
['CPU (USL)', this.fm(r.CPU)],
['CPL (LSL)', this.fm(r.CPL)],
['Cpk', this.fm(r.Cpk)],
['总 PPM', this.formatNum(r.ppmT)]
],
highlightKey: 'Cpk',
ok: this.cpkOk
}
rightBlock = {
title: '正态分布 / PPM',
rows: [
['μ (均值)', this.fm(r.Xa, 4)],
['σ (标准差)', this.fm(r.sO, 4)],
['μ + 3σ', this.fm(r.Xa + 3 * r.sO, 4)],
['μ - 3σ', this.fm(r.Xa - 3 * r.sO, 4)],
['USL 侧 PPM', this.formatNum(r.ppmU)],
['LSL 侧 PPM', this.formatNum(r.ppmL)],
['总 PPM', this.formatNum(r.ppmT)],
['I-CL / MR̄', this.fm(r.Xa, 4) + ' / ' + this.fm(r.MRbar, 4)]
]
}
}
const writeBlock = (block, startCol, titleFill) => {
this.excelMergeSet(ws, row, startCol, row, startCol + 5, block.title, {
fill: titleFill,
font: { bold: true, size: 10, color: { argb: EXCEL_COLORS.white } },
align: { vertical: 'middle', horizontal: 'center' }
})
block.rows.forEach((pair, idx) => {
const rr = row + 1 + idx
const isHi = block.highlightKey && pair[0] === block.highlightKey
const valFill = isHi ? (block.ok ? EXCEL_COLORS.okBg : EXCEL_COLORS.ngBg) : EXCEL_COLORS.white
const valColor = isHi ? (block.ok ? EXCEL_COLORS.ok : EXCEL_COLORS.ng) : EXCEL_COLORS.ink
this.excelMergeSet(ws, rr, startCol, rr, startCol + 2, pair[0], {
fill: EXCEL_COLORS.paper,
font: { size: 9, color: { argb: EXCEL_COLORS.muted } },
align: { vertical: 'middle', horizontal: 'left', indent: 1 }
})
this.excelMergeSet(ws, rr, startCol + 3, rr, startCol + 5, pair[1], {
fill: valFill,
font: { size: 10, bold: true, color: { argb: valColor } },
align: { vertical: 'middle', horizontal: 'right', indent: 1 }
})
ws.getRow(rr).height = 18
})
}
writeBlock(leftBlock, 1, EXCEL_COLORS.accent)
writeBlock(rightBlock, 7, EXCEL_COLORS.steel)
ws.getRow(row).height = 20
return row + 1 + leftBlock.rows.length
},
excelWriteCriteria (ws, row) {
this.excelMergeSet(ws, row, 1, row, 12, '判定准则', {
fill: EXCEL_COLORS.accentSoft,
font: { bold: true, size: 11, color: { argb: EXCEL_COLORS.accent } },
align: { vertical: 'middle', horizontal: 'left', indent: 1 }
})
ws.getRow(row).height = 22
row += 1
this.excelMergeSet(ws, row, 1, row, 6, '准则', {
fill: EXCEL_COLORS.paper,
font: { bold: true, size: 9 },
align: { vertical: 'middle', horizontal: 'center' }
})
this.excelMergeSet(ws, row, 7, row, 9, '实测值', {
fill: EXCEL_COLORS.paper,
font: { bold: true, size: 9 },
align: { vertical: 'middle', horizontal: 'center' }
})
this.excelMergeSet(ws, row, 10, row, 12, '结果', {
fill: EXCEL_COLORS.paper,
font: { bold: true, size: 9 },
align: { vertical: 'middle', horizontal: 'center' }
})
row += 1
this.criteriaList.forEach(c => {
const bg = c.pass ? EXCEL_COLORS.okBg : EXCEL_COLORS.ngBg
const fg = c.pass ? EXCEL_COLORS.ok : EXCEL_COLORS.ng
this.excelMergeSet(ws, row, 1, row, 6, c.label, {
fill: EXCEL_COLORS.white,
font: { size: 10 },
align: { vertical: 'middle', horizontal: 'left', indent: 1 }
})
this.excelMergeSet(ws, row, 7, row, 9, c.value, {
fill: EXCEL_COLORS.white,
font: { size: 10, bold: true },
align: { vertical: 'middle', horizontal: 'center' }
})
this.excelMergeSet(ws, row, 10, row, 12, c.pass ? 'PASS' : 'FAIL', {
fill: bg,
font: { size: 10, bold: true, color: { argb: fg } },
align: { vertical: 'middle', horizontal: 'center' }
})
ws.getRow(row).height = 18
row += 1
})
return row
},
async excelWriteCharts (wb, ws, row) {
this.excelMergeSet(ws, row, 1, row, 12, '控制图 / 分布图', {
fill: EXCEL_COLORS.accentSoft,
font: { bold: true, size: 11, color: { argb: EXCEL_COLORS.accent } },
align: { vertical: 'middle', horizontal: 'left', indent: 1 }
})
ws.getRow(row).height = 22
row += 1
const chartPairs = this.stage === 'ppk'
? [
{ key: 'xbar', title: '01 X-bar 控制图', meta: 'CL:' + this.fm(this.result.Xb, 4) + ' UCL:' + this.fm(this.result.XU, 4) + ' LCL:' + this.fm(this.result.XL, 4) },
{ key: 'r', title: '02 R 控制图', meta: 'CL:' + this.fm(this.result.Rb, 4) + ' UCL:' + this.fm(this.result.RU, 4) + ' LCL:' + this.fm(this.result.RL, 4) }
]
: [
{ key: 'imr', title: '01 I-MR 控制图', meta: 'I-CL:' + this.fm(this.result.Xa, 4) + ' MR̄:' + this.fm(this.result.MRbar, 4) + ' MR-UCL:' + this.fm(this.result.MR_UCL, 4) },
{ key: 'hist', title: '02 直方图 + 正态分布', meta: 'μ:' + this.fm(this.result.Xa, 4) + ' σ:' + this.fm(this.result.sO, 4) }
]
for (let pi = 0; pi < chartPairs.length; pi++) {
const item = chartPairs[pi]
this.excelMergeSet(ws, row, 1, row, 8, item.title, {
fill: EXCEL_COLORS.paper,
font: { bold: true, size: 10, color: { argb: EXCEL_COLORS.ink } },
align: { vertical: 'middle', horizontal: 'left', indent: 1 }
})
this.excelMergeSet(ws, row, 9, row, 12, item.meta, {
fill: EXCEL_COLORS.paper,
font: { size: 8, color: { argb: EXCEL_COLORS.muted } },
align: { vertical: 'middle', horizontal: 'right', indent: 1 }
})
row += 1
const chart = this.chartInst[item.key]
if (chart && typeof chart.getDataURL === 'function') {
try {
const dataUrl = chart.getDataURL({
type: 'png',
pixelRatio: 2,
backgroundColor: '#ffffff'
})
const base64 = dataUrl.replace(/^data:image\/\w+;base64,/, '')
const imgId = wb.addImage({ base64: base64, extension: 'png' })
const imgRow = row - 1
ws.addImage(imgId, {
tl: { col: 0, row: imgRow + 0.2 },
ext: { width: 860, height: 260 }
})
for (let i = 0; i < 14; i++) {
ws.getRow(row + i).height = 15
}
row += 14
} catch (e) {
this.excelMergeSet(ws, row, 1, row, 12, '(图表导出失败)', {
fill: EXCEL_COLORS.warnBg,
font: { size: 9, color: { argb: EXCEL_COLORS.warn } },
align: { vertical: 'middle', horizontal: 'center' }
})
row += 1
}
} else {
this.excelMergeSet(ws, row, 1, row, 12, '(暂无图表,请先完成计算)', {
fill: EXCEL_COLORS.paper,
font: { size: 9, color: { argb: EXCEL_COLORS.muted } },
align: { vertical: 'middle', horizontal: 'center' }
})
row += 1
}
row += 1
}
return row
},
excelWriteDetail (ws, row) {
this.excelMergeSet(ws, row, 1, row, 12, '子组 / 样本明细数据', {
fill: EXCEL_COLORS.accentSoft,
font: { bold: true, size: 11, color: { argb: EXCEL_COLORS.accent } },
align: { vertical: 'middle', horizontal: 'left', indent: 1 }
})
ws.getRow(row).height = 22
row += 1
const headerFill = EXCEL_COLORS.header
const headerFont = { size: 9, bold: true, color: { argb: EXCEL_COLORS.white } }
const center = { vertical: 'middle', horizontal: 'center' }
if (this.stage === 'ppk') {
const headers = [''].concat(this.result.groups.map((_, i) => 'G' + String(i + 1).padStart(2, '0')))
csv += headers.join(',') + '\n'
const groups = this.result.groups || []
const headers = [''].concat(groups.map((_, i) => 'G' + String(i + 1).padStart(2, '0')))
headers.forEach((h, i) => {
const cell = ws.getCell(row, i + 1)
cell.value = h
this.excelStyleCell(cell, { fill: headerFill, font: headerFont, align: center })
})
ws.getRow(row).height = 20
row += 1
for (let j = 0; j < this.nVal; j++) {
const row = ['X' + (j + 1)]
this.result.groups.forEach(g => row.push(g[j] != null ? Number(g[j]).toFixed(3) : ''))
csv += row.join(',') + '\n'
const labelCell = ws.getCell(row, 1)
labelCell.value = 'X' + (j + 1)
this.excelStyleCell(labelCell, { fill: EXCEL_COLORS.paper, font: { bold: true, size: 9 }, align: center })
groups.forEach((g, i) => {
const cell = ws.getCell(row, i + 2)
cell.value = g[j] != null ? Number(Number(g[j]).toFixed(3)) : ''
this.excelStyleCell(cell, { align: center, font: { size: 9 } })
})
ws.getRow(row).height = 18
row += 1
}
csv += ['X̄'].concat(this.result.means.map(v => Number(v).toFixed(4))).join(',') + '\n'
csv += ['R'].concat(this.result.ranges.map(v => Number(v).toFixed(4))).join(',') + '\n'
const meanLabel = ws.getCell(row, 1)
meanLabel.value = 'X̄'
this.excelStyleCell(meanLabel, { fill: EXCEL_COLORS.accentSoft, font: { bold: true, size: 9 }, align: center })
this.result.means.forEach((v, i) => {
const cell = ws.getCell(row, i + 2)
cell.value = Number(Number(v).toFixed(4))
this.excelStyleCell(cell, { fill: EXCEL_COLORS.accentSoft, font: { bold: true, size: 9 }, align: center })
})
row += 1
const rLabel = ws.getCell(row, 1)
rLabel.value = 'R'
this.excelStyleCell(rLabel, { fill: EXCEL_COLORS.amberBg, font: { bold: true, size: 9 }, align: center })
this.result.ranges.forEach((v, i) => {
const cell = ws.getCell(row, i + 2)
cell.value = Number(Number(v).toFixed(4))
this.excelStyleCell(cell, { fill: EXCEL_COLORS.amberBg, font: { size: 9 }, align: center })
})
row += 1
const xJudgeLabel = ws.getCell(row, 1)
xJudgeLabel.value = 'X̄判定'
this.excelStyleCell(xJudgeLabel, { fill: EXCEL_COLORS.paper, font: { bold: true, size: 9 }, align: center })
this.result.means.forEach((v, i) => {
const out = v > this.result.XU || v < this.result.XL
const cell = ws.getCell(row, i + 2)
cell.value = out ? '超限' : 'OK'
this.excelStyleCell(cell, {
fill: out ? EXCEL_COLORS.ngBg : EXCEL_COLORS.okBg,
font: { bold: true, size: 9, color: { argb: out ? EXCEL_COLORS.ng : EXCEL_COLORS.ok } },
align: center
})
})
row += 1
const rJudgeLabel = ws.getCell(row, 1)
rJudgeLabel.value = 'R判定'
this.excelStyleCell(rJudgeLabel, { fill: EXCEL_COLORS.paper, font: { bold: true, size: 9 }, align: center })
this.result.ranges.forEach((v, i) => {
const out = v > this.result.RU
const cell = ws.getCell(row, i + 2)
cell.value = out ? '超限' : 'OK'
this.excelStyleCell(cell, {
fill: out ? EXCEL_COLORS.ngBg : EXCEL_COLORS.okBg,
font: { bold: true, size: 9, color: { argb: out ? EXCEL_COLORS.ng : EXCEL_COLORS.ok } },
align: center
})
})
} else {
const headers = ['#', '子组'].concat(Array.from({ length: this.nVal }, (_, i) => 'X' + (i + 1))).concat(['X̄', 'R', '判定'])
csv += headers.join(',') + '\n'
this.cpkTableRows.forEach((row, i) => {
const vals = row.vals.map(v => (v != null ? Number(v).toFixed(3) : ''))
while (vals.length < this.nVal) vals.push('')
csv += [i + 1, 'G' + String(i + 1).padStart(2, '0')].concat(vals).concat([
Number(row.mean).toFixed(4),
Number(row.range).toFixed(4),
row.out ? '超规' : 'OK'
]).join(',') + '\n'
headers.forEach((h, i) => {
const cell = ws.getCell(row, i + 1)
cell.value = h
this.excelStyleCell(cell, { fill: headerFill, font: headerFont, align: center })
})
ws.getRow(row).height = 20
row += 1
this.cpkTableRows.forEach((dataRow, i) => {
const vals = []
for (let j = 0; j < this.nVal; j++) {
vals.push(dataRow.vals[j] != null ? Number(Number(dataRow.vals[j]).toFixed(3)) : '')
}
const values = [i + 1, 'G' + String(i + 1).padStart(2, '0')].concat(vals).concat([
Number(Number(dataRow.mean).toFixed(4)),
Number(Number(dataRow.range).toFixed(4)),
dataRow.out ? '超规' : 'OK'
])
values.forEach((v, ci) => {
const cell = ws.getCell(row, ci + 1)
cell.value = v
const isJudge = ci === values.length - 1
this.excelStyleCell(cell, {
fill: isJudge ? (dataRow.out ? EXCEL_COLORS.ngBg : EXCEL_COLORS.okBg) : (i % 2 ? EXCEL_COLORS.paper : EXCEL_COLORS.white),
font: {
size: 9,
bold: isJudge || ci === headers.length - 3,
color: { argb: isJudge ? (dataRow.out ? EXCEL_COLORS.ng : EXCEL_COLORS.ok) : EXCEL_COLORS.ink }
},
align: center
})
})
ws.getRow(row).height = 18
row += 1
})
}
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'spc_data.csv'
a.click()
URL.revokeObjectURL(url)
return row
}
}
}

2
static/config/init.js

@ -11,6 +11,8 @@
// 插件, 放置业务之前加载, 以免业务需求依赖插件时, 还未加载出错
// 插件 - echarts
window.SITE_CONFIG.cdnUrl + '/static/plugins/echarts-3.8.5/echarts.common.min.js',
// 插件 - exceljs(SPC等报表导出,勿经webpack打包)
window.SITE_CONFIG.cdnUrl + '/static/plugins/exceljs/exceljs.min.js',
// 插件 - ueditor
window.SITE_CONFIG.cdnUrl + '/static/plugins/ueditor-1.4.3.3/ueditor.config.js',
window.SITE_CONFIG.cdnUrl + '/static/plugins/ueditor-1.4.3.3/ueditor.all.min.js',

45
static/plugins/exceljs/exceljs.min.js
File diff suppressed because it is too large
View File

Loading…
Cancel
Save